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 106 def basename(suffix='') 107 File.basename(@file, suffix) if @file 108 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 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
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
The filename used in backtraces to describe the template.
# File lib/tilt/template.rb 118 def eval_file 119 @file || '(__TEMPLATE__)' 120 end
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
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 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
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
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
# File lib/tilt/template.rb 262 def precompiled_postamble(local_keys) 263 '' 264 end
# File lib/tilt/template.rb 258 def precompiled_preamble(local_keys) 259 '' 260 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 254 def precompiled_template(local_keys) 255 raise NotImplementedError 256 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 180 def prepare 181 end
# File lib/tilt/template.rb 171 def skip_compiled_encoding_detection? 172 @skip_compiled_encoding_detection 173 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 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
# 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
# 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 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
!@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
# 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
# File lib/tilt/template.rb 293 def set_compiled_method_cache 294 @compiled_method = {} 295 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