diff --git a/middleman-core/features/ignore.feature b/middleman-core/features/ignore.feature index 54e06b76..f98444bc 100644 --- a/middleman-core/features/ignore.feature +++ b/middleman-core/features/ignore.feature @@ -1,5 +1,5 @@ Feature: Ignoring paths - Scenario: Ignore a single path + Scenario: Ignore a single path (build) Given a fixture app "ignore-app" And a file named "config.rb" with: """ @@ -12,8 +12,23 @@ Feature: Ignoring paths And the following files should not exist: | build/plain.html | | build/about.html | + + Scenario: Ignore a single path (server) + Given a fixture app "ignore-app" + And a file named "config.rb" with: + """ + ignore 'about.html.erb' + ignore 'plain.html' + """ + And the Server is running + When I go to "/index.html" + Then I should not see "File Not Found" + When I go to "/plain.html" + Then I should see "File Not Found" + When I go to "/about.html" + Then I should see "File Not Found" - Scenario: Ignore a globbed path + Scenario: Ignore a globbed path (build) Given a fixture app "ignore-app" And a file named "config.rb" with: """ @@ -32,8 +47,34 @@ Feature: Ignoring paths | build/reports/index.html | | build/reports/another.html | | build/images/icons/messages.png | + + Scenario: Ignore a globbed path (server) + Given a fixture app "ignore-app" + And a file named "config.rb" with: + """ + ignore '*.erb' + ignore 'reports/*' + ignore 'images/**/*.png' + """ + And the Server is running + When I go to "/plain.html" + Then I should not see "File Not Found" + When I go to "/images/portrait.jpg" + Then I should not see "File Not Found" + When I go to "/images/pic.png" + Then I should not see "File Not Found" + When I go to "/about.html" + Then I should see "File Not Found" + When I go to "/index.html" + Then I should see "File Not Found" + When I go to "/reports/index.html" + Then I should see "File Not Found" + When I go to "/reports/another.html" + Then I should see "File Not Found" + When I go to "/images/icons/messages.png" + Then I should see "File Not Found" - Scenario: Ignore a regex + Scenario: Ignore a regex (build) Given a fixture app "ignore-app" And a file named "config.rb" with: """ @@ -52,8 +93,34 @@ Feature: Ignoring paths | build/reports/index.html | | build/reports/another.html | | build/images/icons/messages.png | + + Scenario: Ignore a regex (server) + Given a fixture app "ignore-app" + And a file named "config.rb" with: + """ + ignore /^.*\.erb/ + ignore /^reports\/.*/ + ignore /^images\.*\.png/ + """ + And the Server is running + When I go to "/plain.html" + Then I should not see "File Not Found" + When I go to "/images/portrait.jpg" + Then I should not see "File Not Found" + When I go to "/images/pic.png" + Then I should not see "File Not Found" + When I go to "/about.html" + Then I should see "File Not Found" + When I go to "/index.html" + Then I should see "File Not Found" + When I go to "/reports/index.html" + Then I should see "File Not Found" + When I go to "/reports/another.html" + Then I should see "File Not Found" + When I go to "/images/icons/messages.png" + Then I should see "File Not Found" - Scenario: Ignore with directory indexes (source file) + Scenario: Ignore with directory indexes (source file, build) Given a fixture app "ignore-app" And a file named "config.rb" with: """ @@ -67,8 +134,24 @@ Feature: Ignoring paths And the following files should not exist: | build/about/index.html | | build/plain/index.html | - - Scenario: Ignore with directory indexes (output path splat) + + Scenario: Ignore with directory indexes (source file, server) + Given a fixture app "ignore-app" + And a file named "config.rb" with: + """ + activate :directory_indexes + ignore 'about.html.erb' + ignore 'plain.html' + """ + And the Server is running + When I go to "/index.html" + Then I should not see "File Not Found" + When I go to "/about/index.html" + Then I should see "File Not Found" + When I go to "/plain/index.html" + Then I should see "File Not Found" + + Scenario: Ignore with directory indexes (output path splat, build) Given a fixture app "ignore-app" And a file named "config.rb" with: """ @@ -82,7 +165,23 @@ Feature: Ignoring paths And the following files should not exist: | build/about/index.html | | build/plain/index.html | - + + Scenario: Ignore with directory indexes (output path splat, server) + Given a fixture app "ignore-app" + And a file named "config.rb" with: + """ + activate :directory_indexes + ignore 'about*' + ignore 'plain*' + """ + And the Server is running + When I go to "/index.html" + Then I should not see "File Not Found" + When I go to "/about/index.html" + Then I should see "File Not Found" + When I go to "/plain/index.html" + Then I should see "File Not Found" + # Scenario: Ignore with directory indexes (output path index) # Given a fixture app "ignore-app" # And a file named "config.rb" with: diff --git a/middleman-core/fixtures/ignore-app/source/about.html.erb b/middleman-core/fixtures/ignore-app/source/about.html.erb index e69de29b..ae21d833 100644 --- a/middleman-core/fixtures/ignore-app/source/about.html.erb +++ b/middleman-core/fixtures/ignore-app/source/about.html.erb @@ -0,0 +1 @@ +About \ No newline at end of file diff --git a/middleman-core/fixtures/ignore-app/source/plain.html b/middleman-core/fixtures/ignore-app/source/plain.html index e69de29b..4061eab2 100644 --- a/middleman-core/fixtures/ignore-app/source/plain.html +++ b/middleman-core/fixtures/ignore-app/source/plain.html @@ -0,0 +1 @@ +Plain \ No newline at end of file diff --git a/middleman-core/lib/middleman-core.rb b/middleman-core/lib/middleman-core.rb index 09c14d8a..88bca456 100755 --- a/middleman-core/lib/middleman-core.rb +++ b/middleman-core/lib/middleman-core.rb @@ -45,11 +45,7 @@ module Middleman autoload :ERb, "middleman-core/renderers/erb" end - module Sitemap - autoload :Store, "middleman-core/sitemap/store" - autoload :Page, "middleman-core/sitemap/page" - autoload :Template, "middleman-core/sitemap/template" - end + autoload :Sitemap, "middleman-core/sitemap" module CoreExtensions # File Change Notifier @@ -207,7 +203,14 @@ module Middleman data end end - + + # Normalize a path to not include a leading slash + # @param [String] path + # @return [String] + def normalize_path(path) + path.sub(/^\//, "").gsub("%20", " ") + end + # Automatically load extensions from available RubyGems # which contain the EXTENSION_FILE # diff --git a/middleman-core/lib/middleman-core/base.rb b/middleman-core/lib/middleman-core/base.rb index cb026a34..9a945d12 100644 --- a/middleman-core/lib/middleman-core/base.rb +++ b/middleman-core/lib/middleman-core/base.rb @@ -223,7 +223,7 @@ class Middleman::Base register Middleman::CoreExtensions::Rendering # Sitemap - register Middleman::CoreExtensions::Sitemap + register Middleman::Sitemap # Setup external helpers register Middleman::CoreExtensions::ExternalHelpers @@ -327,11 +327,13 @@ class Middleman::Base @req = Rack::Request.new(env) @res = Rack::Response.new - if env["PATH_INFO"] == "/__middleman__" && env["REQUEST_METHOD"] == "POST" - if req.params.has_key?("change") - self.files.did_change(req.params["change"]) - elsif req.params.has_key?("delete") - self.files.did_delete(req.params["delete"]) + if env["PATH_INFO"] == "/__middleman__" + if env["REQUEST_METHOD"] == "POST" + if req.params.has_key?("change") + self.files.did_change(req.params["change"]) + elsif req.params.has_key?("delete") + self.files.did_delete(req.params["delete"]) + end end res.status = 200 @@ -372,32 +374,35 @@ class Middleman::Base def process_request # Normalize the path and add index if we're looking at a directory @original_path = env["PATH_INFO"].dup - @request_path = full_path(env["PATH_INFO"].gsub("%20", " ")) + @escaped_path = @original_path.gsub("%20", " ") + @request_path = full_path(@escaped_path) # Run before callbacks run_hook :before - # Get the page object for this path - sitemap_page = sitemap.page_by_destination(@request_path) - + if @escaped_path != @request_path + # Get the resource object for this path + resource = sitemap.find_resource_by_destination_path(@escaped_path) + end + + # Get the resource object for this full path + resource ||= sitemap.find_resource_by_destination_path(@request_path) + # Return 404 if not in sitemap - return not_found unless sitemap_page - - # Return 404 if this path is specifically ignored - return not_found if sitemap_page.ignored? + return not_found unless resource && !resource.ignored? # If this path is a static file, send it immediately - return send_file(sitemap_page.source_file) unless sitemap_page.template? + return send_file(resource.source_file) unless resource.template? # Set the current path for use in helpers self.current_path = @request_path.dup # Set a HTTP content type based on the request's extensions - content_type sitemap_page.mime_type + content_type resource.mime_type begin # Write out the contents of the page - res.write sitemap_page.render + res.write resource.render # Valid content is a 200 status res.status = 200 diff --git a/middleman-core/lib/middleman-core/cli/build.rb b/middleman-core/lib/middleman-core/cli/build.rb index 810702b2..4f9b91ed 100644 --- a/middleman-core/lib/middleman-core/cli/build.rb +++ b/middleman-core/lib/middleman-core/cli/build.rb @@ -95,16 +95,16 @@ module Middleman::Cli # Ignore following method desc "", "", :hide => true - # Render a page to a file. + # Render a resource to a file. # - # @param [Middleman::Sitemap::Page] page + # @param [Middleman::Sitemap::Resource] resource # @return [String] The full path of the file that was written - def render_to_file(page) + def render_to_file(resource) build_dir = self.class.shared_instance.build_dir - output_file = File.join(build_dir, page.destination_path) + output_file = File.join(build_dir, resource.destination_path) begin - response = self.class.shared_rack.get(page.request_path.gsub(/\s/, "%20")) + response = self.class.shared_rack.get(resource.destination_path.gsub(/\s/, "%20")) if response.status == 200 create_file(output_file, response.body, { :force => true }) else @@ -196,10 +196,10 @@ module Middleman::Cli sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css) # Pre-request CSS to give Compass a chance to build sprites - @app.sitemap.pages.select do |p| - p.ext == ".css" - end.each do |p| - Middleman::Cli::Build.shared_rack.get(p.request_path.gsub(/\s/, "%20")) + @app.sitemap.resources.select do |resource| + resource.ext == ".css" + end.each do |resource| + Middleman::Cli::Build.shared_rack.get(resource.destination_path.gsub(/\s/, "%20")) end # Double-check for compass sprites @@ -209,7 +209,7 @@ module Middleman::Cli # find files in the build folder when it needs to generate sprites for the # css files - pages = @app.sitemap.pages.sort do |a, b| + resources = @app.sitemap.resources.sort do |a, b| a_idx = sort_order.index(a.ext) || 100 b_idx = sort_order.index(b.ext) || 100 @@ -217,11 +217,10 @@ module Middleman::Cli end # Loop over all the paths and build them. - pages.each do |page| - next if page.ignored? - next if @config[:glob] && !File.fnmatch(@config[:glob], page.path) + resources.each do |resource| + next if @config[:glob] && !File.fnmatch(@config[:glob], resource.destination_path) - output_path = base.render_to_file(page) + output_path = base.render_to_file(resource) @cleaning_queue.delete(Pathname.new(output_path).realpath) if cleaning? end diff --git a/middleman-core/lib/middleman-core/core_extensions/assets.rb b/middleman-core/lib/middleman-core/core_extensions/assets.rb index 92cc4859..64202cd3 100644 --- a/middleman-core/lib/middleman-core/core_extensions/assets.rb +++ b/middleman-core/lib/middleman-core/core_extensions/assets.rb @@ -28,7 +28,9 @@ module Middleman::CoreExtensions::Assets path else # rewrite paths to use their destination path path = File.join(prefix, path) - path = sitemap.page(path).destination_path if sitemap.exists?(path) + if resource = sitemap.find_resource_by_path(path) + path = resource.path + end File.join(http_prefix, path) end diff --git a/middleman-core/lib/middleman-core/core_extensions/default_helpers.rb b/middleman-core/lib/middleman-core/core_extensions/default_helpers.rb index 8fe8fa82..17829bf7 100644 --- a/middleman-core/lib/middleman-core/core_extensions/default_helpers.rb +++ b/middleman-core/lib/middleman-core/core_extensions/default_helpers.rb @@ -68,7 +68,7 @@ module Middleman::CoreExtensions::DefaultHelpers path = path.gsub(File.extname(path), ".#{asset_ext}") path = path.gsub("/", separator) - yield path if sitemap.exists?(File.join(asset_dir, path)) + yield path if sitemap.find_resource_by_path(File.join(asset_dir, path)) end # Generate body css classes based on the current path diff --git a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb index 63bb2366..2593674e 100644 --- a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb +++ b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb @@ -16,6 +16,8 @@ module Middleman::CoreExtensions::FrontMatter app.extend ClassMethods app.send :include, InstanceMethods app.delegate :frontmatter_changed, :to => :"self.class" + + ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods end alias :included :registered end @@ -33,6 +35,16 @@ module Middleman::CoreExtensions::FrontMatter end end + module ResourceInstanceMethods + + # This page's frontmatter + # @return [Hash] + def data + app.frontmatter(relative_path).first + end + + end + # Frontmatter instance methods module InstanceMethods @@ -54,11 +66,11 @@ module Middleman::CoreExtensions::FrontMatter frontmatter_extension.remove_file(file) end - provides_metadata matcher do |path| - relative_path = path.sub(source_dir, "") + sitemap.provides_metadata matcher do |path| + relative_path = path.sub(self.source_dir, "") - fmdata = if frontmatter_extension.has_data?(relative_path) - frontmatter(relative_path)[0] + fmdata = if self.frontmatter_extension.has_data?(relative_path) + self.frontmatter(relative_path)[0] else {} end @@ -70,6 +82,9 @@ module Middleman::CoreExtensions::FrontMatter { :options => data, :page => fmdata } end + + # Initialize class + frontmatter_extension end # Notify callbacks that the frontmatter changed @@ -109,8 +124,7 @@ module Middleman::CoreExtensions::FrontMatter # Setup ignore callback @app.ignore do |path| - if @app.sitemap.exists?(path) - p = @app.sitemap.page(path) + if p = @app.sitemap.find_resource_by_path(path) !p.proxy? && p.data && p.data["ignored"] == true else false diff --git a/middleman-core/lib/middleman-core/core_extensions/i18n.rb b/middleman-core/lib/middleman-core/core_extensions/i18n.rb index 7253ba36..3e41c8b6 100644 --- a/middleman-core/lib/middleman-core/core_extensions/i18n.rb +++ b/middleman-core/lib/middleman-core/core_extensions/i18n.rb @@ -38,7 +38,7 @@ module Middleman::CoreExtensions::I18n # Don't output localizable files ignore File.join(@templates_dir, "**/*") - provides_metadata_for_path do |url| + sitemap.provides_metadata_for_path do |url| if d = get_localization_data(url) lang, page_id = d instance_vars = Proc.new { diff --git a/middleman-core/lib/middleman-core/core_extensions/rendering.rb b/middleman-core/lib/middleman-core/core_extensions/rendering.rb index 3fa1d369..73f38f96 100644 --- a/middleman-core/lib/middleman-core/core_extensions/rendering.rb +++ b/middleman-core/lib/middleman-core/core_extensions/rendering.rb @@ -121,10 +121,9 @@ module Middleman::CoreExtensions::Rendering engine = nil # If the path is known to the sitemap - if sitemap.exists?(current_path) - page = sitemap.page(current_path) - current_dir = File.dirname(page.source_file) - engine = File.extname(page.source_file)[1..-1].to_sym + if resource = sitemap.find_resource_by_destination_path(current_path) + current_dir = File.dirname(resource.source_file) + engine = File.extname(resource.source_file)[1..-1].to_sym # Look for partials relative to the current path if current_dir != self.source_dir diff --git a/middleman-core/lib/middleman-core/core_extensions/routing.rb b/middleman-core/lib/middleman-core/core_extensions/routing.rb index c7ee9c00..5aff173c 100644 --- a/middleman-core/lib/middleman-core/core_extensions/routing.rb +++ b/middleman-core/lib/middleman-core/core_extensions/routing.rb @@ -52,7 +52,7 @@ module Middleman::CoreExtensions::Routing if url.is_a?(Regexp) || url.include?("*") # Use the metadata loop for matching against paths at runtime - provides_metadata_for_path url do |url| + sitemap.provides_metadata_for_path url do |url| { :options => opts, :blocks => [a_block] } end @@ -80,7 +80,7 @@ module Middleman::CoreExtensions::Routing end # Setup a metadata matcher for rendering those options - provides_metadata_for_path url do |url| + sitemap.provides_metadata_for_path url do |url| { :options => opts, :blocks => [a_block] } end end diff --git a/middleman-core/lib/middleman-core/core_extensions/sitemap.rb b/middleman-core/lib/middleman-core/core_extensions/sitemap.rb deleted file mode 100644 index 89702606..00000000 --- a/middleman-core/lib/middleman-core/core_extensions/sitemap.rb +++ /dev/null @@ -1,103 +0,0 @@ -# Core Sitemap Extensions -module Middleman::CoreExtensions::Sitemap - - # Setup Extension - class << self - - # Once registered - def registered(app) - # Setup callbacks which can exclude paths from the sitemap - app.set :ignored_sitemap_matchers, { - # dotfiles and folders in the root - :root_dotfiles => proc { |file, path| file.match(/^\./) }, - - # Files starting with an dot, but not .htaccess - :source_dotfiles => proc { |file, path| - (file.match(/\/\./) && !file.match(/\/\.htaccess/)) - }, - - # Files starting with an underscore, but not a double-underscore - :partials => proc { |file, path| (file.match(/\/_/) && !file.match(/\/__/)) }, - - :layout => proc { |file, path| - file.match(/^source\/layout\./) || file.match(/^source\/layouts\//) - }, - - # Files without any output extension (layouts, partials) - # :extensionless => proc { |file, path| !path.match(/\./) }, - } - - # Include instance methods - app.send :include, InstanceMethods - end - alias :included :registered - end - - # Sitemap instance methods - module InstanceMethods - - # Extend initialize to listen for change events - def initialize - super - - # Cleanup paths - static_path = source_dir.sub(root, "").sub(/^\//, "") - sitemap_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}}) - - # Register file change callback - files.changed sitemap_regex do |file| - sitemap.touch_file(file) - end - - # Register file delete callback - files.deleted sitemap_regex do |file| - sitemap.remove_file(file) - end - end - - # Get the sitemap class instance - # @return [Middleman::Sitemap::Store] - def sitemap - @_sitemap ||= ::Middleman::Sitemap::Store.new(self) - end - - # Get the page object for the current path - # @return [Middleman::Sitemap::Page] - def current_page - sitemap.page_by_destination(current_path) - end - - # Ignore a path, regex or callback - # @param [String, Regexp] - # @return [void] - def ignore(*args, &block) - sitemap.ignore(*args, &block) - end - - # Proxy one path to another - # @param [String] url - # @param [String] target - # @return [void] - def proxy(*args) - sitemap.proxy(*args) - end - - # Register a handler to provide metadata on a file path - # @param [Regexp] matcher - # @return [Array>] - def provides_metadata(matcher=nil, &block) - @_provides_metadata ||= [] - @_provides_metadata << [block, matcher] if block_given? - @_provides_metadata - end - - # Register a handler to provide metadata on a url path - # @param [Regexp] matcher - # @return [Array>] - def provides_metadata_for_path(matcher=nil, &block) - @_provides_metadata_for_path ||= [] - @_provides_metadata_for_path << [block, matcher] if block_given? - @_provides_metadata_for_path - end - end -end diff --git a/middleman-core/lib/middleman-core/extensions/directory_indexes.rb b/middleman-core/lib/middleman-core/extensions/directory_indexes.rb index 689fb7f6..79b8caa9 100644 --- a/middleman-core/lib/middleman-core/extensions/directory_indexes.rb +++ b/middleman-core/lib/middleman-core/extensions/directory_indexes.rb @@ -9,49 +9,47 @@ module Middleman::Extensions # Once registered def registered(app) - app.after_configuration do - # Register a reroute transform that turns regular paths into indexed paths - sitemap.reroute do |destination, page| - new_index_path = "/#{index_file}" - - # Check if it would be pointless to reroute - path = page.path - page_already_index = path == index_file || path.end_with?(new_index_path) - if page_already_index || File.extname(index_file) != File.extname(path) - next destination - end - - # Check if frontmatter turns directory_index off - d = page.data - next destination if d && d["directory_index"] == false - - # Check if file metadata (options set by "page" in config.rb) turns directory_index off - # TODO: This is crazy - we need a generic way to get metadata for paths - metadata_ignore = false - provides_metadata_for_path.each do |callback, matcher| - if matcher.is_a? Regexp - next if !path.match(matcher) - elsif matcher.is_a? String - next if !File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{path}") - end - - result = instance_exec(path, &callback) - if result[:options] && result[:options][:directory_index] == false - metadata_ignore = true - break - end - end - - next destination if metadata_ignore - - # Not ignored, so reroute - destination.chomp(File.extname(index_file)) + new_index_path - end + app.ready do + sitemap.register_resource_list_manipulator( + :directory_indexes, + DirectoryIndexManager.new(self) + ) end end alias :included :registered end + + class DirectoryIndexManager + def initialize(app) + @app = app + end + + # Update the main sitemap resource list + # @return [void] + def manipulate_resource_list(resources) + index_file = @app.index_file + new_index_path = "/#{index_file}" + + resources.each do |resource| + # Check if it would be pointless to reroute + next if resource.path == index_file || + resource.path.end_with?(new_index_path) || + File.extname(index_file) != resource.ext + + # Check if frontmatter turns directory_index off + d = resource.data + next if d && d["directory_index"] == false + + # Check if file metadata (options set by "page" in config.rb) turns directory_index off + if resource.metadata[:options] && resource.metadata[:options][:directory_index] == false + next + end + + resource.destination_path = resource.path.chomp(File.extname(index_file)) + new_index_path + end + end + end end # Register the extension diff --git a/middleman-core/lib/middleman-core/sitemap.rb b/middleman-core/lib/middleman-core/sitemap.rb new file mode 100644 index 00000000..b3323a8d --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap.rb @@ -0,0 +1,78 @@ +# Core Sitemap Extensions +module Middleman::Sitemap + + autoload :Store, "middleman-core/sitemap/store" + autoload :Resource, "middleman-core/sitemap/resource" + + module Extensions + autoload :OnDisk, "middleman-core/sitemap/extensions/on_disk" + autoload :Proxies, "middleman-core/sitemap/extensions/proxies" + autoload :Ignores, "middleman-core/sitemap/extensions/ignores" + autoload :Traversal, "middleman-core/sitemap/extensions/traversal" + end + + # Setup Extension + class << self + + # Once registered + def registered(app) + + app.register Middleman::Sitemap::Extensions::Proxies + app.register Middleman::Sitemap::Extensions::Ignores + + # Setup callbacks which can exclude paths from the sitemap + app.set :ignored_sitemap_matchers, { + # dotfiles and folders in the root + :root_dotfiles => proc { |file, path| file.match(/^\./) }, + + # Files starting with an dot, but not .htaccess + :source_dotfiles => proc { |file, path| + (file.match(/\/\./) && !file.match(/\/\.htaccess/)) + }, + + # Files starting with an underscore, but not a double-underscore + :partials => proc { |file, path| (file.match(/\/_/) && !file.match(/\/__/)) }, + + :layout => proc { |file, path| + file.match(/^source\/layout\./) || file.match(/^source\/layouts\//) + }, + + # Files without any output extension (layouts, partials) + # :extensionless => proc { |file, path| !path.match(/\./) }, + } + + # Include instance methods + app.send :include, InstanceMethods + + # Initialize Sitemap + app.before_configuration do + sitemap + end + end + alias :included :registered + + end + + # Sitemap instance methods + module InstanceMethods + + # Get the sitemap class instance + # @return [Middleman::Sitemap::Store] + def sitemap + @_sitemap ||= Store.new(self) + end + + # Get the resource object for the current path + # @return [Middleman::Sitemap::Resource] + def current_page + current_resource + end + + # Get the resource object for the current path + # @return [Middleman::Sitemap::Resource] + def current_resource + sitemap.find_resource_by_destination_path(current_path) + end + + end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb b/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb new file mode 100644 index 00000000..06721dee --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb @@ -0,0 +1,81 @@ +module Middleman::Sitemap::Extensions + + module Ignores + + # Setup extension + class << self + + # Once registered + def registered(app) + # Include methods + app.send :include, InstanceMethods + + ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods + end + + alias :included :registered + end + + module ResourceInstanceMethods + def ignored? + @app.ignore_manager.ignored?(path) || + (!proxy? && + @app.ignore_manager.ignored?(source_file.sub("#{@app.source_dir}/", "")) + ) + end + end + + module InstanceMethods + def ignore_manager + @_ignore_manager ||= IgnoreManager.new(self) + end + + def ignore(*args, &block) + ignore_manager.ignore(*args, &block) + end + end + + class IgnoreManager + def initialize(app) + @app = app + + @ignored_callbacks = [] + end + + # Ignore a path or add an ignore callback + # @param [String, Regexp] path, path glob expression, or path regex + # @return [void] + def ignore(path=nil, &block) + if path.is_a? Regexp + @ignored_callbacks << Proc.new {|p| p =~ path } + elsif path.is_a? String + path_clean = ::Middleman.normalize_path(path) + if path_clean.include?("*") # It's a glob + @ignored_callbacks << Proc.new {|p| File.fnmatch(path_clean, p) } + else + @ignored_callbacks << Proc.new {|p| p == path_clean } + end + elsif block_given? + @ignored_callbacks << block + end + + @app.sitemap.rebuild_resource_list!(:added_ignore_rule) + end + + # Whether a path is ignored + # @param [String] path + # @return [Boolean] + def ignored?(path) + path_clean = ::Middleman.normalize_path(path) + @ignored_callbacks.any? { |b| b.call(path_clean) } + end + + # Update the main sitemap resource list + # @return [void] + def manipulate_resource_list(resources) + # No op + resources + end + end + end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb b/middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb new file mode 100644 index 00000000..05102d1b --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb @@ -0,0 +1,120 @@ +module Middleman::Sitemap::Extensions + class OnDisk + + attr_accessor :sitemap + attr_accessor :waiting_for_ready + + def initialize(sitemap) + @sitemap = sitemap + @app = @sitemap.app + + @file_paths_on_disk = [] + + # Cleanup paths + # static_path = @app.source_dir.sub(@app.root, "").sub(/^\//, "") + # sitemap_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}}) + + scoped_self = self + @waiting_for_ready = true + + # Register file change callback + @app.files.changed do |file| + scoped_self.touch_file(file, !scoped_self.waiting_for_ready) + end + + # Register file delete callback + @app.files.deleted do |file| + scoped_self.remove_file(file, !scoped_self.waiting_for_ready) + end + + @app.ready do + scoped_self.waiting_for_ready = false + scoped_self.sitemap.rebuild_resource_list!(:on_disk_ready) + end + end + + # Update or add an on-disk file path + # @param [String] file + # @return [Boolean] + def touch_file(file, rebuild=true) + return false if file == @app.source_dir || File.directory?(file) + + path = file_to_path(file) + return false unless path + + return false if @app.ignored_sitemap_matchers.any? do |name, callback| + callback.call(file, path) + end + + if !@file_paths_on_disk.include?(file) + @file_paths_on_disk << file + @sitemap.rebuild_resource_list!(:added_file) if rebuild + end + end + + # Remove a file from the store + # @param [String] file + # @return [void] + def remove_file(file, rebuild=true) + if @file_paths_on_disk.include?(file) + @file_paths_on_disk.delete(file) + @sitemap.rebuild_resource_list!(:removed_file) if rebuild + end + end + + # Update the main sitemap resource list + # @return [void] + def manipulate_resource_list(resources) + resources + @file_paths_on_disk.map do |file| + ::Middleman::Sitemap::Resource.new( + @sitemap, + file_to_path(file), + File.expand_path(file, @app.root) + ) + end + end + + # Get the URL path for an on-disk file + # @param [String] file + # @return [String] + def file_to_path(file) + file = File.expand_path(file, @app.root) + + prefix = @app.source_dir.sub(/\/$/, "") + "/" + return false unless file.include?(prefix) + + path = file.sub(prefix, "") + extensionless_path(path) + end + + # Get a path without templating extensions + # @param [String] file + # @return [String] + def extensionless_path(file) + path = file.dup + + end_of_the_line = false + while !end_of_the_line + if !::Tilt[path].nil? + path = path.sub(File.extname(path), "") + else + end_of_the_line = true + end + end + + # If there is no extension, look for one + if File.extname(path).empty? + input_ext = File.extname(file) + + if !input_ext.empty? + input_ext = input_ext.split(".").last.to_sym + if @app.template_extensions.has_key?(input_ext) + path << ".#{@app.template_extensions[input_ext]}" + end + end + end + + path + end + end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb new file mode 100644 index 00000000..19780e3a --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb @@ -0,0 +1,97 @@ +module Middleman::Sitemap::Extensions + + module Proxies + + # Setup extension + class << self + + # Once registered + def registered(app) + ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods + + # Include methods + app.send :include, InstanceMethods + end + + alias :included :registered + end + + module ResourceInstanceMethods + # Whether this page is a proxy + # @return [Boolean] + def proxy? + !!@proxied_to + end + + # Set this page to proxy to a target path + # @param [String] target + # @return [void] + def proxy_to(target) + @proxied_to = target + end + + # The path of the page this page is proxied to, or nil if it's not proxied. + # @return [String] + def proxied_to + @proxied_to + end + + # Whether this page has a template file + # @return [Boolean] + def template? + if proxy? + store.find_resource_by_path(proxied_to).template? + else + super + end + end + + def get_source_file + if proxy? + store.find_resource_by_path(proxied_to).source_file + end + end + end + + module InstanceMethods + def proxy_manager + @_proxy_manager ||= ProxyManager.new(self) + end + + def proxy(*args) + proxy_manager.proxy(*args) + end + end + + class ProxyManager + def initialize(app) + @app = app + + @proxy_paths = {} + end + + # Setup a proxy from a path to a target + # @param [String] path + # @param [String] target + # @return [void] + def proxy(path, target) + @proxy_paths[::Middleman.normalize_path(path)] = ::Middleman.normalize_path(target) + @app.sitemap.rebuild_resource_list!(:added_proxy) + end + + # Update the main sitemap resource list + # @return [void] + def manipulate_resource_list(resources) + resources + @proxy_paths.map do |key, value| + p = ::Middleman::Sitemap::Resource.new( + @app.sitemap, + key + ) + p.proxy_to(value) + p + end + end + end + end + +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/traversal.rb b/middleman-core/lib/middleman-core/sitemap/extensions/traversal.rb new file mode 100644 index 00000000..9d49ee45 --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap/extensions/traversal.rb @@ -0,0 +1,77 @@ +module Middleman::Sitemap::Extensions + module Traversal + # This resource's parent resource + # @return [Middleman::Sitemap::Resource, nil] + def parent + parts = path.split("/") + parts.pop if path.include?(app.index_file) + + return nil if parts.length < 1 + + parts.pop + parts << app.index_file + + parent_path = "/" + parts.join("/") + + store.find_resource_by_destination_path(parent_path) + end + + # This resource's child resources + # @return [Array] + def children + return [] unless directory_index? + + if eponymous_directory? + base_path = eponymous_directory_path + prefix = %r|^#{base_path.sub("/", "\\/")}| + else + base_path = path.sub("#{app.index_file}", "") + prefix = %r|^#{base_path.sub("/", "\\/")}| + end + + store.resources.select do |sub_resource| + if sub_resource.path == self.path || sub_resource.path !~ prefix + false + else + inner_path = sub_resource.path.sub(prefix, "") + parts = inner_path.split("/") + if parts.length == 1 + true + elsif parts.length == 2 + parts.last == app.index_file + else + false + end + end + end + end + + # This resource's sibling resources + # @return [Array] + def siblings + return [] unless parent + parent.children.reject { |p| p == self } + end + + # Whether this resource either a directory index, or has the same name as an existing directory in the source + # @return [Boolean] + def directory_index? + path.include?(app.index_file) || path =~ /\/$/ || eponymous_directory? + end + + # Whether the resource has the same name as a directory in the source + # (e.g., if the resource is named 'gallery.html' and a path exists named 'gallery/', this would return true) + # @return [Boolean] + def eponymous_directory? + full_path = File.join(app.source_dir, eponymous_directory_path) + !!(File.exists?(full_path) && File.directory?(full_path)) + end + + # The path for this resource if it were a directory, and not a file + # (e.g., for 'gallery.html' this would return 'gallery/') + # @return [String] + def eponymous_directory_path + path.sub(ext, '/').sub(/\/$/, "") + "/" + end + end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/page.rb b/middleman-core/lib/middleman-core/sitemap/page.rb deleted file mode 100644 index 99d1a2d0..00000000 --- a/middleman-core/lib/middleman-core/sitemap/page.rb +++ /dev/null @@ -1,270 +0,0 @@ -# Sitemap namespace -module Middleman::Sitemap - - # Sitemap Page class - class Page - # @return [Middleman::Sitemap::Store] - attr_accessor :store - - # The source path of this page (relative to the source directory, - # without template extensions) - # @return [String] - attr_accessor :path - - # The path of the page this page is proxied to, or nil if it's not proxied. - # @return [String] - attr_accessor :proxied_to - - # Initialize page with parent store and URL - # @param [Middleman::Sitemap::Store] store - # @param [String] path - def initialize(store, path) - @store = store - @path = path - @source_file = nil - @proxied_to = nil - end - - # Whether this page has a template file - # @return [Boolean] - def template? - if proxy? - store.page(proxied_to).template? - else - return false if source_file.nil? - !::Tilt[source_file].nil? - end - end - - # Internal path to be requested when rendering this page - # @return [String] - def request_path - destination_path - end - - # Set the on-disk source file for this page - # @param [String] src - # @return [void] - def source_file=(src) - @source_file = src - end - - # The on-disk source file - # @return [String] - def source_file - if proxy? - store.page(proxied_to).source_file - else - @source_file - end - end - - # The template instance - # @return [Middleman::Sitemap::Template] - def template - @_template ||= ::Middleman::Sitemap::Template.new(self) - end - - # Extension of the path (i.e. '.js') - # @return [String] - def ext - File.extname(path) - end - - # Mime type of the path - # @return [String] - def mime_type - app.mime_type ext - end - - # Whether this page is a proxy - # @return [Boolean] - def proxy? - !!@proxied_to - end - - # Set this page to proxy to a target path - # @param [String] target - # @return [void] - def proxy_to(target) - @proxied_to = target - end - - # Whether this page is ignored - # @return [Boolean] - def ignored? - return true if store.ignored?(self.path) - - if !@source_file.nil? - relative_source = @source_file.sub(app.source_dir, '') - if self.path.sub(/^\//, "") != relative_source.sub(/^\//, "") - store.ignored?(relative_source) - else - false - end - else - false - end - end - - # Set this page to be ignored - # @return [void] - def ignore - store.ignore(self.path) - end - - # Render this page - # @return [String] - def render(*args, &block) - if template? - if proxy? - t = store.page(proxied_to).template - t.request_path = path - t.render(*args) - else - template.request_path = path - template.render(*args, &block) - end - else # just a static file - File.open(source_file).read - end - end - - # Whether this page either a directory index, or has the same name as an existing directory in the source - # @return [Boolean] - def directory_index? - path.include?(app.index_file) || path =~ /\/$/ || eponymous_directory? - end - - # Whether the page has the same name as a directory in the source - # (e.g., if the page is named 'gallery.html' and a path exists named 'gallery/', this would return true) - # @return [Boolean] - def eponymous_directory? - full_path = File.join(app.source_dir, eponymous_directory_path) - !!(File.exists?(full_path) && File.directory?(full_path)) - end - - # The path for this page if it were a directory, and not a file - # (e.g., for 'gallery.html' this would return 'gallery/') - # @return [String] - def eponymous_directory_path - path.sub('.html', '/').sub(/\/$/, "") + "/" - # TODO: Seems like .html shouldn't be hardcoded here - end - - # A path without the directory index - so foo/index.html becomes - # just foo. Best for linking. - # @return [String] - def url - '/' + destination_path.sub(/#{Regexp.escape(app.index_file)}$/, '') - end - - # Get the relative path from the source - # @return [String] - def relative_path - self.source_file ? self.source_file.sub(app.source_dir, '') : nil - end - - # Get the destination path, relative to the build directory. - # This path can be affected by proxy callbacks. - # @return [String] - def destination_path - store.reroute_callbacks.inject(self.path) do |destination, callback| - callback.call(destination, self) - end - end - - # This page's frontmatter - # @return [Hash] - def data - app.frontmatter(relative_path).first - end - - # This page's parent page - # @return [Middleman::Sitemap::Page, nil] - def parent - parts = path.split("/") - if path.include?(app.index_file) - parts.pop - end - - return nil if parts.length < 1 - - parts.pop - parts.push(app.index_file) - - parent_path = "/" + parts.join("/") - - if store.exists?(parent_path) - store.page(parent_path) - else - nil - end - end - - # This page's child pages - # @return [Array] - def children - return [] unless directory_index? - - if eponymous_directory? - base_path = eponymous_directory_path - prefix = %r|^#{base_path.sub("/", "\\/")}| - else - base_path = path.sub("#{app.index_file}", "") - prefix = %r|^#{base_path.sub("/", "\\/")}| - end - - store.pages.select do |sub_page| - if sub_page == self || sub_page.path !~ prefix || sub_page.ignored? - false - else - inner_path = sub_page.path.sub(prefix, "") - parts = inner_path.split("/") - if parts.length == 1 - true - elsif parts.length == 2 - parts.last == app.index_file - else - false - end - end - end - end - - # This page's sibling pages - # @return [Array] - def siblings - return [] unless parent - parent.children.reject { |p| p == self } - end - - # A cache for extensions and internals to use to store - # information about this page. The cache is cleared whenever - # the page changes. - # @return [Middleman::Cache] - def cache - @cache ||= Middleman::Cache.new - end - - # Clear out the cache whenever this page changes - # @return [void] - def touch - cache.clear - end - - # Clear the cache if the file is deleted - # @return [void] - def delete - touch - end - - protected - - # This page's stored app - # @return [Middleman::Base] - def app - store.app - end - end -end diff --git a/middleman-core/lib/middleman-core/sitemap/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb new file mode 100644 index 00000000..2d06652c --- /dev/null +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -0,0 +1,126 @@ +# Sitemap namespace +module Middleman::Sitemap + + # Sitemap Resource class + class Resource + include Middleman::Sitemap::Extensions::Traversal + + # @return [Middleman::Base] + attr_reader :app + + # @return [Middleman::Sitemap::Store] + attr_reader :store + + # The source path of this resource (relative to the source directory, + # without template extensions) + # @return [String] + attr_reader :path + + # Set the on-disk source file for this resource + # @return [String] + # attr_reader :source_file + + def source_file + @source_file || get_source_file + end + + # Initialize resource with parent store and URL + # @param [Middleman::Sitemap::Store] store + # @param [String] path + # @param [String] source_file + def initialize(store, path, source_file=nil) + @store = store + @app = @store.app + @path = path + @source_file = source_file + + @destination_paths = [@path] + end + + # Whether this resource has a template file + # @return [Boolean] + def template? + return false if source_file.nil? + !::Tilt[source_file].nil? + end + + # Get the metadata for both the current source_file and the current path + # @return [Hash] + def metadata + store.metadata_for_file(source_file).deep_merge( + store.metadata_for_path(path) + ) + end + + # Get the output/preview URL for this resource + # @return [String] + def destination_path + @destination_paths.last + end + + # Set the output/preview URL for this resource + # @param [String] path + # @return [void] + def destination_path=(path) + @destination_paths << path + end + + # The template instance + # @return [Middleman::Sitemap::Template] + def template + @_template ||= ::Middleman::Sitemap::Template.new(self) + end + + # Extension of the path (i.e. '.js') + # @return [String] + def ext + File.extname(path) + end + + # Mime type of the path + # @return [String] + def mime_type + app.mime_type ext + end + + # Render this resource + # @return [String] + def render(opts={}, locs={}, &block) + return File.open(source_file).read unless template? + + puts "== Render Start: #{source_file}" if app.logging? + + md = metadata.dup + opts = md[:options].deep_merge(opts) + locs = md[:locals].deep_merge(locs) + + # Forward remaining data to helpers + if md.has_key?(:page) + app.data.store("page", md[:page]) + end + + md[:blocks].flatten.compact.each do |block| + app.instance_eval(&block) + end + + app.instance_eval(&block) if block_given? + result = app.render_template(source_file, locs, opts) + + puts "== Render End: #{source_file}" if app.logging? + result + end + + # A path without the directory index - so foo/index.html becomes + # just foo. Best for linking. + # @return [String] + def url + '/' + path.sub(/#{Regexp.escape(app.index_file)}$/, '') + end + + # Get the relative path from the source + # @return [String] + def relative_path + source_file ? source_file.sub(app.source_dir, '') : nil + end + end +end diff --git a/middleman-core/lib/middleman-core/sitemap/store.rb b/middleman-core/lib/middleman-core/sitemap/store.rb index 7bc83f65..d6a4d2e7 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -1,10 +1,13 @@ +# Used for merging results of metadata callbacks +require "active_support/core_ext/hash/deep_merge" + # Sitemap namespace module Middleman::Sitemap # The Store class # - # The Store manages a collection of Page objects, which represent - # individual items in the sitemap. Pages are indexed by "source path", + # The Store manages a collection of Resource objects, which represent + # individual items in the sitemap. Resources are indexed by "source path", # which is the path relative to the source directory, minus any template # extensions. All "path" parameters used in this class are source paths. class Store @@ -15,187 +18,131 @@ module Middleman::Sitemap # Initialize with parent app # @param [Middleman::Base] app def initialize(app) - @app = app - @pages = {} - @ignored_callbacks = [] - @reroute_callbacks = [] - end - - # A list of all pages - # @return [Array] - def pages - @pages.values + @app = app + @resources = [] + + @_lookup_cache = { :path => {}, :destination_path => {} } + @resource_list_manipulators = [] + + # Register classes which can manipulate the main site map list + register_resource_list_manipulator(:on_disk, Middleman::Sitemap::Extensions::OnDisk.new(self), false) + + # Proxies + register_resource_list_manipulator(:proxies, @app.proxy_manager, false) + + # Ignores + register_resource_list_manipulator(:ignores, @app.ignore_manager, false) + + rebuild_resource_list!(:after_base_init) end - # Check to see if we know about a specific path - # @param [String] path - # @return [Boolean] - def exists?(path) - @pages.has_key?(normalize_path(path)) + # Register a klass which can manipulate the main site map list + # @param [Class] klass + # @param [Boolean] immediately_rebuild + # @return [void] + def register_resource_list_manipulator(name, inst, immediately_rebuild=true) + @resource_list_manipulators << [name, inst] + rebuild_resource_list!(:registered_new) if immediately_rebuild end - # Ignore a path or add an ignore callback - # @param [String, Regexp] path, path glob expression, or path regex + # Rebuild the list of resources from scratch, using registed manipulators # @return [void] - def ignore(path=nil, &block) - if path.is_a? Regexp - @ignored_callbacks << Proc.new {|p| p =~ path } - elsif path.is_a? String - path_clean = normalize_path(path) - if path_clean.include?("*") # It's a glob - @ignored_callbacks << Proc.new {|p| File.fnmatch(path_clean, p) } - else - @ignored_callbacks << Proc.new {|p| p == path_clean } - end - elsif block_given? - @ignored_callbacks << block + def rebuild_resource_list!(reason=nil) + @resources = @resource_list_manipulators.inject([]) do |result, (_, inst)| + inst.manipulate_resource_list(result) + end + + # Reset lookup cache + cache_structure = { :path => {}, :destination_path => {} } + @_lookup_cache = @resources.inject(cache_structure) do |cache, resource| + cache[:path][resource.path] = resource + cache[:destination_path][resource.destination_path] = resource + cache end end - # Add a callback that will be run with each page's destination path - # and can produce a new destination path or pass through the old one. - # @return [void] - def reroute(&block) - @reroute_callbacks << block if block_given? - end - - # The list of reroute callbacks - # @return [Array] - def reroute_callbacks - @reroute_callbacks - end - - # Setup a proxy from a path to a target - # @param [String] path - # @param [String] target - # @return [void] - def proxy(path, target) - add(path).proxy_to(normalize_path(target)) - app.cache.remove(:proxied_paths) - end - - # Add a new page to the sitemap - # @param [String] path - # @return [Middleman::Sitemap::Page] - def add(path) - path = normalize_path(path) - @pages.fetch(path) { @pages[path] = ::Middleman::Sitemap::Page.new(self, path) } + # Find a resource given its original path + # @param [String] request_path The original path of a resource. + # @return [Middleman::Sitemap::Resource] + def find_resource_by_path(request_path) + request_path = ::Middleman.normalize_path(request_path) + @_lookup_cache[:path][request_path] end - # Get a page instance for a given path, or nil if that page doesn't exist in the sitemap - # @param [String] path - # @return [Middleman::Sitemap::Page] - def page(path) - path = normalize_path(path) - @pages[path] + # Find a resource given its destination path + # @param [String] request_path The destination (output) path of a resource. + # @return [Middleman::Sitemap::Resource] + def find_resource_by_destination_path(request_path) + request_path = ::Middleman.normalize_path(request_path) + @_lookup_cache[:destination_path][request_path] end - - # Find a page given its destination path - # @param [String] The destination (output) path of a page. - # @return [Middleman::Sitemap::Page] - def page_by_destination(destination_path) - destination_path = normalize_path(destination_path) - pages.find do |p| - p.destination_path == destination_path || - p.destination_path == destination_path.sub("/#{@app.index_file}", "") + + # Get the array of all resources + # @param [Boolean] include_ignored Whether to include ignored resources + # @return [Array] + def resources(include_ignored=false) + if include_ignored + @resources + else + @resources.reject(&:ignored?) end end - # Whether a path is ignored - # @param [String] path - # @return [Boolean] - def ignored?(path) - path_clean = normalize_path(path) - @ignored_callbacks.any? { |b| b.call(path_clean) } + # Register a handler to provide metadata on a file path + # @param [Regexp] matcher + # @return [Array>] + def provides_metadata(matcher=nil, &block) + @_provides_metadata ||= [] + @_provides_metadata << [block, matcher] if block_given? + @_provides_metadata end - # Remove a file from the store - # @param [String] file - # @return [void] - def remove_file(file) - path = file_to_path(file) - return false unless path + # Get the metadata for a specific file + # @param [String] source_file + # @return [Hash] + def metadata_for_file(source_file) + blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } - path = normalize_path(path) - if @pages.has_key?(path) - page(path).delete() - @pages.delete(path) + provides_metadata.inject(blank_metadata) do |result, (callback, matcher)| + next result if !matcher.nil? && !source_file.match(matcher) + + metadata = callback.call(source_file) + result.deep_merge(metadata) end end - # Get the URL path for an on-disk file - # @param [String] file - # @return [String] - def file_to_path(file) - file = File.expand_path(file, @app.root) - - prefix = @app.source_dir.sub(/\/$/, "") + "/" - return false unless file.include?(prefix) - - path = file.sub(prefix, "") - extensionless_path(path) + # Register a handler to provide metadata on a url path + # @param [Regexp] matcher + # @return [Array>] + def provides_metadata_for_path(matcher=nil, &block) + @_provides_metadata_for_path ||= [] + @_provides_metadata_for_path << [block, matcher] if block_given? + @_provides_metadata_for_path end - # Update or add an on-disk file path - # @param [String] file - # @return [Boolean] - def touch_file(file) - return false if file == @app.source_dir || File.directory?(file) + # Get the metadata for a specific URL + # @param [String] request_path + # @return [Hash] + def metadata_for_path(request_path) + blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } - path = file_to_path(file) - return false unless path - - return false if @app.ignored_sitemap_matchers.any? do |name, callback| - callback.call(file, path) - end - - # Add generic path - p = add(path) - p.source_file = File.expand_path(file, @app.root) - p.touch - - true - end - - protected - - # Get a path without templating extensions - # @param [String] file - # @return [String] - def extensionless_path(file) - app.cache.fetch(:extensionless_path, file) do - path = file.dup - - end_of_the_line = false - while !end_of_the_line - if !::Tilt[path].nil? - path = path.sub(File.extname(path), "") - else - end_of_the_line = true - end - end - - # If there is no extension, look for one - if File.extname(path).empty? - input_ext = File.extname(file) - - if !input_ext.empty? - input_ext = input_ext.split(".").last.to_sym - if app.template_extensions.has_key?(input_ext) - path << ".#{app.template_extensions[input_ext]}" - end - end + provides_metadata_for_path.inject(blank_metadata) do |result, (callback, matcher)| + case matcher + when Regexp + next result unless request_path.match(matcher) + when String + next result unless File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{request_path}") end - path + metadata = callback.call(request_path) + + if metadata.has_key?(:blocks) + result[:blocks] << metadata[:blocks] + metadata.delete(:blocks) + end + + result.deep_merge(metadata) end end - - # Normalize a path to not include a leading slash - # @param [String] path - # @return [String] - def normalize_path(path) - path.sub(/^\//, "").gsub("%20", " ") - end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/template.rb b/middleman-core/lib/middleman-core/sitemap/template.rb deleted file mode 100644 index 87a52a87..00000000 --- a/middleman-core/lib/middleman-core/sitemap/template.rb +++ /dev/null @@ -1,95 +0,0 @@ -# Used for merging results of metadata callbacks -require "active_support/core_ext/hash/deep_merge" - -# Sitemap namespace -module Middleman::Sitemap - - # Template class - class Template - - # @return [Middleman::Sitemap::Page] - attr_accessor :page - - # @return [Hash] - attr_accessor :options - - # @return [Hash] - attr_accessor :locals - - # @return [String] - attr_accessor :request_path - - # Initialize template with parent page - # @param [Middleman::Sitemap:Page] page - def initialize(page) - @page = page - @options = {} - @locals = {} - @blocks = [] - end - - # Simple aliases - delegate :path, :source_file, :store, :app, :ext, :to => :page - - # Get the metadata for both the current source_file and the current path - # @return [Hash] - def metadata - metadata = @page.cache.fetch(:metadata) do - data = { :options => {}, :locals => {}, :page => {}, :blocks => [] } - - app.provides_metadata.each do |callback, matcher| - next if !matcher.nil? && !source_file.match(matcher) - result = app.instance_exec(source_file, &callback) - data = data.deep_merge(result) - end - - data - end - - app.provides_metadata_for_path.each do |callback, matcher| - if matcher.is_a? Regexp - next if !self.request_path.match(matcher) - elsif matcher.is_a? String - next if !File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{self.request_path}") - end - - result = app.instance_exec(self.request_path, &callback) - if result.has_key?(:blocks) - metadata[:blocks] << result[:blocks] - result.delete(:blocks) - end - - metadata = metadata.deep_merge(result) - end - - metadata - end - - # Render this template - # @param [Hash] opts - # @param [Hash] locs - # @return [String] - def render(opts={}, locs={}, &block) - puts "== Render Start: #{source_file}" if app.logging? - - md = metadata.dup - opts = options.deep_merge(md[:options]).deep_merge(opts) - locs = locals.deep_merge(md[:locals]).deep_merge(locs) - - # Forward remaining data to helpers - if md.has_key?(:page) - app.data.store("page", md[:page]) - end - - md[:blocks].flatten.compact.each do |block| - app.instance_eval(&block) - end - - app.instance_eval(&block) if block_given? - result = app.render_template(source_file, locs, opts) - - puts "== Render End: #{source_file}" if app.logging? - result - end - end -end diff --git a/middleman-more/lib/middleman-more/extensions/asset_hash.rb b/middleman-more/lib/middleman-more/extensions/asset_hash.rb index f3a14331..c5c2f18c 100755 --- a/middleman-more/lib/middleman-more/extensions/asset_hash.rb +++ b/middleman-more/lib/middleman-more/extensions/asset_hash.rb @@ -63,8 +63,7 @@ module Middleman::Extensions asset_path = dirpath.join(asset_path).to_s if relative_path - if @middleman_app.sitemap.exists? asset_path - asset_page = @middleman_app.sitemap.page asset_path + if asset_page = @middleman_app.sitemap.find_page_by_path(asset_path) replacement_path = "/#{asset_page.destination_path}" replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path diff --git a/middleman-more/lib/middleman-more/extensions/cache_buster.rb b/middleman-more/lib/middleman-more/extensions/cache_buster.rb index 291c4ced..3c8ea3ef 100755 --- a/middleman-more/lib/middleman-more/extensions/cache_buster.rb +++ b/middleman-more/lib/middleman-more/extensions/cache_buster.rb @@ -51,10 +51,9 @@ module Middleman::Extensions real_path_dynamic = File.join(build_dir, prefix, path) real_path_dynamic = File.expand_path(real_path_dynamic, root) http_path << "?" + File.mtime(real_path_dynamic).strftime("%s") if File.readable?(real_path_dynamic) - elsif sitemap.exists?(real_path_static) - page = sitemap.page(real_path_static) - if !page.template? - http_path << "?" + File.mtime(page.source_file).strftime("%s") + elsif resource = sitemap.find_resource_by_path(real_path_static) + if !resource.template? + http_path << "?" + File.mtime(resource.source_file).strftime("%s") else # It's a template, possible with partials. We can't really know when # it's updated, so generate fresh cache buster every time durin diff --git a/middleman-more/lib/middleman-more/renderers/liquid.rb b/middleman-more/lib/middleman-more/renderers/liquid.rb index 24981039..b30bd815 100644 --- a/middleman-more/lib/middleman-more/renderers/liquid.rb +++ b/middleman-more/lib/middleman-more/renderers/liquid.rb @@ -22,7 +22,7 @@ module Middleman::Renderers::Liquid Liquid::Template.file_system = Liquid::LocalFileSystem.new(source_dir) # Convert data object into a hash for liquid - provides_metadata %r{\.liquid$} do |path| + sitemap.provides_metadata %r{\.liquid$} do |path| { :locals => { :data => data.to_h } } end end