class Tilt::Template

Base class for template implementations. Subclasses must implement the prepare method and one of the evaluate or precompiled_template methods.

Constants

CLASS_METHOD
USE_BIND_CALL

Attributes

compiled_path[R]

A path ending in .rb that the template code will be written to, then required, instead of being evaled. This is useful for determining coverage of compiled template code, or to use static analysis tools on the compiled template code.

data[R]

Template source; loaded from a file or given directly.

default_encoding[R]

The encoding of the source data. Defaults to the default_encoding-option if present. You may override this method in your template class if you have a better hint of the data's encoding.

file[R]

The name of the file where the template data was loaded from.

line[R]

The line number in file where template data was loaded from.

options[R]

A Hash of template engine specific options. This is passed directly to the underlying engine and is not used by the generic template interface.

Public Class Methods

default_mime_type() click to toggle source

Use `.metadata` instead.

   # File lib/tilt/template.rb
45 def default_mime_type
46   metadata[:mime_type]
47 end
default_mime_type=(value) click to toggle source

Use `.metadata = val` instead.

   # File lib/tilt/template.rb
50 def default_mime_type=(value)
51   metadata[:mime_type] = value
52 end
metadata() click to toggle source

An empty Hash that the template engine can populate with various metadata.

   # File lib/tilt/template.rb
40 def metadata
41   @metadata ||= {}
42 end
new(file=nil, line=nil, options=nil) { |self| ... } click to toggle source

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
61 def initialize(file=nil, line=nil, options=nil)
62   @file, @line, @options = nil, 1, nil
63 
64   process_arg(options)
65   process_arg(line)
66   process_arg(file)
67 
68   raise ArgumentError, "file or block required" unless @file || block_given?
69 
70   @options ||= {}
71 
72   set_compiled_method_cache
73 
74   # Force the encoding of the input data
75   @default_encoding = @options.delete :default_encoding
76 
77   # Skip encoding detection from magic comments and forcing that encoding
78   # for compiled templates
79   @skip_compiled_encoding_detection = @options.delete :skip_compiled_encoding_detection
80 
81   # load template data and prepare (uses binread to avoid encoding issues)
82   @data = block_given? ? yield(self) : read_template_file
83 
84   if @data.respond_to?(:force_encoding)
85     if default_encoding
86       @data = @data.dup if @data.frozen?
87       @data.force_encoding(default_encoding)
88     end
89 
90     if !@data.valid_encoding?
91       raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}"
92     end
93   end
94 
95   prepare
96 end

Public Instance Methods

basename(suffix='') click to toggle source

The basename of the template file.

    # File lib/tilt/template.rb
110 def basename(suffix='')
111   File.basename(@file, suffix) if @file
112 end
compiled_method(locals_keys, scope_class=nil) click to toggle source

The compiled method for the locals keys and scope_class provided. Returns an UnboundMethod, which can be used to define methods directly on the scope class, which are much faster to call than Tilt's normal rendering.

    # File lib/tilt/template.rb
151 def compiled_method(locals_keys, scope_class=nil)
152   key = [scope_class, locals_keys].freeze
153   LOCK.synchronize do
154     if meth = @compiled_method[key]
155       return meth
156     end
157   end
158   meth = compile_template_method(locals_keys, scope_class)
159   LOCK.synchronize do
160     @compiled_method[key] = meth
161   end
162   meth
163 end
compiled_path=(path) click to toggle source

Set the prefix to use for compiled paths.

    # File lib/tilt/template.rb
137 def compiled_path=(path)
138   if path
139     # Use expanded paths when loading, since that is helpful
140     # for coverage.  Remove any .rb suffix, since that will
141     # be added back later.
142     path = File.expand_path(path.sub(/\.rb\z/i, ''))
143   end
144   @compiled_path = path
145 end
eval_file() click to toggle source

The filename used in backtraces to describe the template.

    # File lib/tilt/template.rb
122 def eval_file
123   @file || '(__TEMPLATE__)'
124 end
metadata() click to toggle source

An empty Hash that the template engine can populate with various metadata.

    # File lib/tilt/template.rb
128 def metadata
129   if respond_to?(:allows_script?)
130     self.class.metadata.merge(:allows_script => allows_script?)
131   else
132     self.class.metadata
133   end
134 end
name() click to toggle source

