Merge pull request #416 from middleman/frontmatter_refactor
Refactor Sitemap to run on any template file, be more error tolerant and use less code/memory
This commit is contained in:
commit
f17b19d9de
5 changed files with 167 additions and 226 deletions
|
@ -136,9 +136,6 @@ module Middleman
|
||||||
|
|
||||||
# Activate Data package
|
# Activate Data package
|
||||||
register Middleman::CoreExtensions::Data
|
register Middleman::CoreExtensions::Data
|
||||||
|
|
||||||
# Parse YAML from templates
|
|
||||||
register Middleman::CoreExtensions::FrontMatter
|
|
||||||
|
|
||||||
# Setup custom rendering
|
# Setup custom rendering
|
||||||
register Middleman::CoreExtensions::Rendering
|
register Middleman::CoreExtensions::Rendering
|
||||||
|
@ -161,6 +158,9 @@ module Middleman
|
||||||
# i18n
|
# i18n
|
||||||
register Middleman::CoreExtensions::I18n
|
register Middleman::CoreExtensions::I18n
|
||||||
|
|
||||||
|
# Parse YAML from templates
|
||||||
|
register Middleman::CoreExtensions::FrontMatter
|
||||||
|
|
||||||
# Built-in Extensions
|
# Built-in Extensions
|
||||||
|
|
||||||
# Provide Apache-style index.html files for directories
|
# Provide Apache-style index.html files for directories
|
||||||
|
|
|
@ -1,212 +1,161 @@
|
||||||
# Parsing YAML frontmatter
|
# Extensions namespace
|
||||||
require "yaml"
|
module Middleman::CoreExtensions
|
||||||
|
|
||||||
# Looking up Tilt extensions
|
|
||||||
require "tilt"
|
|
||||||
|
|
||||||
# Frontmatter namespace
|
|
||||||
module Middleman
|
|
||||||
module CoreExtensions
|
|
||||||
module FrontMatter
|
|
||||||
|
|
||||||
# Setup extension
|
# Frontmatter namespace
|
||||||
class << self
|
module FrontMatter
|
||||||
|
|
||||||
|
# Setup extension
|
||||||
|
class << self
|
||||||
|
|
||||||
# Once registered
|
# Once registered
|
||||||
def registered(app)
|
def registered(app)
|
||||||
app.set :frontmatter_extensions, %w(.htm .html .php)
|
# Parsing YAML frontmatter
|
||||||
app.extend ClassMethods
|
require "yaml"
|
||||||
app.send :include, InstanceMethods
|
|
||||||
app.delegate :frontmatter_changed, :to => :"self.class"
|
|
||||||
|
|
||||||
|
app.after_configuration do
|
||||||
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
|
::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<Array<Proc, Regexp>>]
|
|
||||||
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(/\/$/, "") + "/"
|
app.send :include, InstanceMethods
|
||||||
|
|
||||||
matcher = %r{#{static_path}.*(#{exts})}
|
files.changed { |file| frontmatter_manager.clear_data(file) }
|
||||||
|
files.deleted { |file| frontmatter_manager.clear_data(file) }
|
||||||
files.changed matcher do |file|
|
|
||||||
frontmatter_extension.touch_file(file)
|
sitemap.register_resource_list_manipulator(
|
||||||
end
|
:frontmatter,
|
||||||
|
frontmatter_manager
|
||||||
files.deleted matcher do |file|
|
)
|
||||||
frontmatter_extension.remove_file(file)
|
|
||||||
end
|
sitemap.provides_metadata do |path|
|
||||||
|
fmdata = frontmatter_manager.data(path).first
|
||||||
sitemap.provides_metadata matcher do |path|
|
|
||||||
fmdata = if self.frontmatter_extension.has_data?(path)
|
|
||||||
self.frontmatter(path)[0]
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
%w(layout layout_engine).each do |opt|
|
%w(layout layout_engine).each do |opt|
|
||||||
data[opt.to_sym] = fmdata[opt] if fmdata.has_key?(opt)
|
data[opt.to_sym] = fmdata[opt] if fmdata[opt]
|
||||||
end
|
end
|
||||||
|
|
||||||
{ :options => data, :page => fmdata }
|
{ :options => data, :page => fmdata }
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
alias :included :registered
|
||||||
|
end
|
||||||
|
|
||||||
# Core Frontmatter class
|
class FrontmatterManager
|
||||||
class FrontMatter
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
# Initialize frontmatter with current app
|
@cache = {}
|
||||||
# @param [Middleman::Application] app
|
end
|
||||||
def initialize(app)
|
|
||||||
@app = app
|
|
||||||
@source = File.expand_path(@app.source, @app.root)
|
|
||||||
@local_data = {}
|
|
||||||
|
|
||||||
# Setup ignore callback
|
def data(path)
|
||||||
@app.ignore do |path|
|
p = normalize_path(path)
|
||||||
if p = @app.sitemap.find_resource_by_path(path)
|
@cache[p] ||= frontmatter_and_content(p)
|
||||||
!p.proxy? && p.data && p.data["ignored"] == true
|
end
|
||||||
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)
|
def clear_data(path)
|
||||||
content = File.read(file)
|
p = normalize_path(File.expand_path(path, @app.root))
|
||||||
|
@cache.delete(p)
|
||||||
|
end
|
||||||
|
|
||||||
result = parse_front_matter(content)
|
# Parse frontmatter out of a string
|
||||||
|
# @param [String] content
|
||||||
if result
|
# @return [Array<Hash, String>]
|
||||||
data, content = result
|
def parse_front_matter(content)
|
||||||
|
yaml_regex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
||||||
|
if content =~ yaml_regex
|
||||||
|
content = content[($1.size + $2.size)..-1]
|
||||||
|
|
||||||
data = ::Middleman::Util.recursively_enhance(data).freeze
|
begin
|
||||||
@local_data[file] = [data, content]
|
data = YAML.load($1)
|
||||||
@app.cache.set([:raw_template, file], result[1])
|
rescue => e
|
||||||
@app.frontmatter_did_change(file)
|
puts "YAML Exception: #{e.message}"
|
||||||
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<Hash, String>]
|
|
||||||
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
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
[data, content]
|
else
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
[data, content]
|
||||||
|
rescue
|
||||||
|
[{}, content]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the frontmatter and plain content from a file
|
||||||
|
# @param [String] path
|
||||||
|
# @return [Array<Thor::CoreExt::HashWithIndifferentAccess, String>]
|
||||||
|
def frontmatter_and_content(path)
|
||||||
|
full_path = File.expand_path(path, @app.source_dir)
|
||||||
|
content = File.read(full_path)
|
||||||
|
|
||||||
|
result = parse_front_matter(content)
|
||||||
|
|
||||||
|
if result
|
||||||
|
data, content = result
|
||||||
|
data = ::Middleman::Util.recursively_enhance(data).freeze
|
||||||
|
else
|
||||||
|
data = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
[data, content]
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_path(path)
|
||||||
|
path.sub(@app.source_dir, "").sub(/^\//, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update the main sitemap resource list
|
||||||
|
# @return [void]
|
||||||
|
def manipulate_resource_list(resources)
|
||||||
|
resources.each do |r|
|
||||||
|
if !r.proxy? && r.data["ignored"] == true
|
||||||
|
r.frontmatter_ignored = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
resources
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module ResourceInstanceMethods
|
||||||
|
|
||||||
|
def frontmatter_ignored?
|
||||||
|
@_frontmatter_ignored || false
|
||||||
|
end
|
||||||
|
|
||||||
|
def frontmatter_ignored=(v)
|
||||||
|
@_frontmatter_ignored = v
|
||||||
|
end
|
||||||
|
|
||||||
|
def ignored?
|
||||||
|
if frontmatter_ignored?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This page's frontmatter
|
||||||
|
# @return [Hash]
|
||||||
|
def data
|
||||||
|
app.frontmatter_manager.data(source_file).first
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Access the Frontmatter API
|
||||||
|
def frontmatter_manager
|
||||||
|
@_frontmatter_manager ||= FrontmatterManager.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the template data from a path
|
||||||
|
# @param [String] path
|
||||||
|
# @return [String]
|
||||||
|
def template_data_for_file(path)
|
||||||
|
frontmatter_manager.data(path).last
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -35,30 +35,14 @@ module Middleman
|
||||||
# Rendering instance methods
|
# Rendering instance methods
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
|
||||||
# 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 + "/"}})
|
|
||||||
|
|
||||||
self.files.changed render_regex do |file|
|
|
||||||
path = File.expand_path(file, self.root)
|
|
||||||
self.cache.remove(:raw_template, path)
|
|
||||||
end
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add or overwrite a default template extension
|
# Add or overwrite a default template extension
|
||||||
#
|
#
|
||||||
# @param [Hash] extension_map
|
# @param [Hash] extension_map
|
||||||
# @return [void]
|
# @return [Hash]
|
||||||
def template_extensions(extension_map={})
|
def template_extensions(extension_map=nil)
|
||||||
@_template_extensions.merge!(extension_map)
|
@_template_extensions ||= {}
|
||||||
|
@_template_extensions.merge!(extension_map) if extension_map
|
||||||
|
@_template_extensions
|
||||||
end
|
end
|
||||||
|
|
||||||
# Render a template, with layout, given a path
|
# Render a template, with layout, given a path
|
||||||
|
@ -82,12 +66,13 @@ module Middleman
|
||||||
# Store current locs/opts for later
|
# Store current locs/opts for later
|
||||||
@current_locs = locs, @current_opts = opts
|
@current_locs = locs, @current_opts = opts
|
||||||
|
|
||||||
# Keep rendering template until we've used up all extensions. This handles
|
# Keep rendering template until we've used up all extensions. This
|
||||||
# cases like `style.css.sass.erb`
|
# handles cases like `style.css.sass.erb`
|
||||||
|
content = nil
|
||||||
while ::Tilt[path]
|
while ::Tilt[path]
|
||||||
|
opts[:template_body] = content if content
|
||||||
content = render_individual_file(path, locs, opts, context)
|
content = render_individual_file(path, locs, opts, context)
|
||||||
path = File.basename(path, File.extname(path))
|
path = File.basename(path, File.extname(path))
|
||||||
cache.set([:raw_template, path], content)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Certain output file types don't use layouts
|
# Certain output file types don't use layouts
|
||||||
|
@ -175,8 +160,10 @@ module Middleman
|
||||||
@_out_buf, _buf_was = "", @_out_buf
|
@_out_buf, _buf_was = "", @_out_buf
|
||||||
|
|
||||||
# Read from disk or cache the contents of the file
|
# Read from disk or cache the contents of the file
|
||||||
body = cache.fetch(:raw_template, path) do
|
body = if opts[:template_body]
|
||||||
File.read(path)
|
opts.delete(:template_body)
|
||||||
|
else
|
||||||
|
template_data_for_file(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Merge per-extension options from config
|
# Merge per-extension options from config
|
||||||
|
@ -196,6 +183,13 @@ module Middleman
|
||||||
@_out_buf = _buf_was
|
@_out_buf = _buf_was
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the template data from a path
|
||||||
|
# @param [String] path
|
||||||
|
# @return [String]
|
||||||
|
def template_data_for_file(path)
|
||||||
|
File.read(File.expand_path(path, source_dir))
|
||||||
|
end
|
||||||
|
|
||||||
# Get a hash of configuration options for a given file extension, from
|
# Get a hash of configuration options for a given file extension, from
|
||||||
# config.rb
|
# config.rb
|
||||||
#
|
#
|
||||||
|
|
|
@ -53,7 +53,11 @@ module Middleman
|
||||||
def get_source_file
|
def get_source_file
|
||||||
if proxy?
|
if proxy?
|
||||||
proxy_resource = store.find_resource_by_path(proxied_to)
|
proxy_resource = store.find_resource_by_path(proxied_to)
|
||||||
raise "Path #{path} proxies to unknown file #{proxied_to}" unless proxy_resource
|
|
||||||
|
unless proxy_resource
|
||||||
|
raise "Path #{path} proxies to unknown file #{proxied_to}"
|
||||||
|
end
|
||||||
|
|
||||||
proxy_resource.source_file
|
proxy_resource.source_file
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -79,7 +79,7 @@ module Middleman
|
||||||
@local_metadata[:blocks] << metadata[:blocks]
|
@local_metadata[:blocks] << metadata[:blocks]
|
||||||
metadata.delete(:blocks)
|
metadata.delete(:blocks)
|
||||||
end
|
end
|
||||||
@local_metadata.deep_merge(metadata)
|
@local_metadata.deep_merge!(metadata)
|
||||||
@local_metadata[:blocks] << block if block_given?
|
@local_metadata[:blocks] << block if block_given?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -96,12 +96,6 @@ module Middleman
|
||||||
@destination_paths << path
|
@destination_paths << path
|
||||||
end
|
end
|
||||||
|
|
||||||
# The template instance
|
|
||||||
# @return [Middleman::Sitemap::Template]
|
|
||||||
def template
|
|
||||||
@_template ||= ::Middleman::Sitemap::Template.new(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extension of the path (i.e. '.js')
|
# Extension of the path (i.e. '.js')
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def ext
|
def ext
|
||||||
|
@ -134,7 +128,7 @@ module Middleman
|
||||||
md[:blocks].flatten.compact.each do |block|
|
md[:blocks].flatten.compact.each do |block|
|
||||||
app.instance_eval(&block)
|
app.instance_eval(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
app.instance_eval(&block) if block_given?
|
app.instance_eval(&block) if block_given?
|
||||||
result = app.render_template(source_file, locs, opts)
|
result = app.render_template(source_file, locs, opts)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue