sitemap works, tests pass

This commit is contained in:
Thomas Reynolds 2011-11-07 22:34:02 -08:00
parent 41a5d9bbee
commit b5561227f8
32 changed files with 305 additions and 250 deletions

View file

@ -1,6 +1,8 @@
2.1.pre
====
Finally support Compass in Sprockets! Thanks to @xdite and @petebrowne
Middleman::Sitemap object representing the known world
Middleman::FileWatcher proxies file change events
2.0.14
====

View file

@ -15,15 +15,12 @@ Feature: Builder
Then "spaces in file.html" should exist at "test-app" and include "spaces"
Then "images/Read me (example).txt" should exist at "test-app"
Then "images/Child folder/regular_file(example).txt" should exist at "test-app"
And cleanup built app at "test-app"
Scenario: Build glob
Given a built app at "glob-app" with flags "--glob '**/*.sass'"
Given a built app at "glob-app" with flags "--glob '*.css'"
Then "stylesheets/site.css" should exist at "glob-app" and include "html"
Then "index.html" should not exist at "glob-app"
And cleanup built app at "glob-app"
# Scenario: Force relative assets
# Given a built app at "relative-app" with flags "--relative"
# Then "stylesheets/relative_assets.css" should exist at "relative-app" and include "../"
# And cleanup built app at "relative-app"

View file

@ -7,4 +7,3 @@ Feature: Build Clean
Then "should_be_ignored.html" should not exist at "clean-app"
And "should_be_ignored2.html" should not exist at "clean-app"
And "should_be_ignored3.html" should not exist at "clean-app"
And cleanup built app at "clean-app"

View file

@ -11,7 +11,6 @@ Feature: Directory Index
Then "needs_index.html" should not exist at "indexable-app"
Then "a_folder/needs_index.html" should not exist at "indexable-app"
Then "leave_me_alone/index.html" should not exist at "indexable-app"
And cleanup built app at "indexable-app"
Scenario: Preview normal file
Given the Server is running at "indexable-app"

View file

@ -10,7 +10,6 @@ Feature: Dynamic Pages
Then "should_be_ignored.html" should not exist at "test-app"
Then "should_be_ignored2.html" should not exist at "test-app"
Then "should_be_ignored3.html" should not exist at "test-app"
And cleanup built app at "test-app"
Scenario: Preview basic proxy
Given the Server is running at "test-app"

View file

@ -3,7 +3,6 @@ Feature: Web Fonts
Scenario: Checking built folder for content
Given a built app at "fonts-app"
Then "stylesheets/fonts.css" should exist at "fonts-app" and include "/fonts/StMarie-Thin.otf"
And cleanup built app at "fonts-app"
Scenario: Rendering scss
Given the Server is running at "fonts-app"

View file

@ -23,7 +23,6 @@ Feature: Sprockets
Scenario: Multiple engine files should build correctly
Given a built app at "test-app"
Then "javascripts/multiple_engines.js" should exist at "test-app" and include "Hello One"
And cleanup built app at "test-app"
Scenario: Sprockets CSS require //require
Given the Server is running at "test-app"

View file

@ -11,6 +11,10 @@ end
Given /^a built app at "([^"]*)"$/ do |path|
root = File.dirname(File.dirname(File.dirname(__FILE__)))
target = File.join(root, "fixtures", path)
build_target = File.join(target, "build")
FileUtils.rm_rf(build_target)
build_cmd = File.expand_path(File.join(root, "bin", "middleman build"))
`cd #{target} && #{build_cmd}`
end

View file

@ -0,0 +1 @@
I am real

View file

@ -0,0 +1,5 @@
---
layout: false
---
I am real: one

View file

@ -0,0 +1,5 @@
---
layout: false
---
I am real: two

View file

@ -0,0 +1,12 @@
<html>
<head>
<title>My Sample Site</title>
<!-- Comment in layout -->
</head>
<body>
<h1>Welcome</h1>
<h2 id='h2'>H2</h2>
<p>Paragraph</p>
</body>
</html>

View file

@ -0,0 +1 @@
I am real

View file

@ -0,0 +1,5 @@
---
layout: false
---
I am real:

View file

@ -0,0 +1 @@
Static, no code!

View file

@ -0,0 +1 @@
<h1>Ignore me! 3</h1>

Binary file not shown.

View file

@ -0,0 +1,3 @@
@font-face {
font-family: "St Marie";
src: url('/fonts/StMarie-Thin.otf') format('opentype'); }

View file