The template file's basename with all extensions chomped off.

    # File lib/tilt/template.rb
115 def name
116   if bname = basename
117     bname.split('.', 2).first
118   end
119 end
render(scope=nil, locals=nil, &block) click to toggle source

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
101 def render(scope=nil, locals=nil, &block)
102   current_template = Thread.current[:tilt_current_template]
103   Thread.current[:tilt_current_template] = self
104   evaluate(scope || Object.new, locals || EMPTY_HASH, &block)
105 ensure
106   Thread.current[:tilt_current_template] = current_template
107 end

Protected Instance Methods

evaluate(scope, locals, &block) click to toggle source

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
196 def evaluate(scope, locals, &block)
197   locals_keys = locals.keys
198   locals_keys.sort!{|x, y| x.to_s <=> y.to_s}
199 
200   case scope
201   when Object
202     scope_class = Module === scope ? scope : scope.class
203   else
204     # :nocov:
205     scope_class = USE_BIND_CALL ? CLASS_METHOD.bind_call(scope) : CLASS_METHOD.bind(scope).call
206     # :nocov:
207   end
208   method = compiled_method(locals_keys, scope_class)
209 
210   if USE_BIND_CALL
211     method.bind_call(scope, locals, &block)
212   # :nocov:
213   else
214     method.bind(scope).call(locals, &block)
215   # :nocov:
216   end
217 end
precompiled(local_keys) click to toggle source

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
228 def precompiled(local_keys)
229   preamble = precompiled_preamble(local_keys)
230   template = precompiled_template(local_keys)
231   postamble = precompiled_postamble(local_keys)
232   source = String.new
233 
234   unless skip_compiled_encoding_detection?
235     # Ensure that our generated source code has the same encoding as the
236     # the source code generated by the template engine.
237     template_encoding = extract_encoding(template){|t| template = t}
238 
239     if template.encoding != template_encoding
240       # template should never be frozen here. If it was frozen originally,
241       # then extract_encoding should yield a dup.
242       template.force_encoding(template_encoding)
243     end
244   end
245 
246   source.force_encoding(template.encoding)
247   source << preamble << "\n" << template << "\n" << postamble
248 
249   [source, preamble.count("\n")+1]
250 end
precompiled_postamble(local_keys) click to toggle source
    # File lib/tilt/template.rb
266 def precompiled_postamble(local_keys)
267   ''
268 end
precompiled_preamble(local_keys) click to toggle source
    # File lib/tilt/template.rb
262 def precompiled_preamble(local_keys)
263   ''
264 end
precompiled_template(local_keys) click to toggle source

A string containing the (Ruby) source code for the template. The default Template#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, proper encoding, and support for template compilation.

    # File lib/tilt/template.rb
258 def precompiled_template(local_keys)
259   raise NotImplementedError
260 end
prepare() click to toggle source

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.

Empty by default as some subclasses do not need separate preparation.

    # File lib/tilt/template.rb
184 def prepare
185 end
skip_compiled_encoding_detection?() click to toggle source
    # File lib/tilt/template.rb
175 def skip_compiled_encoding_detection?
176   @skip_compiled_encoding_detection
177 end

Private Instance Methods

binary(string) { || ... } click to toggle source
    # File lib/tilt/template.rb
407 def binary(string)
408   original_encoding = string.encoding
409   string.force_encoding(Encoding::BINARY)
410   yield
411 ensure
412   string.force_encoding(original_encoding)
413 end
bind_compiled_method(method_source, offset, scope_class) click to toggle source
    # File lib/tilt/template.rb
345 def bind_compiled_method(method_source, offset, scope_class)
346   path = compiled_path
347   if path && scope_class.name
348     path = path.dup
349 
350     if defined?(@compiled_path_counter)
351       path << '-' << @compiled_path_counter.succ!
352     else
353       @compiled_path_counter = "0".dup
354     end
355     path << ".rb"
356 
357     # Wrap method source in a class block for the scope, so constant lookup works
358     method_source = "class #{scope_class.name}\n#{method_source}\nend"
359 
360     load_compiled_method(path, method_source)
361   else
362     if path
363       warn "compiled_path (#{compiled_path.inspect}) ignored on template with anonymous scope_class (#{scope_class.inspect})"
364     end
365 
366     eval_compiled_method(method_source, offset, scope_class)
367   end
368 end
compile_template_method(local_keys, scope_class=nil) click to toggle source
    # File lib/tilt/template.rb
