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

lazy_map[R]

@private

template_map[R]

@private

Public Class Methods

new() click to toggle source
    # 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

extensions_for(template_class) click to toggle source

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
finalized() click to toggle source

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
initialize_copy(other) click to toggle source

@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
register(template_class, *extensions) click to toggle source

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
register_lazy(class_name, file, *extensions) click to toggle source

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_pipeline(ext, options=EMPTY_HASH) click to toggle source

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
registered?(ext) click to toggle source

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
unregister(*extensions) click to toggle source

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

constant_defined?(name) click to toggle source

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
lazy?(ext) click to toggle source
    # 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
lazy_load(pattern) click to toggle source
    # 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
lookup(ext) click to toggle source
    # File lib/tilt/mapping.rb
306 def lookup(ext)
307   LOCK.synchronize{@template_map[ext]} || lazy_load(ext)
308 end
register_defined_classes(class_names, pattern) { |template_class| ... } click to toggle source
    # 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