class Tilt::Template
methods.
the #prepare method and one of the #evaluate or #template_source
Base class for template implementations. Subclasses must implement
def self.compiled_template_method_remover(site, method_name)
def self.compiled_template_method_remover(site, method_name) proc { |oid| garbage_collect_compiled_template_method(site, method_name) } end
def self.garbage_collect_compiled_template_method(site, method_name)
def self.garbage_collect_compiled_template_method(site, method_name) site.module_eval do begin remove_method(method_name) rescue NameError # method was already removed (ruby >= 1.9) end end end
def basename(suffix='')
def basename(suffix='') File.basename(file, suffix) if file end
def compile_template_method(method_name, locals)
def compile_template_method(method_name, locals) source, offset = precompiled(locals) offset += 1 CompileSite.module_eval <<-RUBY, eval_file, line - offset def #{method_name}(locals) #{source} end RUBY ObjectSpace.define_finalizer self, Template.compiled_template_method_remover(CompileSite, method_name) end
def compiled_method_name(locals_keys)
def compiled_method_name(locals_keys) @compiled_method_names[locals_keys] ||= generate_compiled_method_name(locals_keys) end
def eval_file
def eval_file file || '(__TEMPLATE__)' end
def evaluate(scope, locals, &block)
is guaranteed to be performed in the scope object with the locals
evaluated with instance_eval. In any case, template executation
does not mix in the CompileSite module, the template source is
reused given identical locals keys. When the scope object
the Tilt::CompileSite module, the template is compiled to a method and
Process the template and return the result. When the scope mixes in
def evaluate(scope, locals, &block) if scope.respond_to?(:__tilt__) method_name = compiled_method_name(locals.keys) if scope.respond_to?(method_name) scope.send(method_name, locals, &block) else compile_template_method(method_name, locals) scope.send(method_name, locals, &block) end else evaluate_source(scope, locals, &block) end end
def evaluate_source(scope, locals, &block)
def evaluate_source(scope, locals, &block) source, offset = precompiled(locals) scope.instance_eval(source, eval_file, line - offset) end
def evaluate_source(scope, locals, &block)
def evaluate_source(scope, locals, &block) source, offset = precompiled(locals) file, lineno = eval_file, (line - offset) - 1 scope.instance_eval { Kernel::eval(source, binding, file, lineno) } end
def generate_compiled_method_name(locals_keys)
def generate_compiled_method_name(locals_keys) parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort digest = Digest::MD5.hexdigest(parts.join(':')) "__tilt_#{digest}" end
def initialize(file=nil, line=1, options={}, &block)
a block is required.
it should read template data and return as a String. When file is nil,
default, template data is read from the file. When a block is given,
Create a new template with the file, line, and options specified. By
def initialize(file=nil, line=1, options={}, &block) @file, @line, @options = nil, 1, {} [options, line, file].compact.each do |arg| case when arg.respond_to?(:to_str) ; @file = arg.to_str when arg.respond_to?(:to_int) ; @line = arg.to_int when arg.respond_to?(:to_hash) ; @options = arg.to_hash else raise TypeError end end raise ArgumentError, "file or block required" if (@file || block).nil? # call the initialize_engine method if this is the very first time # an instance of this class has been created. if !self.class.engine_initialized? initialize_engine self.class.engine_initialized = true end # used to generate unique method names for template compilation @stamp = (Time.now.to_f * 10000).to_i @compiled_method_names = {} # load template data and prepare @reader = block || lambda { |t| File.read(@file) } @data = @reader.call(self) prepare end
def initialize_engine
the template class is initialized. This should be used to require the
Called once and only once for each template subclass the first time
def initialize_engine end
def name
def name basename.split('.', 2).first if basename end
def precompiled(locals)
offset. In most cases, overriding the #precompiled_template method is
control over source generation or want to adjust the default line
Template subclasses may override this method when they need complete
offset is the integer line offset where line reporting should begin.
source is the string containing (Ruby) source code for the template and
postamble and returns a two-tuple of the form: [source, offset], where
Generates all template source by combining the preamble, template, and
def precompiled(locals) preamble = precompiled_preamble(locals) parts = [ preamble, precompiled_template(locals), precompiled_postamble(locals) ] [parts.join("\n"), preamble.count("\n") + 1] end
def precompiled_postamble(locals)
string returned from this method is appended to the precompiled
Generates postamble code for the precompiled template source. The
def precompiled_postamble(locals) '' end
def precompiled_preamble(locals)
source line offset, so adding code to the preamble does not effect line
assignment only. Lines included in the preamble are subtracted from the
locals assignment. The default implementation performs locals
Generates preamble code for initializing template state, and performing
def precompiled_preamble(locals) locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n") end
def precompiled_template(locals)
scopes, and support for template compilation when the scope object
Template guarantees correct file/line handling, locals support, custom
or the #precompiled method be overridden. When defined, the base
default Template#evaluate implementation requires either this method
A string containing the (Ruby) source code for the template. The
def precompiled_template(locals) raise NotImplementedError end
def prepare
variables set in this method are available when #evaluate is called.
engine. Called immediately after template data is loaded. Instance
Do whatever preparation is necessary to setup the underlying template
def prepare if respond_to?(:compile!) # backward compat with tilt < 0.6; just in case warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.' compile! else raise NotImplementedError end end
def render(scope=Object.new, locals={}, &block)
block is given, it is typically available within the template via
Render the template in the given scope with the locals specified. If a
def render(scope=Object.new, locals={}, &block) evaluate scope, locals || {}, &block end
def require_template_library(name)
Like Kernel::require but issues a warning urging a manual require when
def require_template_library(name) if Thread.list.size > 1 warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " + "explicit require '#{name}' suggested." end require name end