2014-05-25 04:59:21 +02:00
require 'pathname'
2014-01-03 22:15:02 +01:00
require 'middleman-core/file_renderer'
require 'middleman-core/template_renderer'
2014-07-03 04:04:34 +02:00
require 'middleman-core/contracts'
2014-01-03 22:15:02 +01:00
2014-01-02 06:19:05 +01:00
module Middleman
2014-05-25 04:59:21 +02:00
# 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
2014-07-05 20:17:41 +02:00
extend Forwardable
2014-07-03 04:04:34 +02:00
include Contracts
2014-07-05 20:17:41 +02:00
2014-05-25 04:59:21 +02:00
# Allow templates to directly access the current app instance.
# @return [Middleman::Application]
2014-01-02 06:19:05 +01:00
attr_reader :app
2014-05-25 04:59:21 +02:00
# Required for Padrino's rendering
2014-01-03 23:56:16 +01:00
attr_accessor :current_engine
2014-01-02 06:19:05 +01:00
2014-05-25 04:59:21 +02: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
2014-05-25 04:59:21 +02: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-11-19 18:04:56 +01:00
def initialize ( app , locs = { } , opts = { } )
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
2014-05-25 04:59:21 +02:00
# 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
2014-05-24 09:22:09 +02:00
@_out_buf , buf_was = '' , @_out_buf
buf_was
2014-01-02 06:19:05 +01:00
end
2014-05-25 04:59:21 +02:00
# Restore a previously saved buffer.
#
# @api private
# @param [String] buf_was
# @return [void]
2014-05-24 09:22:09 +02:00
def restore_buffer ( buf_was )
@_out_buf = buf_was
2014-01-02 06:19:05 +01:00
end
2014-05-25 04:59:21 +02:00
# 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
2014-05-24 09:22:09 +02:00
buf_was = save_buffer
2014-01-02 06:19:05 +01:00
2014-05-25 04:59:21 +02: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
2014-05-25 04:59:21 +02: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)
2015-09-17 18:41:17 +02:00
self . current_engine = engine
engine_was = current_engine
2014-01-02 06:19:05 +01:00
2014-05-25 04:59:21 +02:00
# By default, no content is captured
content = ''
# Attempt to capture HTML from block
2014-01-02 06:19:05 +01:00
begin
2014-05-25 04:59:21 +02:00
content = capture_html ( & block ) if block_given?
2014-01-02 06:19:05 +01:00
ensure
2014-05-25 04:59:21 +02:00
# Reset stored buffer, regardless of success
2014-05-24 09:22:09 +02:00
restore_buffer ( buf_was )
2014-01-02 06:19:05 +01:00
end
2014-05-25 04:59:21 +02:00
# 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
2014-05-25 04:59:21 +02:00
# 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.
#
2014-05-25 04:59:21 +02:00
# @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
2014-05-25 04:59:21 +02:00
def render ( _ , name , options = { } , & block )
name = name . to_s
2014-01-02 06:19:05 +01:00
2015-02-24 20:06:28 +01:00
partial_file = locate_partial ( name , false ) || locate_partial ( name , true )
2014-01-02 06:19:05 +01:00
2014-06-29 00:07:43 +02: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-11-19 18:04:56 +01:00
render_file ( partial_file , locs , opts , & block )
2014-07-18 21:54:27 +02:00
end
2014-05-25 04:59:21 +02:00
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.
2014-05-25 04:59:21 +02:00
#
# @api private
2014-07-08 08:43:09 +02:00
# @param [String] partial_path
2014-05-25 04:59:21 +02:00
# @return [String]
2015-02-24 20:06:28 +01:00
Contract String , Maybe [ Bool ] = > Maybe [ IsA [ 'Middleman::SourceFile' ] ]
def locate_partial ( partial_path , try_static = true )
2014-08-24 23:38:06 +02:00
return unless resource = sitemap . find_resource_by_destination_path ( current_path )
2014-05-25 04:59:21 +02:00
# Look for partials relative to the current path
2015-09-09 20:55:56 +02:00
current_dir = resource . file_descriptor [ :relative_path ] . dirname
2014-07-16 03:01:45 +02:00
non_root = partial_path . to_s . sub ( / ^ \/ / , '' )
relative_dir = current_dir + Pathname ( non_root )
2014-05-25 04:59:21 +02:00
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
[
2015-09-09 20:55:56 +02:00
[ relative_dir . to_s , { preferred_engine : resource . file_descriptor [ :relative_path ] . extname [ 1 .. - 1 ] . to_sym } ] ,
2014-07-16 03:01:45 +02:00
[ non_root ] ,
2015-02-24 20:06:28 +01:00
[ non_root , { try_static : try_static } ] ,
[ relative_dir_no_underscore . to_s , { try_static : try_static } ] ,
[ non_root_no_underscore , { try_static : try_static } ]
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
2014-05-25 04:59:21 +02:00
end
2014-02-23 03:24:40 +01:00
2014-08-14 19:14:26 +02:00
protected
2014-05-25 04:59:21 +02:00
# 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.
2014-05-25 04:59:21 +02:00
# @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.
2015-02-24 20:06:28 +01:00
Contract IsA [ 'Middleman::SourceFile' ] , Hash , Hash , Maybe [ Proc ] = > String
2014-07-16 03:01:45 +02:00
def render_file ( file , locs , opts , & block )
2014-11-19 18:04:56 +01:00
_render_with_all_renderers ( file [ :relative_path ] . to_s , locs , self , opts , & block )
end
2015-02-24 20:06:28 +01:00
Contract String , Hash , Any , Hash , Maybe [ Proc ] = > String
2014-11-19 18:04:56 +01:00
def _render_with_all_renderers ( path , locs , context , opts , & block )
# 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 , & block )
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 #{ @app . config [ :source ] } / #{ @app . config [ :layouts_dir ] } . "
end
end
content
2014-01-02 06:19:05 +01:00
end
2014-07-05 20:17:41 +02:00
2014-07-05 19:42:03 +02:00
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