class Tilt::Template
methods.
the #prepare method and one of the #evaluate or #precompiled_template
Base class for template implementations. Subclasses must implement
def self.evaluate(scope, locals, &block)
def self.evaluate(scope, locals, &block) method = compiled_method(locals.keys) method.bind(scope).call(locals, &block) end
def basename(suffix='')
def basename(suffix='') File.basename(file, suffix) if file end
def compile_template_method(locals)
def compile_template_method(locals) source, offset = precompiled(locals) offset += 5 method_name = "__tilt_#{Thread.current.object_id.abs}" Object.class_eval <<-RUBY, eval_file, line - offset #{extract_magic_comment source} TOPOBJECT.class_eval do def #{method_name}(locals) Thread.current[:tilt_vars] = [self, locals] class << self this, locals = Thread.current[:tilt_vars] this.instance_eval do #{source} end end end end RUBY unbind_compiled_method(method_name) end
def compile_template_method(locals)
def compile_template_method(locals) source, offset = precompiled(locals) offset += 1 method_name = "__tilt_#{Thread.current.object_id}" Object.class_eval <<-RUBY, eval_file, line - offset TOPOBJECT.class_eval do def #{method_name}(locals) #{source} end end RUBY unbind_compiled_method(method_name) end
def compiled_method(locals_keys)
def compiled_method(locals_keys) @compiled_method[locals_keys] ||= compile_template_method(locals_keys) end
def eval_file
def eval_file file || '(__TEMPLATE__)' end
def evaluate(scope, locals, &block)
template executation is guaranteed to be performed in the scope object
unbound method which will lead to better performance. In any case,
On the sequential method calls it will compile the template to an
method is called, the template source is evaluated with instance_eval.
Process the template and return the result. The first time this
def evaluate(scope, locals, &block) # Redefine itself to use method compilation the next time: def self.evaluate(scope, locals, &block) method = compiled_method(locals.keys) method.bind(scope).call(locals, &block) end # Use instance_eval the first time: evaluate_source(scope, locals, &block) 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 extract_magic_comment(script)
def extract_magic_comment(script) comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/) return comment if comment and not %w[ascii-8bit binary].include?($1.downcase) "# coding: #{@default_encoding}" if @default_encoding 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.dup 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 hold compiled template methods @compiled_method = {} # used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment) # currently only used if template compiles to ruby @default_encoding = @options.delete :default_encoding # load template data and prepare (uses binread to avoid encoding issues) @reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : 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) template = precompiled_template(locals) magic_comment = extract_magic_comment(template) if magic_comment # Magic comment e.g. "# coding: utf-8" has to be in the first line. # So we copy the magic comment to the first line. preamble = magic_comment + "\n" + preamble end parts = [ preamble, template, 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
def unbind_compiled_method(method_name)
def unbind_compiled_method(method_name) method = TOPOBJECT.instance_method(method_name) TOPOBJECT.class_eval { remove_method(method_name) } method end