Merge pull request #1288 from bhollis/data

@bhollis metadata rewrite/refactor
This commit is contained in:
Ben Hollis 2014-06-14 17:12:10 -07:00
commit e7108a5656
23 changed files with 259 additions and 312 deletions

View file

@ -2,13 +2,21 @@ Feature: Custom layouts
In order easily switch between relative and absolute paths In order easily switch between relative and absolute paths
Scenario: Using custom :layout attribute Scenario: Using custom :layout attribute
Given page "/custom-layout.html" has layout "custom" Given a fixture app "custom-layout-app2"
And a file named "config.rb" with:
"""
page '/custom-layout.html', layout: :custom
"""
And the Server is running at "custom-layout-app2" And the Server is running at "custom-layout-app2"
When I go to "/custom-layout.html" When I go to "/custom-layout.html"
Then I should see "Custom Layout" Then I should see "Custom Layout"
Scenario: Using custom :layout attribute with folders Scenario: Using custom :layout attribute with folders
Given page "/custom-layout-dir/" has layout "custom" Given a fixture app "custom-layout-app2"
And a file named "config.rb" with:
"""
page '/custom-layout-dir/', layout: :custom
"""
And the Server is running at "custom-layout-app2" And the Server is running at "custom-layout-app2"
When I go to "/custom-layout-dir" When I go to "/custom-layout-dir"
Then I should see "Custom Layout" Then I should see "Custom Layout"
@ -18,7 +26,11 @@ Feature: Custom layouts
Then I should see "Custom Layout" Then I should see "Custom Layout"
Scenario: Using custom :layout attribute with folders Scenario: Using custom :layout attribute with folders
Given page "/custom-layout-dir" has layout "custom" Given a fixture app "custom-layout-app2"
And a file named "config.rb" with:
"""
page '/custom-layout-dir', layout: :custom
"""
And the Server is running at "custom-layout-app2" And the Server is running at "custom-layout-app2"
When I go to "/custom-layout-dir" When I go to "/custom-layout-dir"
Then I should see "Custom Layout" Then I should see "Custom Layout"
@ -28,7 +40,11 @@ Feature: Custom layouts
Then I should see "Custom Layout" Then I should see "Custom Layout"
Scenario: Using custom :layout attribute with folders Scenario: Using custom :layout attribute with folders
Given page "/custom-layout-dir/index.html" has layout "custom" Given a fixture app "custom-layout-app2"
And a file named "config.rb" with:
"""
page '/custom-layout-dir/index.html', layout: :custom
"""
And the Server is running at "custom-layout-app2" And the Server is running at "custom-layout-app2"
When I go to "/custom-layout-dir" When I go to "/custom-layout-dir"
Then I should see "Custom Layout" Then I should see "Custom Layout"

View file

@ -1,6 +0,0 @@
Given /^page "([^\"]*)" has layout "([^\"]*)"$/ do |url, layout|
@initialize_commands ||= []
@initialize_commands << lambda {
page(url, layout: layout.to_sym)
}
end

View file

@ -1,10 +1,5 @@
require 'middleman-core/core_extensions/routing'
module Middleman module Middleman
class ConfigContext class ConfigContext
# page routing
include Middleman::CoreExtensions::Routing
attr_reader :app attr_reader :app
# Whitelist methods that can reach out. # Whitelist methods that can reach out.

View file

@ -45,6 +45,11 @@ Middleman::Extensions.register :lorem, auto_activate: :before_configuration do
Middleman::Extensions::Lorem Middleman::Extensions::Lorem
end end
Middleman::Extensions.register :routing, auto_activate: :before_configuration do
require 'middleman-core/core_extensions/routing'
Middleman::CoreExtensions::Routing
end
### ###
# Setup Optional Extensions # Setup Optional Extensions
### ###

View file

@ -83,9 +83,6 @@ module Middleman
# Search the root of the project for required files # Search the root of the project for required files
$LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root) $LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
# Evaluate a passed block if given
config_context.instance_exec(&block) if block_given?
super super
::Middleman::Extension.clear_after_extension_callbacks ::Middleman::Extension.clear_after_extension_callbacks
@ -105,6 +102,9 @@ module Middleman
run_hook :before_configuration run_hook :before_configuration
# Evaluate a passed block if given
config_context.instance_exec(&block) if block_given?
# Check for and evaluate local configuration in `config.rb` # Check for and evaluate local configuration in `config.rb`
local_config = File.join(root, 'config.rb') local_config = File.join(root, 'config.rb')
if File.exist? local_config if File.exist? local_config

View file