322 def compile_template_method(local_keys, scope_class=nil)
323   source, offset = precompiled(local_keys)
324   local_code = local_extraction(local_keys)
325 
326   method_name = "__tilt_#{Thread.current.object_id.abs}"
327   method_source = String.new
328   method_source.force_encoding(source.encoding)
329 
330   if freeze_string_literals?
331     method_source << "# frozen-string-literal: true\n"
332   end
333 
334   # Don't indent method source, to avoid indentation warnings when using compiled paths
335   method_source << "::Tilt::TOPOBJECT.class_eval do\ndef #{method_name}(locals)\n#{local_code}\n"
336 
337   offset += method_source.count("\n")
338   method_source << source
339   method_source << "\nend;end;"
340 
341   bind_compiled_method(method_source, offset, scope_class)
342   unbind_compiled_method(method_name)
343 end
eval_compiled_method(method_source, offset, scope_class) click to toggle source
    # File lib/tilt/template.rb
370 def eval_compiled_method(method_source, offset, scope_class)
371   (scope_class || Object).class_eval(method_source, eval_file, line - offset)
372 end
extract_encoding(script, &block) click to toggle source
    # File lib/tilt/template.rb
388 def extract_encoding(script, &block)
389   extract_magic_comment(script, &block) || script.encoding
390 end
extract_magic_comment(script) { |script| ... } click to toggle source
    # File lib/tilt/template.rb
392 def extract_magic_comment(script)
393   if script.frozen?
394     script = script.dup
395     yield script
396   end
397 
398   binary(script) do
399     script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
400   end
401 end
freeze_string_literals?() click to toggle source
    # File lib/tilt/template.rb
403 def freeze_string_literals?
404   false
405 end
load_compiled_method(path, method_source) click to toggle source
    # File lib/tilt/template.rb
374 def load_compiled_method(path, method_source)
375   File.binwrite(path, method_source)
376 
377   # Use load and not require, so unbind_compiled_method does not
378   # break if the same path is used more than once.
379   load path
380 end
local_extraction(local_keys) click to toggle source
    # File lib/tilt/template.rb
301 def local_extraction(local_keys)
302   assignments = local_keys.map do |k|
303     if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/
304       "#{k} = locals[#{k.inspect}]"
305     else
306       raise "invalid locals key: #{k.inspect} (keys must be variable names)"
307     end
308   end
309 
310   s = "locals = locals[:locals]"
311   if assignments.delete(s)
312     # If there is a locals key itself named `locals`, delete it from the ordered keys so we can
313     # assign it last. This is important because the assignment of all other locals depends on the
314     # `locals` local variable still matching the `locals` method argument given to the method
315     # created in `#compile_template_method`.
316     assignments << s
317   end
318 
319   assignments.join("\n")
320 end
process_arg(arg) click to toggle source

!@endgroup

    # File lib/tilt/template.rb
274 def process_arg(arg)
275   if arg
276     case
277     when arg.respond_to?(:to_str)  ; @file = arg.to_str
278     when arg.respond_to?(:to_int)  ; @line = arg.to_int
279     when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
280     when arg.respond_to?(:path)    ; @file = arg.path
281     when arg.respond_to?(:to_path) ; @file = arg.to_path
282     else raise TypeError, "Can't load the template file. Pass a string with a path " +
283       "or an object that responds to 'to_str', 'path' or 'to_path'"
284     end
285   end
286 end
read_template_file() click to toggle source
    # File lib/tilt/template.rb
288 def read_template_file
289   data = File.binread(file)
290   # Set it to the default external (without verifying)
291   # :nocov:
292   data.force_encoding(Encoding.default_external) if Encoding.default_external
293   # :nocov:
294   data
295 end
set_compiled_method_cache() click to toggle source
    # File lib/tilt/template.rb
297 def set_compiled_method_cache
298   @compiled_method = {}
299 end
unbind_compiled_method(method_name) click to toggle source
    # File lib/tilt/template.rb
382 def unbind_compiled_method(method_name)
383   method = TOPOBJECT.instance_method(method_name)
384   TOPOBJECT.class_eval { remove_method(method_name) }
385   method
386 end