diff --git a/middleman-core/features/wildcard_page_helper.feature b/middleman-core/features/wildcard_page_helper.feature index baa75556..5ac7a25e 100644 --- a/middleman-core/features/wildcard_page_helper.feature +++ b/middleman-core/features/wildcard_page_helper.feature @@ -15,5 +15,5 @@ Feature: Wildcards in Page helper Then I should see "Normal Layout" When I go to "/admin/" Then I should see "Admin Layout" - When I go to "/admin/page.html" + When I go to "/admin/page/" Then I should see "Admin Layout" \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/base.rb b/middleman-core/lib/middleman-core/base.rb index 8c9d02a4..7ab77383 100644 --- a/middleman-core/lib/middleman-core/base.rb +++ b/middleman-core/lib/middleman-core/base.rb @@ -370,13 +370,13 @@ class Middleman::Base # Run before callbacks run_hook :before - - # Return 404 if not in sitemap - return not_found unless sitemap.exists?(@request_path) - + # Get the page object for this path - sitemap_page = sitemap.page(@request_path) - + sitemap_page = sitemap.page_by_destination(@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? @@ -494,4 +494,4 @@ protected end res['Content-Type'] = mime_type end -end \ No newline at end of file +end diff --git a/middleman-core/lib/middleman-core/cli/build.rb b/middleman-core/lib/middleman-core/cli/build.rb index fc6913e4..c2e0f567 100644 --- a/middleman-core/lib/middleman-core/cli/build.rb +++ b/middleman-core/lib/middleman-core/cli/build.rb @@ -99,27 +99,20 @@ module Middleman::Cli # Ignore following method desc "", "", :hide => true - # Render a template to a file. + # Render a page to a file. # - # @param [String] source - # @param [String] destination - # @param [Hash] config - # @return [String] the actual destination file path that was created - def tilt_template(source, destination, config={}) + # @param [Middleman::Sitemap::Page] page + # @return [void] + def tilt_template(page) build_dir = self.class.shared_instance.build_dir - request_path = destination.sub(/^#{build_dir}/, "") - config[:force] = true + output_file = File.join(self.class.shared_instance.build_dir, page.destination_path) begin - destination, request_path = self.class.shared_instance.reroute_builder(destination, request_path) - - response = self.class.shared_rack.get(request_path.gsub(/\s/, "%20")) - - create_file(destination, response.body, config) - - destination + response = self.class.shared_rack.get(page.request_path.gsub(/\s/, "%20")) + create_file(output_file, response.body, { :force => true }) rescue - say_status :error, destination, :red + say_status :error, output_file, :red + puts $! abort end end @@ -213,6 +206,8 @@ module Middleman::Cli # Sort paths to be built by the above order. This is primarily so Compass can # find files in the build folder when it needs to generate sprites for the # css files + + # TODO: deal with pages, not paths paths = @app.sitemap.all_paths.sort do |a, b| a_ext = File.extname(a) b_ext = File.extname(b) @@ -225,21 +220,15 @@ module Middleman::Cli # Loop over all the paths and build them. paths.each do |path| - file_source = path - file_destination = File.join(given_destination, file_source.gsub(source, '.')) - file_destination.gsub!('/./', '/') + page = @app.sitemap.page(path) - if @app.sitemap.proxied?(file_source) - file_source = @app.sitemap.page(file_source).proxied_to - elsif @app.sitemap.page(file_source).ignored? - next - end - - next if @config[:glob] && !File.fnmatch(@config[:glob], file_source) + next if page.ignored? + next if @config[:glob] && !File.fnmatch(@config[:glob], path) - file_destination = base.tilt_template(file_source, file_destination) + base.tilt_template(page) - @cleaning_queue.delete(Pathname.new(file_destination).realpath) if cleaning? + output_path = File.join(@destination, page.destination_path) + @cleaning_queue.delete(Pathname.new(output_path).realpath) if cleaning? end end end diff --git a/middleman-core/lib/middleman-core/core_extensions/builder.rb b/middleman-core/lib/middleman-core/core_extensions/builder.rb index 5ba2b160..7f67222b 100644 --- a/middleman-core/lib/middleman-core/core_extensions/builder.rb +++ b/middleman-core/lib/middleman-core/core_extensions/builder.rb @@ -16,7 +16,8 @@ module Middleman::CoreExtensions::Builder # Build Class Methods module ClassMethods # Get a list of callbacks which can modify a files build path - # + # Each callback takes a destination path and a request path and + # returns a new destination path, or false if it doesn't want to reroute. # @return [Array] def build_reroute(&block) @build_rerouters ||= [] @@ -29,14 +30,14 @@ module Middleman::CoreExtensions::Builder module InstanceMethods # Run through callbacks and get the new values # - # @param [String] destination The current destination of the built file - # @param [String] request_path The current request path of the file - # @return [Array] The new values + # @param [String] destination The current destination path of the built file + # @param [String] request_path The request path of the file + # @return [String] The new destination path def reroute_builder(destination, request_path) result = [destination, request_path] build_reroute.each do |block| - output = instance_exec(destination, request_path, &block) + output = block.call(destination, request_path) if output result = output break diff --git a/middleman-core/lib/middleman-core/core_extensions/routing.rb b/middleman-core/lib/middleman-core/core_extensions/routing.rb index d5ec1808..6be37409 100644 --- a/middleman-core/lib/middleman-core/core_extensions/routing.rb +++ b/middleman-core/lib/middleman-core/core_extensions/routing.rb @@ -64,7 +64,7 @@ module Middleman::CoreExtensions::Routing # Setup proxy if opts.has_key?(:proxy) - reroute(url, opts[:proxy]) + proxy(url, opts[:proxy]) if opts.has_key?(:ignore) && opts[:ignore] ignore(opts[:proxy]) @@ -89,4 +89,4 @@ module Middleman::CoreExtensions::Routing end end end -end \ No newline at end of file +end diff --git a/middleman-core/lib/middleman-core/core_extensions/sitemap.rb b/middleman-core/lib/middleman-core/core_extensions/sitemap.rb index a2c7e6b9..4366d976 100644 --- a/middleman-core/lib/middleman-core/core_extensions/sitemap.rb +++ b/middleman-core/lib/middleman-core/core_extensions/sitemap.rb @@ -74,10 +74,10 @@ module Middleman::CoreExtensions::Sitemap # @param [String] url # @param [String] target # @return [void] - def reroute(*args) + def proxy(*args) sitemap.proxy(*args) end - + # Register a handler to provide metadata on a file path # @param [Regexp] matcher # @return [Array>] @@ -96,4 +96,4 @@ module Middleman::CoreExtensions::Sitemap @_provides_metadata_for_path end end -end \ No newline at end of file +end diff --git a/middleman-core/lib/middleman-core/extensions/directory_indexes.rb b/middleman-core/lib/middleman-core/extensions/directory_indexes.rb index 1c8ea2df..4c591c9d 100644 --- a/middleman-core/lib/middleman-core/extensions/directory_indexes.rb +++ b/middleman-core/lib/middleman-core/extensions/directory_indexes.rb @@ -12,83 +12,43 @@ module Middleman::Extensions # Include methods app.send :include, InstanceMethods - # Before requests - app.before do - prefix = @original_path.sub(/\/$/, "") - indexed_path = prefix + "/" + index_file - extensioned_path = prefix + File.extname(index_file) - - is_ignored = false - fm_ignored = false - - # If the sitemap knows about the path - if sitemap.exists?(@original_path) - # Inspect frontmatter - d = sitemap.page(@original_path).data - - # Allow the frontmatter to ignore a directory index - if !d.nil? && d.has_key?("directory_index") && d["directory_index"] == false - fm_ignored = true - else - next - end - else - # Otherwise check this extension for list of ignored indexes - is_ignored = ignored_directory_indexes.include?(extensioned_path) - end + 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}" + frontmatter_ignore = false - # If we're going to remap to a directory index - if !sitemap.exists?(indexed_path) && !is_ignored && !fm_ignored - parts = @original_path.split("/") - last_part = parts.last - last_part_ext = File.extname(last_part) - - # Change the request - if last_part_ext.blank? - # This is a folder, redirect to index - @request_path = extensioned_path - end - end - end - - # Basically does the same as above, but in build mode - app.build_reroute do |destination, request_path| - index_ext = File.extname(index_file) - new_index_path = "/#{index_file}" - frontmatter_ignore = false - - # Check for file and frontmatter - if sitemap.exists?(request_path) - p = sitemap.page(request_path) - d = p.data - if !d.nil? + # Check for file and frontmatter + d = page.data + if !page.data.nil? frontmatter_ignore = d.has_key?("directory_index") && d["directory_index"] == false end - end - # Only reroute if not ignored - if ignored_directory_indexes.include?(request_path) - false - elsif request_path =~ /#{new_index_path}$/ - false - elsif frontmatter_ignore - false - else - [ - destination.sub(/#{index_ext.gsub(".", "\\.")}$/, new_index_path), - request_path - ] + index_ext = File.extname(index_file) + + # Only reroute if not ignored + path = page.path + if ignored_directory_indexes.include? page + destination + elsif path == index_file || path.end_with?(new_index_path) + destination + elsif frontmatter_ignore + destination + elsif index_ext != File.extname(path) + destination + else + destination.chomp(File.extname(index_file)) + new_index_path + end end end end - + alias :included :registered end - # Directory indexes instance methods module InstanceMethods # A list of pages which will not use directory indexes - # @return [Array] + # @return [Array] def ignored_directory_indexes @_ignored_directory_indexes ||= [] end @@ -100,7 +60,7 @@ module Middleman::Extensions # @return [void] def page(url, options={}, &block) if options.has_key?(:directory_index) && !options["directory_index"] - ignored_directory_indexes << url + ignored_directory_indexes << sitemap.page(url) else super end @@ -110,4 +70,4 @@ module Middleman::Extensions # Register the extension register :directory_indexes, DirectoryIndexes -end \ No newline at end of file +end diff --git a/middleman-core/lib/middleman-core/sitemap/page.rb b/middleman-core/lib/middleman-core/sitemap/page.rb index 8a8fab81..b14faca7 100644 --- a/middleman-core/lib/middleman-core/sitemap/page.rb +++ b/middleman-core/lib/middleman-core/sitemap/page.rb @@ -6,6 +6,8 @@ module Middleman::Sitemap # @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 @@ -41,9 +43,9 @@ module Middleman::Sitemap # @return [String] def request_path if proxy? - store.page(proxied_to).path + store.page(proxied_to).destination_path else - path + destination_path end end @@ -185,6 +187,16 @@ module Middleman::Sitemap 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 + # TODO: memoize this value + store.reroute_callbacks.inject(self.path) do |destination, callback| + callback.call(destination, self) + end + end # This page's frontmatter # @return [Hash, nil] @@ -263,4 +275,4 @@ module Middleman::Sitemap store.app end end -end \ No newline at end of file +end diff --git a/middleman-core/lib/middleman-core/sitemap/store.rb b/middleman-core/lib/middleman-core/sitemap/store.rb index 9071f1b6..b14dac67 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -2,6 +2,11 @@ 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", + # 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 # @return [Middleman::Base] @@ -16,24 +21,25 @@ module Middleman::Sitemap @ignored_globs = [] @ignored_regexes = [] @ignored_callbacks = [] + @reroute_callbacks = [] end # Check to see if we know about a specific path # @param [String] path # @return [Boolean] def exists?(path) - @pages.has_key?(path.sub(/^\//, "")) + @pages.has_key?(normalize_path(path)) end # Ignore a path or add an ignore callback - # @param [String, Regexp] path + # @param [String, Regexp] path, path glob expression, or path regex # @return [void] def ignore(path=nil, &block) if !path.nil? && path.include?("*") - path_clean = path.sub(/^\//, "") + path_clean = normalize_path(path) @ignored_globs << path_clean unless @ignored_globs.include?(path_clean) elsif path.is_a? String - path_clean = path.sub(/^\//, "") + path_clean = normalize_path(path) @ignored_paths << path_clean unless @ignored_paths.include?(path_clean) elsif path.is_a? Regexp @ignored_regexes << path unless @ignored_regexes.include?(path) @@ -42,33 +48,52 @@ module Middleman::Sitemap 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) - page(path) { proxy_to(target.sub(%r{^/}, "")) } + page(path).proxy_to(normalize_path(target)) app.cache.remove(:proxied_paths) end # Get a page instance for a given path # @param [String] path # @return [Middleman::Sitemap::Page] - def page(path, &block) - path = path.sub(/^\//, "").gsub("%20", " ") - @pages[path] = ::Middleman::Sitemap::Page.new(self, path) unless @pages.has_key?(path) - @pages[path].instance_exec(&block) if block_given? - @pages[path] + def page(path) + path = normalize_path(path) + @pages.fetch(path) { @pages[path] = ::Middleman::Sitemap::Page.new(self, path) } + end + + # Find a page given its destination path + def page_by_destination(destination_path) + # TODO: memoize this + destination_path = normalize_path(destination_path) + @pages.values.find {|p| p.destination_path == destination_path } end # Loop over known pages + # @yield [path, page] # @return [void] - def each(&block) + def each @pages.each do |k, v| yield k, v end end - + # Get all known paths # @return [Array] def all_paths @@ -79,15 +104,15 @@ module Middleman::Sitemap # @param [String] path # @return [Boolean] def ignored?(path) - path_clean = path.sub(/^\//, "") - - # $stderr.puts path_clean, @ignored_globs, @ignored_paths + path_clean = normalize_path(path) return true if @ignored_paths.include?(path_clean) return true if @ignored_globs.any? { |g| File.fnmatch(g, path_clean) } return true if @ignored_regexes.any? { |r| r.match(path_clean) } return true if @ignored_callbacks.any? { |b| b.call(path_clean) } - + + # TODO: We should also check ignored_sitemap_matchers here + false end @@ -101,7 +126,7 @@ module Middleman::Sitemap # @param [String] path # @return [Boolean] def generic?(path) - generic_paths.include?(path.sub(/^\//, "")) + generic_paths.include?(normalize_path(path)) end # Get a list of generic paths @@ -116,7 +141,7 @@ module Middleman::Sitemap # @param [String] path # @return [Boolean] def proxied?(path) - proxied_paths.include?(path.sub(/^\//, "")) + proxied_paths.include?(normalize_path(path)) end # Get a list of proxied paths @@ -134,7 +159,7 @@ module Middleman::Sitemap path = file_to_path(file) return false unless path - path = path.sub(/^\//, "") + path = normalize_path(path) if @pages.has_key?(path) page(path).delete() @pages.delete(path) @@ -189,7 +214,7 @@ module Middleman::Sitemap # Get a path without templating extensions # @param [String] file - # @param [String] + # @return [String] def extensionless_path(file) app.cache.fetch(:extensionless_path, file) do path = file.dup @@ -206,5 +231,12 @@ module Middleman::Sitemap path 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