@ -10,6 +10,9 @@ require 'active_support/json'
# Extensions namespace # Extensions namespace
module Middleman::CoreExtensions module Middleman::CoreExtensions
class FrontMatter < ::Middleman::Extension class FrontMatter < ::Middleman::Extension
# Try to run after routing but before directory_indexes
self.resource_list_manipulator_priority = 90
YAML_ERRORS = [StandardError] YAML_ERRORS = [StandardError]
# https://github.com/tenderlove/psych/issues/23 # https://github.com/tenderlove/psych/issues/23
@ -29,61 +32,34 @@ module Middleman::CoreExtensions
app.files.deleted { |file| ext.clear_data(file) } app.files.deleted { |file| ext.clear_data(file) }
end end
# Modify each resource to add data & options from frontmatter.
def manipulate_resource_list(resources)
resources.each do |resource|
next if resource.source_file.blank?
fmdata = data(resource.source_file).first.dup
# Copy over special options
# TODO: Should we make people put these under "options" instead of having
# special known keys?
opts = fmdata.extract!(:layout, :layout_engine, :renderer_options, :directory_index, :content_type)
opts[:renderer_options].symbolize_keys! if opts.key?(:renderer_options)
ignored = fmdata.delete(:ignored)
# TODO: Enhance data? NOOOO
# TODO: stringify-keys? immutable/freeze?
resource.add_metadata options: opts, page: fmdata
resource.ignore! if ignored == true && !resource.proxy?
# TODO: Save new template here somewhere?
end
end
def after_configuration def after_configuration
app.ignore %r{\.frontmatter$} app.ignore %r{\.frontmatter$}
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
app.sitemap.provides_metadata do |path|
fmdata = data(path).first
data = {}
[:layout, :layout_engine].each do |opt|
data[opt] = fmdata[opt] unless fmdata[opt].nil?
end
if fmdata[:renderer_options]
data[:renderer_options] = {}
fmdata[:renderer_options].each do |k, v|
data[:renderer_options][k.to_sym] = v
end
end
{ options: data }
end
end
module ResourceInstanceMethods
def ignored?
if !proxy? && raw_data[:ignored] == true
true
else
super
end
end
# This page's frontmatter without being enhanced for access by either symbols or strings.
# Used internally
# @private
# @return [Hash]
def raw_data
app.extensions[:front_matter].data(source_file).first
end
# This page's frontmatter
# @return [Hash]
def data
@enhanced_data ||= ::Middleman::Util.recursively_enhance(raw_data).freeze
end
# Override Resource#content_type to take into account frontmatter
def content_type
# Allow setting content type in frontmatter too
raw_data.fetch :content_type do
super
end
end
end end
# Get the template data from a path # Get the template data from a path

View file

@ -27,9 +27,9 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
end end
def after_configuration def after_configuration
app.files.reload_path(app.config[:locals_dir] || options[:data]) app.files.reload_path(app.config[:locales_dir] || options[:data])
@locales_glob = File.join(app.config[:locals_dir] || options[:data], '**', '*.{rb,yml,yaml}') @locales_glob = File.join(app.config[:locales_dir] || options[:data], '**', '*.{rb,yml,yaml}')
@locales_regex = convert_glob_to_regex(@locales_glob) @locales_regex = convert_glob_to_regex(@locales_glob)
@maps = {} @maps = {}
@ -42,7 +42,6 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
# Don't output localizable files # Don't output localizable files
app.ignore File.join(options[:templates_dir], '**') app.ignore File.join(options[:templates_dir], '**')
app.sitemap.provides_metadata_for_path(&method(:metadata_for_path))
app.files.changed(&method(:on_file_changed)) app.files.changed(&method(:on_file_changed))
app.files.deleted(&method(:on_file_changed)) app.files.deleted(&method(:on_file_changed))
end end
@ -56,14 +55,12 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
delegate :logger, to: :app delegate :logger, to: :app
def langs def langs
@_langs ||= known_languages @langs ||= known_languages
end end
# Update the main sitemap resource list # Update the main sitemap resource list
# @return [void] # @return [void]
def manipulate_resource_list(resources) def manipulate_resource_list(resources)
@_localization_data = {}
new_resources = [] new_resources = []
resources.each do |resource| resources.each do |resource|
@ -81,6 +78,12 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
new_resources << build_resource(path, resource.path, page_id, lang) new_resources << build_resource(path, resource.path, page_id, lang)
end end
end end
# This is for backwards compatibility with the old provides_metadata-based code
# that used to be in this extension, but I don't know how much sense it makes.
unless resource.options[:lang]
resource.add_metadata options: { lang: @mount_at_root }, locals: { lang: @mount_at_root }
end
end end
resources + new_resources resources + new_resources
@ -90,7 +93,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
def on_file_changed(file) def on_file_changed(file)
if @locales_regex =~ file if @locales_regex =~ file
@_langs = nil # Clear langs cache @langs = nil # Clear langs cache
::I18n.reload! ::I18n.reload!
end end
end end
@ -112,24 +115,6 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
end end
end end
def metadata_for_path(url)
if d = localization_data(url)
lang, page_id = d
else
# Default to the @mount_at_root lang
page_id = nil
lang = @mount_at_root
end
{
locals: {
lang: lang,
page_id: page_id
},
options: { lang: lang }
}
end
def known_languages def known_languages
if options[:langs] if options[:langs]
Array(options[:langs]).map(&:to_sym) Array(options[:langs]).map(&:to_sym)
@ -144,11 +129,6 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
end end
end end
def localization_data(path)
@_localization_data ||= {}
@_localization_data[path]
end
# Parse locale extension filename # Parse locale extension filename
# @return [lang, path, basename] # @return [lang, path, basename]
# will return +nil+ if no locale extension # will return +nil+ if no locale extension
@ -183,10 +163,9 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
path = path.sub(options[:templates_dir] + '/', '') path = path.sub(options[:templates_dir] + '/', '')
@_localization_data[path] = [lang, path, localized_page_id]
p = ::Middleman::Sitemap::Resource.new(app.sitemap, path) p = ::Middleman::Sitemap::Resource.new(app.sitemap, path)
p.proxy_to(source_path) p.proxy_to(source_path)
p.add_metadata locals: { lang: lang, page_id: path }, options: { lang: lang }
::I18n.locale = old_locale ::I18n.locale = old_locale
p p

