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

lazy_map[R]

@private

template_map[R]

@private

Public Class Methods

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

[](file) click to toggle source

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
Also aliased as: template_for
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
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
initialize_copy(other) click to toggle source

@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
new(file, line=nil, options={}, &block) click to toggle source

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
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
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
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
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_pipeline(ext, options={}) 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
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
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
180 def registered?(ext)
181   @template_map.has_key?(ext.downcase) or lazy?(ext)
182 end
template_for(file)
Alias for: []
templates_for(file) click to toggle source

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
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
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

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
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
lazy?(ext) click to toggle source
    # 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
lazy_load(pattern) click to toggle source
    # 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
lookup(ext) click to toggle source
    # File lib/tilt/mapping.rb
272 def lookup(ext)
273   @template_map[ext] || lazy_load(ext)
274 end
split(file) click to toggle source
    # 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