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. The following options are respected and are used by Tilt::Template itself and not the underlying template libraries:

:default_encoding

Force the encoding of the template to the given encoding.

:skip_compiled_encoding_detection

Do not scan template code for an encoding magic comment.

:fixed_locals

Force a specific method parameter signature, and call the method with a splat of locals, instead of passing the locals hash as a positional argument, and extracting locals from that. Should be a string containing the parameters for the compiled method, surrounded by parentheses. Can be set to false to disable the scan for embedded fixed locals.

:extract_fixed_locals

Whether embedded fixed locals should be scanned for and extracted from the template code.

:default_fixed_locals

Similar to fixed_locals, but lowest priority, only used if :fixed_locals is not provided and no embedded locals are found (or scanned for).

:scope_class

Force the scope class used for the method. By default, uses the class of the scope provided to render.

    # File lib/tilt/template.rb
 82 def initialize(file=nil, line=nil, options=nil)
 83   @file, @line, @options = nil, 1, nil
 84 
 85   process_arg(options)
 86   process_arg(line)
 87   process_arg(file)
 88 
 89   raise ArgumentError, "file or block required" unless @file || block_given?
 90 
 91   @options ||= {}
 92 
 93   # Force a specific scope class, instead of using the class of the provided
 94   # scope as the scope class.
 95   @scope_class = @options.delete :scope_class
 96 
 97   # Force the encoding of the input data
 98   @default_encoding = @options.delete :default_encoding
 99 
100   # Skip encoding detection from magic comments and forcing that encoding
101   # for compiled templates
102   @skip_compiled_encoding_detection = @options.delete :skip_compiled_encoding_detection
103 
104   # Compiled path to use.  This must be specified as an option if
105   # providing the :scope_class option and using fixed locals,
106   # since template compilation occurs during initialization in that case.
107   if compiled_path = @options.delete(:compiled_path)
108     self.compiled_path = compiled_path
109   end
110 
111   # load template data and prepare (uses binread to avoid encoding issues)
112   @data = block_given? ? yield(self) : read_template_file
113 
114   if @data.respond_to?(:force_encoding)
115     if default_encoding
116       @data = _dup_string_if_frozen(@data)
117       @data.force_encoding(default_encoding)
118     end
119 
120     if !@data.valid_encoding?
121       raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}"
122     end
123   end
124 
125   set_fixed_locals
126   prepare
127   set_compiled_method_cache
128 end

Public Instance Methods

basename(suffix='') click to toggle source

The basename of the template file.

    # File lib/tilt/template.rb
138 def basename(suffix='')
139   File.basename(@file, suffix) if @file
140 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
191 def compiled_method(locals_keys, scope_class=nil)
192   if @fixed_locals
193     if @scope_class
194       return @compiled_method
195     else
196       key = scope_class
197     end
198   elsif @scope_class
199     key = locals_keys.dup.freeze
200   else
201     key = [scope_class, locals_keys].freeze
202   end
203 
204   LOCK.synchronize do
205     if meth = @compiled_method[key]
206       return meth
207     end
208   end
209   meth = compile_template_method(locals_keys, scope_class)
210   LOCK.synchronize do
211     @compiled_method[key] = meth
212   end
213   meth
214 end
compiled_path=(path) click to toggle source

Set the prefix to use for compiled paths, similar to using the :compiled_path template option. Note that this only has affect for future template compilations. When using the :scope_class template option, and using fixed_locals, calling this after the template is created has no effect, since the template is compiled during initialization in that case. It is recommended to use the :compiled_path template option instead of this method in new code.

    # File lib/tilt/template.rb
177 def compiled_path=(path)
178   if path
179     # Use expanded paths when loading, since that is helpful
180     # for coverage.  Remove any .rb suffix, since that will
181     # be added back later.
182     path = File.expand_path(path.sub(/\.rb\z/i, ''))
183   end
184   @compiled_path = path
185 end
eval_file() click to toggle source

The filename used in backtraces to describe the template.

    # File lib/tilt/template.rb
150 def eval_file
151   @file || '(__TEMPLATE__)'
152 end
fixed_locals?() click to toggle source

Whether the template uses fixed locals.

    # File lib/tilt/template.rb
155 def fixed_locals?
156   @fixed_locals ? true : false
157 end
metadata() click to toggle source

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

    # File lib/tilt/template.rb
161 def metadata
162   if respond_to?(:allows_script?)
163     self.class.metadata.merge(:allows_script => allows_script?)
164   else
165     self.class.metadata
166   end
167 end
name() click to toggle source

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

    # File lib/tilt/template.rb
