class Tilt::Mapping
Tilt::Mapping
associates file extensions with template implementations.
mapping = Tilt::Mapping.new mapping.register(Tilt::RDocTemplate, 'rdoc') mapping['index.rdoc'] # => Tilt::RDocTemplate mapping.new('index.rdoc').render
You can use {#register} to register a template class by file extension, {#registered?} to see if a file extension is mapped, {#[]} to lookup template classes, and {#new} to instantiate template objects.
Mapping
also supports lazy template implementations. Note that regularly registered template implementations always have preference over lazily registered template implementations. You should use {#register} if you depend on a specific template implementation and {#register_lazy} if there are multiple alternatives.
mapping = Tilt::Mapping.new mapping.register_lazy('RDiscount::Template', 'rdiscount/template', 'md') mapping['index.md'] # => RDiscount::Template
{#register_lazy} takes a class name, a filename, and a list of file extensions. When you try to lookup a template name that matches the file extension, Tilt
will automatically try to require the filename and constantize the class name.
Unlike {#register}, there can be multiple template implementations registered lazily to the same file extension. Tilt
will attempt to load the template implementations in order (registered last would be tried first), returning the first which doesn't raise LoadError.
If all of the registered template implementations fails, Tilt
will raise the exception of the first, since that was the most preferred one.
mapping = Tilt::Mapping.new mapping.register_lazy('Bluecloth::Template', 'bluecloth/template', 'md') mapping.register_lazy('RDiscount::Template', 'rdiscount/template', 'md') mapping['index.md'] # => RDiscount::Template
In the previous example we say that RDiscount has a *higher priority* than BlueCloth. Tilt
will first try to `require “rdiscount/template”`, falling back to `require “bluecloth/template”`. If none of these are successful, the first error will be raised.
Constants
- LOCK
Attributes
@private
@private
Public Class Methods
# File lib/tilt/mapping.rb 55 def initialize 56 @template_map = Hash.new 57 @lazy_map = Hash.new { |h, k| h[k] = [] } 58 end
Public Instance Methods
Looks up a template class based on file name and/or extension.
@example
mapping['views/hello.erb'] # => Tilt::ERBTemplate mapping['hello.erb'] # => Tilt::ERBTemplate mapping['erb'] # => Tilt::ERBTemplate
@return [template class]
# File lib/tilt/mapping.rb 209 def [](file) 210 _, ext = split(file) 211 ext && lookup(ext) 212 end
Finds the extensions the template class has been registered under. @param [template class] template_class
# File lib/tilt/mapping.rb 240 def extensions_for(template_class) 241 res = [] 242 template_map.each do |ext, klass| 243 res << ext if template_class == klass 244 end 245 lazy_map.each do |ext, choices| 246 res << ext if choices.any? { |klass, file| template_class.to_s == klass } 247 end 248 res.uniq 249 end
@private
# File lib/tilt/mapping.rb 61 def initialize_copy(other) 62 @template_map = other.template_map.dup 63 @lazy_map = other.lazy_map.dup 64 end
Instantiates a new template class based on the file.
@raise [RuntimeError] if there is no template class registered for the
file name.
@example
mapping.new('index.mt') # => instance of MyEngine::Template
@see Tilt::Template.new
# File lib/tilt/mapping.rb 193 def new(file, line=nil, options={}, &block) 194 if template_class = self[file] 195 template_class.new(file, line, options, &block) 196 else 197 fail "No template engine registered for #{File.basename(file)}" 198 end 199 end
Registers a template implementation by file extension. There can only be one template implementation per file extension, and this method will override any existing mapping.
@param template_class @param extensions [Array<String>] List of extensions. @return [void]
@example
mapping.register MyEngine::Template, 'mt' mapping['index.mt'] # => MyEngine::Template
# File lib/tilt/mapping.rb 105 def register(template_class, *extensions) 106 if template_class.respond_to?(:to_str) 107 # Support register(ext, template_class) too 108 extensions, template_class = [template_class], extensions[0] 109 end 110 111 extensions.each do |ext| 112 @template_map[ext.to_s] = template_class 113 end 114 end
Registers a lazy template implementation by file extension. You can have multiple lazy template implementations defined on the same file extension, in which case the template implementation defined last will be attempted loaded first.
@param class_name [String] Class name of a template class. @param file [String] Filename where the template class is defined. @param extensions [Array<String>] List of extensions. @return [void]
@example
mapping.register_lazy 'MyEngine::Template', 'my_engine/template', 'mt' defined?(MyEngine::Template) # => false mapping['index.mt'] # => MyEngine::Template defined?(MyEngine::Template) # => true
# File lib/tilt/mapping.rb 82 def register_lazy(class_name, file, *extensions) 83 # Internal API 84 if class_name.is_a?(Symbol) 85 Tilt.autoload class_name, file 86 class_name = "Tilt::#{class_name}" 87 end 88 89 extensions.each do |ext| 90 @lazy_map[ext].unshift([class_name, file]) 91 end 92 end
Register a new template class using the given extension that represents a pipeline of multiple existing template, where the output from the previous template is used as input to the next template.
This will register a template class that processes the input with the erb template processor, and takes the output of that and feeds it to the scss template processor, returning the output of the scss template processor as the result of the pipeline.
@param ext [String] Primary extension to register @option :templates [Array<String>] Extensions of templates
to execute in order (defaults to the ext.split('.').reverse)
@option :extra_exts [Array<String>] Additional extensions to register @option String [Hash] Options hash for individual template in the
pipeline (key is extension).
@return [void]
@example
mapping.register_pipeline('scss.erb') mapping.register_pipeline('scss.erb', 'erb'=>{:outvar=>'@foo'}) mapping.register_pipeline('scsserb', :extra_exts => 'scss.erb', :templates=>['erb', 'scss'])
# File lib/tilt/mapping.rb 140 def register_pipeline(ext, options={}) 141 templates = options[:templates] || ext.split('.').reverse 142 templates = templates.map{|t| [self[t], options[t] || {}]} 143 144 klass = Class.new(Pipeline) 145 klass.send(:const_set, :TEMPLATES, templates) 146 147 register(klass, ext, *Array(options[:extra_exts])) 148 klass 149 end
Checks if a file extension is registered (either eagerly or lazily) in this mapping.
@param ext [String] File extension.
@example
mapping.registered?('erb') # => true mapping.registered?('nope') # => false
# File lib/tilt/mapping.rb 180 def registered?(ext) 181 @template_map.has_key?(ext.downcase) or lazy?(ext) 182 end
Looks up a list of template classes based on file name. If the file name has multiple extensions, it will return all template classes matching the extensions from the end.
@example
mapping.templates_for('views/index.haml.erb') # => [Tilt::ERBTemplate, Tilt::HamlTemplate]
@return [Array<template class>]
# File lib/tilt/mapping.rb 225 def templates_for(file) 226 templates = [] 227 228 while true 229 prefix, ext = split(file) 230 break unless ext 231 templates << lookup(ext) 232 file = prefix 233 end 234 235 templates 236 end
Unregisters an extension. This removes the both normal registrations and lazy registrations.
@param extensions [Array<String>] List of extensions. @return nil
@example
mapping.register MyEngine::Template, 'mt' mapping['index.mt'] # => MyEngine::Template mapping.unregister('mt') mapping['index.mt'] # => nil
# File lib/tilt/mapping.rb 162 def unregister(*extensions) 163 extensions.each do |ext| 164 ext = ext.to_s 165 @template_map.delete(ext) 166 @lazy_map.delete(ext) 167 end 168 169 nil 170 end
Private Instance Methods
The proper behavior (in MRI) for autoload? is to return `false` when the constant/file has been explicitly required.
However, in JRuby it returns `true` even after it's been required. In that case it turns out that `defined?` returns `“constant”` if it exists and `nil` when it doesn't. This is actually a second bug: `defined?` should resolve autoload (aka. actually try to require the file).
We use the second bug in order to resolve the first bug.
# File lib/tilt/mapping.rb 329 def constant_defined?(name) 330 name.split('::').inject(Object) do |scope, n| 331 return false if scope.autoload?(n) || !scope.const_defined?(n) 332 scope.const_get(n) 333 end 334 end
# File lib/tilt/mapping.rb 253 def lazy?(ext) 254 ext = ext.downcase 255 @lazy_map.has_key?(ext) && !@lazy_map[ext].empty? 256 end
# File lib/tilt/mapping.rb 278 def lazy_load(pattern) 279 return unless @lazy_map.has_key?(pattern) 280 281 LOCK.enter 282 entered = true 283 284 choices = @lazy_map[pattern] 285 286 # Check if a template class is already present 287 choices.each do |class_name, file| 288 template_class = constant_defined?(class_name) 289 if template_class 290 register(template_class, pattern) 291 return template_class 292 end 293 end 294 295 first_failure = nil 296 297 # Load in order 298 choices.each do |class_name, file| 299 begin 300 require file 301 # It's safe to eval() here because constant_defined? will 302 # raise NameError on invalid constant names 303 template_class = eval(class_name) 304 rescue LoadError => ex 305 first_failure ||= ex 306 else 307 register(template_class, pattern) 308 return template_class 309 end 310 end 311 312 raise first_failure if first_failure 313 ensure 314 LOCK.exit if entered 315 end
# File lib/tilt/mapping.rb 272 def lookup(ext) 273 @template_map[ext] || lazy_load(ext) 274 end
# File lib/tilt/mapping.rb 258 def split(file) 259 pattern = file.to_s.downcase 260 full_pattern = pattern.dup 261 262 until registered?(pattern) 263 return if pattern.empty? 264 pattern = File.basename(pattern) 265 pattern.sub!(/^[^.]*\.?/, '') 266 end 267 268 prefix_size = full_pattern.size - pattern.size 269 [full_pattern[0,prefix_size-1], pattern] 270 end