move rendering into specialized File and Template rendering classes.
This commit is contained in:
parent
c06fbcfc93
commit
f40903e663
|
@ -1,6 +1,3 @@
|
|||
# Using Tilt for templating
|
||||
require 'tilt'
|
||||
|
||||
# i18n Built-in
|
||||
require 'i18n'
|
||||
|
||||
|
@ -25,6 +22,8 @@ require 'middleman-core/configuration'
|
|||
require 'middleman-core/core_extensions'
|
||||
|
||||
require 'middleman-core/config_context'
|
||||
require 'middleman-core/file_renderer'
|
||||
require 'middleman-core/template_renderer'
|
||||
|
||||
# Core Middleman Class
|
||||
module Middleman
|
||||
|
@ -161,17 +160,21 @@ module Middleman
|
|||
# Template cache
|
||||
attr_reader :cache
|
||||
|
||||
attr_reader :template_context_class
|
||||
|
||||
attr_reader :generic_template_context
|
||||
delegate :link_to, :image_tag, :to => :generic_template_context
|
||||
|
||||
# Initialize the Middleman project
|
||||
def initialize(&block)
|
||||
@cache = ::Tilt::Cache.new
|
||||
@logger = ::Middleman::Logger.singleton
|
||||
@template_context_class = Class.new(Middleman::TemplateContext)
|
||||
@generic_template_context = @template_context_class.new(self)
|
||||
@config_context = ConfigContext.new(self, @template_context_class)
|
||||
|
||||
::Middleman::FileRenderer.cache.clear
|
||||
::Middleman::TemplateRenderer.cache.clear
|
||||
|
||||
# Setup the default values from calls to set before initialization
|
||||
self.class.config.load_settings(self.class.superclass.config.all_settings)
|
||||
|
||||
|
@ -199,6 +202,10 @@ module Middleman
|
|||
super
|
||||
end
|
||||
|
||||
def add_to_instance(name, &func)
|
||||
self.define_singleton_method(name, &func)
|
||||
end
|
||||
|
||||
def add_to_config_context(name, &func)
|
||||
@config_context.define_singleton_method(name, &func)
|
||||
end
|
||||
|
|
|
@ -99,10 +99,6 @@ module Middleman
|
|||
end
|
||||
end
|
||||
|
||||
# Custom error class for handling
|
||||
class TemplateNotFound < RuntimeError
|
||||
end
|
||||
|
||||
# Rendering instance methods
|
||||
module InstanceMethods
|
||||
|
||||
|
@ -115,289 +111,6 @@ module Middleman
|
|||
@_template_extensions.merge!(extension_map) if extension_map
|
||||
@_template_extensions
|
||||
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={}, blocks=[])
|
||||
extension = File.extname(path)
|
||||
engine = extension[1..-1].to_sym
|
||||
|
||||
if defined?(::I18n)
|
||||
old_locale = ::I18n.locale
|
||||
::I18n.locale = opts[:lang] if opts[:lang]
|
||||
end
|
||||
|
||||
# Sandboxed class for template eval
|
||||
context = @template_context_class.new(self, locs, opts)
|
||||
|
||||
if context.respond_to?(:init_haml_helpers)
|
||||
context.init_haml_helpers
|
||||
end
|
||||
|
||||
blocks.each do |block|
|
||||
context.instance_eval(&block)
|
||||
end
|
||||
|
||||
# Keep rendering template until we've used up all extensions. This
|
||||
# handles cases like `style.css.sass.erb`
|
||||
content = nil
|
||||
while ::Tilt[path]
|
||||
begin
|
||||
opts[:template_body] = content if content
|
||||
content = render_individual_file(path, locs, opts, context)
|
||||
path = File.basename(path, File.extname(path))
|
||||
rescue LocalJumpError
|
||||
raise "Tried to render a layout (calls yield) at #{path} like it was a template. Non-default layouts need to be in #{source}/#{layout_dir}."
|
||||
end
|
||||
end
|
||||
|
||||
# If we need a layout and have a layout, use it
|
||||
if 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).
|
||||
::I18n.locale = old_locale if defined?(::I18n)
|
||||
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, &block)
|
||||
path = path.to_s
|
||||
|
||||
# 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)
|
||||
context.current_engine, engine_was = engine, context.current_engine
|
||||
|
||||
# Save current buffer for later
|
||||
_buf_was = context.save_buffer
|
||||
|
||||
# Read from disk or cache the contents of the file
|
||||
body = if opts[:template_body]
|
||||
opts.delete(:template_body)
|
||||
else
|
||||
template_data_for_file(path)
|
||||
end
|
||||
|
||||
# Merge per-extension options from config
|
||||
extension = File.extname(path)
|
||||
options = opts.dup.merge(options_for_ext(extension))
|
||||
options[:outvar] ||= '@_out_buf'
|
||||
options.delete(:layout)
|
||||
|
||||
# Overwrite with frontmatter options
|
||||
options = options.deep_merge(options[:renderer_options]) if options[:renderer_options]
|
||||
|
||||
template_class = Tilt[path]
|
||||
# Allow hooks to manipulate the template before render
|
||||
self.class.callbacks_for_hook(:before_render).each do |callback|
|
||||
newbody = callback.call(body, path, locs, template_class)
|
||||
body = newbody if newbody # Allow the callback to return nil to skip it
|
||||
end
|
||||
|
||||
# Read compiled template from disk or cache
|
||||
template = cache.fetch(:compiled_template, extension, options, body) do
|
||||
::Tilt.new(path, 1, options) { body }
|
||||
end
|
||||
|
||||
# Render using Tilt
|
||||
content = template.render(context, locs, &block)
|
||||
|
||||
# Allow hooks to manipulate the result after render
|
||||
self.class.callbacks_for_hook(:after_render).each do |callback|
|
||||
content = callback.call(content, path, locs, template_class)
|
||||
end
|
||||
|
||||
output = ::ActiveSupport::SafeBuffer.new ''
|
||||
output.safe_concat content
|
||||
output
|
||||
ensure
|
||||
# Reset stored buffer
|
||||
context.restore_buffer(_buf_was)
|
||||
context.current_engine = engine_was
|
||||
end
|
||||
|
||||
# Get the template data from a path
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
def template_data_for_file(path)
|
||||
if extensions[:frontmatter]
|
||||
extensions[:frontmatter].template_data_for_file(path)
|
||||
else
|
||||
File.read(File.expand_path(path, source_dir))
|
||||
end
|
||||
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 |mapping_ext, engines|
|
||||
next unless engines.include? extension_class
|
||||
engine_options = config[mapping_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] : config[: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(config[:layouts_dir], 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(config[:layouts_dir], name.to_s))
|
||||
end
|
||||
|
||||
# Return the path
|
||||
layout_path
|
||||
end
|
||||
|
||||
# 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={})
|
||||
# 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 = Util.strip_leading_slash(request_path)
|
||||
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 || files.exists?(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
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'rack/lint'
|
|||
require 'rack/head'
|
||||
|
||||
require 'middleman-core/util'
|
||||
require 'middleman-core/template_renderer'
|
||||
|
||||
module Middleman
|
||||
module CoreExtensions
|
||||
|
@ -242,7 +243,7 @@ module Middleman
|
|||
res.write output
|
||||
# Valid content is a 200 status
|
||||
res.status = 200
|
||||
rescue Middleman::CoreExtensions::Rendering::TemplateNotFound => e
|
||||
rescue Middleman::TemplateRenderer::TemplateNotFound => e
|
||||
res.write "Error: #{e.message}"
|
||||
res.status = 500
|
||||
end
|
||||
|
|
119
middleman-core/lib/middleman-core/file_renderer.rb
Normal file
119
middleman-core/lib/middleman-core/file_renderer.rb
Normal file
|
@ -0,0 +1,119 @@
|
|||
require 'tilt'
|
||||
require 'active_support/core_ext/string/output_safety'
|
||||
|
||||
module Middleman
|
||||
|
||||
class FileRenderer
|
||||
|
||||
def self.cache
|
||||
@_cache ||= ::Tilt::Cache.new
|
||||
end
|
||||
|
||||
delegate :cache, :to => :"self.class"
|
||||
|
||||
def initialize(app, path)
|
||||
@app = app
|
||||
@path = path.to_s
|
||||
end
|
||||
|
||||
# Render an on-disk file. Used for everything, including layouts.
|
||||
#
|
||||
# @param [Hash] locs
|
||||
# @param [Hash] opts
|
||||
# @param [Class] context
|
||||
# @return [String]
|
||||
def render(locs = {}, opts = {}, context, &block)
|
||||
path = @path.dup
|
||||
|
||||
# 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)
|
||||
context.current_engine, engine_was = engine, context.current_engine
|
||||
|
||||
# Save current buffer for later
|
||||
_buf_was = context.save_buffer
|
||||
|
||||
# Read from disk or cache the contents of the file
|
||||
body = if opts[:template_body]
|
||||
opts.delete(:template_body)
|
||||
else
|
||||
get_template_data_for_file
|
||||
end
|
||||
|
||||
# Merge per-extension options from config
|
||||
extension = File.extname(path)
|
||||
options = opts.dup.merge(options_for_ext(extension))
|
||||
options[:outvar] ||= '@_out_buf'
|
||||
options.delete(:layout)
|
||||
|
||||
# Overwrite with frontmatter options
|
||||
options = options.deep_merge(options[:renderer_options]) if options[:renderer_options]
|
||||
|
||||
template_class = ::Tilt[path]
|
||||
# Allow hooks to manipulate the template before render
|
||||
@app.class.callbacks_for_hook(:before_render).each do |callback|
|
||||
newbody = callback.call(body, path, locs, template_class)
|
||||
body = newbody if newbody # Allow the callback to return nil to skip it
|
||||
end
|
||||
|
||||
# Read compiled template from disk or cache
|
||||
template = cache.fetch(:compiled_template, extension, options, body) do
|
||||
::Tilt.new(path, 1, options) { body }
|
||||
end
|
||||
|
||||
# Render using Tilt
|
||||
content = template.render(context, locs, &block)
|
||||
|
||||
# Allow hooks to manipulate the result after render
|
||||
@app.class.callbacks_for_hook(:after_render).each do |callback|
|
||||
content = callback.call(content, path, locs, template_class)
|
||||
end
|
||||
|
||||
output = ::ActiveSupport::SafeBuffer.new ''
|
||||
output.safe_concat content
|
||||
output
|
||||
ensure
|
||||
# Reset stored buffer
|
||||
context.restore_buffer(_buf_was)
|
||||
context.current_engine = engine_was
|
||||
end
|
||||
|
||||
# Get the template data from a path
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
def get_template_data_for_file
|
||||
if @app.extensions[:frontmatter]
|
||||
@app.extensions[:frontmatter].template_data_for_file(@path)
|
||||
else
|
||||
File.read(File.expand_path(@path, source_dir))
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# 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 |mapping_ext, engines|
|
||||
next unless engines.include? extension_class
|
||||
engine_options = @app.config[mapping_ext.to_sym] || {}
|
||||
options.merge!(engine_options)
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,7 @@
|
|||
require 'middleman-core/sitemap/extensions/traversal'
|
||||
require 'middleman-core/sitemap/extensions/content_type'
|
||||
require 'middleman-core/file_renderer'
|
||||
require 'middleman-core/template_renderer'
|
||||
|
||||
module Middleman
|
||||
|
||||
|
@ -106,7 +108,7 @@ module Middleman
|
|||
# @return [String]
|
||||
def render(opts={}, locs={}, &block)
|
||||
if !template?
|
||||
return app.template_data_for_file(source_file)
|
||||
return ::Middleman::FileRenderer.new(@app, source_file).get_template_data_for_file
|
||||
end
|
||||
|
||||
relative_source = Pathname(source_file).relative_path_from(Pathname(app.root))
|
||||
|
@ -140,7 +142,8 @@ module Middleman
|
|||
opts[:layout] = false if %w(.js .json .css .txt).include?(self.ext)
|
||||
end
|
||||
|
||||
app.render_template(source_file, locs, opts, blocks)
|
||||
renderer = ::Middleman::TemplateRenderer.new(@app, source_file)
|
||||
renderer.render(locs, opts, blocks)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
require 'middleman-core/file_renderer'
|
||||
require 'middleman-core/template_renderer'
|
||||
|
||||
module Middleman
|
||||
class TemplateContext
|
||||
attr_reader :app
|
||||
|
@ -27,7 +30,7 @@ module Middleman
|
|||
# Save current buffer for later
|
||||
_buf_was = save_buffer
|
||||
|
||||
layout_path = @app.locate_layout(layout_name, self.current_engine)
|
||||
layout_path = ::Middleman::TemplateRenderer.locate_layout(@app, layout_name, self.current_engine)
|
||||
|
||||
extension = File.extname(layout_path)
|
||||
engine = extension[1..-1].to_sym
|
||||
|
@ -46,7 +49,8 @@ module Middleman
|
|||
restore_buffer(_buf_was)
|
||||
end
|
||||
|
||||
concat_safe_content @app.render_individual_file(layout_path, @current_locs || {}, @current_opts || {}, self) { content }
|
||||
file_renderer = ::Middleman::FileRenderer.new(@app, layout_path)
|
||||
concat_safe_content file_renderer.render(@current_locs || {}, @current_opts || {}, self) { content }
|
||||
ensure
|
||||
self.current_engine = engine_was
|
||||
end
|
||||
|
@ -75,30 +79,31 @@ module Middleman
|
|||
relative_dir = File.join(current_dir.sub(%r{^#{Regexp.escape(self.source_dir)}/?}, ''), data)
|
||||
|
||||
# Try to use the current engine first
|
||||
found_partial, found_engine = @app.resolve_template(relative_dir, :preferred_engine => engine, :try_without_underscore => true)
|
||||
found_partial, found_engine = ::Middleman::TemplateRenderer.resolve_template(@app, relative_dir, :preferred_engine => engine, :try_without_underscore => true)
|
||||
|
||||
# Fall back to any engine available
|
||||
if !found_partial
|
||||
found_partial, found_engine = @app.resolve_template(relative_dir, :try_without_underscore => true)
|
||||
found_partial, found_engine = ::Middleman::TemplateRenderer.resolve_template(@app, relative_dir, :try_without_underscore => true)
|
||||
end
|
||||
end
|
||||
|
||||
# Look in the partials_dir for the partial with the current engine
|
||||
partials_path = File.join(config[:partials_dir], data)
|
||||
if !found_partial && !engine.nil?
|
||||
found_partial, found_engine = @app.resolve_template(partials_path, :preferred_engine => engine, :try_without_underscore => true)
|
||||
found_partial, found_engine = ::Middleman::TemplateRenderer.resolve_template(@app, partials_path, :preferred_engine => engine, :try_without_underscore => true)
|
||||
end
|
||||
|
||||
# Look in the root with any engine
|
||||
if !found_partial
|
||||
found_partial, found_engine = @app.resolve_template(partials_path, :try_without_underscore => true)
|
||||
found_partial, found_engine = ::Middleman::TemplateRenderer.resolve_template(@app, partials_path, :try_without_underscore => true)
|
||||
end
|
||||
|
||||
# Render the partial if found, otherwide throw exception
|
||||
if found_partial
|
||||
@app.render_individual_file(found_partial, locals, options, self, &block)
|
||||
file_renderer = ::Middleman::FileRenderer.new(@app, found_partial)
|
||||
file_renderer.render(locals, options, self, &block)
|
||||
else
|
||||
raise ::Middleman::CoreExtensions::Rendering::TemplateNotFound, "Could not locate partial: #{data}"
|
||||
raise ::Middleman::TemplateRenderer::TemplateNotFound, "Could not locate partial: #{data}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
230
middleman-core/lib/middleman-core/template_renderer.rb
Normal file
230
middleman-core/lib/middleman-core/template_renderer.rb
Normal file
|
@ -0,0 +1,230 @@
|
|||
require 'tilt'
|
||||
require 'active_support/core_ext/string/output_safety'
|
||||
require 'middleman-core/template_context'
|
||||
require 'middleman-core/file_renderer'
|
||||
|
||||
module Middleman
|
||||
|
||||
class TemplateRenderer
|
||||
|
||||
def self.cache
|
||||
@_cache ||= ::Tilt::Cache.new
|
||||
end
|
||||
|
||||
delegate :cache, :to => :"self.class"
|
||||
|
||||
# Custom error class for handling
|
||||
class TemplateNotFound < RuntimeError; end
|
||||
|
||||
def initialize(app, path)
|
||||
@app = app
|
||||
@path = path
|
||||
end
|
||||
|
||||
# Render a template, with layout, given a path
|
||||
#
|
||||
# @param [Hash] locs
|
||||
# @param [Hash] opts
|
||||
# @return [String]
|
||||
def render(locs={}, opts={}, blocks=[])
|
||||
path = @path.dup
|
||||
extension = File.extname(path)
|
||||
engine = extension[1..-1].to_sym
|
||||
|
||||
if defined?(::I18n)
|
||||
old_locale = ::I18n.locale
|
||||
::I18n.locale = opts[:lang] if opts[:lang]
|
||||
end
|
||||
|
||||
# Sandboxed class for template eval
|
||||
context = @app.template_context_class.new(@app, locs, opts)
|
||||
|
||||
if context.respond_to?(:init_haml_helpers)
|
||||
context.init_haml_helpers
|
||||
end
|
||||
|
||||
blocks.each do |block|
|
||||
context.instance_eval(&block)
|
||||
end
|
||||
|
||||
# Keep rendering template until we've used up all extensions. This
|
||||
# handles cases like `style.css.sass.erb`
|
||||
content = nil
|
||||
while ::Tilt[path]
|
||||
begin
|
||||
opts[:template_body] = content if content
|
||||
|
||||
content_renderer = ::Middleman::FileRenderer.new(@app, path)
|
||||
content = content_renderer.render(locs, opts, context)
|
||||
|
||||
path = File.basename(path, File.extname(path))
|
||||
rescue LocalJumpError
|
||||
raise "Tried to render a layout (calls yield) at #{path} like it was a template. Non-default layouts need to be in #{source}/#{layout_dir}."
|
||||
end
|
||||
end
|
||||
|
||||
# If we need a layout and have a layout, use it
|
||||
if layout_path = fetch_layout(engine, opts)
|
||||
layout_renderer = ::Middleman::FileRenderer.new(@app, layout_path)
|
||||
content = layout_renderer.render(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).
|
||||
::I18n.locale = old_locale if defined?(::I18n)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# 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] : @app.config[:layout]
|
||||
return false unless local_layout
|
||||
|
||||
# Look for engine-specific options
|
||||
engine_options = @app.config.respond_to?(engine) ? @app.config.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::TemplateRenderer::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)
|
||||
self.class.locate_layout(@app, name, preferred_engine)
|
||||
end
|
||||
|
||||
# Find a layout on-disk, optionally using a specific engine
|
||||
# @param [String] name
|
||||
# @param [Symbol] preferred_engine
|
||||
# @return [String]
|
||||
def self.locate_layout(app, 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(app, name, :preferred_engine => preferred_engine)
|
||||
|
||||
# Check layouts folder
|
||||
if !layout_path
|
||||
layout_path, layout_engine = resolve_template(app, File.join(app.config[:layouts_dir], name.to_s), :preferred_engine => preferred_engine)
|
||||
end
|
||||
end
|
||||
|
||||
# Check root, no preference
|
||||
if !layout_path
|
||||
layout_path, layout_engine = resolve_template(app, name)
|
||||
end
|
||||
|
||||
# Check layouts folder, no preference
|
||||
if !layout_path
|
||||
layout_path, layout_engine = resolve_template(app, File.join(app.config[:layouts_dir], name.to_s))
|
||||
end
|
||||
|
||||
# Return the path
|
||||
layout_path
|
||||
end
|
||||
|
||||
# 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={})
|
||||
self.class.resolve_template(@app, request_path, options)
|
||||
end
|
||||
|
||||
# Find a template on disk given a output path
|
||||
# @param [String] request_path
|
||||
# @param [Hash] options
|
||||
# @return [Array<String, Symbol>, Boolean]
|
||||
def self.resolve_template(app, 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 = Util.strip_leading_slash(request_path)
|
||||
on_disk_path = File.expand_path(relative_path, app.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 || app.files.exists?(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
|
Loading…
Reference in a new issue