143 def name
144   if bname = basename
145     bname.split('.', 2).first
146   end
147 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
133 def render(scope=nil, locals=nil, &block)
134   evaluate(scope || Object.new, locals || EMPTY_HASH, &block)
135 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
247 def evaluate(scope, locals, &block)
248   if @fixed_locals
249     locals_keys = EMPTY_ARRAY
250   else
251     locals_keys = locals.keys
252     locals_keys.sort!{|x, y| x.to_s <=> y.to_s}
253   end
254 
255   unless scope_class = @scope_class
256     scope_class = case scope
257     when Object
258       Module === scope ? scope : scope.class
259     else
260       # :nocov:
261       USE_BIND_CALL ? CLASS_METHOD.bind_call(scope) : CLASS_METHOD.bind(scope).call
262       # :nocov:
263     end
264   end
265 
266   evaluate_method(compiled_method(locals_keys, scope_class), scope, locals, &block)
267 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
278 def precompiled(local_keys)
279   preamble = precompiled_preamble(local_keys)
280   template = precompiled_template(local_keys)
281   postamble = precompiled_postamble(local_keys)
282   source = String.new
283 
284   unless skip_compiled_encoding_detection?
285     # Ensure that our generated source code has the same encoding as the
286     # the source code generated by the template engine.
287     template_encoding = extract_encoding(template){|t| template = t}
288 
289     if template.encoding != template_encoding
290       # template should never be frozen here. If it was frozen originally,
291       # then extract_encoding should yield a dup.
292       template.force_encoding(template_encoding)
293     end
294   end
295 
296   source.force_encoding(template.encoding)
297   source << preamble << "\n" << template << "\n" << postamble
298 
299   [source, preamble.count("\n")+1]
300 end
precompiled_postamble(local_keys) click to toggle source
    # File lib/tilt/template.rb
316 def precompiled_postamble(local_keys)
317   ''
318 end
precompiled_preamble(local_keys) click to toggle source
    # File lib/tilt/template.rb
312 def precompiled_preamble(local_keys)
313   ''
314 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
308 def precompiled_template(local_keys)
309   raise NotImplementedError
310 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
235 def prepare
236 end
skip_compiled_encoding_detection?() click to toggle source
    # File lib/tilt/template.rb
226 def skip_compiled_encoding_detection?
227   @skip_compiled_encoding_detection
228 end

Private Instance Methods

_dup_string_if_frozen(string) click to toggle source
    # File lib/tilt/template.rb
325 def _dup_string_if_frozen(string)
326   +string
327 end
binary(string) { || ... } click to toggle source
    # File lib/tilt/template.rb
544 def binary(string)
545   original_encoding = string.encoding
546   string.force_encoding(Encoding::BINARY)
547   yield
548 ensure
549   string.force_encoding(original_encoding)
550 end
bind_compiled_method(method_source, offset, scope_class) click to toggle source
    # File lib/tilt/template.rb
