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('Maruku::Template', 'maruku/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 Maruku. Tilt
will first try to ‘require “rdiscount/template”`, falling back to `require “maruku/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 131 def initialize 132 @template_map = Hash.new 133 @lazy_map = Hash.new { |h, k| h[k] = [] } 134 end
Public Instance Methods
Finds the extensions the template class has been registered under. @param [template class] template_class
# File lib/tilt/mapping.rb 287 def extensions_for(template_class) 288 res = [] 289 LOCK.synchronize{@template_map.to_a}.each do |ext, klass| 290 res << ext if template_class == klass 291 end 292 LOCK.synchronize{@lazy_map.to_a}.each do |ext, choices| 293 res << ext if LOCK.synchronize{choices.dup}.any? { |klass, file| template_class.to_s == klass } 294 end 295 res.uniq! 296 res 297 end
Return a finalized mapping. A finalized mapping will only include support for template libraries already loaded, and will not allow registering new template libraries or lazy loading template libraries not yet loaded. Finalized mappings improve performance by not requiring synchronization and ensure that the mapping will not attempt to load additional files (useful when restricting file system access after template libraries in use are loaded).
# File lib/tilt/mapping.rb 151 def finalized 152 LOCK.synchronize{@lazy_map.dup}.each do |pattern, classes| 153 register_defined_classes(LOCK.synchronize{classes.map(&:first)}, pattern) 154 end 155 156 # Check if a template class is already present 157 FinalizedMapping.new(LOCK.synchronize{@template_map.dup}.freeze) 158 end
@private
# File lib/tilt/mapping.rb 137 def initialize_copy(other) 138 LOCK.synchronize do 139 @template_map = other.template_map.dup 140 @lazy_map = other.lazy_map.dup 141 end 142 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 200 def register(template_class, *extensions) 201 if template_class.respond_to?(:to_str) 202 # Support register(ext, template_class) too 203 extensions, template_class = [template_class], extensions[0] 204 end 205 206 extensions.each do |ext| 207 ext = ext.to_s 208 LOCK.synchronize do 209 @template_map[ext] = template_class 210 end 211 end 212 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 176 def register_lazy(class_name, file, *extensions) 177 # Internal API 178 if class_name.is_a?(Symbol) 179 Tilt.autoload class_name, file 180 class_name = "Tilt::#{class_name}" 181 end 182 183 v = [class_name, file].freeze 184 extensions.each do |ext| 185 LOCK.synchronize{@lazy_map[ext].unshift(v)} 186 end 187 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 238 def register_pipeline(ext, options=EMPTY_HASH) 239 templates = options[:templates] || ext.split('.').reverse 240 templates = templates.map{|t| [self[t], options[t] || EMPTY_HASH]} 241 242 klass = Class.new(Pipeline) 243 klass.send(:const_set, :TEMPLATES, templates) 244 245 register(klass, ext, *Array(options[:extra_exts])) 246 klass 247 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 280 def registered?(ext) 281 ext_downcase = ext.downcase 282 LOCK.synchronize{@template_map.has_key?(ext_downcase)} or lazy?(ext) 283 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 260 def unregister(*extensions) 261 extensions.each do |ext| 262 ext = ext.to_s 263 LOCK.synchronize do 264 @template_map.delete(ext) 265 @lazy_map.delete(ext) 266 end 267 end 268 269 nil 270 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 360 def constant_defined?(name) 361 name.split('::').inject(Object) do |scope, n| 362 return false if scope.autoload?(n) || !scope.const_defined?(n) 363 scope.const_get(n) 364 end 365 end
# File lib/tilt/mapping.rb 301 def lazy?(ext) 302 ext = ext.downcase 303 LOCK.synchronize{@lazy_map.has_key?(ext) && !@lazy_map[ext].empty?} 304 end
# File lib/tilt/mapping.rb 320 def lazy_load(pattern) 321 choices = LOCK.synchronize{@lazy_map[pattern].dup} 322 323 # Check if a template class is already present 324 register_defined_classes(choices.map(&:first), pattern) do |template_class| 325 return template_class 326 end 327 328 first_failure = nil 329 330 # Load in order 331 choices.each do |class_name, file| 332 begin 333 require file 334 # It's safe to eval() here because constant_defined? will 335 # raise NameError on invalid constant names 336 template_class = eval(class_name) 337 rescue LoadError => ex 338 first_failure ||= ex 339 else 340 register(template_class, pattern) 341 return template_class 342 end 343 end 344 345 raise first_failure 346 end
# File lib/tilt/mapping.rb 306 def lookup(ext) 307 LOCK.synchronize{@template_map[ext]} || lazy_load(ext) 308 end
# File lib/tilt/mapping.rb 310 def register_defined_classes(class_names, pattern) 311 class_names.each do |class_name| 312 template_class = constant_defined?(class_name) 313 if template_class 314 register(template_class, pattern) 315 yield template_class if block_given? 316 end 317 end 318 end