@ -0,0 +1,46 @@
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline; }
body {
line-height: 1; }
ol, ul {
list-style: none; }
table {
border-collapse: collapse;
border-spacing: 0; }
caption, th, td {
text-align: left;
font-weight: normal;
vertical-align: middle; }
q, blockquote {
quotes: none; }
q:before, q:after, blockquote:before, blockquote:after {
content: "";
content: none; }
a img {
border: none; }
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
display: block; }

View file

@ -0,0 +1 @@
Indexable

View file

@ -0,0 +1 @@
Stay away

View file

@ -0,0 +1 @@
Indexable

View file

@ -0,0 +1 @@
Regular

View file

@ -61,12 +61,11 @@ require "sinatra/base"
# Top-level Middleman object
module Middleman
# Auto-load modules on-demand
autoload :Base, "middleman/base"
autoload :Sitemap, "middleman/sitemap"
autoload :Builder, "middleman/builder"
autoload :CLI, "middleman/cli"
autoload :Templates, "middleman/templates"
autoload :Guard, "middleman/guard"
autoload :Base, "middleman/base"
autoload :Builder, "middleman/builder"
autoload :CLI, "middleman/cli"
autoload :Templates, "middleman/templates"
autoload :Guard, "middleman/guard"
# Custom Renderers
module Renderers
@ -80,6 +79,11 @@ module Middleman
end
module CoreExtensions
# Guard Proxy
autoload :FileWatcher, "middleman/core_extensions/file_watcher"
autoload :Sitemap, "middleman/core_extensions/sitemap"
# Add Builder callbacks
autoload :Builder, "middleman/core_extensions/builder"
@ -187,13 +191,14 @@ module Middleman
:AccessLog => []
}
app = ::Middleman.server
app.set :environment, options[:environment].to_sym
opts[:app] = app.new
app_class = options[:app] ||= ::Middleman.server
app_class.set :environment, options[:environment].to_sym
opts[:app] = app_class.new
opts[:server] = 'thin'
$stderr.puts "== The Middleman is standing watch on port #{opts[:Port]}"
::Rack::Server.new(opts).start
server = ::Rack::Server.new(opts)
server.start
server
end
end

View file

@ -38,6 +38,12 @@ module Middleman::Base
app.set :views, "source"
# Add Builder Callbacks
app.register Middleman::CoreExtensions::FileWatcher
# Sitemap
app.register Middleman::CoreExtensions::Sitemap
# Add Builder Callbacks
app.register Middleman::CoreExtensions::Builder
@ -92,7 +98,8 @@ module Middleman::Base
# See if Tilt cannot handle this file
app.before_processing(:base) do |result|
request_path = request.path_info.gsub("%20", " ")
should_be_ignored = !(request["is_proxy"]) && settings.excluded_paths.include?("/#{request_path}")
should_be_ignored = !(request["is_proxy"]) && settings.sitemap.ignored_path?("/#{request_path}")
if result && !should_be_ignored
extensionless_path, template_engine = result

View file