442 def bind_compiled_method(method_source, offset, scope_class)
443   path = compiled_path
444   if path && scope_class.name
445     path = path.dup
446 
447     if defined?(@compiled_path_counter)
448       path << '-' << @compiled_path_counter.succ!
449     else
450       @compiled_path_counter = "0".dup
451     end
452     path << ".rb"
453 
454     # Wrap method source in a class block for the scope, so constant lookup works
455     if freeze_string_literals?
456       method_source_prefix = "# frozen-string-literal: true\n"
457       method_source = method_source.sub(/\A# frozen-string-literal: true\n/, '')
458     end
459     method_source = "#{method_source_prefix}class #{scope_class.name}\n#{method_source}\nend"
460 
461     load_compiled_method(path, method_source)
462   else
463     if path
464       warn "compiled_path (#{compiled_path.inspect}) ignored on template with anonymous scope_class (#{scope_class.inspect})"
465     end
466 
467     eval_compiled_method(method_source, offset, scope_class)
468   end
469 end
compile_template_method(local_keys, scope_class=nil) click to toggle source

:nocov:

    # File lib/tilt/template.rb
414 def compile_template_method(local_keys, scope_class=nil)
415   source, offset = precompiled(local_keys)
416   if @fixed_locals
417     method_args = @fixed_locals
418   else
419     method_args = "(locals)"
420     local_code = local_extraction(local_keys)
421   end
422 
423   method_name = "__tilt_#{Thread.current.object_id.abs}"
424   method_source = String.new
425   method_source.force_encoding(source.encoding)
426 
427   if freeze_string_literals?
428     method_source << "# frozen-string-literal: true\n"
429   end
430 
431   # Don't indent method source, to avoid indentation warnings when using compiled paths
432   method_source << "::Tilt::TOPOBJECT.class_eval do\ndef #{method_name}#{method_args}\n#{local_code}\n"
433 
434   offset += method_source.count("\n")
435   method_source << source
436   method_source << "\nend;end;"
437 
438   bind_compiled_method(method_source, offset, scope_class)
439   unbind_compiled_method(method_name)
440 end
eval_compiled_method(method_source, offset, scope_class) click to toggle source
    # File lib/tilt/template.rb
471 def eval_compiled_method(method_source, offset, scope_class)
472   (scope_class || Object).class_eval(method_source, eval_file, line - offset)
473 end
evaluate_method(method, scope, locals, &block) click to toggle source
    # File lib/tilt/template.rb
390 def evaluate_method(method, scope, locals, &block)
391   if @fixed_locals
392     method.bind_call(scope, **locals, &block)
393   else
394     method.bind_call(scope, locals, &block)
395   end
396 end
extract_encoding(script, &block) click to toggle source
    # File lib/tilt/template.rb
523 def extract_encoding(script, &block)
524   extract_magic_comment(script, &block) || script.encoding
525 end
extract_fixed_locals() click to toggle source

Extract fixed locals from the template code string. Should return nil if there are no fixed locals specified, or a method argument string surrounded by parentheses if there are fixed locals. The method argument string will be used when defining the template method if given.

    # File lib/tilt/template.rb
517 def extract_fixed_locals
518   if @data.is_a?(String) && (match = /\#\s*locals:\s*(\(.*\))/.match(@data))
519     match[1]
520   end
521 end
extract_magic_comment(script) { |script| ... } click to toggle source
    # File lib/tilt/template.rb
527 def extract_magic_comment(script)
528   was_frozen = script.frozen?
529   script = _dup_string_if_frozen(script)
530 
531   if was_frozen
532     yield script
533   end
534 
535   binary(script) do
536     script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
537   end
538 end
freeze_string_literals?() click to toggle source
    # File lib/tilt/template.rb
540 def freeze_string_literals?
541   false
542 end
load_compiled_method(path, method_source) click to toggle source
    # File lib/tilt/template.rb
475 def load_compiled_method(path, method_source)
476   File.binwrite(path, method_source)
477 
478   # Use load and not require, so unbind_compiled_method does not
479   # break if the same path is used more than once.
480   load path
481 end
local_extraction(local_keys) click to toggle source
    # File lib/tilt/template.rb
368 def local_extraction(local_keys)
369   assignments = local_keys.map do |k|
370     if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/
371       "#{k} = locals[#{k.inspect}]"
372     else
373       raise "invalid locals key: #{k.inspect} (keys must be variable names)"
374     end
375   end
376 
377   s = "locals = locals[:locals]"
378   if assignments.delete(s)
379     # If there is a locals key itself named `locals`, delete it from the ordered keys so we can
380     # assign it last. This is important because the assignment of all other locals depends on the
381     # `locals` local variable still matching the `locals` method argument given to the method
382     # created in `#compile_template_method`.
383     assignments << s
384   end
385 
386   assignments.join("\n")
387 end
process_arg(arg) click to toggle source

:nocov:

    # File lib/tilt/template.rb
336 def process_arg(arg)
337   if arg
338     case
339     when arg.respond_to?(:to_str)  ; @file = arg.to_str
340     when arg.respond_to?(:to_int)  ; @line = arg.to_int
341     when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
342     when arg.respond_to?(:path)    ; @file = arg.path
343     when arg.respond_to?(:to_path) ; @file = arg.to_path
344     else raise TypeError, "Can't load the template file. Pass a string with a path " +
345       "or an object that responds to 'to_str', 'path' or 'to_path'"
346     end
347   end
348 end
read_template_file() click to toggle source
    # File lib/tilt/template.rb
350 def read_template_file
351   data = File.binread(file)
352   # Set it to the default external (without verifying)
353   # :nocov:
354   data.force_encoding(Encoding.default_external) if Encoding.default_external
355   # :nocov:
356   data
357 end
set_compiled_method_cache() click to toggle source
    # File lib/tilt/template.rb
359 def set_compiled_method_cache
360   @compiled_method = if @fixed_locals && @scope_class
361     # No hash needed, only a single compiled method per template.
362     compile_template_method(EMPTY_ARRAY, @scope_class)
363   else
364     {}
365   end
366 end
set_fixed_locals() click to toggle source

Set the fixed locals for the template, which may be nil if no fixed locals can be determined.

    # File lib/tilt/template.rb
491 def set_fixed_locals
492   fixed_locals = @options.delete(:fixed_locals)
493   extract_fixed_locals = @options.delete(:extract_fixed_locals)
494   default_fixed_locals = @options.delete(:default_fixed_locals)
495 
496   if fixed_locals.nil?
497     if extract_fixed_locals.nil?
498       extract_fixed_locals = Tilt.extract_fixed_locals
499     end
500 
501     if extract_fixed_locals
502       fixed_locals = extract_fixed_locals()
503     end
504 
505     if fixed_locals.nil?
506       fixed_locals = default_fixed_locals
507     end
508   end
509 
510   @fixed_locals = fixed_locals
511 end
unbind_compiled_method(method_name) click to toggle source
    # File lib/tilt/template.rb
483 def unbind_compiled_method(method_name)
484   method = TOPOBJECT.instance_method(method_name)
485   TOPOBJECT.class_eval { remove_method(method_name) }
486   method
487 end