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 = _dup_string_if_frozen(@data)
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
106 def basename(suffix='')
107   File.basename(@file, suffix) if @file
108 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
147 def compiled_method(locals_keys, scope_class=nil)
148   key = [scope_class, locals_keys].freeze
149   LOCK.synchronize do
150     if meth = @compiled_method[key]
151       return meth
152     end
153   end
154   meth = compile_template_method(locals_keys, scope_class)
155   LOCK.synchronize do
156     @compiled_method[key] = meth
157   end
158   meth
159 end
compiled_path=(path) click to toggle source

Set the prefix to use for compiled paths.

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

The filename used in backtraces to describe the template.

    # File lib/tilt/template.rb
118 def eval_file
119   @file || '(__TEMPLATE__)'
120 end
metadata() click to toggle source

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

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

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

    # File lib/tilt/template.rb
111 def name
112   if bname = basename
113     bname.split('.', 2).first
114   end
115 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   evaluate(scope || Object.new, locals || EMPTY_HASH, &block)
103 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
192 def evaluate(scope, locals, &block)
193   locals_keys = locals.keys
194   locals_keys.sort!{|x, y| x.to_s <=> y.to_s}
195 
196   case scope
197   when Object
198     scope_class = Module === scope ? scope : scope.class
199   else
200     # :nocov:
201     scope_class = USE_BIND_CALL ? CLASS_METHOD.bind_call(scope) : CLASS_METHOD.bind(scope).call
202     # :nocov:
203   end
204   method = compiled_method(locals_keys, scope_class)
205 
206   if USE_BIND_CALL
207     method.bind_call(scope, locals, &block)
208   # :nocov:
209   else
210     method.bind(scope).call(locals, &block)
211   # :nocov:
212   end
213 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
224 def precompiled(local_keys)
225   preamble = precompiled_preamble(local_keys)
226   template = precompiled_template(local_keys)
227   postamble = precompiled_postamble(local_keys)
228   source = String.new
229 
230   unless skip_compiled_encoding_detection?
231     # Ensure that our generated source code has the same encoding as the
232     # the source code generated by the template engine.
233     template_encoding = extract_encoding(template){|t| template = t}
234 
235     if template.encoding != template_encoding
236       # template should never be frozen here. If it was frozen originally,
237       # then extract_encoding should yield a dup.
238       template.force_encoding(template_encoding)
239     end
240   end
241 
242   source.force_encoding(template.encoding)
243   source << preamble << "\n" << template << "\n" << postamble
244 
245   [source, preamble.count("\n")+1]
246 end
precompiled_postamble(local_keys) click to toggle source
    # File lib/tilt/template.rb
262 def precompiled_postamble(local_keys)
263   ''
264 end
precompiled_preamble(local_keys) click to toggle source
    # File lib/tilt/template.rb
258 def precompiled_preamble(local_keys)
259   ''
260 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
254 def precompiled_template(local_keys)
255   raise NotImplementedError
256 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
180 def prepare
181 end
skip_compiled_encoding_detection?() click to toggle source
    # File lib/tilt/template.rb
171 def skip_compiled_encoding_detection?
172   @skip_compiled_encoding_detection
173 end

Private Instance Methods

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

:nocov:

    # File lib/tilt/template.rb
282 def process_arg(arg)
283   if arg
284     case
285     when arg.respond_to?(:to_str)  ; @file = arg.to_str
286     when arg.respond_to?(:to_int)  ; @line = arg.to_int
287     when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
288     when arg.respond_to?(:path)    ; @file = arg.path
289     when arg.respond_to?(:to_path) ; @file = arg.to_path
290     else raise TypeError, "Can't load the template file. Pass a string with a path " +
291       "or an object that responds to 'to_str', 'path' or 'to_path'"
292     end
293   end
294 end
read_template_file() click to toggle source
    # File lib/tilt/template.rb
296 def read_template_file
297   data = File.binread(file)
298   # Set it to the default external (without verifying)
299   # :nocov:
300   data.force_encoding(Encoding.default_external) if Encoding.default_external
301   # :nocov:
302   data
303 end
set_compiled_method_cache() click to toggle source
    # File lib/tilt/template.rb
305 def set_compiled_method_cache
306   @compiled_method = {}
307 end
unbind_compiled_method(method_name) click to toggle source
    # File lib/tilt/template.rb
394 def unbind_compiled_method(method_name)
395   method = TOPOBJECT.instance_method(method_name)
396   TOPOBJECT.class_eval { remove_method(method_name) }
397   method
398 end