middleman/middleman-core/lib/middleman-core/file_renderer.rb

119 lines
3.5 KiB
Ruby

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