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
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.
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.
The name of the file where the 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.
Public Class Methods
Source
# File lib/tilt/template.rb 45 def default_mime_type 46 metadata[:mime_type] 47 end
Use ‘.metadata` instead.
Source
# File lib/tilt/template.rb 50 def default_mime_type=(value) 51 metadata[:mime_type] = value 52 end
Use ‘.metadata = val` instead.
Source
# File lib/tilt/template.rb 40 def metadata 41 @metadata ||= {} 42 end
An empty Hash that the template engine can populate with various metadata.
Source
# 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
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.
Public Instance Methods
Source
# File lib/tilt/template.rb 138 def basename(suffix='') 139 File.basename(@file, suffix) if @file 140 end
The basename of the template file.
Source
# 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
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.
Source
# 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
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.
Source
# File lib/tilt/template.rb 150 def eval_file 151 @file || '(__TEMPLATE__)' 152 end
The filename used in backtraces to describe the template.
Source
# File lib/tilt/template.rb 155 def fixed_locals? 156 @fixed_locals ? true : false 157 end
Whether the template uses fixed locals.
Source
# 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
An empty Hash that the template engine can populate with various metadata.
Source
# File lib/tilt/template.rb 143 def name 144 if bname = basename 145 bname.split('.', 2).first 146 end 147 end
The template file’s basename with all extensions chomped off.
Source
# File lib/tilt/template.rb 133 def render(scope=nil, locals=nil, &block) 134 evaluate(scope || Object.new, locals || EMPTY_HASH, &block) 135 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
.
Protected Instance Methods
Source
# 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
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.
Source
# 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
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.
Source
# File lib/tilt/template.rb 316 def precompiled_postamble(local_keys) 317 '' 318 end
Source
# File lib/tilt/template.rb 312 def precompiled_preamble(local_keys) 313 '' 314 end
Source
# File lib/tilt/template.rb 308 def precompiled_template(local_keys) 309 raise NotImplementedError 310 end
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.
Source
# File lib/tilt/template.rb 235 def prepare 236 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.
Empty by default as some subclasses do not need separate preparation.
Source
# File lib/tilt/template.rb 226 def skip_compiled_encoding_detection? 227 @skip_compiled_encoding_detection 228 end
Private Instance Methods
Source
# File lib/tilt/template.rb 325 def _dup_string_if_frozen(string) 326 +string 327 end
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
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
Source
# 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
:nocov:
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
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
Source
# File lib/tilt/template.rb 523 def extract_encoding(script, &block) 524 extract_magic_comment(script, &block) || script.encoding 525 end
Source
# 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 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.
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
Source
# File lib/tilt/template.rb 540 def freeze_string_literals? 541 false 542 end
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
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
Source
# 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
:nocov:
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
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
Source
# 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
Set the fixed locals for the template, which may be nil if no fixed locals can be determined.
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