diff --git a/middleman-core/lib/middleman-core.rb b/middleman-core/lib/middleman-core.rb index 3a4c6d72..8e698cd5 100755 --- a/middleman-core/lib/middleman-core.rb +++ b/middleman-core/lib/middleman-core.rb @@ -3,7 +3,12 @@ libdir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) # Top-level Middleman namespace -module Middleman; end +module Middleman + + # Backwards compatibility namespace + module Features; end + +end require "middleman-core/version" require "middleman-core/util" diff --git a/middleman-core/lib/middleman-core/application.rb b/middleman-core/lib/middleman-core/application.rb index ad2459a7..6cd0bdd5 100644 --- a/middleman-core/lib/middleman-core/application.rb +++ b/middleman-core/lib/middleman-core/application.rb @@ -9,6 +9,8 @@ require "middleman-core/vendor/hooks-0.2.0/lib/hooks" require "middleman-core/sitemap" +require "middleman-core/core_extensions" + # Core Middleman Class module Middleman class Application @@ -160,14 +162,35 @@ module Middleman register Middleman::CoreExtensions::I18n # Built-in Extensions - Middleman::Extensions.register(:directory_indexes) { - Middleman::Extensions::DirectoryIndexes } - Middleman::Extensions.register(:lorem) { - Middleman::Extensions::Lorem } - Middleman::Extensions.register(:automatic_image_sizes) { - Middleman::Extensions::AutomaticImageSizes } - Middleman::Extensions.register(:asset_host) { - Middleman::Extensions::AssetHost } + + # Provide Apache-style index.html files for directories + Middleman::Extensions.register(:directory_indexes) do + require "middleman-core/extensions/directory_indexes" + Middleman::Extensions::DirectoryIndexes + end + + # Lorem provides a handful of helpful prototyping methods to generate + # words, paragraphs, fake images, names and email addresses. + Middleman::Extensions.register(:lorem) do + require "middleman-core/extensions/lorem" + Middleman::Extensions::Lorem + end + + # AutomaticImageSizes inspects the images used in your dynamic templates + # and automatically adds width and height attributes to their HTML + # elements. + Middleman::Extensions.register(:automatic_image_sizes) do + require "middleman-core/extensions/automatic_image_sizes" + Middleman::Extensions::AutomaticImageSizes + end + + # AssetHost allows you to setup multiple domains to host your static + # assets. Calls to asset paths in dynamic templates will then rotate + # through each of the asset servers to better spread the load. + Middleman::Extensions.register(:asset_host) do + require "middleman-core/extensions/asset_host" + Middleman::Extensions::AssetHost + end # Initialize the Middleman project def initialize(&block) diff --git a/middleman-core/lib/middleman-core/cli.rb b/middleman-core/lib/middleman-core/cli.rb index f0439a64..255c9e24 100644 --- a/middleman-core/lib/middleman-core/cli.rb +++ b/middleman-core/lib/middleman-core/cli.rb @@ -5,13 +5,6 @@ require "thor/group" # CLI Module module Middleman::Cli - module Cli - autoload :Build, "middleman-core/cli/build" - autoload :Init, "middleman-core/cli/init" - autoload :Extension, "middleman-core/cli/extension" - autoload :Server, "middleman-core/cli/server" - end - # The base task from which everything else etends class Base < Thor diff --git a/middleman-core/lib/middleman-core/core_extensions.rb b/middleman-core/lib/middleman-core/core_extensions.rb new file mode 100644 index 00000000..cd792d75 --- /dev/null +++ b/middleman-core/lib/middleman-core/core_extensions.rb @@ -0,0 +1,39 @@ +# Rack Request +require "middleman-core/core_extensions/request" + +# File Change Notifier +require "middleman-core/core_extensions/file_watcher" + +# Add Builder callbacks +require "middleman-core/core_extensions/builder" + +# Custom Feature API +require "middleman-core/core_extensions/extensions" + +# Asset Path Pipeline +require "middleman-core/core_extensions/assets" + +# Data looks at the data/ folder for YAML files and makes them available +# to dynamic requests. +require "middleman-core/core_extensions/data" + +# Parse YAML from templates +require "middleman-core/core_extensions/front_matter" + +# External helpers looks in the helpers/ folder for helper modules +require "middleman-core/core_extensions/external_helpers" + +# DefaultHelpers are the built-in dynamic template helpers. +require "middleman-core/core_extensions/default_helpers" + +# Extended version of Padrino's rendering +require "middleman-core/core_extensions/rendering" + +# Pass custom options to views +require "middleman-core/core_extensions/routing" + +# Catch and show exceptions at the Rack level +require "middleman-core/core_extensions/show_exceptions" + +# i18n +require "middleman-core/core_extensions/i18n" \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/assets.rb b/middleman-core/lib/middleman-core/core_extensions/assets.rb index ad2f0527..bf9a340f 100644 --- a/middleman-core/lib/middleman-core/core_extensions/assets.rb +++ b/middleman-core/lib/middleman-core/core_extensions/assets.rb @@ -1,39 +1,43 @@ -# Base helper to manipulate asset paths -module Middleman::CoreExtensions::Assets - - # Extension registered - class << self - # @private - def registered(app) - # Disable Padrino cache buster - app.set :asset_stamp, false - - # Include helpers - app.send :include, InstanceMethod - end - alias :included :registered - end - - # Methods to be mixed-in to Middleman::Application - module InstanceMethod +module Middleman + module CoreExtensions - # Get the URL of an asset given a type/prefix - # - # @param [String] path The path (such as "photo.jpg") - # @param [String] prefix The type prefix (such as "images") - # @return [String] The fully qualified asset url - def asset_url(path, prefix="") - # Don't touch assets which already have a full path - if path.include?("//") - path - else # rewrite paths to use their destination path - path = File.join(prefix, path) - if resource = sitemap.find_resource_by_path(path) - path = resource.destination_path + # Base helper to manipulate asset paths + module Assets + + # Extension registered + class << self + def registered(app) + # Disable Padrino cache buster + app.set :asset_stamp, false + + # Include helpers + app.send :include, InstanceMethod end + alias :included :registered + end + + # Methods to be mixed-in to Middleman::Application + module InstanceMethod + + # Get the URL of an asset given a type/prefix + # + # @param [String] path The path (such as "photo.jpg") + # @param [String] prefix The type prefix (such as "images") + # @return [String] The fully qualified asset url + def asset_url(path, prefix="") + # Don't touch assets which already have a full path + if path.include?("//") + path + else # rewrite paths to use their destination path + path = File.join(prefix, path) + if resource = sitemap.find_resource_by_path(path) + path = resource.destination_path + end - File.join(http_prefix, path) + File.join(http_prefix, path) + end + end end end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/builder.rb b/middleman-core/lib/middleman-core/core_extensions/builder.rb index 4b901b69..ffbad37e 100644 --- a/middleman-core/lib/middleman-core/core_extensions/builder.rb +++ b/middleman-core/lib/middleman-core/core_extensions/builder.rb @@ -1,12 +1,17 @@ -# Convenience methods to allow config.rb to talk to the Builder -module Middleman::CoreExtensions::Builder +module Middleman + module CoreExtensions + + # Convenience methods to allow config.rb to talk to the Builder + module Builder - # Extension registered - class << self - # @private - def registered(app) - app.define_hook :after_build + # Extension registered + class << self + # @private + def registered(app) + app.define_hook :after_build + end + alias :included :registered + end end - alias :included :registered end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/data.rb b/middleman-core/lib/middleman-core/core_extensions/data.rb index 5302b458..d5605d26 100755 --- a/middleman-core/lib/middleman-core/core_extensions/data.rb +++ b/middleman-core/lib/middleman-core/core_extensions/data.rb @@ -1,175 +1,180 @@ -# Data formats -require "yaml" -require "active_support/json" - -# The data extension parses YAML and JSON files in the data/ directory -# and makes them available to config.rb, templates and extensions -module Middleman::CoreExtensions::Data +module Middleman + module CoreExtensions + + # The data extension parses YAML and JSON files in the data/ directory + # and makes them available to config.rb, templates and extensions + module Data - # Extension registered - class << self - # @private - def registered(app) - app.set :data_dir, "data" - app.send :include, InstanceMethods - end - alias :included :registered - end + # Extension registered + class << self + # @private + def registered(app) + # Data formats + require "yaml" + require "active_support/json" + + app.set :data_dir, "data" + app.send :include, InstanceMethods + end + alias :included :registered + end - # Instance methods - module InstanceMethods - # Setup data files before anything else so they are available when - # parsing config.rb - def initialize - self.files.changed DataStore.matcher do |file| - self.data.touch_file(file) if file.match(%r{^#{self.data_dir}\/}) - end + # Instance methods + module InstanceMethods + # Setup data files before anything else so they are available when + # parsing config.rb + def initialize + self.files.changed DataStore.matcher do |file| + self.data.touch_file(file) if file.match(%r{^#{self.data_dir}\/}) + end - self.files.deleted DataStore.matcher do |file| - self.data.remove_file(file) if file.match(%r{^#{self.data_dir}\/}) - end + self.files.deleted DataStore.matcher do |file| + self.data.remove_file(file) if file.match(%r{^#{self.data_dir}\/}) + end - super - end + super + end - # The data object - # - # @return [DataStore] - def data - @_data ||= DataStore.new(self) - end - end - - # The core logic behind the data extension. - class DataStore - - # Static methods - class << self - - # The regex which tells Middleman which files are for data - # - # @return [Regexp] - def matcher - %r{[\w-]+\.(yml|yaml|json)$} - end - end - - # Store static data hash - # - # @param [Symbol] name Name of the data, used for namespacing - # @param [Hash] content The content for this data - # @return [void] - def store(name=nil, content=nil) - @_local_sources ||= {} - @_local_sources[name.to_s] = content unless name.nil? || content.nil? - @_local_sources - end - - # Store callback-based data - # - # @param [Symbol] name Name of the data, used for namespacing - # @param [Proc] proc The callback which will return data - # @return [void] - def callbacks(name=nil, proc=nil) - @_callback_sources ||= {} - @_callback_sources[name.to_s] = proc unless name.nil? || proc.nil? - @_callback_sources - end - - # Setup data store - # - # @param [Middleman::Application] app The current instance of Middleman - def initialize(app) - @app = app - @local_data = {} - end - - # Update the internal cache for a given file path - # - # @param [String] file The file to be re-parsed - # @return [void] - def touch_file(file) - file = File.expand_path(file, @app.root) - extension = File.extname(file) - basename = File.basename(file, extension) - - if %w(.yaml .yml).include?(extension) - data = YAML.load_file(file) - elsif extension == ".json" - data = ActiveSupport::JSON.decode(File.read(file)) - else - return - end - - @local_data[basename] = ::Middleman::Util.recursively_enhance(data) - end - - # Remove a given file from the internal cache - # - # @param [String] file The file to be cleared - # @return [void] - def remove_file(file) - extension = File.extname(file) - basename = File.basename(file, extension) - @local_data.delete(basename) if @local_data.has_key?(basename) - end - - # Get a hash hash from either internal static data or a callback - # - # @param [String, Symbol] path The name of the data namespace - # @return [Hash, nil] - def data_for_path(path) - response = nil - - @@local_sources ||= {} - @@callback_sources ||= {} - - if self.store.has_key?(path.to_s) - response = self.store[path.to_s] - elsif self.callbacks.has_key?(path.to_s) - response = self.callbacks[path.to_s].call() - end - - response - end - - # "Magically" find namespaces of data if they exist - # - # @param [String] path The namespace to search for - # @return [Hash, nil] - def method_missing(path) - if @local_data.has_key?(path.to_s) - return @local_data[path.to_s] - else - result = data_for_path(path) - - if result - return ::Middleman::Util.recursively_enhance(result) + # The data object + # + # @return [DataStore] + def data + @_data ||= DataStore.new(self) end end - - super - end + + # The core logic behind the data extension. + class DataStore - # Convert all the data into a static hash - # - # @return [Hash] - def to_h - data = {} + # Static methods + class << self - self.store.each do |k, v| - data[k] = data_for_path(k) + # The regex which tells Middleman which files are for data + # + # @return [Regexp] + def matcher + %r{[\w-]+\.(yml|yaml|json)$} + end + end + + # Store static data hash + # + # @param [Symbol] name Name of the data, used for namespacing + # @param [Hash] content The content for this data + # @return [void] + def store(name=nil, content=nil) + @_local_sources ||= {} + @_local_sources[name.to_s] = content unless name.nil? || content.nil? + @_local_sources + end + + # Store callback-based data + # + # @param [Symbol] name Name of the data, used for namespacing + # @param [Proc] proc The callback which will return data + # @return [void] + def callbacks(name=nil, proc=nil) + @_callback_sources ||= {} + @_callback_sources[name.to_s] = proc unless name.nil? || proc.nil? + @_callback_sources + end + + # Setup data store + # + # @param [Middleman::Application] app The current instance of Middleman + def initialize(app) + @app = app + @local_data = {} + end + + # Update the internal cache for a given file path + # + # @param [String] file The file to be re-parsed + # @return [void] + def touch_file(file) + file = File.expand_path(file, @app.root) + extension = File.extname(file) + basename = File.basename(file, extension) + + if %w(.yaml .yml).include?(extension) + data = YAML.load_file(file) + elsif extension == ".json" + data = ActiveSupport::JSON.decode(File.read(file)) + else + return + end + + @local_data[basename] = ::Middleman::Util.recursively_enhance(data) + end + + # Remove a given file from the internal cache + # + # @param [String] file The file to be cleared + # @return [void] + def remove_file(file) + extension = File.extname(file) + basename = File.basename(file, extension) + @local_data.delete(basename) if @local_data.has_key?(basename) + end + + # Get a hash hash from either internal static data or a callback + # + # @param [String, Symbol] path The name of the data namespace + # @return [Hash, nil] + def data_for_path(path) + response = nil + + @@local_sources ||= {} + @@callback_sources ||= {} + + if self.store.has_key?(path.to_s) + response = self.store[path.to_s] + elsif self.callbacks.has_key?(path.to_s) + response = self.callbacks[path.to_s].call() + end + + response + end + + # "Magically" find namespaces of data if they exist + # + # @param [String] path The namespace to search for + # @return [Hash, nil] + def method_missing(path) + if @local_data.has_key?(path.to_s) + return @local_data[path.to_s] + else + result = data_for_path(path) + + if result + return ::Middleman::Util.recursively_enhance(result) + end + end + + super + end + + # Convert all the data into a static hash + # + # @return [Hash] + def to_h + data = {} + + self.store.each do |k, v| + data[k] = data_for_path(k) + end + + self.callbacks.each do |k, v| + data[k] = data_for_path(k) + end + + (@local_data || {}).each do |k, v| + data[k] = v + end + + data + end end - - self.callbacks.each do |k, v| - data[k] = data_for_path(k) - end - - (@local_data || {}).each do |k, v| - data[k] = v - end - - data end end end \ No newline at end of file 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 51a02b58..f695037b 100644 --- a/middleman-core/lib/middleman-core/core_extensions/default_helpers.rb +++ b/middleman-core/lib/middleman-core/core_extensions/default_helpers.rb @@ -1,119 +1,123 @@ -require "active_support/core_ext/integer/inflections" -require 'padrino-helpers' +module Middleman + module CoreExtensions + # Built-in helpers + module DefaultHelpers -# Built-in helpers -module Middleman::CoreExtensions::DefaultHelpers + # Extension registered + class << self + # @private + def registered(app) + require "active_support/core_ext/integer/inflections" + require 'padrino-helpers' + + app.helpers ::Padrino::Helpers::OutputHelpers + app.helpers ::Padrino::Helpers::TagHelpers + app.helpers ::Padrino::Helpers::AssetTagHelpers + app.helpers ::Padrino::Helpers::FormHelpers + app.helpers ::Padrino::Helpers::FormatHelpers + app.helpers ::Padrino::Helpers::RenderHelpers + app.helpers ::Padrino::Helpers::NumberHelpers + # app.helpers ::Padrino::Helpers::TranslationHelpers - # Extension registered - class << self - # @private - def registered(app) - app.helpers ::Padrino::Helpers::OutputHelpers - app.helpers ::Padrino::Helpers::TagHelpers - app.helpers ::Padrino::Helpers::AssetTagHelpers - app.helpers ::Padrino::Helpers::FormHelpers - app.helpers ::Padrino::Helpers::FormatHelpers - app.helpers ::Padrino::Helpers::RenderHelpers - app.helpers ::Padrino::Helpers::NumberHelpers - # app.helpers ::Padrino::Helpers::TranslationHelpers - - app.helpers Helpers - end - alias :included :registered - end - - # The helpers - module Helpers - # Output a stylesheet link tag based on the current path - # - # @param [String] separator How to break up path in parts - # @return [String] - def auto_stylesheet_link_tag(separator="/") - auto_tag(:css, separator) do |path| - stylesheet_link_tag path + app.helpers Helpers + end + alias :included :registered end - end - # Output a javascript tag based on the current path - # - # @param [String] separator How to break up path in parts - # @return [String] - def auto_javascript_include_tag(separator="/") - auto_tag(:js, separator) do |path| - javascript_include_tag path - end - end + # The helpers + module Helpers + # Output a stylesheet link tag based on the current path + # + # @param [String] separator How to break up path in parts + # @return [String] + def auto_stylesheet_link_tag(separator="/") + auto_tag(:css, separator) do |path| + stylesheet_link_tag path + end + end - # Output a stylesheet link tag based on the current path - # - # @param [Symbol] asset_ext The type of asset - # @param [String] separator How to break up path in parts - # @param [String] asset_dir Where to look for assets - # @return [void] - def auto_tag(asset_ext, separator="/", asset_dir=nil) - if asset_dir.nil? - asset_dir = case asset_ext - when :js then js_dir - when :css then css_dir + # Output a javascript tag based on the current path + # + # @param [String] separator How to break up path in parts + # @return [String] + def auto_javascript_include_tag(separator="/") + auto_tag(:js, separator) do |path| + javascript_include_tag path + end + end + + # Output a stylesheet link tag based on the current path + # + # @param [Symbol] asset_ext The type of asset + # @param [String] separator How to break up path in parts + # @param [String] asset_dir Where to look for assets + # @return [void] + def auto_tag(asset_ext, separator="/", asset_dir=nil) + if asset_dir.nil? + asset_dir = case asset_ext + when :js then js_dir + when :css then css_dir + end + end + + # If the basename of the request as no extension, assume we are serving a + # directory and join index_file to the path. + path = full_path(current_path.dup) + path = path.sub(%r{^/}, '') + path = path.gsub(File.extname(path), ".#{asset_ext}") + path = path.gsub("/", separator) + + yield path if sitemap.find_resource_by_path(File.join(asset_dir, path)) + end + + # Generate body css classes based on the current path + # + # @return [String] + def page_classes + path = current_path.dup + path << index_file if path.match(%r{/$}) + path = path.gsub(%r{^/}, '') + + classes = [] + parts = path.split('.')[0].split('/') + parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') } + + classes.join(' ') + end + + # Get the path of a file of a given type + # + # @param [Symbol] kind The type of file + # @param [String] source The path to the file + # @return [String] + def asset_path(kind, source) + return source if source =~ /^http/ + asset_folder = case kind + when :css then css_dir + when :js then js_dir + when :images then images_dir + else kind.to_s + end + source = source.to_s.gsub(/\s/, '') + ignore_extension = (kind == :images) # don't append extension + source << ".#{kind}" unless ignore_extension or source =~ /\.#{kind}/ + result_path = source if source =~ %r{^/} # absolute path + result_path ||= asset_url(source, asset_folder) + "#{result_path}" + end + + def link_to(*args, &block) + url_arg_index = block_given? ? 0 : 1 + if url = args[url_arg_index] + # Only try to work with absolute URLs + if url.start_with? '/' + resource = sitemap.find_resource_by_path(url) + args[url_arg_index] = resource.url if resource + end + end + super(*args, &block) end end - - # If the basename of the request as no extension, assume we are serving a - # directory and join index_file to the path. - path = full_path(current_path.dup) - path = path.sub(%r{^/}, '') - path = path.gsub(File.extname(path), ".#{asset_ext}") - path = path.gsub("/", separator) - - yield path if sitemap.find_resource_by_path(File.join(asset_dir, path)) - end - - # Generate body css classes based on the current path - # - # @return [String] - def page_classes - path = current_path.dup - path << index_file if path.match(%r{/$}) - path = path.gsub(%r{^/}, '') - - classes = [] - parts = path.split('.')[0].split('/') - parts.each_with_index { |path, i| classes << parts.first(i+1).join('_') } - - classes.join(' ') - end - - # Get the path of a file of a given type - # - # @param [Symbol] kind The type of file - # @param [String] source The path to the file - # @return [String] - def asset_path(kind, source) - return source if source =~ /^http/ - asset_folder = case kind - when :css then css_dir - when :js then js_dir - when :images then images_dir - else kind.to_s - end - source = source.to_s.gsub(/\s/, '') - ignore_extension = (kind == :images) # don't append extension - source << ".#{kind}" unless ignore_extension or source =~ /\.#{kind}/ - result_path = source if source =~ %r{^/} # absolute path - result_path ||= asset_url(source, asset_folder) - "#{result_path}" - end - - def link_to(*args, &block) - url_arg_index = block_given? ? 0 : 1 - if url = args[url_arg_index] - # Only try to work with absolute URLs - if url.start_with? '/' - resource = sitemap.find_resource_by_path(url) - args[url_arg_index] = resource.url if resource - end - end - super(*args, &block) end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/extensions.rb b/middleman-core/lib/middleman-core/core_extensions/extensions.rb index f5a22bc7..8dc87bbc 100644 --- a/middleman-core/lib/middleman-core/core_extensions/extensions.rb +++ b/middleman-core/lib/middleman-core/core_extensions/extensions.rb @@ -28,125 +28,129 @@ # methods to use in your views. Some modify the output on-the-fly. And some # apply computationally-intensive changes to your final build files. -# Using for version parsing -require "rubygems" - # Namespace extensions module -module Middleman::CoreExtensions::Extensions +module Middleman + module CoreExtensions + module Extensions - # Register extension - class << self - # @private - def included(app) - # app.set :default_extensions, [] - app.define_hook :after_configuration - app.define_hook :before_configuration - app.define_hook :build_config - app.define_hook :development_config - - app.extend ClassMethods - app.send :include, InstanceMethods - app.delegate :configure, :to => :"self.class" - end - end + # Register extension + class << self + # @private + def included(app) + # Using for version parsing + require "rubygems" - # Class methods - module ClassMethods - # Add a callback to run in a specific environment - # - # @param [String, Symbol] env The environment to run in - # @return [void] - def configure(env, &block) - send("#{env}_config", &block) - end - - # Alias `extensions` to access registered extensions - # - # @return [Array] - def extensions - @extensions ||= [] - end - - # Register a new extension - # - # @param [Module] extension Extension modules to register - # @param [Hash] options Per-extension options hash - # @return [void] - def register(extension, options={}, &block) - @extensions ||= [] - @extensions += [extension] + # app.set :default_extensions, [] + app.define_hook :after_configuration + app.define_hook :before_configuration + app.define_hook :build_config + app.define_hook :development_config - extend extension - if extension.respond_to?(:registered) - if extension.method(:registered).arity === 1 - extension.registered(self, &block) - else - extension.registered(self, options, &block) + app.extend ClassMethods + app.send :include, InstanceMethods + app.delegate :configure, :to => :"self.class" + end + end + + # Class methods + module ClassMethods + # Add a callback to run in a specific environment + # + # @param [String, Symbol] env The environment to run in + # @return [void] + def configure(env, &block) + send("#{env}_config", &block) + end + + # Alias `extensions` to access registered extensions + # + # @return [Array] + def extensions + @extensions ||= [] + end + + # Register a new extension + # + # @param [Module] extension Extension modules to register + # @param [Hash] options Per-extension options hash + # @return [void] + def register(extension, options={}, &block) + @extensions ||= [] + @extensions += [extension] + + extend extension + if extension.respond_to?(:registered) + if extension.method(:registered).arity === 1 + extension.registered(self, &block) + else + extension.registered(self, options, &block) + end + end end end - end - end - # Instance methods - module InstanceMethods - # This method is available in the project's `config.rb`. - # It takes a underscore-separated symbol, finds the appropriate - # feature module and includes it. - # - # activate :lorem - # - # @param [Symbol, Module] ext Which extension to activate - # @return [void] - def activate(ext, options={}, &block) - # Make :i18n a no-op - return if ext == :i18n + # Instance methods + module InstanceMethods + # This method is available in the project's `config.rb`. + # It takes a underscore-separated symbol, finds the appropriate + # feature module and includes it. + # + # activate :lorem + # + # @param [Symbol, Module] ext Which extension to activate + # @return [void] + def activate(ext, options={}, &block) + # Make :i18n a no-op + return if ext == :i18n - ext_module = if ext.is_a?(Module) - ext - else - ::Middleman::Extensions.load(ext.to_sym) - end + ext_module = if ext.is_a?(Module) + ext + else + ::Middleman::Extensions.load(ext.to_sym) + end - if ext_module.nil? - puts "== Unknown Extension: #{ext}" - else - puts "== Activating: #{ext}" if logging? - self.class.register(ext_module, options, &block) - end - end + if ext_module.nil? + puts "== Unknown Extension: #{ext}" + else + puts "== Activating: #{ext}" if logging? + self.class.register(ext_module, options, &block) + end + end - # Load features before starting server - def initialize - super + # Load features before starting server + def initialize + super - self.class.inst = self - run_hook :before_configuration + self.class.inst = self + run_hook :before_configuration - # Search the root of the project for required files - $LOAD_PATH.unshift(root) + # Search the root of the project for required files + $LOAD_PATH.unshift(root) - # Check for and evaluate local configuration - local_config = File.join(root, "config.rb") - if File.exists? local_config - puts "== Reading: Local config" if logging? - instance_eval File.read(local_config), local_config, 1 - end + # Check for and evaluate local configuration + local_config = File.join(root, "config.rb") + if File.exists? local_config + puts "== Reading: Local config" if logging? + instance_eval File.read(local_config), local_config, 1 + end - run_hook :build_config if build? - run_hook :development_config if development? + run_hook :build_config if build? + run_hook :development_config if development? - run_hook :after_configuration + run_hook :after_configuration - # Add in defaults - default_extensions.each do |ext| - activate ext - end + # Add in defaults + default_extensions.each do |ext| + activate ext + end - if logging? - self.class.extensions.each do |ext| - puts "== Extension: #{ext}" + if logging? + self.class.extensions.each do |ext| + puts "== Extension: #{ext}" + end + end end end end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/external_helpers.rb b/middleman-core/lib/middleman-core/core_extensions/external_helpers.rb index 015f9e10..4a9d779e 100644 --- a/middleman-core/lib/middleman-core/core_extensions/external_helpers.rb +++ b/middleman-core/lib/middleman-core/core_extensions/external_helpers.rb @@ -1,35 +1,39 @@ # Load helpers in helpers/ -module Middleman::CoreExtensions::ExternalHelpers +module Middleman + module CoreExtensions + module ExternalHelpers - # Setup extension - class << self + # Setup extension + class << self - # once registered - def registered(app) - # Setup a default helpers paths - app.set :helpers_dir, "helpers" - app.set :helpers_filename_glob, "**/*_helper.rb" - app.set :helpers_filename_to_module_name_proc, Proc.new { |filename| - basename = File.basename(filename, File.extname(filename)) - basename.camelcase - } + # once registered + def registered(app) + # Setup a default helpers paths + app.set :helpers_dir, "helpers" + app.set :helpers_filename_glob, "**/*_helper.rb" + app.set :helpers_filename_to_module_name_proc, Proc.new { |filename| + basename = File.basename(filename, File.extname(filename)) + basename.camelcase + } - # After config - app.after_configuration do - helpers_path = File.expand_path(helpers_dir, root) - next unless File.exists?(helpers_path) + # After config + app.after_configuration do + helpers_path = File.expand_path(helpers_dir, root) + next unless File.exists?(helpers_path) - Dir[File.join(helpers_path, helpers_filename_glob)].each do |filename| - module_name = helpers_filename_to_module_name_proc.call(filename) - next unless module_name + Dir[File.join(helpers_path, helpers_filename_glob)].each do |filename| + module_name = helpers_filename_to_module_name_proc.call(filename) + next unless module_name - require filename - next unless Object.const_defined?(module_name.to_sym) + require filename + next unless Object.const_defined?(module_name.to_sym) - helpers Object.const_get(module_name.to_sym) + helpers Object.const_get(module_name.to_sym) + end + end end + alias :included :registered end end - alias :included :registered end end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb index 3563ef35..190c35f7 100644 --- a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb +++ b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb @@ -1,153 +1,157 @@ -require "find" -require "middleman-core/watcher" -require "set" - # API for watching file change events -module Middleman::CoreExtensions::FileWatcher +module Middleman + module CoreExtensions + module FileWatcher - # Setup extension - class << self + # Setup extension + class << self - # Once registered - def registered(app) - app.extend ClassMethods - app.send :include, InstanceMethods + # Once registered + def registered(app) + require "find" + require "middleman-core/watcher" + require "set" + + app.extend ClassMethods + app.send :include, InstanceMethods - # Before parsing config, load the data/ directory - app.before_configuration do - data_path = File.join(self.root, self.data_dir) - self.files.reload_path(data_path) if File.exists?(data_path) + # Before parsing config, load the data/ directory + app.before_configuration do + data_path = File.join(self.root, self.data_dir) + self.files.reload_path(data_path) if File.exists?(data_path) + end + + # After config, load everything else + app.ready do + self.files.reload_path(self.root) + end + end + alias :included :registered end - - # After config, load everything else - app.ready do - self.files.reload_path(self.root) + + # Class methods + module ClassMethods + + # Access the file api + # @return [Middleman::CoreExtensions::FileWatcher::API] + def files + @_files_api ||= API.new + end end - end - alias :included :registered - end - # Class methods - module ClassMethods + # Instance methods + module InstanceMethods - # Access the file api - # @return [Middleman::CoreExtensions::FileWatcher::API] - def files - @_files_api ||= API.new - end - end - - # Instance methods - module InstanceMethods - - # Access the file api - # @return [Middleman::CoreExtensions::FileWatcher::API] - def files - api = self.class.files - api.instance ||= self - api - end - end - - # Core File Change API class - class API - attr_accessor :instance, :known_paths - - # Initialize api and internal path cache - def initialize - self.known_paths = Set.new - end - - # Add callback to be run on file change - # - # @param [nil,Regexp] matcher A Regexp to match the change path against - # @return [Array] - def changed(matcher=nil, &block) - @_changed ||= [] - @_changed << [block, matcher] if block_given? - @_changed - end - - # Add callback to be run on file deletion - # - # @param [nil,Regexp] matcher A Regexp to match the deleted path against - # @return [Array] - def deleted(matcher=nil, &block) - @_deleted ||= [] - @_deleted << [block, matcher] if block_given? - @_deleted - end - - # Notify callbacks that a file changed - # - # @param [String] path The file that changed - # @return [void] - def did_change(path) - puts "== File Change: #{path}" if instance.logging? && !::Middleman::Watcher.ignore_list.any? { |r| path.match(r) } - self.known_paths << path - self.run_callbacks(path, :changed) - end - - # Notify callbacks that a file was deleted - # - # @param [String] path The file that was deleted - # @return [void] - def did_delete(path) - puts "== File Deletion: #{path}" if instance.logging? && !::Middleman::Watcher.ignore_list.any? { |r| path.match(r) } - self.known_paths.delete(path) - self.run_callbacks(path, :deleted) - end - - # Manually trigger update events - # - # @param [String] path The path to reload - # @return [void] - def reload_path(path) - relative_path = path.sub("#{self.instance.root}/", "") - subset = self.known_paths.select { |p| p.match(%r{^#{relative_path}}) } - - Find.find(path) do |path| - next if File.directory?(path) - next if Middleman::Watcher.ignore_list.any? { |r| path.match(r) } - relative_path = path.sub("#{self.instance.root}/", "") - subset.delete(relative_path) - self.did_change(relative_path) - end if File.exists?(path) - - subset.each do |removed_path| - self.did_delete(removed_path) + # Access the file api + # @return [Middleman::CoreExtensions::FileWatcher::API] + def files + api = self.class.files + api.instance ||= self + api + end end - end - - # Like reload_path, but only triggers events on new files - # - # @param [String] path The path to reload - # @return [void] - def find_new_files(path) - relative_path = path.sub("#{self.instance.root}/", "") - subset = self.known_paths.select { |p| p.match(%r{^#{relative_path}}) } - - Find.find(path) do |file| - next if File.directory?(file) - next if Middleman::Watcher.ignore_list.any? { |r| path.match(r) } - relative_path = file.sub("#{self.instance.root}/", "") - self.did_change(relative_path) unless subset.include?(relative_path) - end if File.exists?(path) - end + + # Core File Change API class + class API + attr_accessor :instance, :known_paths - protected - # Notify callbacks for a file given an array of callbacks - # - # @param [String] path The file that was changed - # @param [Symbol] callbacks_name The name of the callbacks method - # @return [void] - def run_callbacks(path, callbacks_name) - return if ::Middleman::Watcher.ignore_list.any? { |r| path.match(r) } + # Initialize api and internal path cache + def initialize + self.known_paths = Set.new + end + + # Add callback to be run on file change + # + # @param [nil,Regexp] matcher A Regexp to match the change path against + # @return [Array] + def changed(matcher=nil, &block) + @_changed ||= [] + @_changed << [block, matcher] if block_given? + @_changed + end + + # Add callback to be run on file deletion + # + # @param [nil,Regexp] matcher A Regexp to match the deleted path against + # @return [Array] + def deleted(matcher=nil, &block) + @_deleted ||= [] + @_deleted << [block, matcher] if block_given? + @_deleted + end + + # Notify callbacks that a file changed + # + # @param [String] path The file that changed + # @return [void] + def did_change(path) + puts "== File Change: #{path}" if instance.logging? && !::Middleman::Watcher.ignore_list.any? { |r| path.match(r) } + self.known_paths << path + self.run_callbacks(path, :changed) + end - self.send(callbacks_name).each do |callback, matcher| - next if path.match(%r{^#{self.instance.build_dir}/}) - next unless matcher.nil? || path.match(matcher) - self.instance.instance_exec(path, &callback) + # Notify callbacks that a file was deleted + # + # @param [String] path The file that was deleted + # @return [void] + def did_delete(path) + puts "== File Deletion: #{path}" if instance.logging? && !::Middleman::Watcher.ignore_list.any? { |r| path.match(r) } + self.known_paths.delete(path) + self.run_callbacks(path, :deleted) + end + + # Manually trigger update events + # + # @param [String] path The path to reload + # @return [void] + def reload_path(path) + relative_path = path.sub("#{self.instance.root}/", "") + subset = self.known_paths.select { |p| p.match(%r{^#{relative_path}}) } + + Find.find(path) do |path| + next if File.directory?(path) + next if Middleman::Watcher.ignore_list.any? { |r| path.match(r) } + relative_path = path.sub("#{self.instance.root}/", "") + subset.delete(relative_path) + self.did_change(relative_path) + end if File.exists?(path) + + subset.each do |removed_path| + self.did_delete(removed_path) + end + end + + # Like reload_path, but only triggers events on new files + # + # @param [String] path The path to reload + # @return [void] + def find_new_files(path) + relative_path = path.sub("#{self.instance.root}/", "") + subset = self.known_paths.select { |p| p.match(%r{^#{relative_path}}) } + + Find.find(path) do |file| + next if File.directory?(file) + next if Middleman::Watcher.ignore_list.any? { |r| path.match(r) } + relative_path = file.sub("#{self.instance.root}/", "") + self.did_change(relative_path) unless subset.include?(relative_path) + end if File.exists?(path) + end + + protected + # Notify callbacks for a file given an array of callbacks + # + # @param [String] path The file that was changed + # @param [Symbol] callbacks_name The name of the callbacks method + # @return [void] + def run_callbacks(path, callbacks_name) + return if ::Middleman::Watcher.ignore_list.any? { |r| path.match(r) } + + self.send(callbacks_name).each do |callback, matcher| + next if path.match(%r{^#{self.instance.build_dir}/}) + next unless matcher.nil? || path.match(matcher) + self.instance.instance_exec(path, &callback) + end + end end end end 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 c863a439..13659ec1 100644 --- a/middleman-core/lib/middleman-core/core_extensions/front_matter.rb +++ b/middleman-core/lib/middleman-core/core_extensions/front_matter.rb @@ -5,204 +5,208 @@ require "yaml" require "tilt" # Frontmatter namespace -module Middleman::CoreExtensions::FrontMatter +module Middleman + module CoreExtensions + module FrontMatter - # Setup extension - class << self + # Setup extension + class << self - # Once registered - def registered(app) - app.set :frontmatter_extensions, %w(.htm .html .php) - app.extend ClassMethods - app.send :include, InstanceMethods - app.delegate :frontmatter_changed, :to => :"self.class" + # Once registered + def registered(app) + app.set :frontmatter_extensions, %w(.htm .html .php) + 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 - - # Frontmatter class methods - module ClassMethods - - # Register callback on frontmatter updates - # @param [Regexp] matcher - # @return [Array>] - def frontmatter_changed(matcher=nil, &block) - @_frontmatter_changed ||= [] - @_frontmatter_changed << [block, matcher] if block_given? - @_frontmatter_changed - end - end - - module ResourceInstanceMethods - - # This page's frontmatter - # @return [Hash] - def data - app.frontmatter(source_file).first - end - - end - - # Frontmatter instance methods - module InstanceMethods - - # Override init - def initialize - exts = frontmatter_extensions.join("|").gsub(".", "\.") - - static_path = source_dir.sub(root, "").sub(/^\//, "").sub(/\/$/, "") + "/" - - matcher = %r{#{static_path}.*(#{exts})} - - files.changed matcher do |file| - frontmatter_extension.touch_file(file) - end - - files.deleted matcher do |file| - frontmatter_extension.remove_file(file) - end - - sitemap.provides_metadata matcher do |path| - fmdata = if self.frontmatter_extension.has_data?(path) - self.frontmatter(path)[0] - else - {} + ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods end - - data = {} - %w(layout layout_engine).each do |opt| - data[opt.to_sym] = fmdata[opt] if fmdata.has_key?(opt) - end - - { :options => data, :page => fmdata } + alias :included :registered end - - # Initialize class - frontmatter_extension - - super - end - - # Notify callbacks that the frontmatter changed - # @param [String] path - # @return [void] - def frontmatter_did_change(path) - frontmatter_changed.each do |callback, matcher| - next if path.match(%r{^#{build_dir}/}) - next if !matcher.nil? && !path.match(matcher) - instance_exec(path, &callback) - end - end - - # Get the frontmatter object - # @return [Middleman::CoreExtensions::FrontMatter::FrontMatter] - def frontmatter_extension - @_frontmatter_extension ||= FrontMatter.new(self) - end - - # Get the frontmatter for a given path - # @param [String] path - # @return [Hash] - def frontmatter(path) - frontmatter_extension.data(path) - end - end - # Core Frontmatter class - class FrontMatter + # Frontmatter class methods + module ClassMethods - # Initialize frontmatter with current app - # @param [Middleman::Application] app - def initialize(app) - @app = app - @source = File.expand_path(@app.source, @app.root) - @local_data = {} - - # Setup ignore callback - @app.ignore do |path| - if p = @app.sitemap.find_resource_by_path(path) - !p.proxy? && p.data && p.data["ignored"] == true - else - false + # Register callback on frontmatter updates + # @param [Regexp] matcher + # @return [Array>] + def frontmatter_changed(matcher=nil, &block) + @_frontmatter_changed ||= [] + @_frontmatter_changed << [block, matcher] if block_given? + @_frontmatter_changed end end - end + + module ResourceInstanceMethods + + # This page's frontmatter + # @return [Hash] + def data + app.frontmatter(source_file).first + end - # Whether the frontmatter knows about a path - # @param [String] path - # @return [Boolean] - def has_data?(path) - @local_data.has_key?(path.to_s) - end + end + + # Frontmatter instance methods + module InstanceMethods - # Update frontmatter if a file changes - # @param [String] file - # @return [void] - def touch_file(file) - extension = File.extname(file).sub(/\./, "") - return unless ::Tilt.mappings.has_key?(extension) + # Override init + def initialize + exts = frontmatter_extensions.join("|").gsub(".", "\.") - file = File.expand_path(file, @app.root) - content = File.read(file) + static_path = source_dir.sub(root, "").sub(/^\//, "").sub(/\/$/, "") + "/" + + matcher = %r{#{static_path}.*(#{exts})} - result = parse_front_matter(content) + files.changed matcher do |file| + frontmatter_extension.touch_file(file) + end + + files.deleted matcher do |file| + frontmatter_extension.remove_file(file) + end + + sitemap.provides_metadata matcher do |path| + fmdata = if self.frontmatter_extension.has_data?(path) + self.frontmatter(path)[0] + else + {} + end + + data = {} + %w(layout layout_engine).each do |opt| + data[opt.to_sym] = fmdata[opt] if fmdata.has_key?(opt) + end + + { :options => data, :page => fmdata } + end + + # Initialize class + frontmatter_extension + + super + end + + # Notify callbacks that the frontmatter changed + # @param [String] path + # @return [void] + def frontmatter_did_change(path) + frontmatter_changed.each do |callback, matcher| + next if path.match(%r{^#{build_dir}/}) + next if !matcher.nil? && !path.match(matcher) + instance_exec(path, &callback) + end + end + + # Get the frontmatter object + # @return [Middleman::CoreExtensions::FrontMatter::FrontMatter] + def frontmatter_extension + @_frontmatter_extension ||= FrontMatter.new(self) + end + + # Get the frontmatter for a given path + # @param [String] path + # @return [Hash] + def frontmatter(path) + frontmatter_extension.data(path) + end + end + + # Core Frontmatter class + class FrontMatter + + # Initialize frontmatter with current app + # @param [Middleman::Application] app + def initialize(app) + @app = app + @source = File.expand_path(@app.source, @app.root) + @local_data = {} + + # Setup ignore callback + @app.ignore do |path| + if p = @app.sitemap.find_resource_by_path(path) + !p.proxy? && p.data && p.data["ignored"] == true + else + false + end + end + end + + # Whether the frontmatter knows about a path + # @param [String] path + # @return [Boolean] + def has_data?(path) + @local_data.has_key?(path.to_s) + end + + # Update frontmatter if a file changes + # @param [String] file + # @return [void] + def touch_file(file) + extension = File.extname(file).sub(/\./, "") + return unless ::Tilt.mappings.has_key?(extension) + + file = File.expand_path(file, @app.root) + content = File.read(file) + + result = parse_front_matter(content) - if result - data, content = result + if result + data, content = result - data = ::Middleman::Util.recursively_enhance(data).freeze - @local_data[file] = [data, content] - @app.cache.set([:raw_template, file], result[1]) - @app.frontmatter_did_change(file) - end - end - - # Update frontmatter if a file is delted - # @param [String] file - # @return [void] - def remove_file(file) - file = File.expand_path(file, @app.root) - - if @local_data.has_key?(file) - @app.cache.remove(:raw_template, file) - @local_data.delete(file) - end - end - - # Get the frontmatter for a given path - # @param [String] path - # @return [Hash] - def data(path) - if @local_data.has_key?(path.to_s) - @local_data[path.to_s] - else - [ ::Middleman::Util.recursively_enhance({}).freeze, nil ] - end - end - - private - # Parse frontmatter out of a string - # @param [String] content - # @return [Array] - def parse_front_matter(content) - yaml_regex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m - if content =~ yaml_regex - content = content[($1.size + $2.size)..-1] - - begin - data = YAML.load($1) - rescue => e - puts "YAML Exception: #{e.message}" - return false + data = ::Middleman::Util.recursively_enhance(data).freeze + @local_data[file] = [data, content] + @app.cache.set([:raw_template, file], result[1]) + @app.frontmatter_did_change(file) + end end + + # Update frontmatter if a file is delted + # @param [String] file + # @return [void] + def remove_file(file) + file = File.expand_path(file, @app.root) + + if @local_data.has_key?(file) + @app.cache.remove(:raw_template, file) + @local_data.delete(file) + end + end + + # Get the frontmatter for a given path + # @param [String] path + # @return [Hash] + def data(path) + if @local_data.has_key?(path.to_s) + @local_data[path.to_s] + else + [ ::Middleman::Util.recursively_enhance({}).freeze, nil ] + end + end + + private + # Parse frontmatter out of a string + # @param [String] content + # @return [Array] + def parse_front_matter(content) + yaml_regex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m + if content =~ yaml_regex + content = content[($1.size + $2.size)..-1] + + begin + data = YAML.load($1) + rescue => e + puts "YAML Exception: #{e.message}" + return false + end - else - return false + else + return false + end + + [data, content] + end end - - [data, content] end end end diff --git a/middleman-core/lib/middleman-core/core_extensions/i18n.rb b/middleman-core/lib/middleman-core/core_extensions/i18n.rb index ff059069..7113197a 100644 --- a/middleman-core/lib/middleman-core/core_extensions/i18n.rb +++ b/middleman-core/lib/middleman-core/core_extensions/i18n.rb @@ -1,142 +1,146 @@ # i18n Namespace -module Middleman::CoreExtensions::I18n +module Middleman + module CoreExtensions + module I18n - # Setup extension - class << self + # Setup extension + class << self - # Once registerd - def registered(app) - app.set :locales_dir, "locales" + # Once registerd + def registered(app) + app.set :locales_dir, "locales" - app.send :include, InstanceMethods + app.send :include, InstanceMethods - # Needed for helpers as well - app.after_configuration do - # This is for making the tests work - since the tests - # don't completely reload middleman, I18n.load_path can get - # polluted with paths from other test app directories that don't - # exist anymore. - ::I18n.load_path.delete_if {|path| path =~ %r{tmp/aruba}} - ::I18n.load_path += Dir[File.join(root, locales_dir, "*.yml")] - ::I18n.reload! - end - end - alias :included :registered - end - - class Localizer - def initialize(app) - @app = app - @maps = {} - end - - def setup(options) - @options = options - - @lang_map = @options[:lang_map] || {} - @path = @options[:path] || "/:locale/" - @templates_dir = @options[:templates_dir] || "localizable" - @mount_at_root = @options.has_key?(:mount_at_root) ? @options[:mount_at_root] : langs.first - - if !@app.build? - puts "== Locales: #{langs.join(", ")}" - end - - # Don't output localizable files - @app.ignore File.join(@templates_dir, "**") - - @app.sitemap.provides_metadata_for_path do |url| - if d = get_localization_data(url) - lang, page_id = d - instance_vars = Proc.new { - ::I18n.locale = lang - @lang = lang - @page_id = page_id - } - { :blocks => [instance_vars] } - else - {} - end - end - - @app.sitemap.register_resource_list_manipulator( - :i18n, - @app.i18n - ) - end - - def langs - @options[:langs] || begin - Dir[File.join(@app.root, @app.locales_dir, "*.yml")].map { |file| - File.basename(file).gsub(".yml", "") - }.sort.map(&:to_sym) - end - end - - def get_localization_data(path) - @_localization_data ||= {} - @_localization_data[path] - end - - # Update the main sitemap resource list - # @return [void] - def manipulate_resource_list(resources) - @_localization_data = {} - - new_resources = [] - - resources.each do |resource| - next unless File.fnmatch(File.join(@templates_dir, "**"), resource.path) - - page_id = File.basename(resource.path, File.extname(resource.path)) - - langs.map do |lang| - ::I18n.locale = lang - - localized_page_id = ::I18n.t("paths.#{page_id}", :default => page_id) - path = resource.path.sub(@templates_dir, "") - - # Build lang path - if @mount_at_root == lang - prefix = "/" - else - replacement = @lang_map.has_key?(lang) ? @lang_map[lang] : lang - prefix = @path.sub(":locale", replacement.to_s) + # Needed for helpers as well + app.after_configuration do + # This is for making the tests work - since the tests + # don't completely reload middleman, I18n.load_path can get + # polluted with paths from other test app directories that don't + # exist anymore. + ::I18n.load_path.delete_if {|path| path =~ %r{tmp/aruba}} + ::I18n.load_path += Dir[File.join(root, locales_dir, "*.yml")] + ::I18n.reload! end - - path = ::Middleman::Util.normalize_path( - File.join(prefix, path.sub(page_id, localized_page_id)) + end + alias :included :registered + end + + class Localizer + def initialize(app) + @app = app + @maps = {} + end + + def setup(options) + @options = options + + @lang_map = @options[:lang_map] || {} + @path = @options[:path] || "/:locale/" + @templates_dir = @options[:templates_dir] || "localizable" + @mount_at_root = @options.has_key?(:mount_at_root) ? @options[:mount_at_root] : langs.first + + if !@app.build? + puts "== Locales: #{langs.join(", ")}" + end + + # Don't output localizable files + @app.ignore File.join(@templates_dir, "**") + + @app.sitemap.provides_metadata_for_path do |url| + if d = get_localization_data(url) + lang, page_id = d + instance_vars = Proc.new { + ::I18n.locale = lang + @lang = lang + @page_id = page_id + } + { :blocks => [instance_vars] } + else + {} + end + end + + @app.sitemap.register_resource_list_manipulator( + :i18n, + @app.i18n ) + end + + def langs + @options[:langs] || begin + Dir[File.join(@app.root, @app.locales_dir, "*.yml")].map { |file| + File.basename(file).gsub(".yml", "") + }.sort.map(&:to_sym) + end + end + + def get_localization_data(path) + @_localization_data ||= {} + @_localization_data[path] + end + + # Update the main sitemap resource list + # @return [void] + def manipulate_resource_list(resources) + @_localization_data = {} + + new_resources = [] + + resources.each do |resource| + next unless File.fnmatch(File.join(@templates_dir, "**"), resource.path) + + page_id = File.basename(resource.path, File.extname(resource.path)) + + langs.map do |lang| + ::I18n.locale = lang + + localized_page_id = ::I18n.t("paths.#{page_id}", :default => page_id) + path = resource.path.sub(@templates_dir, "") - @_localization_data[path] = [lang, path, localized_page_id] + # Build lang path + if @mount_at_root == lang + prefix = "/" + else + replacement = @lang_map.has_key?(lang) ? @lang_map[lang] : lang + prefix = @path.sub(":locale", replacement.to_s) + end - p = ::Middleman::Sitemap::Resource.new( - @app.sitemap, - path - ) - p.proxy_to(resource.path) + path = ::Middleman::Util.normalize_path( + File.join(prefix, path.sub(page_id, localized_page_id)) + ) - new_resources << p + @_localization_data[path] = [lang, path, localized_page_id] + + p = ::Middleman::Sitemap::Resource.new( + @app.sitemap, + path + ) + p.proxy_to(resource.path) + + new_resources << p + end + end + + resources + new_resources end end - - resources + new_resources - end - end - # Frontmatter class methods - module InstanceMethods + # Frontmatter class methods + module InstanceMethods - # Initialize the i18n - def i18n - @_i18n ||= Localizer.new(self) - end + # Initialize the i18n + def i18n + @_i18n ||= Localizer.new(self) + end - # Main i18n API - def localize(options={}) - settings.after_configuration do - i18n.setup(options) + # Main i18n API + def localize(options={}) + settings.after_configuration do + i18n.setup(options) + end + end end end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/rendering.rb b/middleman-core/lib/middleman-core/core_extensions/rendering.rb index 4e5d44ae..1f4df24c 100644 --- a/middleman-core/lib/middleman-core/core_extensions/rendering.rb +++ b/middleman-core/lib/middleman-core/core_extensions/rendering.rb @@ -9,359 +9,363 @@ class Tilt::Template end # Rendering extension -module Middleman::CoreExtensions::Rendering +module Middleman + module CoreExtensions + module Rendering - # Setup extension - class << self + # Setup extension + class << self - # Once registered - def registered(app) - # Include methods - app.send :include, InstanceMethods + # Once registered + def registered(app) + # Include methods + app.send :include, InstanceMethods - # Activate custom renderers - app.register Middleman::Renderers::ERb - end + # Activate custom renderers + app.register Middleman::Renderers::ERb + end - alias :included :registered - end + alias :included :registered + end - # Custom error class for handling - class TemplateNotFound < RuntimeError - end + # Custom error class for handling + class TemplateNotFound < RuntimeError + end - # Rendering instance methods - module InstanceMethods + # Rendering instance methods + module InstanceMethods - # Override init to clear cache on file removal - def initialize - # Default extension map - @_template_extensions = { + # Override init to clear cache on file removal + def initialize + # Default extension map + @_template_extensions = { - } + } - static_path = source_dir.sub(self.root, "").sub(/^\//, "") - render_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}}) + static_path = source_dir.sub(self.root, "").sub(/^\//, "") + render_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}}) - self.files.changed render_regex do |file| - path = File.expand_path(file, self.root) - self.cache.remove(:raw_template, path) - end + self.files.changed render_regex do |file| + path = File.expand_path(file, self.root) + self.cache.remove(:raw_template, path) + end - super - end + super + end - # Add or overwrite a default template extension - # - # @param [Hash] extension_map - # @return [void] - def template_extensions(extension_map={}) - @_template_extensions.merge!(extension_map) - end + # Add or overwrite a default template extension + # + # @param [Hash] extension_map + # @return [void] + def template_extensions(extension_map={}) + @_template_extensions.merge!(extension_map) + end - # Render a template, with layout, given a path - # - # @param [String] path - # @param [Hash] locs - # @param [Hash] opts - # @return [String] - def render_template(path, locs={}, opts={}) - # Detect the remdering engine from the extension - extension = File.extname(path) - engine = extension[1..-1].to_sym + # Render a template, with layout, given a path + # + # @param [String] path + # @param [Hash] locs + # @param [Hash] opts + # @return [String] + def render_template(path, locs={}, opts={}) + # Detect the remdering engine from the extension + extension = File.extname(path) + engine = extension[1..-1].to_sym - # Store last engine for later (could be inside nested renders) - @current_engine, engine_was = engine, @current_engine + # Store last engine for later (could be inside nested renders) + @current_engine, engine_was = engine, @current_engine - # Use a dup of self as a context so that instance variables set within - # the template don't persist for other templates. - context = self.dup + # Use a dup of self as a context so that instance variables set within + # the template don't persist for other templates. + context = self.dup - # Store current locs/opts for later - @current_locs = locs, @current_opts = opts + # Store current locs/opts for later + @current_locs = locs, @current_opts = opts - # Keep rendering template until we've used up all extensions. This handles - # cases like `style.css.sass.erb` - while ::Tilt[path] - content = render_individual_file(path, locs, opts, context) - path = File.basename(path, File.extname(path)) - cache.set([:raw_template, path], content) - end + # Keep rendering template until we've used up all extensions. This handles + # cases like `style.css.sass.erb` + while ::Tilt[path] + content = render_individual_file(path, locs, opts, context) + path = File.basename(path, File.extname(path)) + cache.set([:raw_template, path], content) + end - # Certain output file types don't use layouts - needs_layout = !%w(.js .json .css .txt).include?(extension) + # Certain output file types don't use layouts + needs_layout = !%w(.js .json .css .txt).include?(extension) - # If we need a layout and have a layout, use it - if needs_layout && layout_path = fetch_layout(engine, opts) - content = render_individual_file(layout_path, locs, opts, context) { content } - end + # If we need a layout and have a layout, use it + if needs_layout && layout_path = fetch_layout(engine, opts) + content = render_individual_file(layout_path, locs, opts, context) { content } + end - # Return result - content - ensure - # Pop all the saved variables from earlier as we may be returning to a - # previous render (layouts, partials, nested layouts). - @current_engine = engine_was - @content_blocks = nil - @current_locs = nil - @current_opts = nil - end + # Return result + content + ensure + # Pop all the saved variables from earlier as we may be returning to a + # previous render (layouts, partials, nested layouts). + @current_engine = engine_was + @content_blocks = nil + @current_locs = nil + @current_opts = nil + end - # Sinatra/Padrino compatible render method signature referenced by some view - # helpers. Especially partials. - # - # @param [String, Symbol] engine - # @param [String, Symbol] data - # @param [Hash] options - # @return [String] - def render(engine, data, options={}, &block) - data = data.to_s + # Sinatra/Padrino compatible render method signature referenced by some view + # helpers. Especially partials. + # + # @param [String, Symbol] engine + # @param [String, Symbol] data + # @param [Hash] options + # @return [String] + def render(engine, data, options={}, &block) + data = data.to_s - locals = options[:locals] + locals = options[:locals] - found_partial = false - engine = nil + found_partial = false + engine = nil - # If the path is known to the sitemap - if resource = sitemap.find_resource_by_path(current_path) - current_dir = File.dirname(resource.source_file) - engine = File.extname(resource.source_file)[1..-1].to_sym + # If the path is known to the sitemap + if resource = sitemap.find_resource_by_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 - relative_dir = File.join(current_dir.sub("#{self.source_dir}/", ""), data) + # Look for partials relative to the current path + if current_dir != self.source_dir + relative_dir = File.join(current_dir.sub("#{self.source_dir}/", ""), data) - # Try to use the current engine first - found_partial, found_engine = resolve_template(relative_dir, :preferred_engine => engine, :try_without_underscore => true) + # Try to use the current engine first + found_partial, found_engine = resolve_template(relative_dir, :preferred_engine => engine, :try_without_underscore => true) - # Fall back to any engine available + # Fall back to any engine available + if !found_partial + found_partial, found_engine = resolve_template(relative_dir, :try_without_underscore => true) + end + end + end + + # Look in the root for the partial with the current engine + if !found_partial && !engine.nil? + found_partial, found_engine = resolve_template(data, :preferred_engine => engine, :try_without_underscore => true) + end + + # Look in the root with any engine if !found_partial - found_partial, found_engine = resolve_template(relative_dir, :try_without_underscore => true) - end - end - end - - # Look in the root for the partial with the current engine - if !found_partial && !engine.nil? - found_partial, found_engine = resolve_template(data, :preferred_engine => engine, :try_without_underscore => true) - end - - # Look in the root with any engine - if !found_partial - found_partial, found_engine = resolve_template(data, :try_without_underscore => true) - end - - # Render the partial if found, otherwide throw exception - if found_partial - render_individual_file(found_partial, locals, options, self, &block) - else - raise ::Middleman::CoreExtensions::Rendering::TemplateNotFound, "Could not locate partial: #{data}" - end - end - - # Render an on-disk file. Used for everything, including layouts. - # - # @param [String, Symbol] path - # @param [Hash] locs - # @param [Hash] opts - # @param [Class] context - # @return [String] - def render_individual_file(path, locs = {}, opts = {}, context = self, &block) - path = path.to_s - - # Save current buffere for later - @_out_buf, _buf_was = "", @_out_buf - - # Read from disk or cache the contents of the file - body = cache.fetch(:raw_template, path) do - File.read(path) - end - - # Merge per-extension options from config - extension = File.extname(path) - options = opts.merge(options_for_ext(extension)) - options[:outvar] ||= '@_out_buf' - - # Read compiled template from disk or cache - template = cache.fetch(:compiled_template, options, body) do - ::Tilt.new(path, 1, options) { body } - end - - # Render using Tilt - template.render(context, locs, &block) - ensure - # Reset stored buffer - @_out_buf = _buf_was - end - - # Get a hash of configuration options for a given file extension, from - # config.rb - # - # @param [String] ext - # @return [Hash] - def options_for_ext(ext) - # Read options for extension from config/Tilt or cache - cache.fetch(:options_for_ext, ext) do - options = {} - - # Find all the engines which handle this extension in tilt. Look for - # config variables of that name and merge it - extension_class = ::Tilt[ext] - ::Tilt.mappings.each do |ext, engines| - next unless engines.include? extension_class - engine_options = respond_to?(ext.to_sym) ? send(ext.to_sym) : {} - options.merge!(engine_options) - end - - options - end - end - - # Find a layout for a given engine - # - # @param [Symbol] engine - # @param [Hash] opts - # @return [String] - def fetch_layout(engine, opts) - # The layout name comes from either the system default or the options - local_layout = opts.has_key?(:layout) ? opts[:layout] : layout - return false unless local_layout - - # Look for engine-specific options - engine_options = respond_to?(engine) ? send(engine) : {} - - # The engine for the layout can be set in options, engine_options or passed - # into this method - layout_engine = if opts.has_key?(:layout_engine) - opts[:layout_engine] - elsif engine_options.has_key?(:layout_engine) - engine_options[:layout_engine] - else - engine - end - - # Automatic mode - if local_layout == :_auto_layout - # Look for :layout of any extension - # If found, use it. If not, continue - locate_layout(:layout, layout_engine) || false - else - # Look for specific layout - # If found, use it. If not, error. - if layout_path = locate_layout(local_layout, layout_engine) - layout_path - else - raise ::Middleman::CoreExtensions::Rendering::TemplateNotFound, "Could not locate layout: #{local_layout}" - end - end - end - - # Find a layout on-disk, optionally using a specific engine - # @param [String] name - # @param [Symbol] preferred_engine - # @return [String] - def locate_layout(name, preferred_engine=nil) - # Whether we've found the layout - layout_path = false - - # If we prefer a specific engine - if !preferred_engine.nil? - # Check root - layout_path, layout_engine = resolve_template(name, :preferred_engine => preferred_engine) - - # Check layouts folder - if !layout_path - layout_path, layout_engine = resolve_template(File.join("layouts", name.to_s), :preferred_engine => preferred_engine) - end - end - - # Check root, no preference - if !layout_path - layout_path, layout_engine = resolve_template(name) - end - - # Check layouts folder, no preference - if !layout_path - layout_path, layout_engine = resolve_template(File.join("layouts", name.to_s)) - end - - # Return the path - layout_path - end - - # Allow layouts to be wrapped in the contents of other layouts - # @param [String, Symbol] layout_name - # @return [void] - def wrap_layout(layout_name, &block) - content = capture(&block) if block_given? - layout_path = locate_layout(layout_name, current_engine) - concat render_individual_file(layout_path, @current_locs || {}, @current_opts || {}, self) { content } - end - - # The currently rendering engine - # @return [Symbol, nil] - def current_engine - @current_engine ||= nil - end - - # Find a template on disk given a output path - # @param [String] request_path - # @param [Hash] options - # @return [Array, Boolean] - def resolve_template(request_path, options={}) - # Find the path by searching or using the cache - request_path = request_path.to_s - cache.fetch(:resolve_template, request_path, options) do - relative_path = request_path.sub(%r{^/}, "") - on_disk_path = File.expand_path(relative_path, self.source_dir) - - # By default, any engine will do - preferred_engine = "*" - - # Unless we're specifically looking for a preferred engine - if options.has_key?(:preferred_engine) - extension_class = ::Tilt[options[:preferred_engine]] - matched_exts = [] - - # Get a list of extensions for a preferred engine - # TODO: Cache this - ::Tilt.mappings.each do |ext, engines| - next unless engines.include? extension_class - matched_exts << ext + found_partial, found_engine = resolve_template(data, :try_without_underscore => true) end - # Change the glob to only look for the matched extensions - if matched_exts.length > 0 - preferred_engine = "{" + matched_exts.join(",") + "}" + # Render the partial if found, otherwide throw exception + if found_partial + render_individual_file(found_partial, locals, options, self, &block) else - return false + raise ::Middleman::CoreExtensions::Rendering::TemplateNotFound, "Could not locate partial: #{data}" end end - # Look for files that match - path_with_ext = on_disk_path + "." + preferred_engine - - found_path = Dir[path_with_ext].find do |path| - ::Tilt[path] + # Render an on-disk file. Used for everything, including layouts. + # + # @param [String, Symbol] path + # @param [Hash] locs + # @param [Hash] opts + # @param [Class] context + # @return [String] + def render_individual_file(path, locs = {}, opts = {}, context = self, &block) + path = path.to_s + + # Save current buffere for later + @_out_buf, _buf_was = "", @_out_buf + + # Read from disk or cache the contents of the file + body = cache.fetch(:raw_template, path) do + File.read(path) + end + + # Merge per-extension options from config + extension = File.extname(path) + options = opts.merge(options_for_ext(extension)) + options[:outvar] ||= '@_out_buf' + + # Read compiled template from disk or cache + template = cache.fetch(:compiled_template, options, body) do + ::Tilt.new(path, 1, options) { body } + end + + # Render using Tilt + template.render(context, locs, &block) + ensure + # Reset stored buffer + @_out_buf = _buf_was end - - if !found_path && options[:try_without_underscore] && - path_no_underscore = path_with_ext. - sub(relative_path, relative_path.sub(/^_/, ""). - sub(/\/_/, "/")) - found_path = Dir[path_no_underscore].find do |path| - ::Tilt[path] + + # Get a hash of configuration options for a given file extension, from + # config.rb + # + # @param [String] ext + # @return [Hash] + def options_for_ext(ext) + # Read options for extension from config/Tilt or cache + cache.fetch(:options_for_ext, ext) do + options = {} + + # Find all the engines which handle this extension in tilt. Look for + # config variables of that name and merge it + extension_class = ::Tilt[ext] + ::Tilt.mappings.each do |ext, engines| + next unless engines.include? extension_class + engine_options = respond_to?(ext.to_sym) ? send(ext.to_sym) : {} + options.merge!(engine_options) + end + + options end end + + # Find a layout for a given engine + # + # @param [Symbol] engine + # @param [Hash] opts + # @return [String] + def fetch_layout(engine, opts) + # The layout name comes from either the system default or the options + local_layout = opts.has_key?(:layout) ? opts[:layout] : layout + return false unless local_layout + + # Look for engine-specific options + engine_options = respond_to?(engine) ? send(engine) : {} + + # The engine for the layout can be set in options, engine_options or passed + # into this method + layout_engine = if opts.has_key?(:layout_engine) + opts[:layout_engine] + elsif engine_options.has_key?(:layout_engine) + engine_options[:layout_engine] + else + engine + end + + # Automatic mode + if local_layout == :_auto_layout + # Look for :layout of any extension + # If found, use it. If not, continue + locate_layout(:layout, layout_engine) || false + else + # Look for specific layout + # If found, use it. If not, error. + if layout_path = locate_layout(local_layout, layout_engine) + layout_path + else + raise ::Middleman::CoreExtensions::Rendering::TemplateNotFound, "Could not locate layout: #{local_layout}" + end + end + end + + # Find a layout on-disk, optionally using a specific engine + # @param [String] name + # @param [Symbol] preferred_engine + # @return [String] + def locate_layout(name, preferred_engine=nil) + # Whether we've found the layout + layout_path = false + + # If we prefer a specific engine + if !preferred_engine.nil? + # Check root + layout_path, layout_engine = resolve_template(name, :preferred_engine => preferred_engine) - # If we found one, return it and the found engine - if found_path || (File.exists?(on_disk_path) && !File.directory?(on_disk_path)) - engine = found_path ? File.extname(found_path)[1..-1].to_sym : nil - [ found_path || on_disk_path, engine ] - else - false + # Check layouts folder + if !layout_path + layout_path, layout_engine = resolve_template(File.join("layouts", name.to_s), :preferred_engine => preferred_engine) + end + end + + # Check root, no preference + if !layout_path + layout_path, layout_engine = resolve_template(name) + end + + # Check layouts folder, no preference + if !layout_path + layout_path, layout_engine = resolve_template(File.join("layouts", name.to_s)) + end + + # Return the path + layout_path + end + + # Allow layouts to be wrapped in the contents of other layouts + # @param [String, Symbol] layout_name + # @return [void] + def wrap_layout(layout_name, &block) + content = capture(&block) if block_given? + layout_path = locate_layout(layout_name, current_engine) + concat render_individual_file(layout_path, @current_locs || {}, @current_opts || {}, self) { content } + end + + # The currently rendering engine + # @return [Symbol, nil] + def current_engine + @current_engine ||= nil + end + + # Find a template on disk given a output path + # @param [String] request_path + # @param [Hash] options + # @return [Array, Boolean] + def resolve_template(request_path, options={}) + # Find the path by searching or using the cache + request_path = request_path.to_s + cache.fetch(:resolve_template, request_path, options) do + relative_path = request_path.sub(%r{^/}, "") + on_disk_path = File.expand_path(relative_path, self.source_dir) + + # By default, any engine will do + preferred_engine = "*" + + # Unless we're specifically looking for a preferred engine + if options.has_key?(:preferred_engine) + extension_class = ::Tilt[options[:preferred_engine]] + matched_exts = [] + + # Get a list of extensions for a preferred engine + # TODO: Cache this + ::Tilt.mappings.each do |ext, engines| + next unless engines.include? extension_class + matched_exts << ext + end + + # Change the glob to only look for the matched extensions + if matched_exts.length > 0 + preferred_engine = "{" + matched_exts.join(",") + "}" + else + return false + end + end + + # Look for files that match + path_with_ext = on_disk_path + "." + preferred_engine + + found_path = Dir[path_with_ext].find do |path| + ::Tilt[path] + end + + if !found_path && options[:try_without_underscore] && + path_no_underscore = path_with_ext. + sub(relative_path, relative_path.sub(/^_/, ""). + sub(/\/_/, "/")) + found_path = Dir[path_no_underscore].find do |path| + ::Tilt[path] + end + end + + # If we found one, return it and the found engine + if found_path || (File.exists?(on_disk_path) && !File.directory?(on_disk_path)) + engine = found_path ? File.extname(found_path)[1..-1].to_sym : nil + [ found_path || on_disk_path, engine ] + else + false + end + end end end end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/request.rb b/middleman-core/lib/middleman-core/core_extensions/request.rb index cfa9d18c..cb706668 100644 --- a/middleman-core/lib/middleman-core/core_extensions/request.rb +++ b/middleman-core/lib/middleman-core/core_extensions/request.rb @@ -1,6 +1,7 @@ -# Base helper to manipulate asset paths module Middleman module CoreExtensions + + # Base helper to manipulate asset paths module Request # Extension registered diff --git a/middleman-core/lib/middleman-core/core_extensions/routing.rb b/middleman-core/lib/middleman-core/core_extensions/routing.rb index 5aff173c..c51f23e0 100644 --- a/middleman-core/lib/middleman-core/core_extensions/routing.rb +++ b/middleman-core/lib/middleman-core/core_extensions/routing.rb @@ -1,88 +1,92 @@ # Routing extension -module Middleman::CoreExtensions::Routing +module Middleman + module CoreExtensions + module Routing - # Setup extension - class << self + # Setup extension + class << self - # Once registered - def registered(app) - # Include methods - app.send :include, InstanceMethods - end + # Once registered + def registered(app) + # Include methods + app.send :include, InstanceMethods + end - alias :included :registered - end + alias :included :registered + end - # Routing instance methods - module InstanceMethods + # Routing instance methods + module InstanceMethods - # Takes a block which allows many pages to have the same layout - # - # with_layout :admin do - # page "/admin/" - # page "/admin/login.html" - # end - # - # @param [String, Symbol] layout_name - # @return [void] - def with_layout(layout_name, &block) - old_layout = layout + # Takes a block which allows many pages to have the same layout + # + # with_layout :admin do + # page "/admin/" + # page "/admin/login.html" + # end + # + # @param [String, Symbol] layout_name + # @return [void] + def with_layout(layout_name, &block) + old_layout = layout - set :layout, layout_name - instance_exec(&block) if block_given? - ensure - set :layout, old_layout - end - - # The page method allows the layout to be set on a specific path - # - # page "/about.html", :layout => false - # page "/", :layout => :homepage_layout - # - # @param [String] url - # @param [Hash] opts - # @return [void] - def page(url, opts={}, &block) - a_block = block_given? ? block : nil - - # Default layout - opts[:layout] = layout if opts[:layout].nil? - - # If the url is a regexp - if url.is_a?(Regexp) || url.include?("*") - - # Use the metadata loop for matching against paths at runtime - sitemap.provides_metadata_for_path url do |url| - { :options => opts, :blocks => [a_block] } + set :layout, layout_name + instance_exec(&block) if block_given? + ensure + set :layout, old_layout end - - return - end + + # The page method allows the layout to be set on a specific path + # + # page "/about.html", :layout => false + # page "/", :layout => :homepage_layout + # + # @param [String] url + # @param [Hash] opts + # @return [void] + def page(url, opts={}, &block) + a_block = block_given? ? block : nil - # Normalized path - url = full_path(url) + # Default layout + opts[:layout] = layout if opts[:layout].nil? - # Setup proxy - if opts.has_key?(:proxy) - proxy(url, opts[:proxy]) + # If the url is a regexp + if url.is_a?(Regexp) || url.include?("*") - if opts.has_key?(:ignore) && opts[:ignore] - ignore(opts[:proxy]) - opts.delete(:ignore) - end + # Use the metadata loop for matching against paths at runtime + sitemap.provides_metadata_for_path url do |url| + { :options => opts, :blocks => [a_block] } + end - opts.delete(:proxy) - else - if opts.has_key?(:ignore) && opts[:ignore] - ignore(url) - opts.delete(:ignore) - end - end + return + end - # Setup a metadata matcher for rendering those options - sitemap.provides_metadata_for_path url do |url| - { :options => opts, :blocks => [a_block] } + # Normalized path + url = full_path(url) + + # Setup proxy + if opts.has_key?(:proxy) + proxy(url, opts[:proxy]) + + if opts.has_key?(:ignore) && opts[:ignore] + ignore(opts[:proxy]) + opts.delete(:ignore) + end + + opts.delete(:proxy) + else + if opts.has_key?(:ignore) && opts[:ignore] + ignore(url) + opts.delete(:ignore) + end + end + + # Setup a metadata matcher for rendering those options + sitemap.provides_metadata_for_path url do |url| + { :options => opts, :blocks => [a_block] } + end + end end end end -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/core_extensions/show_exceptions.rb b/middleman-core/lib/middleman-core/core_extensions/show_exceptions.rb index 260e04d8..ca45a49e 100644 --- a/middleman-core/lib/middleman-core/core_extensions/show_exceptions.rb +++ b/middleman-core/lib/middleman-core/core_extensions/show_exceptions.rb @@ -1,26 +1,31 @@ +# Require lib +require 'rack/showexceptions' + # Support rack/showexceptions during development -module Middleman::CoreExtensions::ShowExceptions +module Middleman + module CoreExtensions + module ShowExceptions - # Setup extension - class << self + # Setup extension + class << self - # Once registered - def registered(app) - # Require lib - require 'rack/showexceptions' - - # When in dev - app.configure :development do - # Include middlemare - if show_exceptions - use ::Middleman::CoreExtensions::ShowExceptions::Middleware + # Once registered + def registered(app) + # When in dev + app.configure :development do + # Include middlemare + if show_exceptions + use ::Middleman::CoreExtensions::ShowExceptions::Middleware + end + end end end + + # Custom exception class + # TODO: Style this ourselves + class Middleware < ::Rack::ShowExceptions + end + end end - - # Custom exception class - # TODO: Style this ourselves - class Middleware < ::Rack::ShowExceptions - end end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/extensions.rb b/middleman-core/lib/middleman-core/extensions.rb index a7d94b2a..d42ebca3 100644 --- a/middleman-core/lib/middleman-core/extensions.rb +++ b/middleman-core/lib/middleman-core/extensions.rb @@ -1,72 +1,7 @@ module Middleman - # Backwards compatibility namespace - module Features - end - - module CoreExtensions - # Rack Request - autoload :Request, "middleman-core/core_extensions/request" - - # File Change Notifier - autoload :FileWatcher, "middleman-core/core_extensions/file_watcher" - - # In-memory Sitemap - autoload :Sitemap, "middleman-core/core_extensions/sitemap" - - # Add Builder callbacks - autoload :Builder, "middleman-core/core_extensions/builder" - - # Custom Feature API - autoload :Extensions, "middleman-core/core_extensions/extensions" - - # Asset Path Pipeline - autoload :Assets, "middleman-core/core_extensions/assets" - - # Data looks at the data/ folder for YAML files and makes them available - # to dynamic requests. - autoload :Data, "middleman-core/core_extensions/data" - - # Parse YAML from templates - autoload :FrontMatter, "middleman-core/core_extensions/front_matter" - - # External helpers looks in the helpers/ folder for helper modules - autoload :ExternalHelpers, "middleman-core/core_extensions/external_helpers" - - # DefaultHelpers are the built-in dynamic template helpers. - autoload :DefaultHelpers, "middleman-core/core_extensions/default_helpers" - - # Extended version of Padrino's rendering - autoload :Rendering, "middleman-core/core_extensions/rendering" - - # Pass custom options to views - autoload :Routing, "middleman-core/core_extensions/routing" - - # Catch and show exceptions at the Rack level - autoload :ShowExceptions, "middleman-core/core_extensions/show_exceptions" - - # i18n - autoload :I18n, "middleman-core/core_extensions/i18n" - end - module Extensions - # Provide Apache-style index.html files for directories - autoload :DirectoryIndexes, "middleman-core/extensions/directory_indexes" - # Lorem provides a handful of helpful prototyping methods to generate - # words, paragraphs, fake images, names and email addresses. - autoload :Lorem, "middleman-core/extensions/lorem" - - # AutomaticImageSizes inspects the images used in your dynamic templates - # and automatically adds width and height attributes to their HTML - # elements. - autoload :AutomaticImageSizes, "middleman-core/extensions/automatic_image_sizes" - - # AssetHost allows you to setup multiple domains to host your static - # assets. Calls to asset paths in dynamic templates will then rotate - # through each of the asset servers to better spread the load. - autoload :AssetHost, "middleman-core/extensions/asset_host" - class << self def registered @_registered ||= {} diff --git a/middleman-core/lib/middleman-core/extensions/directory_indexes.rb b/middleman-core/lib/middleman-core/extensions/directory_indexes.rb index 79b8caa9..36cb3c3f 100644 --- a/middleman-core/lib/middleman-core/extensions/directory_indexes.rb +++ b/middleman-core/lib/middleman-core/extensions/directory_indexes.rb @@ -51,7 +51,4 @@ module Middleman::Extensions end end end - - # Register the extension - register :directory_indexes, DirectoryIndexes -end +end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/extensions/lorem.rb b/middleman-core/lib/middleman-core/extensions/lorem.rb index c7ce50d7..418d7bfc 100644 --- a/middleman-core/lib/middleman-core/extensions/lorem.rb +++ b/middleman-core/lib/middleman-core/extensions/lorem.rb @@ -192,7 +192,4 @@ module Middleman::Extensions end end end - - # Register the extension - register :lorem, Lorem end \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/sitemap.rb b/middleman-core/lib/middleman-core/sitemap.rb index b3323a8d..c69e3b6a 100644 --- a/middleman-core/lib/middleman-core/sitemap.rb +++ b/middleman-core/lib/middleman-core/sitemap.rb @@ -1,78 +1,78 @@ +require "middleman-core/sitemap/store" +require "middleman-core/sitemap/resource" + +require "middleman-core/sitemap/extensions/on_disk" +require "middleman-core/sitemap/extensions/proxies" +require "middleman-core/sitemap/extensions/ignores" + # Core Sitemap Extensions -module Middleman::Sitemap +module Middleman - autoload :Store, "middleman-core/sitemap/store" - autoload :Resource, "middleman-core/sitemap/resource" + module Sitemap - 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 + # Setup Extension + class << self - # Once registered - def registered(app) + # Once registered + def registered(app) - app.register Middleman::Sitemap::Extensions::Proxies - app.register Middleman::Sitemap::Extensions::Ignores + 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(/^\./) }, + # 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 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(/\/__/)) }, + # 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\//) - }, + :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(/\./) }, - } + # Files without any output extension (layouts, partials) + # :extensionless => proc { |file, path| !path.match(/\./) }, + } - # Include instance methods - app.send :include, InstanceMethods + # Include instance methods + app.send :include, InstanceMethods - # Initialize Sitemap - app.before_configuration do - sitemap + # Initialize Sitemap + app.before_configuration do + sitemap + end end - end - alias :included :registered + alias :included :registered - end + end - # Sitemap instance methods - module InstanceMethods + # 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 - # 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 index 9eca8636..8e0741ef 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb @@ -1,85 +1,91 @@ -module Middleman::Sitemap::Extensions +module Middleman + + module Sitemap + + module Extensions - module Ignores + module Ignores - # Setup extension - class << self + # Setup extension + class << self - # Once registered - def registered(app) - # Include methods - app.send :include, InstanceMethods + # Once registered + def registered(app) + # Include methods + app.send :include, InstanceMethods - ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods - end - - alias :included :registered - end - - # Helpers methods for Resources - module ResourceInstanceMethods - - # Whether the Resource is ignored - # @return [Boolean] - def ignored? - @app.ignore_manager.ignored?(path) || - (!proxy? && - @app.ignore_manager.ignored?(source_file.sub("#{@app.source_dir}/", "")) - ) - end - end - - # Ignore-related instance methods - module InstanceMethods - def ignore_manager - @_ignore_manager ||= IgnoreManager.new(self) - 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) - ignore_manager.ignore(path, &block) - end - end - - # Class to handle managing ignores - class IgnoreManager - def initialize(app) - @app = app - - # Array of callbacks which can ass ignored - @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) - original_callback_size = @ignored_callbacks.size - - if path.is_a? Regexp - @ignored_callbacks << Proc.new {|p| p =~ path } - elsif path.is_a? String - path_clean = ::Middleman::Util.normalize_path(path) - if path_clean.include?("*") # It's a glob - @ignored_callbacks << Proc.new {|p| File.fnmatch(path_clean, p) } - else - # Add a specific-path ignore unless that path is already covered - @ignored_callbacks << Proc.new {|p| p == path_clean } unless ignored?(path_clean) + ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods end - elsif block_given? - @ignored_callbacks << block + + alias :included :registered end - end + + # Helpers methods for Resources + module ResourceInstanceMethods + + # Whether the Resource is ignored + # @return [Boolean] + def ignored? + @app.ignore_manager.ignored?(path) || + (!proxy? && + @app.ignore_manager.ignored?(source_file.sub("#{@app.source_dir}/", "")) + ) + end + end + + # Ignore-related instance methods + module InstanceMethods + def ignore_manager + @_ignore_manager ||= IgnoreManager.new(self) + 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) + ignore_manager.ignore(path, &block) + end + end + + # Class to handle managing ignores + class IgnoreManager + def initialize(app) + @app = app - # Whether a path is ignored - # @param [String] path - # @return [Boolean] - def ignored?(path) - path_clean = ::Middleman::Util.normalize_path(path) - @ignored_callbacks.any? { |b| b.call(path_clean) } + # Array of callbacks which can ass ignored + @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) + original_callback_size = @ignored_callbacks.size + + if path.is_a? Regexp + @ignored_callbacks << Proc.new {|p| p =~ path } + elsif path.is_a? String + path_clean = ::Middleman::Util.normalize_path(path) + if path_clean.include?("*") # It's a glob + @ignored_callbacks << Proc.new {|p| File.fnmatch(path_clean, p) } + else + # Add a specific-path ignore unless that path is already covered + @ignored_callbacks << Proc.new {|p| p == path_clean } unless ignored?(path_clean) + end + elsif block_given? + @ignored_callbacks << block + end + end + + # Whether a path is ignored + # @param [String] path + # @return [Boolean] + def ignored?(path) + path_clean = ::Middleman::Util.normalize_path(path) + @ignored_callbacks.any? { |b| b.call(path_clean) } + end + end end 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 index fe869d8d..f1ca99f6 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb @@ -1,77 +1,84 @@ require 'set' -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 = Set.new - - 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 = @sitemap.file_to_path(file) - return false unless path - - ignored = @app.ignored_sitemap_matchers.any? do |name, callback| - callback.call(file, path) - end - - @file_paths_on_disk << file unless ignored - - # Rebuild the sitemap any time a file is touched - # in case one of the other manipulators - # (like asset_hash) cares about the contents of this file, - # whether or not it belongs in the sitemap (like a partial) - @sitemap.rebuild_resource_list!(:touched_file) if rebuild - end +module Middleman + + module Sitemap - # Remove a file from the store - # @param [String] file - # @return [void] - def remove_file(file, rebuild=true) - if @file_paths_on_disk.delete?(file) - @sitemap.rebuild_resource_list!(:removed_file) if rebuild - end - end + module Extensions + + class OnDisk + + attr_accessor :sitemap + attr_accessor :waiting_for_ready + + def initialize(sitemap) + @sitemap = sitemap + @app = @sitemap.app + + @file_paths_on_disk = Set.new + + 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 = @sitemap.file_to_path(file) + return false unless path + + ignored = @app.ignored_sitemap_matchers.any? do |name, callback| + callback.call(file, path) + end + + @file_paths_on_disk << file unless ignored + + # Rebuild the sitemap any time a file is touched + # in case one of the other manipulators + # (like asset_hash) cares about the contents of this file, + # whether or not it belongs in the sitemap (like a partial) + @sitemap.rebuild_resource_list!(:touched_file) if rebuild + end + + # Remove a file from the store + # @param [String] file + # @return [void] + def remove_file(file, rebuild=true) + if @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, - @sitemap.file_to_path(file), - File.expand_path(file, @app.root) - ) + # 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, + @sitemap.file_to_path(file), + File.expand_path(file, @app.root) + ) + end + end end end 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 index b421bcea..892258b0 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb @@ -1,99 +1,104 @@ -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? - proxy_resource = store.find_resource_by_path(proxied_to) - raise "Path #{path} proxies to unknown file #{proxied_to}" unless proxy_resource - proxy_resource.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::Util.normalize_path(path)] = ::Middleman::Util.normalize_path(target) - @app.sitemap.rebuild_resource_list!(:added_proxy) - end +module Middleman - # 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 + module Sitemap + + module 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? + proxy_resource = store.find_resource_by_path(proxied_to) + raise "Path #{path} proxies to unknown file #{proxied_to}" unless proxy_resource + proxy_resource.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::Util.normalize_path(path)] = ::Middleman::Util.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 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 index 9d49ee45..7b67a9d4 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/traversal.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/traversal.rb @@ -1,77 +1,84 @@ -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 +module Middleman - # This resource's child resources - # @return [Array] - def children - return [] unless directory_index? + module Sitemap + + module 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 - 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 + # This resource's child resources + # @return [Array] + def children + return [] unless directory_index? - 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 + if eponymous_directory? + base_path = eponymous_directory_path + prefix = %r|^#{base_path.sub("/", "\\/")}| else - false + 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 - - # 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/resource.rb b/middleman-core/lib/middleman-core/sitemap/resource.rb index f224b1fe..3d0c805a 100644 --- a/middleman-core/lib/middleman-core/sitemap/resource.rb +++ b/middleman-core/lib/middleman-core/sitemap/resource.rb @@ -1,148 +1,153 @@ -# Sitemap namespace -module Middleman::Sitemap +require "middleman-core/sitemap/extensions/traversal" + +module Middleman + + # Sitemap namespace + module Sitemap - # Sitemap Resource class - class Resource - include Middleman::Sitemap::Extensions::Traversal + # Sitemap Resource class + class Resource + include Middleman::Sitemap::Extensions::Traversal - # @return [Middleman::Application] - attr_reader :app + # @return [Middleman::Application] + attr_reader :app - # @return [Middleman::Sitemap::Store] - attr_reader :store + # @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 + # 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 + # Set the on-disk source file for this resource + # @return [String] + # attr_reader :source_file - def source_file - @source_file || get_source_file - end + 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 + # 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] + @destination_paths = [@path] - @local_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } - 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 - result = store.metadata_for_file(source_file).dup - - path_meta = store.metadata_for_path(path).dup - if path_meta.has_key?(:blocks) - result[:blocks] << path_meta[:blocks] - path_meta.delete(:blocks) + @local_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } end - result.deep_merge!(path_meta) - - local_meta = @local_metadata.dup - if local_meta.has_key?(:blocks) - result[:blocks] << local_meta[:blocks] - local_meta.delete(:blocks) + + # Whether this resource has a template file + # @return [Boolean] + def template? + return false if source_file.nil? + !::Tilt[source_file].nil? end - result.deep_merge!(local_meta) + + # Get the metadata for both the current source_file and the current path + # @return [Hash] + def metadata + result = store.metadata_for_file(source_file).dup - result - end + path_meta = store.metadata_for_path(path).dup + if path_meta.has_key?(:blocks) + result[:blocks] << path_meta[:blocks] + path_meta.delete(:blocks) + end + result.deep_merge!(path_meta) - # Merge in new metadata specific to this resource. - # @param [Hash] metadata A metadata block like provides_metadata_for_path takes - def add_metadata(metadata={}, &block) - if metadata.has_key?(:blocks) - @local_metadata[:blocks] << metadata[:blocks] - metadata.delete(:blocks) + local_meta = @local_metadata.dup + if local_meta.has_key?(:blocks) + result[:blocks] << local_meta[:blocks] + local_meta.delete(:blocks) + end + result.deep_merge!(local_meta) + + result + end + + # Merge in new metadata specific to this resource. + # @param [Hash] metadata A metadata block like provides_metadata_for_path takes + def add_metadata(metadata={}, &block) + if metadata.has_key?(:blocks) + @local_metadata[:blocks] << metadata[:blocks] + metadata.delete(:blocks) + end + @local_metadata.deep_merge(metadata) + @local_metadata[:blocks] << block if block_given? end - @local_metadata.deep_merge(metadata) - @local_metadata[:blocks] << block if block_given? - end - # Get the output/preview URL for this resource - # @return [String] - def destination_path - @destination_paths.last - 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 + # 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 + # 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 + # 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 + # 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? + # Render this resource + # @return [String] + def render(opts={}, locs={}, &block) + return File.open(source_file).read unless template? - start_time = Time.now - puts "== Render Start: #{source_file}" if app.logging? + start_time = Time.now + puts "== Render Start: #{source_file}" if app.logging? - md = metadata.dup - opts = md[:options].deep_merge(opts) - locs = md[:locals].deep_merge(locs) + 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]) + # 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} (#{(Time.now - start_time).round(2)}s)" if app.logging? + result 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} (#{(Time.now - start_time).round(2)}s)" 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 - '/' + destination_path.sub(/#{Regexp.escape(app.index_file)}$/, '') + # 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 end 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 3974f9b8..5e425927 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -1,208 +1,211 @@ # Used for merging results of metadata callbacks require "active_support/core_ext/hash/deep_merge" -# Sitemap namespace -module Middleman::Sitemap +module Middleman - # The Store class - # - # 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 + # Sitemap namespace + module Sitemap + + # The Store class + # + # 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 - # @return [Middleman::Application] - attr_accessor :app + # @return [Middleman::Application] + attr_accessor :app - # Initialize with parent app - # @param [Middleman::Application] app - def initialize(app) - @app = app - @resources = [] - @_cached_metadata = {} - @_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) - end - - # Register a klass which can manipulate the main site map list - # @param [Symbol] name Name of the manipulator for debugging - # @param [Class, Module] inst Abstract namespace which can update the resource list - # @param [Boolean] immediately_rebuild Whether the resource list should be immediately recalculated - # @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 - - # Rebuild the list of resources from scratch, using registed manipulators - # @return [void] - def rebuild_resource_list!(reason=nil) - @resources = @resource_list_manipulators.inject([]) do |result, (_, inst)| - inst.manipulate_resource_list(result) - end - - # Reset lookup cache - @_lookup_cache = { :path => {}, :destination_path => {} } - @resources.each do |resource| - @_lookup_cache[:path][resource.path] = resource - @_lookup_cache[:destination_path][resource.destination_path] = resource - end - end - - # 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::Util.normalize_path(request_path) - @_lookup_cache[:path][request_path] - end - - # 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::Util.normalize_path(request_path) - @_lookup_cache[:destination_path][request_path] - end - - # 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 - - # 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 - - # 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 => [] } - - provides_metadata.inject(blank_metadata) do |result, (callback, matcher)| - next result if !matcher.nil? && !source_file.match(matcher) - - metadata = callback.call(source_file) - - if metadata.has_key?(:blocks) - result[:blocks] << metadata[:blocks] - metadata.delete(:blocks) - end - - result.deep_merge(metadata) - end - end - - # Register a handler to provide metadata on a url path - # @param [Regexp] matcher - # @param [Symbol] origin an indicator of where this metadata came from - only one - # block per [matcher, origin] pair may exist. - # @return [Array>] - def provides_metadata_for_path(matcher=nil, origin=nil, &block) - @_provides_metadata_for_path ||= [] - if block_given? - if origin - existing_provider = @_provides_metadata_for_path.find {|b,m,o| o == origin && m == matcher} - end - - if existing_provider - existing_provider[0] = block - else - @_provides_metadata_for_path << [block, matcher, origin] - end - + # Initialize with parent app + # @param [Middleman::Application] app + def initialize(app) + @app = app + @resources = [] @_cached_metadata = {} - end - @_provides_metadata_for_path - end - - # Get the metadata for a specific URL - # @param [String] request_path - # @return [Hash] - def metadata_for_path(request_path) - return @_cached_metadata[request_path] if @_cached_metadata[request_path] - - blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } + @_lookup_cache = { :path => {}, :destination_path => {} } + @resource_list_manipulators = [] - @_cached_metadata[request_path] = 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 - - metadata = callback.call(request_path) - - if metadata.has_key?(:blocks) - result[:blocks] << metadata[:blocks] - metadata.delete(:blocks) - end - - result.deep_merge(metadata) + # 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) 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), "") + # Register a klass which can manipulate the main site map list + # @param [Symbol] name Name of the manipulator for debugging + # @param [Class, Module] inst Abstract namespace which can update the resource list + # @param [Boolean] immediately_rebuild Whether the resource list should be immediately recalculated + # @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 + + # Rebuild the list of resources from scratch, using registed manipulators + # @return [void] + def rebuild_resource_list!(reason=nil) + @resources = @resource_list_manipulators.inject([]) do |result, (_, inst)| + inst.manipulate_resource_list(result) + end + + # Reset lookup cache + @_lookup_cache = { :path => {}, :destination_path => {} } + @resources.each do |resource| + @_lookup_cache[:path][resource.path] = resource + @_lookup_cache[:destination_path][resource.destination_path] = resource + end + end + + # 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::Util.normalize_path(request_path) + @_lookup_cache[:path][request_path] + end + + # 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::Util.normalize_path(request_path) + @_lookup_cache[:destination_path][request_path] + end + + # 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 - end_of_the_line = true + @resources.reject(&:ignored?) end 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 + + # 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 => [] } + + provides_metadata.inject(blank_metadata) do |result, (callback, matcher)| + next result if !matcher.nil? && !source_file.match(matcher) + + metadata = callback.call(source_file) - # If there is no extension, look for one - if File.extname(path).empty? - input_ext = File.extname(file) + if metadata.has_key?(:blocks) + result[:blocks] << metadata[:blocks] + metadata.delete(:blocks) + end - 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]}" + result.deep_merge(metadata) + end + end + + # Register a handler to provide metadata on a url path + # @param [Regexp] matcher + # @param [Symbol] origin an indicator of where this metadata came from - only one + # block per [matcher, origin] pair may exist. + # @return [Array>] + def provides_metadata_for_path(matcher=nil, origin=nil, &block) + @_provides_metadata_for_path ||= [] + if block_given? + if origin + existing_provider = @_provides_metadata_for_path.find {|b,m,o| o == origin && m == matcher} + end + + if existing_provider + existing_provider[0] = block + else + @_provides_metadata_for_path << [block, matcher, origin] + end + + @_cached_metadata = {} + end + @_provides_metadata_for_path + end + + # Get the metadata for a specific URL + # @param [String] request_path + # @return [Hash] + def metadata_for_path(request_path) + return @_cached_metadata[request_path] if @_cached_metadata[request_path] + + blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] } + + @_cached_metadata[request_path] = 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 + + metadata = callback.call(request_path) + + if metadata.has_key?(:blocks) + result[:blocks] << metadata[:blocks] + metadata.delete(:blocks) + end + + 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) + 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 - end - path + # 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 -end +end \ No newline at end of file