View file

@ -48,7 +48,7 @@ module Middleman
# Liquid Support # Liquid Support
begin begin
require 'middleman-core/renderers/liquid' require 'middleman-core/renderers/liquid'
app.send :include, Middleman::Renderers::Liquid Middleman::Extensions.register :liquid, Middleman::Renderers::Liquid, auto_activate: :before_configuration
rescue LoadError rescue LoadError
end end

View file

@ -1,43 +1,84 @@
# Routing extension # Routing extension
module Middleman module Middleman
module CoreExtensions module CoreExtensions
module Routing class Routing < Extension
# The page method allows the layout to be set on a specific path # This should always run late, but not as late as :directory_indexes,
# so it can add metadata to any pages generated by other extensions
self.resource_list_manipulator_priority = 80
def initialize(app, options_hash={}, &block)
super
@page_configs = []
end
def before_configuration
app.add_to_config_context :page, &method(:page)
end
def manipulate_resource_list(resources)
resources.each do |resource|
@page_configs.each do |matcher, metadata|
case matcher
when Regexp
next unless resource.path =~ matcher
when String
next unless File.fnmatch('/' + Util.strip_leading_slash(matcher), "/#{resource.path}")
end
resource.add_metadata metadata
end
end
end
# The page method allows options to be set for a given source path, regex, or glob.
# Options that may be set include layout, locals, proxy, andx ignore.
# #
# page "/about.html", layout: false # @example
# page "/", layout: :homepage_layout # page '/about.html', layout: false
# @example
# page '/index.html', layout: :homepage_layout
# @example
# page '/foo.html', locals: { foo: 'bar' }
# #
# @param [String] url # @param [String, Regexp] path A source path, or a Regexp/glob that can match multiple resources.
# @param [Hash] opts # @params [Hash] opts Options to apply to all matching resources. Undocumented options are passed on as page metadata to be used by extensions.
# @option opts [Symbol, Boolean, String] layout The layout name to use (e.g. `:article`) or `false` to disable layout.
# @option opts [Boolean] directory_indexes Whether or not the `:directory_indexes` extension applies to these paths.
# @option opts [Hash] locals Local variables for the template. These will be available when the template renders.
# @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}.
# @option opts [String] proxy The source path for a template to proxy this path to. Only valid when a single path is provided. Prefer using the `proxy` method to do this.
# @option opts [Boolean] ignore Set to `true` to ignore the provided path(s). Only valid when a single path is provided. Prefer using the `ignore` method to do this.
# @return [void] # @return [void]
def page(url, opts={}) def page(path, opts={})
options = opts.dup options = opts.dup
# Default layout # Default layout
# TODO: This seems wrong
options[:layout] = @app.config[:layout] if options[:layout].nil? options[:layout] = @app.config[:layout] if options[:layout].nil?
metadata = { options: options, locals: options.delete(:locals) || {} } # TODO: You can set options and locals, but not data
metadata = { options: options, locals: options.delete(:locals) || {}, page: options.delete(:data) || {} }
# If the url is a regexp # If the path is a regexp
unless url.is_a?(Regexp) || url.include?('*') unless path.is_a?(Regexp) || path.include?('*')
# Normalized path # Normalized path
url = '/' + Middleman::Util.normalize_path(url) path = '/' + Middleman::Util.normalize_path(path)
if url.end_with?('/') || File.directory?(File.join(@app.source_dir, url)) if path.end_with?('/') || File.directory?(File.join(@app.source_dir, path))
url = File.join(url, @app.config[:index_file]) path = File.join(path, @app.config[:index_file])
end end
# Setup proxy # Setup proxy
if target = options.delete(:proxy) if target = options.delete(:proxy)
# TODO: deprecate proxy through page? # TODO: deprecate proxy through page?
@app.proxy(url, target, opts.dup) @app.proxy(path, target, opts.dup)
return return
elsif options.delete(:ignore) elsif options.delete(:ignore)
# TODO: deprecate ignore through page? # TODO: deprecate ignore through page?
@app.ignore(url) @app.ignore(path)
end end
end end
# Setup a metadata matcher for rendering those options @page_configs << [path, metadata]
@app.sitemap.provides_metadata_for_path(url) { |_| metadata }
end end
end end
end end

