middleman/middleman-core/lib/middleman-core/template_context.rb

183 lines
6.1 KiB
Ruby
Raw Normal View History

require 'pathname'
require 'middleman-core/file_renderer'
require 'middleman-core/template_renderer'
2014-07-03 04:04:34 +02:00
require 'middleman-core/contracts'
2014-01-02 06:19:05 +01:00
module Middleman
# The TemplateContext Class
#
# A clean context, separate from Application, in which templates can be executed.
# All helper methods and values available in a template, but be accessible here.
# Also implements two helpers: wrap_layout & render (used by padrino's partial method).
# A new context is created for each render of a path, but that context is shared through
# the request, passed from template, to layouts and partials.
2014-01-02 06:19:05 +01:00
class TemplateContext
extend Forwardable
2014-07-03 04:04:34 +02:00
include Contracts
# Allow templates to directly access the current app instance.
# @return [Middleman::Application]
2014-01-02 06:19:05 +01:00
attr_reader :app
# Required for Padrino's rendering
attr_accessor :current_engine
2014-01-02 06:19:05 +01:00
# Shorthand references to global values on the app instance.
2014-07-16 03:01:45 +02:00
def_delegators :@app, :config, :logger, :sitemap, :server?, :build?, :environment?, :data, :extensions, :root
2014-01-02 06:19:05 +01:00
# Initialize a context with the current app and predefined locals and options hashes.
#
# @param [Middleman::Application] app
# @param [Hash] locs
# @param [Hash] opts
2014-07-14 22:19:34 +02:00
def initialize(app, locs={}.freeze, opts={}.freeze)
2014-01-02 06:19:05 +01:00
@app = app
2014-07-14 22:19:34 +02:00
@locs = locs
@opts = opts
2014-01-02 06:19:05 +01:00
end
# Return the current buffer to the caller and clear the value internally.
# Used when moving between templates when rendering layouts or partials.
#
# @api private
# @return [String] The old buffer.
2014-01-02 06:19:05 +01:00
def save_buffer
@_out_buf, buf_was = '', @_out_buf
buf_was
2014-01-02 06:19:05 +01:00
end
# Restore a previously saved buffer.
#
# @api private
# @param [String] buf_was
# @return [void]
def restore_buffer(buf_was)
@_out_buf = buf_was
2014-01-02 06:19:05 +01:00
end
# Allow layouts to be wrapped in the contents of other layouts.
#
2014-01-02 06:19:05 +01:00
# @param [String, Symbol] layout_name
# @return [void]
def wrap_layout(layout_name, &block)
# Save current buffer for later
buf_was = save_buffer
2014-01-02 06:19:05 +01:00
# Find a layout for this file
2014-07-16 03:01:45 +02:00
layout_file = ::Middleman::TemplateRenderer.locate_layout(@app, layout_name, current_engine)
2014-01-02 06:19:05 +01:00
# Get the layout engine
2014-07-16 03:01:45 +02:00
extension = File.extname(layout_file[:relative_path])
2014-01-02 06:19:05 +01:00
engine = extension[1..-1].to_sym
# Store last engine for later (could be inside nested renders)
2014-04-29 19:50:21 +02:00
self.current_engine, engine_was = engine, current_engine
2014-01-02 06:19:05 +01:00
# By default, no content is captured
content = ''
# Attempt to capture HTML from block
2014-01-02 06:19:05 +01:00
begin
content = capture_html(&block) if block_given?
2014-01-02 06:19:05 +01:00
ensure
# Reset stored buffer, regardless of success
restore_buffer(buf_was)
2014-01-02 06:19:05 +01:00
end
# Render the layout, with the contents of the block inside.
2014-07-16 03:01:45 +02:00
concat_safe_content render_file(layout_file, @locs, @opts) { content }
2014-01-02 06:19:05 +01:00
ensure
# Reset engine back to template's value, regardless of success
2014-01-02 06:19:05 +01:00
self.current_engine = engine_was
end
# Sinatra/Padrino compatible render method signature referenced by some view
# helpers. Especially partials.
#
# @param [String, Symbol] name The partial to render.
2014-01-02 06:19:05 +01:00
# @param [Hash] options
# @return [String]
2014-07-03 04:04:34 +02:00
Contract Any, Or[Symbol, String], Hash => String
def render(_, name, options={}, &block)
name = name.to_s
2014-01-02 06:19:05 +01:00
2014-07-16 03:01:45 +02:00
partial_file = locate_partial(name)
2014-01-02 06:19:05 +01:00
return "" unless partial_file
2014-07-16 03:01:45 +02:00
raise ::Middleman::TemplateRenderer::TemplateNotFound, "Could not locate partial: #{name}" unless partial_file
2014-01-02 06:19:05 +01:00
2014-08-14 19:14:26 +02:00
source_path = sitemap.file_to_path(partial_file)
r = sitemap.find_resource_by_path(source_path)
2014-01-02 06:19:05 +01:00
2014-08-14 19:14:26 +02:00
if (r && !r.template?) || (Tilt[partial_file[:full_path]].nil? && partial_file[:full_path].exist?)
File.read(partial_file[:full_path])
2014-07-18 21:54:27 +02:00
else
opts = options.dup
locs = opts.delete(:locals)
2014-07-16 03:01:45 +02:00
render_file(partial_file, locs.freeze, opts.freeze, &block)
2014-07-18 21:54:27 +02:00
end
end
2014-01-02 06:19:05 +01:00
2014-07-08 08:43:09 +02:00
# Locate a partial relative to the current path or the source dir, given a partial's path.
#
# @api private
2014-07-08 08:43:09 +02:00
# @param [String] partial_path
# @return [String]
2014-07-16 03:01:45 +02:00
Contract String => Maybe[IsA['Middleman::SourceFile']]
2014-07-08 08:43:09 +02:00
def locate_partial(partial_path)
2014-08-24 23:38:06 +02:00
return unless resource = sitemap.find_resource_by_destination_path(current_path)
# Look for partials relative to the current path
2014-07-16 03:01:45 +02:00
current_dir = resource.source_file[:relative_path].dirname
non_root = partial_path.to_s.sub(/^\//, '')
relative_dir = current_dir + Pathname(non_root)
2014-07-16 03:01:45 +02:00
non_root_no_underscore = non_root.sub(/^_/, '').sub(/\/_/, '/')
relative_dir_no_underscore = current_dir + Pathname(non_root_no_underscore)
2014-01-02 06:19:05 +01:00
2014-07-16 03:01:45 +02:00
partial_file = nil
2014-07-18 21:54:27 +02:00
[
2014-07-16 03:01:45 +02:00
[relative_dir.to_s, { preferred_engine: resource.source_file[:relative_path].extname[1..-1].to_sym }],
[non_root],
2014-08-14 19:14:26 +02:00
[non_root, { try_static: true }],
2014-07-16 03:01:45 +02:00
[relative_dir_no_underscore.to_s, { try_static: true }],
[non_root_no_underscore, { try_static: true }]
2014-07-18 21:54:27 +02:00
].each do |args|
2014-07-16 03:01:45 +02:00
partial_file = ::Middleman::TemplateRenderer.resolve_template(@app, *args)
break if partial_file
2014-07-18 21:54:27 +02:00
end
2014-07-08 08:43:09 +02:00
2014-08-14 19:14:26 +02:00
partial_file || nil
end
2014-08-14 19:14:26 +02:00
protected
# Render a path with locs, opts and contents block.
#
# @api private
2014-07-16 03:01:45 +02:00
# @param [Middleman::SourceFile] file The file.
# @param [Hash] locs Template locals.
# @param [Hash] opts Template options.
# @param [Proc] block A block will be evaluated to return internal contents.
# @return [String] The resulting content string.
2014-07-16 03:01:45 +02:00
Contract IsA['Middleman::SourceFile'], Hash, Hash, Proc => String
def render_file(file, locs, opts, &block)
file_renderer = ::Middleman::FileRenderer.new(@app, file[:relative_path].to_s)
file_renderer.render(locs, opts, self, &block)
2014-01-02 06:19:05 +01:00
end
def current_path
@locs[:current_path]
end
# Get the resource object for the current path
# @return [Middleman::Sitemap::Resource]
def current_resource
return nil unless current_path
sitemap.find_resource_by_destination_path(current_path)
end
alias_method :current_page, :current_resource
2014-01-02 06:19:05 +01:00
end
end