more docs

This commit is contained in:
Thomas Reynolds 2012-01-14 12:46:22 -08:00
parent 3ef11f3add
commit 5cc204e848
2 changed files with 135 additions and 17 deletions

View file

@ -1,25 +1,37 @@
# Shutup Tilt Warnings # Shutup Tilt Warnings
# @private
class Tilt::Template class Tilt::Template
def warn(*args) def warn(*args)
# Kernel.warn(*args) # Kernel.warn(*args)
end end
end end
# Rendering extension
module Middleman::CoreExtensions::Rendering module Middleman::CoreExtensions::Rendering
# Setup extension
class << self class << self
# Once registered
def registered(app) def registered(app)
# Include methods
app.send :include, InstanceMethods app.send :include, InstanceMethods
# Activate custom renderers # Activate custom renderers
app.register Middleman::Renderers::ERb app.register Middleman::Renderers::ERb
end end
alias :included :registered alias :included :registered
end end
# Custom error class for handling
class TemplateNotFound < RuntimeError class TemplateNotFound < RuntimeError
end end
# Rendering instance methods
module InstanceMethods module InstanceMethods
# Override init to clear cache on file removal
def initialize def initialize
super super
@ -32,39 +44,61 @@ module Middleman::CoreExtensions::Rendering
end end
end 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={}) def render_template(path, locs={}, opts={})
# Detect the remdering engine from the extension
extension = File.extname(path) extension = File.extname(path)
engine = extension[1..-1].to_sym engine = extension[1..-1].to_sym
# Store last engine for later (could be inside nested renders)
@current_engine, engine_was = engine, @current_engine @current_engine, engine_was = engine, @current_engine
# Use a dup of self as a context so that instance variables set within # Use a dup of self as a context so that instance variables set within
# the template don't persist for other templates. # the template don't persist for other templates.
context = self.dup context = self.dup
# 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
# cases like `style.css.sass.erb`
while ::Tilt[path] while ::Tilt[path]
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) cache.set([:raw_template, path], content)
end end
# Certain output file types don't use layouts
needs_layout = !%w(.js .css .txt).include?(extension) needs_layout = !%w(.js .css .txt).include?(extension)
# If we need a layout and have a layout, use it
if needs_layout && layout_path = fetch_layout(engine, opts) if needs_layout && layout_path = fetch_layout(engine, opts)
content = render_individual_file(layout_path, locs, opts, context) { content } content = render_individual_file(layout_path, locs, opts, context) { content }
end end
# Return result
content content
ensure 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 @current_engine = engine_was
@content_blocks = nil @content_blocks = nil
@current_locs = nil @current_locs = nil
@current_opts = nil @current_opts = nil
end end
# Sinatra/Padrino render method signature. # 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) def render(engine, data, options={}, &block)
data = data.to_s data = data.to_s
@ -73,30 +107,37 @@ module Middleman::CoreExtensions::Rendering
found_partial = false found_partial = false
engine = nil engine = nil
# If the path is known to the sitemap
if sitemap.exists?(current_path) if sitemap.exists?(current_path)
page = sitemap.page(current_path) page = sitemap.page(current_path)
current_dir = File.dirname(page.source_file) current_dir = File.dirname(page.source_file)
engine = File.extname(page.source_file)[1..-1].to_sym engine = File.extname(page.source_file)[1..-1].to_sym
# Look for partials relative to the current path
if current_dir != self.source_dir if current_dir != self.source_dir
relative_dir = File.join(current_dir.sub("#{self.source_dir}/", ""), data) 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) found_partial, found_engine = resolve_template(relative_dir, :preferred_engine => engine)
# Fall back to any engine available
if !found_partial if !found_partial
found_partial, found_engine = resolve_template(relative_dir) found_partial, found_engine = resolve_template(relative_dir)
end end
end end
end end
# Look in the root for the partial with the current engine
if !found_partial && !engine.nil? if !found_partial && !engine.nil?
found_partial, found_engine = resolve_template(data, :preferred_engine => engine) found_partial, found_engine = resolve_template(data, :preferred_engine => engine)
end end
# Look in the root with any engine
if !found_partial if !found_partial
found_partial, found_engine = resolve_template(data) found_partial, found_engine = resolve_template(data)
end end
# Render the partial if found, otherwide throw exception
if found_partial if found_partial
render_individual_file(found_partial, locals, options, self, &block) render_individual_file(found_partial, locals, options, self, &block)
else else
@ -104,34 +145,53 @@ module Middleman::CoreExtensions::Rendering
end end
end end
# @private # 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) def render_individual_file(path, locs = {}, opts = {}, context = self, &block)
path = path.to_s path = path.to_s
# Save current buffere for later
@_out_buf, _buf_was = "", @_out_buf @_out_buf, _buf_was = "", @_out_buf
# Read from disk or cache the contents of the file
body = cache.fetch(:raw_template, path) do body = cache.fetch(:raw_template, path) do
File.read(path) File.read(path)
end end
# Merge per-extension options from config
extension = File.extname(path) extension = File.extname(path)
options = opts.merge(options_for_ext(extension)) options = opts.merge(options_for_ext(extension))
options[:outvar] ||= '@_out_buf' options[:outvar] ||= '@_out_buf'
# Read compiled template from disk or cache
template = cache.fetch(:compiled_template, options, body) do template = cache.fetch(:compiled_template, options, body) do
::Tilt.new(path, 1, options) { body } ::Tilt.new(path, 1, options) { body }
end end
# Render using Tilt
template.render(context, locs, &block) template.render(context, locs, &block)
ensure ensure
# Reset stored buffer
@_out_buf = _buf_was @_out_buf = _buf_was
end end
# @private # Get a hash of configuration options for a given file extension, from
# config.rb
#
# @param [String] ext
# @return [Hash]
def options_for_ext(ext) def options_for_ext(ext)
# Read options for extension from config/Tilt or cache
cache.fetch(:options_for_ext, ext) do cache.fetch(:options_for_ext, ext) do
options = {} 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] extension_class = ::Tilt[ext]
::Tilt.mappings.each do |ext, engines| ::Tilt.mappings.each do |ext, engines|
next unless engines.include? extension_class next unless engines.include? extension_class
@ -143,13 +203,21 @@ module Middleman::CoreExtensions::Rendering
end end
end end
# @private # Find a layout for a given engine
#
# @param [Symbol] engine
# @param [Hash] opts
# @return [String]
def fetch_layout(engine, opts) 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 local_layout = opts.has_key?(:layout) ? opts[:layout] : layout
return false unless local_layout return false unless local_layout
# Look for engine-specific options
engine_options = respond_to?(engine) ? send(engine) : {} 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) layout_engine = if opts.has_key?(:layout_engine)
opts[:layout_engine] opts[:layout_engine]
elsif engine_options.has_key?(:layout_engine) elsif engine_options.has_key?(:layout_engine)
@ -158,7 +226,7 @@ module Middleman::CoreExtensions::Rendering
engine engine
end end
# Automatic # Automatic mode
if local_layout == :_auto_layout if local_layout == :_auto_layout
# Look for :layout of any extension # Look for :layout of any extension
# If found, use it. If not, continue # If found, use it. If not, continue
@ -174,10 +242,15 @@ module Middleman::CoreExtensions::Rendering
end end
end end
# @private # 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) def locate_layout(name, preferred_engine=nil)
# Whether we've found the layout
layout_path = false layout_path = false
# If we prefer a specific engine
if !preferred_engine.nil? if !preferred_engine.nil?
# Check root # Check root
layout_path, layout_engine = resolve_template(name, :preferred_engine => preferred_engine) layout_path, layout_engine = resolve_template(name, :preferred_engine => preferred_engine)
@ -198,38 +271,52 @@ module Middleman::CoreExtensions::Rendering
layout_path, layout_engine = resolve_template(File.join("layouts", name.to_s)) layout_path, layout_engine = resolve_template(File.join("layouts", name.to_s))
end end
# Return the path
layout_path layout_path
end 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) def wrap_layout(layout_name, &block)
content = capture(&block) if block_given? content = capture(&block) if block_given?
layout_path = locate_layout(layout_name, current_engine) layout_path = locate_layout(layout_name, current_engine)
concat render_individual_file(layout_path, @current_locs || {}, @current_opts || {}, self) { content } concat render_individual_file(layout_path, @current_locs || {}, @current_opts || {}, self) { content }
end end
# The currently rendering engine
# @return [Symbol, nil]
def current_engine def current_engine
@current_engine ||= nil @current_engine ||= nil
end end
# @private # Find a template on disk given a output path
# @param [String] request_path
# @param [Hash] options
# @return [Array<String, Symbol>, Boolean]
def resolve_template(request_path, options={}) def resolve_template(request_path, options={})
# Find the path by searching or using the cache
request_path = request_path.to_s request_path = request_path.to_s
cache.fetch(:resolve_template, request_path, options) do cache.fetch(:resolve_template, request_path, options) do
relative_path = request_path.sub(%r{^/}, "") relative_path = request_path.sub(%r{^/}, "")
on_disk_path = File.expand_path(relative_path, self.source_dir) on_disk_path = File.expand_path(relative_path, self.source_dir)
# By default, any engine will do
preferred_engine = "*" preferred_engine = "*"
# Unless we're specifically looking for a preferred engine
if options.has_key?(:preferred_engine) if options.has_key?(:preferred_engine)
extension_class = ::Tilt[options[:preferred_engine]] extension_class = ::Tilt[options[:preferred_engine]]
matched_exts = [] matched_exts = []
# Get a list of extensions for a preferred engine
# TODO: Cache this # TODO: Cache this
::Tilt.mappings.each do |ext, engines| ::Tilt.mappings.each do |ext, engines|
next unless engines.include? extension_class next unless engines.include? extension_class
matched_exts << ext matched_exts << ext
end end
# Change the glob to only look for the matched extensions
if matched_exts.length > 0 if matched_exts.length > 0
preferred_engine = "{" + matched_exts.join(",") + "}" preferred_engine = "{" + matched_exts.join(",") + "}"
else else
@ -237,11 +324,13 @@ module Middleman::CoreExtensions::Rendering
end end
end end
# Look for files that match
path_with_ext = on_disk_path + "." + preferred_engine path_with_ext = on_disk_path + "." + preferred_engine
found_path = Dir[path_with_ext].find do |path| found_path = Dir[path_with_ext].find do |path|
::Tilt[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)) 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 engine = found_path ? File.extname(found_path)[1..-1].to_sym : nil
[ found_path || on_disk_path, engine ] [ found_path || on_disk_path, engine ]