View file

@ -1,14 +1,16 @@
require 'rack/showexceptions'
# Support rack/showexceptions during development # Support rack/showexceptions during development
module Middleman::CoreExtensions module Middleman::CoreExtensions
class ShowExceptions < ::Middleman::Extension class ShowExceptions < ::Middleman::Extension
def initialize(app, options_hash={}, &block) def initialize(app, options_hash={}, &block)
super super
require 'rack/showexceptions' app.config.define_setting :show_exceptions, true, 'Whether to catch and display exceptions'
end end
def after_configuration def after_configuration
app.use ::Rack::ShowExceptions app.use ::Rack::ShowExceptions if app.config[:show_exceptions]
end end
end end
end end

View file

@ -16,11 +16,8 @@ class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
resource.destination_path.end_with?(new_index_path) || resource.destination_path.end_with?(new_index_path) ||
File.extname(index_file) != resource.ext File.extname(index_file) != resource.ext
# Check if frontmatter turns directory_index off # Check if file metadata (options set by "page" in config.rb or frontmatter) turns directory_index off
next if resource.raw_data[:directory_index] == false next if resource.options[:directory_index] == false
# Check if file metadata (options set by "page" in config.rb) turns directory_index off
next if resource.metadata[:options][:directory_index] == false
resource.destination_path = resource.destination_path.chomp(File.extname(index_file)) + new_index_path resource.destination_path = resource.destination_path.chomp(File.extname(index_file)) + new_index_path
end end

View file

@ -44,11 +44,10 @@ module Middleman
data = @resource.data data = @resource.data
props['Data'] = data.inspect unless data.empty? props['Data'] = data.inspect unless data.empty?
meta = @resource.metadata options = @resource.options
options = meta[:options]
props['Options'] = options.inspect unless options.empty? props['Options'] = options.inspect unless options.empty?
locals = meta[:locals].keys locals = @resource.locals.keys
props['Locals'] = locals.join(', ') unless locals.empty? props['Locals'] = locals.join(', ') unless locals.empty?
props props

View file

@ -4,23 +4,19 @@ require 'liquid'
module Middleman module Middleman
module Renderers module Renderers
# Liquid Renderer # Liquid Renderer
module Liquid class Liquid < Middleman::Extension
# Setup extension
class << self
# Once registerd
def registered(app)
# After config, setup liquid partial paths # After config, setup liquid partial paths
app.after_configuration do def after_configuration
::Liquid::Template.file_system = ::Liquid::LocalFileSystem.new(source_dir) ::Liquid::Template.file_system = ::Liquid::LocalFileSystem.new(app.source_dir)
end
def manipulate_resource_list(resources)
resources.each do |resource|
# Convert data object into a hash for liquid # Convert data object into a hash for liquid
sitemap.provides_metadata %r{\.liquid$} do if resource.source_file =~ %r{\.liquid$}
{ locals: { data: data.to_h } } resource.add_metadata locals: { data: app.data.to_h }
end end
end end
end
alias_method :included, :registered
end end
end end
end end

View file

@ -5,8 +5,8 @@ module Middleman::Sitemap::Extensions
module ContentType module ContentType
# The preferred MIME content type for this resource # The preferred MIME content type for this resource
def content_type def content_type
# Allow explcitly setting content type from page/proxy options # Allow explcitly setting content type from page/proxy options or frontmatter
meta_type = metadata[:options][:content_type] meta_type = options[:content_type]
return meta_type if meta_type return meta_type if meta_type
# Look up mime type based on extension # Look up mime type based on extension

View file

