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:
Thomas Reynolds 2012-05-08 21:11:03 -07:00
commit f17b19d9de
5 changed files with 167 additions and 226 deletions

View file

@ -137,9 +137,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

View file

@ -1,12 +1,7 @@
# Parsing YAML frontmatter # Extensions namespace
require "yaml" module Middleman::CoreExtensions
# Looking up Tilt extensions # Frontmatter namespace
require "tilt"
# Frontmatter namespace
module Middleman
module CoreExtensions
module FrontMatter module FrontMatter
# Setup extension # Setup extension
@ -14,177 +9,53 @@ module Middleman
# 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 app.send :include, InstanceMethods
module ClassMethods
# Register callback on frontmatter updates files.changed { |file| frontmatter_manager.clear_data(file) }
# @param [Regexp] matcher files.deleted { |file| frontmatter_manager.clear_data(file) }
# @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 sitemap.register_resource_list_manipulator(
:frontmatter,
frontmatter_manager
)
# This page's frontmatter sitemap.provides_metadata do |path|
# @return [Hash] fmdata = frontmatter_manager.data(path).first
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
{}
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
end end
alias :included :registered
# Get the frontmatter object
# @return [Middleman::CoreExtensions::FrontMatter::FrontMatter]
def frontmatter_extension
@_frontmatter_extension ||= FrontMatter.new(self)
end end
# Get the frontmatter for a given path class FrontmatterManager
# @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) def initialize(app)
@app = app @app = app
@source = File.expand_path(@app.source, @app.root) @cache = {}
@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 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
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) def data(path)
if @local_data.has_key?(path.to_s) p = normalize_path(path)
@local_data[path.to_s] @cache[p] ||= frontmatter_and_content(p)
else end
[ ::Middleman::Util.recursively_enhance({}).freeze, nil ]
end def clear_data(path)
p = normalize_path(File.expand_path(path, @app.root))
@cache.delete(p)
end end
private
# Parse frontmatter out of a string # Parse frontmatter out of a string
# @param [String] content # @param [String] content
# @return [Array<Hash, String>] # @return [Array<Hash, String>]
@ -205,8 +76,86 @@ module Middleman
end end
[data, content] [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
end end
resources
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 end

View file

@ -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
# #

View file

@ -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

View file

@ -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