Base class for template implementations. Subclasses must implement the prepare method and one of the evaluate or precompiled_template methods.
Template source; loaded from a file or given directly.
The name of the file where the template data was loaded from.
The line number in file where template data was loaded from.
A Hash of template engine specific options. This is passed directly to the underlying engine and is not used by the generic template interface.
Create a new template with the file, line, and options specified. By default, template data is read from the file. When a block is given, it should read template data and return as a String. When file is nil, a block is required.
All arguments are optional.
# File lib/tilt/template.rb, line 38 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 when arg.respond_to?(:path) ; @file = arg.path 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
Whether or not this template engine allows executing Ruby script within the
template. If this is false, scope
and locals
will
generally not be used, nor will the provided block be avaiable via
yield
. This should be overridden by template subclasses.
# File lib/tilt/template.rb, line 100 def allows_script? true end
The basename of the template file.
# File lib/tilt/template.rb, line 81 def basename(suffix='') File.basename(file, suffix) if file end
The filename used in backtraces to describe the template.
# File lib/tilt/template.rb, line 91 def eval_file file || '(__TEMPLATE__)' end
The template file's basename with all extensions chomped off.
# File lib/tilt/template.rb, line 86 def name basename.split('.', 2).first if basename end
Render the template in the given scope with the locals specified. If a
block is given, it is typically available within the template via
yield
.
# File lib/tilt/template.rb, line 76 def render(scope=Object.new, locals={}, &block) evaluate scope, locals || {}, &block end
The compiled method for the locals keys provided.
# File lib/tilt/template.rb, line 206 def compiled_method(locals_keys) @compiled_method[locals_keys] ||= compile_template_method(locals_keys) end
Execute the compiled template and return the result string. Template evaluation is guaranteed to be performed in the scope object with the locals specified and with support for yielding to the block.
This method is only used by source generating templates. Subclasses that override render() may not support all features.
# File lib/tilt/template.rb, line 142 def evaluate(scope, locals, &block) method = compiled_method(locals.keys) method.bind(scope).call(locals, &block) end
Called once and only once for each template subclass the first time the template class is initialized. This should be used to require the underlying template library and perform any initial setup.
# File lib/tilt/template.rb, line 108 def initialize_engine end
Generates all template source by combining the preamble, template, and postamble and returns a two-tuple of the form: [source, offset], where source is the string containing (Ruby) source code for the template and offset is the integer line offset where line reporting should begin.
Template subclasses may override this method when they need complete control over source generation or want to adjust the default line offset. In most cases, overriding the precompiled_template method is easier and more appropriate.
# File lib/tilt/template.rb, line 156 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
Generates postamble code for the precompiled template source. The string returned from this method is appended to the precompiled template source.
# File lib/tilt/template.rb, line 201 def precompiled_postamble(locals) '' end
Generates preamble code for initializing template state, and performing locals assignment. The default implementation performs locals assignment only. Lines included in the preamble are subtracted from the source line offset, so adding code to the preamble does not effect line reporting in Kernel::caller and backtraces.
# File lib/tilt/template.rb, line 188 def precompiled_preamble(locals) locals.map do |k,v| if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/ "#{k} = locals[#{k.inspect}]" else raise "invalid locals key: #{k.inspect} (keys must be variable names)" end end.join("\n") end
A string containing the (Ruby) source code for the template. The default #evaluate implementation requires either this method or the precompiled method be overridden. When defined, the base Template guarantees correct file/line handling, locals support, custom scopes, and support for template compilation when the scope object allows it.
# File lib/tilt/template.rb, line 179 def precompiled_template(locals) raise NotImplementedError end
Do whatever preparation is necessary to setup the underlying template engine. Called immediately after template data is loaded. Instance variables set in this method are available when evaluate is called.
Subclasses must provide an implementation of this method.
# File lib/tilt/template.rb, line 126 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
Like Kernel#require but issues a warning urging a manual require when running under a threaded environment.
# File lib/tilt/template.rb, line 113 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
# File lib/tilt/template.rb, line 212 def compile_template_method(locals) source, offset = precompiled(locals) method_name = "__tilt_#{Thread.current.object_id.abs}" method_source = <<-RUBY #{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 RUBY offset += method_source.count("\n") method_source << source method_source << "\nend;end;end;end" Object.class_eval method_source, eval_file, line - offset unbind_compiled_method(method_name) end
# File lib/tilt/template.rb, line 237 def extract_magic_comment(script) comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/) if comment && !%w[ascii-8bit binary].include?($1.downcase) comment elsif @default_encoding "# coding: #{@default_encoding}" end end
# File lib/tilt/template.rb, line 231 def unbind_compiled_method(method_name) method = TOPOBJECT.instance_method(method_name) TOPOBJECT.class_eval { remove_method(method_name) } method end