@ -3,15 +3,15 @@ module Middleman
module Extensions module Extensions
# Class to handle managing ignores # Class to handle managing ignores
class Ignores class Ignores
def initialize(sitemap) def initialize(app, sitemap)
@app = sitemap.app @app = app
@app.add_to_config_context :ignore, &method(:create_ignore) @app.add_to_config_context :ignore, &method(:create_ignore)
@app.define_singleton_method(:ignore, &method(:create_ignore)) @app.define_singleton_method :ignore, &method(:create_ignore)
# Array of callbacks which can ass ignored # Array of callbacks which can ass ignored
@ignored_callbacks = [] @ignored_callbacks = []
sitemap.define_singleton_method(:ignored?, &method(:ignored?)) sitemap.define_singleton_method :ignored?, &method(:ignored?)
::Middleman::Sitemap::Resource.send :include, IgnoreResourceInstanceMethods ::Middleman::Sitemap::Resource.send :include, IgnoreResourceInstanceMethods
end end
@ -48,13 +48,20 @@ module Middleman
# Helpers methods for Resources # Helpers methods for Resources
module IgnoreResourceInstanceMethods module IgnoreResourceInstanceMethods
# Ignore a resource directly, without going through the whole
# ignore filter stuff.
def ignore!
@ignored = true
end
# Whether the Resource is ignored # Whether the Resource is ignored
# @return [Boolean] # @return [Boolean]
def ignored? def ignored?
@app.sitemap.ignored?(path) || return true if @ignored
(!proxy? && # Ignore based on the source path (without template extensions)
@app.sitemap.ignored?(source_file.sub("#{@app.source_dir}/", '')) return true if @app.sitemap.ignored?(path)
) # This allows files to be ignored by their source file name (with template extensions)
!proxy? && @app.sitemap.ignored?(source_file.sub("#{@app.source_dir}/", ''))
end end
end end
end end

View file

@ -7,23 +7,25 @@ module Middleman
attr_accessor :sitemap attr_accessor :sitemap
attr_accessor :waiting_for_ready attr_accessor :waiting_for_ready
def initialize(sitemap) def initialize(app, sitemap)
@sitemap = sitemap @sitemap = sitemap
@app = @sitemap.app @app = app
@file_paths_on_disk = Set.new @file_paths_on_disk = Set.new
scoped_self = self scoped_self = self
@waiting_for_ready = true @waiting_for_ready = true
@app.before_configuration do
# Register file change callback # Register file change callback
@app.files.changed do |file| files.changed do |file|
scoped_self.touch_file(file) scoped_self.touch_file(file)
end end
# Register file delete callback # Register file delete callback
@app.files.deleted do |file| files.deleted do |file|
scoped_self.remove_file(file) scoped_self.remove_file(file)
end end
end
@app.ready do @app.ready do
scoped_self.waiting_for_ready = false scoped_self.waiting_for_ready = false

View file

@ -4,8 +4,8 @@ module Middleman
# Manages the list of proxy configurations and manipulates the sitemap # Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations # to include new resources based on those configurations
class Proxies class Proxies
def initialize(sitemap) def initialize(app)
@app = sitemap.app @app = app
@app.add_to_config_context :proxy, &method(:create_proxy) @app.add_to_config_context :proxy, &method(:create_proxy)
@app.define_singleton_method(:proxy, &method(:create_proxy)) @app.define_singleton_method(:proxy, &method(:create_proxy))
@ -109,10 +109,10 @@ module Middleman
# if there is no resource. # if there is no resource.
# @return [Sitemap::Resource] # @return [Sitemap::Resource]
def proxied_to_resource def proxied_to_resource
proxy_resource = store.find_resource_by_path(proxied_to) proxy_resource = @store.find_resource_by_path(proxied_to)
unless proxy_resource unless proxy_resource
raise "Path #{path} proxies to unknown file #{proxied_to}:#{store.resources.map(&:path)}" raise "Path #{path} proxies to unknown file #{proxied_to}:#{@store.resources.map(&:path)}"
end end
if proxy_resource.proxy? if proxy_resource.proxy?

View file

@ -6,8 +6,8 @@ module Middleman
# Manages the list of proxy configurations and manipulates the sitemap # Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations # to include new resources based on those configurations
class Redirects class Redirects
def initialize(sitemap) def initialize(app)
@app = sitemap.app @app = app
@app.add_to_config_context :redirect, &method(:create_redirect) @app.add_to_config_context :redirect, &method(:create_redirect)
@redirects = {} @redirects = {}
@ -53,7 +53,7 @@ module Middleman
end end
def render(*) def render(*)
url = ::Middleman::Util.url_for(store.app, @request_path, url = ::Middleman::Util.url_for(@store.app, @request_path,
relative: false, relative: false,
find_resource: true find_resource: true
) )
@ -75,24 +75,17 @@ module Middleman
end end
end end
# def request_path
# @request_path
# end
def binary? def binary?
false false
end end
def raw_data
{}
end
def ignored? def ignored?
false false
end end
def metadata # rubocop:disable AccessorMethodName
@local_metadata.dup def get_source_file
''
end end
end end
end end