View file

@ -1,17 +1,30 @@
# Routing extension
module Middleman::CoreExtensions::Routing module Middleman::CoreExtensions::Routing
# Setup extension
class << self class << self
# Once registered
def registered(app) def registered(app)
# Include methods
app.send :include, InstanceMethods app.send :include, InstanceMethods
end end
alias :included :registered alias :included :registered
end end
# Routing instance methods
module InstanceMethods module InstanceMethods
# Takes a block which allows many pages to have the same layout # Takes a block which allows many pages to have the same layout
# with_layout :admin do #
# page "/admin/" # with_layout :admin do
# page "/admin/login.html" # page "/admin/"
# end # page "/admin/login.html"
# end
#
# @param [String, Symbol] layout_name
# @return [void]
def with_layout(layout_name, &block) def with_layout(layout_name, &block)
old_layout = layout old_layout = layout
@ -22,16 +35,26 @@ module Middleman::CoreExtensions::Routing
end end
# The page method allows the layout to be set on a specific path # The page method allows the layout to be set on a specific path
# page "/about.html", :layout => false #
# page "/", :layout => :homepage_layout # page "/about.html", :layout => false
# page "/", :layout => :homepage_layout
#
# @param [String] url
# @param [Hash] opts
# @return [void]
def page(url, opts={}, &block) def page(url, opts={}, &block)
a_block = block_given? ? block : nil a_block = block_given? ? block : nil
# If the url is a string with an asterisk, it is a glob and should
# be converted to a Regexp
if url.include?("*") if url.include?("*")
url = Regexp.new(url.gsub("*", "(.*?)").gsub(/^\//, "^")) url = Regexp.new(url.gsub("*", "(.*?)").gsub(/^\//, "^"))
end end
# If the url is a regexp
if url.is_a?(Regexp) if url.is_a?(Regexp)
# Use the metadata loop for matching against paths at runtime
provides_metadata_for_path url do |url| provides_metadata_for_path url do |url|
{ :options => opts, :blocks => [a_block] } { :options => opts, :blocks => [a_block] }
end end
@ -39,10 +62,13 @@ module Middleman::CoreExtensions::Routing
return return
end end
# Default layout
opts[:layout] = layout if opts[:layout].nil? opts[:layout] = layout if opts[:layout].nil?
# Normalized path
url = full_path(url) url = full_path(url)
# Setup proxy
if opts.has_key?(:proxy) if opts.has_key?(:proxy)
reroute(url, opts[:proxy]) reroute(url, opts[:proxy])
@ -59,7 +85,10 @@ module Middleman::CoreExtensions::Routing
end end
end end
# If we have a block or opts
if a_block || !opts.empty? if a_block || !opts.empty?
# Setup a metadata matcher for rendering those options
provides_metadata_for_path url do |url| provides_metadata_for_path url do |url|
{ :options => opts, :blocks => [a_block] } { :options => opts, :blocks => [a_block] }
end end