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.
Template
source; loaded from a file or given directly.
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.
The line number in file
where 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
Use ‘.metadata` instead.
# File lib/tilt/template.rb 45 def default_mime_type 46 metadata[:mime_type] 47 end
Use ‘.metadata = val` instead.
# File lib/tilt/template.rb 50 def default_mime_type=(value) 51 metadata[:mime_type] = value 52 end
An empty Hash that the template engine can populate with various metadata.
# File lib/tilt/template.rb 40 def metadata 41 @metadata ||= {} 42 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.
# 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
The basename of the template file.
# File lib/tilt/template.rb 110 def basename(suffix='') 111 File.basename(@file, suffix) if @file 112 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.
# 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
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
The filename used in backtraces to describe the template.
# File lib/tilt/template.rb 122 def eval_file 123 @file || '(__TEMPLATE__)' 124 end
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
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 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
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
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
# File lib/tilt/template.rb 266 def precompiled_postamble(local_keys) 267 '' 268 end
# File lib/tilt/template.rb 262 def precompiled_preamble(local_keys) 263 '' 264 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.
# File lib/tilt/template.rb 258 def precompiled_template(local_keys) 259 raise NotImplementedError 260 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.
# File lib/tilt/template.rb 184 def prepare 185 end
# File lib/tilt/template.rb 175 def skip_compiled_encoding_detection? 176 @skip_compiled_encoding_detection 177 end
Private Instance Methods
# 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
# 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
# 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
# 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
# File lib/tilt/template.rb 388 def extract_encoding(script, &block) 389 extract_magic_comment(script, &block) || script.encoding 390 end
# 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
# File lib/tilt/template.rb 403 def freeze_string_literals? 404 false 405 end
# 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
# 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
!@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
# 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
# File lib/tilt/template.rb 297 def set_compiled_method_cache 298 @compiled_method = {} 299 end
# 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