View file

@ -4,8 +4,8 @@ module Middleman
class RequestEndpoints class RequestEndpoints
# Manages the list of proxy configurations and manipulates the sitemap # Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations # to include new resources based on those configurations
def initialize(sitemap) def initialize(app)
@app = sitemap.app @app = app
@app.add_to_config_context :endpoint, &method(:create_endpoint) @app.add_to_config_context :endpoint, &method(:create_endpoint)
@endpoints = {} @endpoints = {}
@ -49,12 +49,13 @@ module Middleman
class EndpointResource < ::Middleman::Sitemap::Resource class EndpointResource < ::Middleman::Sitemap::Resource
attr_accessor :output attr_accessor :output
def initialize(store, path, source_file) def initialize(store, path, request_path)
@request_path = ::Middleman::Util.normalize_path(source_file)
super(store, path) super(store, path)
@request_path = ::Middleman::Util.normalize_path(request_path)
end end
attr_reader :request_path
def template? def template?
true true
end end
@ -63,22 +64,17 @@ module Middleman
return output.call if output return output.call if output
end end
attr_reader :request_path
def binary? def binary?
false false
end end
def raw_data
{}
end
def ignored? def ignored?
false false
end end
def metadata # rubocop:disable AccessorMethodName
@local_metadata.dup def get_source_file
''
end end
end end
end end

View file

