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
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

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
341 def bind_compiled_method(method_source, offset, scope_class)
342   path = compiled_path
343   if path && scope_class.name
344     path = path.dup
345 
346     if defined?(@compiled_path_counter)
347       path << '-' << @compiled_path_counter.succ!
348     else
349       @compiled_path_counter = "0".dup
350     end
351     path << ".rb"
352 
353     # Wrap method source in a class block for the scope, so constant lookup works
354     if freeze_string_literals?
355       method_source_prefix = "# frozen-string-literal: true\n"
356       method_source = method_source.sub(/\A# frozen-string-literal: true\n/, '')
357     end
358     method_source = "#{method_source_prefix}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
318 def compile_template_method(local_keys, scope_class=nil)
319   source, offset = precompiled(local_keys)
320   local_code = local_extraction(local_keys)
321 
322   method_name = "__tilt_#{Thread.current.object_id.abs}"
323   method_source = String.new
324   method_source.force_encoding(source.encoding)
325 
326   if freeze_string_literals?
327     method_source << "# frozen-string-literal: true\n"
328   end
329 
330   # Don't indent method source, to avoid indentation warnings when using compiled paths
331   method_source << "::Tilt::TOPOBJECT.class_eval do\ndef #{method_name}(locals)\n#{local_code}\n"
332 
333   offset += method_source.count("\n")
334   method_source << source
335   method_source << "\nend;end;"
336 
337   bind_compiled_method(method_source, offset, scope_class)
338   unbind_compiled_method(method_name)
339 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
297 def local_extraction(local_keys)
298   assignments = local_keys.map do |k|
299     if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/
300       "#{k} = locals[#{k.inspect}]"
301     else
302       raise "invalid locals key: #{k.inspect} (keys must be variable names)"
303     end
304   end
305 
306   s = "locals = locals[:locals]"
307   if assignments.delete(s)
308     # If there is a locals key itself named `locals`, delete it from the ordered keys so we can
309     # assign it last. This is important because the assignment of all other locals depends on the
310     # `locals` local variable still matching the `locals` method argument given to the method
311     # created in `#compile_template_method`.
312     assignments << s
313   end
314 
315   assignments.join("\n")
316 end
process_arg(arg) click to toggle source

!@endgroup

    # File lib/tilt/template.rb
270 def process_arg(arg)
271   if arg
272     case
273     when arg.respond_to?(:to_str)  ; @file = arg.to_str
274     when arg.respond_to?(:to_int)  ; @line = arg.to_int
275     when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
276     when arg.respond_to?(:path)    ; @file = arg.path
277     when arg.respond_to?(:to_path) ; @file = arg.to_path
278     else raise TypeError, "Can't load the template file. Pass a string with a path " +
279       "or an object that responds to 'to_str', 'path' or 'to_path'"
280     end
281   end
282 end
read_template_file() click to toggle source
    # File lib/tilt/template.rb
284 def read_template_file
285   data = File.binread(file)
286   # Set it to the default external (without verifying)
287   # :nocov:
288   data.force_encoding(Encoding.default_external) if Encoding.default_external
289   # :nocov:
290   data
291 end
set_compiled_method_cache() click to toggle source
    # File lib/tilt/template.rb
293 def set_compiled_method_cache
294   @compiled_method = {}
295 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