@ -12,9 +12,6 @@ module Middleman
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source
# source = File.expand_path(find_in_source_paths(source.to_s))
# context = instance_eval('binding')
request_path = destination.sub(/^#{SHARED_SERVER.build_dir}/, "")
begin
@ -22,8 +19,6 @@ module Middleman
request_path.gsub!(/\s/, "%20")
response = Middleman::Builder.shared_rack.get(request_path)
dequeue_file_from destination if cleaning?
create_file destination, nil, config do
response.body
@ -31,54 +26,6 @@ module Middleman
rescue
end
end
def clean!(destination)
return unless cleaning?
queue_current_paths_from destination
add_clean_up_callback
end
def cleaning?
options.has_key?("clean") && options["clean"]
end
def add_clean_up_callback
clean_up_callback = lambda do
files = @cleaning_queue.select { |q| File.file? q }
directories = @cleaning_queue.select { |q| File.directory? q }
files.each { |f| remove_file f, :force => true }
directories = directories.sort_by {|d| d.length }.reverse!
directories.each do |d|
remove_file d, :force => true if directory_empty? d
end
end
self.class.after_run :clean_up_callback do
clean_up_callback.call
end
end
def directory_empty?(directory)
Dir["#{directory}/*"].empty?
end
def queue_current_paths_from(destination)
@cleaning_queue = []
Find.find(destination) do |path|
next if path.match(/\/\./)
unless path == destination
@cleaning_queue << path.sub(destination, destination[/([^\/]+?)$/])
end
end
end
def dequeue_file_from(destination)
@cleaning_queue.delete_if {|q| q == destination }
end
end
class Builder < Thor::Group
@ -114,15 +61,11 @@ module Middleman
def build_all_files
self.class.shared_rack
if options.has_key?("glob")
action GlobAction.new(self, SHARED_SERVER.views, SHARED_SERVER.build_dir, { :force => true, :glob => options["glob"] })
else
action DirectoryAction.new(self, SHARED_SERVER.views, SHARED_SERVER.build_dir, { :force => true })
opts = { }
opts[:glob] = options["glob"] if options.has_key?("glob")
opts[:clean] = options["clean"] if options.has_key?("clean")
SHARED_SERVER.proxied_paths.each do |url, proxy|
tilt_template(url.gsub(/^\//, "#{SHARED_SERVER.build_dir}/"), { :force => true })
end
end
action GlobAction.new(self, SHARED_SERVER, opts)
end
@@hooks = {}
@ -143,113 +86,99 @@ module Middleman
end
end
class BaseAction < ::Thor::Actions::EmptyDirectory
class GlobAction < ::Thor::Actions::EmptyDirectory
attr_reader :source
def initialize(base, source, destination=nil, config={}, &block)
def initialize(base, app, config={}, &block)
@app = app
source = @app.views
@destination = @app.build_dir
@source = File.expand_path(base.find_in_source_paths(source.to_s))
@block = block
super(base, destination, { :recursive => true }.merge(config))
super(base, destination, config)
end
def invoke!
base.clean! destination
queue_current_paths if cleaning?
execute!
clean! if cleaning?
end
def revoke!
execute!
end
protected
def handle_path(file_source)
# Skip partials prefixed with an underscore while still handling files prefixed with 2 consecutive underscores
return unless file_source.gsub(SHARED_SERVER.root, '').split('/').select { |p| p[/^_[^_]/] }.empty?
file_extension = File.extname(file_source)
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
file_destination.gsub!('/./', '/')
handled_by_tilt = ::Tilt.mappings.has_key?(file_extension.gsub(/^\./, ""))
if handled_by_tilt
file_destination.gsub!(file_extension, "")
end
destination = base.tilt_template(file_source, file_destination, config, &@block)
end
end
class GlobAction < BaseAction
def clean!
files = @cleaning_queue.select { |q| File.file? q }
directories = @cleaning_queue.select { |q| File.directory? q }
protected
def execute!
Dir[File.join(source, @config[:glob])].each do |path|
file_name = path.gsub(SHARED_SERVER.views + "/", "")
if file_name == "layouts"
false
elsif file_name.include?("layout.") && file_name.split(".").length == 2
false
else
next if File.directory?(path)
files.each do |f|
base.remove_file f, :force => true
end
handle_path(path)
directories = directories.sort_by {|d| d.length }.reverse!
true
directories.each do |d|
base.remove_file d, :force => true if directory_empty? d
end
end
def cleaning?
@config.has_key?(:clean) && @config[:clean]
end
def directory_empty?(directory)
Dir["#{directory}/*"].empty?
end
def queue_current_paths
@cleaning_queue = []
Find.find(@destination) do |path|
next if path.match(/\/\./)
unless path == destination
@cleaning_queue << path.sub(@destination, destination[/([^\/]+?)$/])
end
end
end
end
class DirectoryAction < BaseAction
def invoke!
base.empty_directory given_destination, config
super
end
protected
def handle_directory(lookup, &block)
lookup = File.join(lookup, '*')
def execute!
paths = @app.sitemap.all_paths.sort do |a, b|
a_dir = a.split("/").first
b_dir = b.split("/").first
results = Dir[lookup].sort do |a, b|
simple_a = a.gsub(SHARED_SERVER.root + "/", '').gsub(SHARED_SERVER.views + "/", '')
simple_b = b.gsub(SHARED_SERVER.root + "/", '').gsub(SHARED_SERVER.views + "/", '')
a_dir = simple_a.split("/").first
b_dir = simple_b.split("/").first
if a_dir == SHARED_SERVER.images_dir
if a_dir == @app.images_dir
-1
elsif b_dir == SHARED_SERVER.images_dir
elsif b_dir == @app.images_dir
1
else
0
end
end
results = results.select(&block) if block_given?
results.each do |file_source|
if File.directory?(file_source)
handle_directory(file_source)
paths.each do |path|
file_source = path
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
file_destination.gsub!('/./', '/')
if @app.sitemap.generic_path?(file_source)
# no-op
elsif @app.sitemap.proxied_path?(file_source)
file_source = @app.sitemap.path_target(file_source)
elsif @app.sitemap.ignored_path?(file_source)
next
end
handle_path(file_source)
end
end
def execute!
handle_directory(source) do |path|
file_name = path.gsub(SHARED_SERVER.views + "/", "")
if file_name == "layouts"
false
elsif file_name.include?("layout.") && file_name.split(".").length == 2
false
else
true
@cleaning_queue.delete(file_destination) if cleaning?
if @config[:glob]
next unless File.fnmatch(@config[:glob], file_source)
end
base.tilt_template(file_source, file_destination, { :force => true })
end
end
end
end

View file

@ -59,7 +59,7 @@ module Middleman::CoreExtensions::Features
feature = Middleman::Features.const_get(feature)
end
$stderr.puts "== Activating: #{feature}" if logging?
puts "== Activating: #{feature}" if logging?
register feature
end
@ -75,12 +75,10 @@ module Middleman::CoreExtensions::Features
# Load features before starting server
def new
set :sitemap, ::Middleman::Sitemap.new(self)
# Check for and evaluate local configuration
local_config = File.join(self.root, "config.rb")
if File.exists? local_config
$stderr.puts "== Reading: Local config" if logging?
puts "== Reading: Local config" if logging?
class_eval File.read(local_config)
set :app_file, File.expand_path(local_config)
end
@ -94,7 +92,7 @@ module Middleman::CoreExtensions::Features
if logging?
extensions.each do |ext|
$stderr.puts "== Extension: #{ext}"
puts "== Extension: #{ext}"
end
end

View file

@ -0,0 +1,30 @@
module Middleman::CoreExtensions::FileWatcher
class << self
def registered(app)
app.extend ClassMethods
end
alias :included :registered
end
module ClassMethods
def file_did_change(path)
@run_after_file_change ||= []
@run_after_file_change.each { |block| block.call(path) }
end
def on_file_change(&block)
@run_after_file_change ||= []
@run_after_file_change << block
end
def file_did_delete(path)
@run_after_file_delete ||= []
@run_after_file_delete.each { |block| block.call(path) }
end
def on_file_delete(&block)
@run_after_file_delete ||= []
@run_after_file_delete << block
end
end
end

View file

@ -48,12 +48,7 @@ module Middleman::CoreExtensions::Routing
# Keep a path from building
def ignore(path)
# New sitemap based ignore
settings.sitemap.ignore_path(path)
settings.excluded_paths << paths_for_url(path)
settings.excluded_paths.flatten!
settings.excluded_paths.uniq!
end
# The page method allows the layout to be set on a specific path
@ -64,10 +59,8 @@ module Middleman::CoreExtensions::Routing
options[:layout] = settings.layout if options[:layout].nil?
if options.has_key?(:proxy)
# New sitemap based proxy
settings.sitemap.set_path(url, options[:proxy])
settings.proxied_paths[url] = options[:proxy]
if options.has_key?(:ignore) && options[:ignore]
settings.ignore(options[:proxy])
end
@ -79,17 +72,11 @@ module Middleman::CoreExtensions::Routing
paths_for_url(url).each do |p|
get(p) do
# New sitemap based rerouting
if settings.sitemap.path_is_proxy?(url)
request["is_proxy"] = true
request.path_info = settings.sitemap.path_target(url)
end
if settings.proxied_paths.has_key?(url)
request["is_proxy"] = true
request.path_info = settings.proxied_paths[url]
end
instance_eval(&block) if has_block
process_request(options)
end

View file

@ -1,88 +1,116 @@
require 'find'
module Middleman
class Sitemap
def self.singleton
@@singleton || nil
module Middleman::CoreExtensions::Sitemap
class << self
def registered(app)
app.set :sitemap, SitemapStore.new(app)
end
alias :included :registered
end
class SitemapStore
def initialize(app)
@app = app
@map = {}
@ignored_paths = nil
@generic_paths = nil
@proxied_paths = nil
@ignored_paths = false
@generic_paths = false
@proxied_paths = false
@source = File.expand_path(@app.views, @app.root)
build_static_map
each do |request, destination|
$stderr.puts request
@app.on_file_change do |file|
touch_file(file)
end
@app.on_file_delete do |file|
remove_file(file)
end
@@singleton = self
end
# Check to see if we know about a specific path
def path_exists?(path)
path = path.sub(/^\//, "")
@map.has_key?(path)
end
def path_is_proxy?(path)
path = path.sub(/^\//, "")
return false if !path_exists?(path)
@map[path].is_a?(String)
end
def path_target(path)
path = path.sub(/^\//, "")
@map[path]
end
def set_path(path, target=true)
path = path.sub(/^\//, "")
target = target.sub(/^\//, "") if target.is_a?(String)
@map[path] = target
@ignored_paths = nil if target.nil?
@generic_paths = nil if target === true
@proxied_paths = nil if target.is_a?(String)
@ignored_paths = false if target === false
@generic_paths = false if target === true
@proxied_paths = false if target.is_a?(String)
end
def ignore_path(path)
set_path(path, nil)
set_path(path, false)
end
def each(&block)
@map.each do |k, v|
next if v.nil?
yield(k, v)
end
end
def all_paths
@map.keys
end
def ignored_path?(path)
path = path.sub(/^\//, "")
ignored_paths.include?(path)
end
def ignored_paths
@ignored_paths ||= begin
ignored = []
each do |k, v|
ignored << k unless v.nil?
ignored << k if v === false
end
ignored
end
end
def generic_path?(path)
path = path.sub(/^\//, "")
generic_paths.include?(path)
end
def generic_paths
@generic_paths ||= begin
generic = []
each do |k, v|
generic << k unless v === true
generic << k if v === true
end
generic
end
end
def proxied_path?(path)
path = path.sub(/^\//, "")
proxied_paths.include?(path)
end
def proxied_paths
@proxied_paths ||= begin
proxied = []
each do |k, v|
proxied << k unless target.is_a?(String)
proxied << k if v.is_a?(String)
end
proxied
end
@ -101,16 +129,12 @@ module Middleman
end
def remove_path(path)
if @map.has_key?(path)
@map.delete(path)
end
path = path.sub(/^\//, "")
@map.delete(path) if path_exists?(path)
end
protected
def build_static_map
# found_template = resolve_template(request_path, :raise_exceptions => false)
Find.find(@source) do |file|
add_file(file)
end
@ -152,34 +176,4 @@ module Middleman
true
end
end
end
module Guard
class MiddlemanSitemap < Guard
def initialize(watchers = [], options = {})
super
@options = options
end
def run_on_change(files)
files.each do |file|
::Middleman::Sitemap.singleton.touch_file(file)
end
end
def run_on_deletion(files)
files.each do |file|
::Middleman::Sitemap.singleton.remove_file(file)
end
end
end
end
# Add Sitemap guard
Middleman::Guard.add_guard do
%Q{
guard 'middlemansitemap' do
watch(%r{^source/(.*)})
end
}
end

View file

@ -9,8 +9,7 @@ end
module Middleman
module Guard
def self.add_guard(&block)
@additional_guards ||= []
@additional_guards << block
# Deprecation Warning
end
def self.start(options={}, livereload={})
@ -20,16 +19,10 @@ module Middleman
end
guardfile_contents = %Q{
guard 'middlemanconfig'#{options_hash} do
watch("config.rb")
watch(%r{^lib/^[^\.](.*)\.rb$})
guard 'middleman'#{options_hash} do
watch(%r{(.*)})
end
}
(@additional_guards || []).each do |block|
result = block.call(options, livereload)
guardfile_contents << result unless result.nil?
end
::Guard.start({ :guardfile_contents => guardfile_contents })
end
@ -37,7 +30,7 @@ module Middleman
end
module Guard
class MiddlemanConfig < Guard
class Middleman < Guard
def initialize(watchers = [], options = {})
super
@options = options
@ -48,14 +41,46 @@ module Guard
end
def run_on_change(paths)
needs_to_restart = false
paths.each do |path|
if path.match(%{^config\.rb}) || path.match(%r{^lib/^[^\.](.*)\.rb$})
needs_to_restart = true
break
end
end
if needs_to_restart
server_restart
elsif !@app.nil?
paths.each do |path|
@app.file_did_change(path)
end
end
end
def run_on_deletion(paths)
if !@app.nil?
paths.each do |path|
@app.file_did_delete(path)
end
end
end
private
def server_restart
server_stop
server_start
end
private
def server_start
@app = ::Middleman.server
puts "== The Middleman is standing watch on port #{@options[:Port]}"
@server_job = fork do
::Middleman.start_server(@options)
opts = @options.dup
opts[:app] = @app
::Middleman.start_server(opts)
end
end
@ -64,9 +89,7 @@ module Guard
Process.kill("KILL", @server_job)
Process.wait @server_job
@server_job = nil
# @server_options[:app] = nil
@app = nil
end
end
end
require "middleman/sitemap"
end