From 0ac9c0b66246e59c911e3e349393bc622a7dd869 Mon Sep 17 00:00:00 2001 From: Thomas Reynolds Date: Sat, 7 Jan 2012 16:57:50 -0800 Subject: [PATCH] document sitemap, make ignore accept paths, regexps and blocks --- .../lib/middleman-core/cli/build.rb | 7 +- .../middleman-core/core_extensions/sitemap.rb | 55 ++++++--- .../lib/middleman-core/sitemap/page.rb | 105 ++++++++++++++---- .../lib/middleman-core/sitemap/store.rb | 92 ++++++++++++--- .../lib/middleman-core/sitemap/template.rb | 60 ++++++---- 5 files changed, 241 insertions(+), 78 deletions(-) diff --git a/middleman-core/lib/middleman-core/cli/build.rb b/middleman-core/lib/middleman-core/cli/build.rb index d2b19b6a..09248ff4 100644 --- a/middleman-core/lib/middleman-core/cli/build.rb +++ b/middleman-core/lib/middleman-core/cli/build.rb @@ -2,6 +2,8 @@ require "rack" require "rack/test" +require 'find' + # CLI Module module Middleman::Cli @@ -87,13 +89,16 @@ module Middleman::Cli # Set the root path to the Middleman::Base's root source_root(shared_instance.root) + + # Ignore following method + desc "", "", :hide => true + # Render a template to a file. # # @param [String] source # @param [String] destination # @param [Hash] config # @return [String] the actual destination file path that was created - desc "", "", :hide => true def tilt_template(source, destination, config={}) build_dir = self.class.shared_instance.build_dir request_path = destination.sub(/^#{build_dir}/, "") diff --git a/middleman-core/lib/middleman-core/core_extensions/sitemap.rb b/middleman-core/lib/middleman-core/core_extensions/sitemap.rb index f4de74e3..1afb0cf2 100644 --- a/middleman-core/lib/middleman-core/core_extensions/sitemap.rb +++ b/middleman-core/lib/middleman-core/core_extensions/sitemap.rb @@ -1,9 +1,10 @@ -require "active_support/core_ext/hash/deep_merge" -require 'find' - +# 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(/^\./) }, @@ -19,50 +20,74 @@ module Middleman::CoreExtensions::Sitemap # 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 - static_path = source_dir.sub(self.root, "").sub(/^\//, "") + # Cleanup paths + static_path = source_dir.sub(root, "").sub(/^\//, "") sitemap_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}}) - self.files.changed sitemap_regex do |file| - self.sitemap.touch_file(file) + # Register file change callback + files.changed sitemap_regex do |file| + sitemap.touch_file(file) end - - self.files.deleted sitemap_regex do |file| - self.sitemap.remove_file(file) + + # 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) + @_sitemap ||= ::Middleman::Sitemap::Store.new(self) end + # Get the page object for the current path + # @return [Middleman::Sitemap::Page] def current_page sitemap.page(current_path) end - # Keep a path from building - def ignore(path) - sitemap.ignore(path) + # Ignore a path, regex or callback + # @param [String, Regexp] + # @return [void] + def ignore(*args) + sitemap.ignore(*args) end - def reroute(url, target) - sitemap.proxy(url, target) + # Proxy one path to another + # @param [String] url + # @param [String] target + # @return [void] + def reroute(*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? diff --git a/middleman-core/lib/middleman-core/sitemap/page.rb b/middleman-core/lib/middleman-core/sitemap/page.rb index 7717495a..b682ee0d 100644 --- a/middleman-core/lib/middleman-core/sitemap/page.rb +++ b/middleman-core/lib/middleman-core/sitemap/page.rb @@ -1,7 +1,23 @@ +# Sitemap namespace module Middleman::Sitemap + + # Sitemap Page class class Page - attr_accessor :store, :path, :proxied_to, :status + # @return [Middleman::Sitemap::Store] + attr_accessor :store + # @return [String] + attr_accessor :path + + # @return [Middleman::Sitemap::Page] + attr_accessor :proxied_to + + # @return [Symbol] + attr_accessor :status + + # Initialize page with parent store and URL + # @param [Middleman::Sitemap::Store] store + # @param [String] path def initialize(store, path) @store = store @path = path @@ -10,6 +26,8 @@ module Middleman::Sitemap @proxied_to = nil end + # Whether this page has a template file + # @return [Boolean] def template? if proxy? store.page(proxied_to).template? @@ -19,10 +37,8 @@ module Middleman::Sitemap end end - def source_file=(src) - @source_file = src - end - + # Internal path to be requested when rendering this page + # @return [String] def request_path if proxy? store.page(proxied_to).path @@ -31,6 +47,15 @@ module Middleman::Sitemap end 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 @@ -39,99 +64,124 @@ module Middleman::Sitemap end end + # The template instance + # @return [Middleman::Sitemap::Template] def template @_template ||= ::Middleman::Sitemap::Template.new(self) end + # Extension of the path + # @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? @status == :proxy end + # Set this page to proxy to a target path + # @param [String] target + # @return [void] def proxy_to(target) @status = :proxy @proxied_to = target end + # Whether this page is a generic page + # @return [Boolean] def generic? @status == :generic end + # Set this page to be a generic page + # @return [void] def make_generic @status = :generic + # TODO: Remove from ignore array? end + # Whether this page is ignored + # @return [Boolean] def ignored? - @status == :ignored + store.ignored?(self.path) end + # Set this page to be ignored + # @return [void] def ignore - @status = :ignored + store.ignore(self.path) end + # If this is a template, refresh contents + # @return [void] def touch template.touch if template? end - def custom_renderer(&block) - @_custom_renderer ||= nil - @_custom_renderer = block if block_given? - @_custom_renderer + # If this is a template, remove contents + # @return [void] + def delete + template.delete if template? end + # Render this page + # @return [String] def render(*args, &block) return unless template? if proxy? - # Forward blocks - # forward_blocks = template.blocks.compact - # forward_blocks << block if block_given? t = store.page(proxied_to).template t.request_path = path t.render(*args) - # do - # forward_blocks.each do |block| - # instance_exec(&block) - # end - # end - elsif !custom_renderer.nil? - params = args.dup - params << block if block_given? - instance_exec(*params, &custom_renderer) else template.request_path = path template.render(*args, &block) end end + # Whether this page is a directory index + # @return [Boolean] def directory_index? path.include?(app.index_file) || path =~ /\/$/ || eponymous_directory? end + # Whether this page is a eponymous_directory + # @return [Boolean] def eponymous_directory? !!Dir.exists?(File.join(app.source_dir, eponymous_directory_path)) end + # The path for the eponymous_directory of this page + # @return [String] def eponymous_directory_path path.sub('.html', '/').sub(/\/$/, "") + "/" + # TODO: Seems like .html shouldn't be hardcoded here end + # Get the relative path from the source + # @return [String] def relative_path source_file.sub(app.source_dir, '') end + # This page's frontmatter + # @return [Hash, nil] def data data, content = app.frontmatter(relative_path) data || nil end + # This page's parent page + # @return [Middleman::Sitemap::Page, nil] def parent parts = path.split("/") if path.include?(app.index_file) @@ -153,6 +203,8 @@ module Middleman::Sitemap end end + # This page's child pages + # @return [Array] def children return [] unless directory_index? @@ -183,14 +235,19 @@ module Middleman::Sitemap end.reject { |p| p.ignored? } end + # This page's sibling pages + # @return [Array] def siblings return [] unless parent parent.children.reject { |p| p == self } end protected + + # This page's stored app + # @return [Middleman::Base] def app - @store.app + store.app end end end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap/store.rb b/middleman-core/lib/middleman-core/sitemap/store.rb index 75b22eb8..b873775f 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -1,34 +1,55 @@ +# Sitemap namespace module Middleman::Sitemap + + # The Store class class Store + + # @return [Middleman::Base] attr_accessor :app + # Initialize with parent app + # @param [Middleman::Base] app def initialize(app) @app = app @pages = {} + @ignored_paths = [] + @ignored_regexes = [] + @ignored_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(/^\//, "")) end - def set_context(path, opts={}, blk=nil) - page(path) do - template.options = opts - template.blocks = [blk] + # Ignore a path or add an ignore callback + # @param [String, Regexp] path + # @return [void] + def ignore(path=nil, &block) + if path.is_a? String + path_clean = path.sub(/^\//, "") + @ignored_paths << path_clean unless @ignored_paths.include?(path_clean) + elsif path.is_a? Regexp + @ignored_regexes << path unless @ignored_regexes.include?(path) + elsif block_given? + @ignored_callbacks << block end end - def ignore(path) - page(path) { ignore } - app.cache.remove(:ignored_paths) - 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{^/}, "")) } 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) @@ -36,54 +57,86 @@ module Middleman::Sitemap @pages[path] end + # Loop over known pages + # @return [void] def each(&block) @pages.each do |k, v| yield k, v end end + # Get all known paths + # @return [Array] def all_paths @pages.keys end + # Whether a path is ignored + # @param [String] path + # @return [Boolean] def ignored?(path) - ignored_paths.include?(path.sub(/^\//, "")) + path_clean = path.sub(/^\//, "") + + return true if @ignored_paths.include?(path_clean) + return true if @ignored_regexes.any? { |r| r.match(path_clean) } + return true if @ignored_callbacks.any? { |b| b.call(path_clean) } + + false end + # Get a list of ignored paths + # @return [Array] def ignored_paths - app.cache.fetch :ignored_paths do - @pages.values.select(&:ignored?).map(&:path) - end + @pages.values.select(&:ignored?).map(&:path) end + # Whether the given path is generic + # @param [String] path + # @return [Boolean] def generic?(path) generic_paths.include?(path.sub(/^\//, "")) end + # Get a list of generic paths + # @return [Array] def generic_paths app.cache.fetch :generic_paths do @pages.values.select(&:generic?).map(&:path) end end + # Whether the given path is proxied + # @param [String] path + # @return [Boolean] def proxied?(path) proxied_paths.include?(path.sub(/^\//, "")) end + # Get a list of proxied paths + # @return [Array] def proxied_paths app.cache.fetch :proxied_paths do @pages.values.select(&:proxy?).map(&:path) end 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 path = path.sub(/^\//, "") - @pages.delete(path) if @pages.has_key?(path) + if @pages.has_key?(path) + page(path).delete() + @pages.delete(path) + 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) @@ -96,6 +149,9 @@ module Middleman::Sitemap 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) @@ -114,11 +170,19 @@ module Middleman::Sitemap true end + # Whether the sitemap should completely ignore a given file/path + # @param [String] file + # @param [String] path + # @return [Boolean] def sitemap_should_ignore?(file, path) @app.sitemap_ignore.every(&:call) end protected + + # Get a path without templating extensions + # @param [String] file + # @param [String] def extensionless_path(file) app.cache.fetch(:extensionless_path, file) do path = file.dup diff --git a/middleman-core/lib/middleman-core/sitemap/template.rb b/middleman-core/lib/middleman-core/sitemap/template.rb index 74dd89c6..1c376db3 100644 --- a/middleman-core/lib/middleman-core/sitemap/template.rb +++ b/middleman-core/lib/middleman-core/sitemap/template.rb @@ -1,8 +1,26 @@ +# Used for merging results of metadata callbacks +require "active_support/core_ext/hash/deep_merge" + +# Sitemap namespace module Middleman::Sitemap + # Template class class Template - attr_accessor :page, :options, :locals, :blocks, :request_path + + # @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 = {} @@ -10,30 +28,23 @@ module Middleman::Sitemap @blocks = [] end - def path - page.path - end - - def source_file - page.source_file - end - - def store - page.store - end - - def app - store.app - end - - def ext - page.ext - end + # Simple aliases + delegate :path, :source_file, :store, :app, :ext, :to => :page + # Clear internal frontmatter cache for file if it changes + # @return [void] def touch app.cache.remove(:metadata, source_file) end + # Clear internal frontmatter cache for file if it is deleted + # @return [void] + def delete + app.cache.remove(:metadata, source_file) + end + + # Get the metadata for both the current source_file and the current path + # @return [Hash] def metadata metadata = app.cache.fetch(:metadata, source_file) do data = { :options => {}, :locals => {}, :page => {}, :blocks => [] } @@ -59,12 +70,17 @@ module Middleman::Sitemap 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? @@ -77,10 +93,6 @@ module Middleman::Sitemap app.data.store("page", md[:page]) end - blocks.compact.each do |block| - app.instance_eval(&block) - end - md[:blocks].flatten.compact.each do |block| app.instance_eval(&block) end