@ -7,13 +7,13 @@ module Middleman
def parent def parent
parts = path.split('/') parts = path.split('/')
tail = parts.pop tail = parts.pop
is_index = (tail == app.config[:index_file]) is_index = (tail == @app.config[:index_file])
return nil if is_index && parts.length < 1 return nil if is_index && parts.length < 1
test_expr = parts.join('\\/') test_expr = parts.join('\\/')
# eponymous reverse-lookup # eponymous reverse-lookup
found = store.resources.find do |candidate| found = @store.resources.find do |candidate|
candidate.path =~ %r!^#{test_expr}(?:\.[a-zA-Z0-9]+|\/)$! candidate.path =~ %r!^#{test_expr}(?:\.[a-zA-Z0-9]+|\/)$!
end end
@ -21,7 +21,7 @@ module Middleman
found found
else else
parts.pop if is_index parts.pop if is_index
store.find_resource_by_destination_path("#{parts.join('/')}/#{app.config[:index_file]}") @store.find_resource_by_destination_path("#{parts.join('/')}/#{@app.config[:index_file]}")
end end
end end
@ -34,11 +34,11 @@ module Middleman
base_path = eponymous_directory_path base_path = eponymous_directory_path
prefix = %r{^#{base_path.sub("/", "\\/")}} prefix = %r{^#{base_path.sub("/", "\\/")}}
else else
base_path = path.sub("#{app.config[:index_file]}", '') base_path = path.sub("#{@app.config[:index_file]}", '')
prefix = %r{^#{base_path.sub("/", "\\/")}} prefix = %r{^#{base_path.sub("/", "\\/")}}
end end
store.resources.select do |sub_resource| @store.resources.select do |sub_resource|
if sub_resource.path == path || sub_resource.path !~ prefix if sub_resource.path == path || sub_resource.path !~ prefix
false false
else else
@ -47,7 +47,7 @@ module Middleman
if parts.length == 1 if parts.length == 1
true true
elsif parts.length == 2 elsif parts.length == 2
parts.last == app.config[:index_file] parts.last == @app.config[:index_file]
else else
false false
end end
@ -65,17 +65,17 @@ module Middleman
# Whether this resource is either a directory index, or has the same name as an existing directory in the source # Whether this resource is either a directory index, or has the same name as an existing directory in the source
# @return [Boolean] # @return [Boolean]
def directory_index? def directory_index?
path.include?(app.config[:index_file]) || path =~ /\/$/ || eponymous_directory? path.include?(@app.config[:index_file]) || path =~ /\/$/ || eponymous_directory?
end end
# Whether the resource has the same name as a directory in the source # 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) # (e.g., if the resource is named 'gallery.html' and a path exists named 'gallery/', this would return true)
# @return [Boolean] # @return [Boolean]
def eponymous_directory? def eponymous_directory?
if !path.end_with?("/#{app.config[:index_file]}") && destination_path.end_with?("/#{app.config[:index_file]}") if !path.end_with?("/#{@app.config[:index_file]}") && destination_path.end_with?("/#{@app.config[:index_file]}")
return true return true
end end
full_path = File.join(app.source_dir, eponymous_directory_path) full_path = File.join(@app.source_dir, eponymous_directory_path)
File.exist?(full_path) && File.directory?(full_path) File.exist?(full_path) && File.directory?(full_path)
end end

View file

@ -11,27 +11,24 @@ module Middleman
include Middleman::Sitemap::Extensions::Traversal include Middleman::Sitemap::Extensions::Traversal
include Middleman::Sitemap::Extensions::ContentType include Middleman::Sitemap::Extensions::ContentType
# @return [Middleman::Application]
attr_reader :app
delegate :logger, :instrument, to: :app
# @return [Middleman::Sitemap::Store]
attr_reader :store
# The source path of this resource (relative to the source directory, # The source path of this resource (relative to the source directory,
# without template extensions) # without template extensions)
# @return [String] # @return [String]
attr_reader :path attr_reader :path
# The output path for this resource # The output path in the build directory for this resource
# @return [String] # @return [String]
attr_accessor :destination_path attr_accessor :destination_path
# The path to use when requesting this resource. Normally it's
# the same as {#destination_path} but it can be overridden in subclasses.
# @return [String]
alias_method :request_path, :destination_path
# Set the on-disk source file for this resource # Set the on-disk source file for this resource
# @return [String] # @return [String]
# attr_reader :source_file
def source_file def source_file
# TODO: Make this work when get_source_file doesn't exist
@source_file || get_source_file @source_file || get_source_file
end end
@ -46,7 +43,11 @@ module Middleman
@source_file = source_file @source_file = source_file
@destination_path = @path @destination_path = @path
@local_metadata = { options: {}, locals: {} } # Options are generally rendering/sitemap options
# Locals are local variables for rendering this resource's template
# Page are data that is exposed through this resource's data member.
# Note: It is named 'page' for backwards compatibility with older MM.
@metadata = { options: {}, locals: {}, page: {} }
end end
# Whether this resource has a template file # Whether this resource has a template file
@ -56,29 +57,39 @@ module Middleman
!::Tilt[source_file].nil? !::Tilt[source_file].nil?
end end
# Get the metadata for both the current source_file and the current path
# @return [Hash]
def metadata
result = store.metadata_for_path(path).dup
file_meta = store.metadata_for_file(source_file).dup
result.deep_merge!(file_meta)
local_meta = @local_metadata.dup
result.deep_merge!(local_meta)
result
end
# Merge in new metadata specific to this resource. # Merge in new metadata specific to this resource.
# @param [Hash] meta A metadata block like provides_metadata_for_path takes # @param [Hash] meta A metadata block with keys :options, :locals, :page.
# Options are generally rendering/sitemap options
# Locals are local variables for rendering this resource's template
# Page are data that is exposed through this resource's data member.
# Note: It is named 'page' for backwards compatibility with older MM.
def add_metadata(meta={}) def add_metadata(meta={})
@local_metadata.deep_merge!(meta.dup) @metadata.deep_merge!(meta)
end end
# The output/preview URL for this resource # The metadata for this resource
# @return [String] # @return [Hash]
attr_accessor :destination_path attr_reader :metadata
# Data about this resource, populated from frontmatter or extensions.
# @return [HashWithIndifferentAccess]
def data
# TODO: Should this really be a HashWithIndifferentAccess?
::Middleman::Util.recursively_enhance(metadata[:page]).freeze
end
# Options about how this resource is rendered, such as its :layout,
# :renderer_options, and whether or not to use :directory_indexes.
# @return [Hash]
def options
metadata[:options]
end
# Local variable mappings that are used when rendering the template for this resource.
# @return [Hash]
def locals
metadata[:locals]
end
# Extension of the path (i.e. '.js') # Extension of the path (i.e. '.js')
# @return [String] # @return [String]
@ -86,19 +97,15 @@ module Middleman
File.extname(path) File.extname(path)
end end
def request_path
destination_path
end
# Render this resource # Render this resource
# @return [String] # @return [String]
def render(opts={}, locs={}) def render(opts={}, locs={})
return ::Middleman::FileRenderer.new(@app, source_file).template_data_for_file unless template? return ::Middleman::FileRenderer.new(@app, source_file).template_data_for_file unless template?
relative_source = Pathname(source_file).relative_path_from(Pathname(app.root)) relative_source = Pathname(source_file).relative_path_from(Pathname(@app.root))
instrument 'render.resource', path: relative_source, destination_path: destination_path do @app.instrument 'render.resource', path: relative_source, destination_path: destination_path do
md = metadata.dup md = metadata
opts = md[:options].deep_merge(opts) opts = md[:options].deep_merge(opts)
locs = md[:locals].deep_merge(locs) locs = md[:locals].deep_merge(locs)
locs[:current_path] ||= destination_path locs[:current_path] ||= destination_path
@ -118,11 +125,11 @@ module Middleman
# @return [String] # @return [String]
def url def url
url_path = destination_path url_path = destination_path
if app.config[:strip_index_file] if @app.config[:strip_index_file]
url_path = url_path.sub(/(^|\/)#{Regexp.escape(app.config[:index_file])}$/, url_path = url_path.sub(/(^|\/)#{Regexp.escape(@app.config[:index_file])}$/,
app.config[:trailing_slash] ? '/' : '') @app.config[:trailing_slash] ? '/' : '')
end end
File.join(app.config[:http_prefix], url_path) File.join(@app.config[:http_prefix], url_path)
end end
# Whether the source file is binary. # Whether the source file is binary.

View file

@ -20,14 +20,14 @@ module Middleman
# extensions. All "path" parameters used in this class are source paths. # extensions. All "path" parameters used in this class are source paths.
class Store class Store
# @return [Middleman::Application] # @return [Middleman::Application]
attr_accessor :app attr_reader :app
# Initialize with parent app # Initialize with parent app
# @param [Middleman::Application] app # @param [Middleman::Application] app
def initialize(app) def initialize(app)
@app = app @app = app
@resources = [] @resources = []
@_cached_metadata = {} # TODO: Should this be a set or hash?
@resource_list_manipulators = [] @resource_list_manipulators = []
@needs_sitemap_rebuild = true @needs_sitemap_rebuild = true
@ -35,26 +35,26 @@ module Middleman
reset_lookup_cache! reset_lookup_cache!
# Handle ignore commands # Handle ignore commands
Middleman::Sitemap::Extensions::Ignores.new(self) Middleman::Sitemap::Extensions::Ignores.new(@app, self)
# Extensions # Extensions
{ {
# Register classes which can manipulate the main site map list # Register classes which can manipulate the main site map list
on_disk: Middleman::Sitemap::Extensions::OnDisk, on_disk: Middleman::Sitemap::Extensions::OnDisk.new(@app, self),
# Request Endpoints # Request Endpoints
request_endpoints: Middleman::Sitemap::Extensions::RequestEndpoints, request_endpoints: Middleman::Sitemap::Extensions::RequestEndpoints.new(@app),
# Proxies # Proxies
proxies: Middleman::Sitemap::Extensions::Proxies, proxies: Middleman::Sitemap::Extensions::Proxies.new(@app),
# Redirects # Redirects
redirects: Middleman::Sitemap::Extensions::Redirects redirects: Middleman::Sitemap::Extensions::Redirects.new(@app)
}.each do |k, m| }.each do |k, m|
register_resource_list_manipulator(k, m.new(self)) register_resource_list_manipulator(k, m)
end end
app.config_context.class.send :delegate, :sitemap, to: :app @app.config_context.class.send :delegate, :sitemap, to: :app
end end
# Register an object which can transform the sitemap resource list. Best to register # Register an object which can transform the sitemap resource list. Best to register
@ -128,63 +128,6 @@ module Middleman
@resources_not_ignored = nil @resources_not_ignored = nil
end end
# Register a handler to provide metadata on a file path
# @param [Regexp] matcher
# @return [Array<Array<Proc, Regexp>>]
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: {} }
provides_metadata.reduce(blank_metadata) do |result, (callback, matcher)|
next result if matcher && !source_file.match(matcher)
metadata = callback.call(source_file).dup
result.deep_merge(metadata)
end
end
# Register a handler to provide metadata on a url path
# @param [Regexp] matcher
# @return [Array<Array<Proc, Regexp>>]
def provides_metadata_for_path(matcher=nil, &block)
@_provides_metadata_for_path ||= []
if block_given?
@_provides_metadata_for_path << [block, matcher]
@_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: {} }
@_cached_metadata[request_path] = provides_metadata_for_path.reduce(blank_metadata) do |result, (callback, matcher)|
case matcher
when Regexp
next result unless request_path =~ matcher
when String
next result unless File.fnmatch('/' + Util.strip_leading_slash(matcher), "/#{request_path}")
end
metadata = callback.call(request_path).dup
result.deep_merge(metadata)
end
end
# Get the URL path for an on-disk file # Get the URL path for an on-disk file
# @param [String] file # @param [String] file
# @return [String] # @return [String]

View file

@ -41,12 +41,11 @@ Given /^the Server is running$/ do
ENV['MM_ROOT'] = root_dir ENV['MM_ROOT'] = root_dir
initialize_commands = @initialize_commands || [] initialize_commands = @initialize_commands || []
initialize_commands.unshift lambda { config[:show_exceptions] = false }
@server_inst = Middleman::Application.server.inst do @server_inst = Middleman::Application.server.inst do
app.initialized do
initialize_commands.each do |p| initialize_commands.each do |p|
config_context.instance_exec(&p) instance_exec(&p)
end
end end
end end