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
@deprecated Use `.metadata` instead.
# File lib/tilt/template.rb 44 def default_mime_type 45 metadata[:mime_type] 46 end
@deprecated Use `.metadata = val` instead.
# File lib/tilt/template.rb 49 def default_mime_type=(value) 50 metadata[:mime_type] = value 51 end
An empty Hash that the template engine can populate with various metadata.
# File lib/tilt/template.rb 39 def metadata 40 @metadata ||= {} 41 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 60 def initialize(file=nil, line=1, options={}, &block) 61 @file, @line, @options = nil, 1, {} 62 63 [options, line, file].compact.each do |arg| 64 case 65 when arg.respond_to?(:to_str) ; @file = arg.to_str 66 when arg.respond_to?(:to_int) ; @line = arg.to_int 67 when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup 68 when arg.respond_to?(:path) ; @file = arg.path 69 when arg.respond_to?(:to_path) ; @file = arg.to_path 70 else raise TypeError, "Can't load the template file. Pass a string with a path " + 71 "or an object that responds to 'to_str', 'path' or 'to_path'" 72 end 73 end 74 75 raise ArgumentError, "file or block required" if (@file || block).nil? 76 77 # used to hold compiled template methods 78 @compiled_method = {} 79 80 # used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment) 81 # currently only used if template compiles to ruby 82 @default_encoding = @options.delete :default_encoding 83 84 # load template data and prepare (uses binread to avoid encoding issues) 85 @reader = block || lambda { |t| read_template_file } 86 @data = @reader.call(self) 87 88 if @data.respond_to?(:force_encoding) 89 if default_encoding 90 @data = @data.dup if @data.frozen? 91 @data.force_encoding(default_encoding) 92 end 93 94 if !@data.valid_encoding? 95 raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}" 96 end 97 end 98 99 prepare 100 end
Public Instance Methods
The basename of the template file.
# File lib/tilt/template.rb 115 def basename(suffix='') 116 File.basename(file, suffix) if file 117 end
Set the prefix to use for compiled paths.
# File lib/tilt/template.rb 140 def compiled_path=(path) 141 if path 142 # Use expanded paths when loading, since that is helpful 143 # for coverage. Remove any .rb suffix, since that will 144 # be added back later. 145 path = File.expand_path(path.sub(/\.rb\z/i, '')) 146 end 147 @compiled_path = path 148 end
The filename used in backtraces to describe the template.
# File lib/tilt/template.rb 125 def eval_file 126 file || '(__TEMPLATE__)' 127 end
An empty Hash that the template engine can populate with various metadata.
# File lib/tilt/template.rb 131 def metadata 132 if respond_to?(:allows_script?) 133 self.class.metadata.merge(:allows_script => allows_script?) 134 else 135 self.class.metadata 136 end 137 end
The template file's basename with all extensions chomped off.
# File lib/tilt/template.rb 120 def name 121 basename.split('.', 2).first if basename 122 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 105 def render(scope=nil, locals={}, &block) 106 scope ||= Object.new 107 current_template = Thread.current[:tilt_current_template] 108 Thread.current[:tilt_current_template] = self 109 evaluate(scope, locals || {}, &block) 110 ensure 111 Thread.current[:tilt_current_template] = current_template 112 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 178 def evaluate(scope, locals, &block) 179 locals_keys = locals.keys 180 locals_keys.sort!{|x, y| x.to_s <=> y.to_s} 181 182 case scope 183 when Object 184 scope_class = Module === scope ? scope : scope.class 185 else 186 scope_class = USE_BIND_CALL ? CLASS_METHOD.bind_call(scope) : CLASS_METHOD.bind(scope).call 187 end 188 method = compiled_method(locals_keys, scope_class) 189 190 if USE_BIND_CALL 191 method.bind_call(scope, locals, &block) 192 else 193 method.bind(scope).call(locals, &block) 194 end 195 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 206 def precompiled(local_keys) 207 preamble = precompiled_preamble(local_keys) 208 template = precompiled_template(local_keys) 209 postamble = precompiled_postamble(local_keys) 210 source = String.new 211 212 # Ensure that our generated source code has the same encoding as the 213 # the source code generated by the template engine. 214 if source.respond_to?(:force_encoding) 215 template_encoding = extract_encoding(template) 216 217 source.force_encoding(template_encoding) 218 template.force_encoding(template_encoding) 219 end 220 221 source << preamble << "\n" << template << "\n" << postamble 222 223 [source, preamble.count("\n")+1] 224 end
# File lib/tilt/template.rb 240 def precompiled_postamble(local_keys) 241 '' 242 end
# File lib/tilt/template.rb 236 def precompiled_preamble(local_keys) 237 '' 238 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 232 def precompiled_template(local_keys) 233 raise NotImplementedError 234 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.
Subclasses must provide an implementation of this method.
# File lib/tilt/template.rb 165 def prepare 166 raise NotImplementedError 167 end
Private Instance Methods
# File lib/tilt/template.rb 357 def binary(string) 358 original_encoding = string.encoding 359 string.force_encoding(Encoding::BINARY) 360 yield 361 ensure 362 string.force_encoding(original_encoding) 363 end
# File lib/tilt/template.rb 300 def bind_compiled_method(method_source, offset, scope_class, local_keys) 301 path = compiled_path 302 if path && scope_class.name 303 path = path.dup 304 305 if defined?(@compiled_path_counter) 306 path << '-' << @compiled_path_counter.succ! 307 else 308 @compiled_path_counter = "0".dup 309 end 310 path << ".rb" 311 312 # Wrap method source in a class block for the scope, so constant lookup works 313 method_source = "class #{scope_class.name}\n#{method_source}\nend" 314 315 load_compiled_method(path, method_source) 316 else 317 if path 318 warn "compiled_path (#{compiled_path.inspect}) ignored on template with anonymous scope_class (#{scope_class.inspect})" 319 end 320 321 eval_compiled_method(method_source, offset, scope_class) 322 end 323 end
# File lib/tilt/template.rb 274 def compile_template_method(local_keys, scope_class=nil) 275 source, offset = precompiled(local_keys) 276 local_code = local_extraction(local_keys) 277 278 method_name = "__tilt_#{Thread.current.object_id.abs}" 279 method_source = String.new 280 281 if method_source.respond_to?(:force_encoding) 282 method_source.force_encoding(source.encoding) 283 end 284 285 if freeze_string_literals? 286 method_source << "# frozen-string-literal: true\n" 287 end 288 289 # Don't indent method source, to avoid indentation warnings when using compiled paths 290 method_source << "::Tilt::TOPOBJECT.class_eval do\ndef #{method_name}(locals)\n#{local_code}\n" 291 292 offset += method_source.count("\n") 293 method_source << source 294 method_source << "\nend;end;" 295 296 bind_compiled_method(method_source, offset, scope_class, local_keys) 297 unbind_compiled_method(method_name) 298 end
The compiled method for the locals keys provided.
# File lib/tilt/template.rb 258 def compiled_method(locals_keys, scope_class=nil) 259 LOCK.synchronize do 260 @compiled_method[[scope_class, locals_keys]] ||= compile_template_method(locals_keys, scope_class) 261 end 262 end
# File lib/tilt/template.rb 325 def eval_compiled_method(method_source, offset, scope_class) 326 (scope_class || Object).class_eval(method_source, eval_file, line - offset) 327 end
# File lib/tilt/template.rb 343 def extract_encoding(script) 344 extract_magic_comment(script) || script.encoding 345 end
# File lib/tilt/template.rb 347 def extract_magic_comment(script) 348 binary(script) do 349 script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1] 350 end 351 end
# File lib/tilt/template.rb 353 def freeze_string_literals? 354 false 355 end
# File lib/tilt/template.rb 329 def load_compiled_method(path, method_source) 330 File.binwrite(path, method_source) 331 332 # Use load and not require, so unbind_compiled_method does not 333 # break if the same path is used more than once. 334 load path 335 end
# File lib/tilt/template.rb 264 def local_extraction(local_keys) 265 local_keys.map do |k| 266 if k.to_s =~ /\A[a-z_][a-zA-Z_0-9]*\z/ 267 "#{k} = locals[#{k.inspect}]" 268 else 269 raise "invalid locals key: #{k.inspect} (keys must be variable names)" 270 end 271 end.join("\n") 272 end
!@endgroup
# File lib/tilt/template.rb 248 def read_template_file 249 data = File.open(file, 'rb') { |io| io.read } 250 if data.respond_to?(:force_encoding) 251 # Set it to the default external (without verifying) 252 data.force_encoding(Encoding.default_external) if Encoding.default_external 253 end 254 data 255 end
# File lib/tilt/template.rb 337 def unbind_compiled_method(method_name) 338 method = TOPOBJECT.instance_method(method_name) 339 TOPOBJECT.class_eval { remove_method(method_name) } 340 method 341 end