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

AUTOLOAD_IS_BROKEN
LOCK

Attributes

lazy_map[R]

@private

template_map[R]

@private

Public Class Methods

new() click to toggle source
   # File lib/tilt/mapping.rb
54 def initialize
55   @template_map = Hash.new
56   @lazy_map = Hash.new { |h, k| h[k] = [] }
57 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
152 def [](file)
153   _, ext = split(file)
154   ext && lookup(ext)
155 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
183 def extensions_for(template_class)
184   res = []
185   template_map.each do |ext, klass|
186     res << ext if template_class == klass
187   end
188   lazy_map.each do |ext, choices|
189     res << ext if choices.any? { |klass, file| template_class.to_s == klass }
190   end
191   res
192 end
initialize_copy(other) click to toggle source

@private

   # File lib/tilt/mapping.rb
60 def initialize_copy(other)
61   @template_map = other.template_map.dup
62   @lazy_map = other.lazy_map.dup
63 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
136 def new(file, line=nil, options={}, &block)
137   if template_class = self[file]
138     template_class.new(file, line, options, &block)
139   else
140     fail "No template engine registered for #{File.basename(file)}"
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
104 def register(template_class, *extensions)
105   if template_class.respond_to?(:to_str)
106     # Support register(ext, template_class) too
107     extensions, template_class = [template_class], extensions[0]
108   end
109 
110   extensions.each do |ext|
111     @template_map[ext.to_s] = template_class
112   end
113 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
81 def register_lazy(class_name, file, *extensions)
82   # Internal API
83   if class_name.is_a?(Symbol)
84     Tilt.autoload class_name, file
85     class_name = "Tilt::#{class_name}"
86   end
87 
88   extensions.each do |ext|
89     @lazy_map[ext].unshift([class_name, file])
90   end
91 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
123 def registered?(ext)
124   @template_map.has_key?(ext.downcase) or lazy?(ext)
125 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
168 def templates_for(file)
169   templates = []
170 
171   while true
172     prefix, ext = split(file)
173     break unless ext
174     templates << lookup(ext)
175     file = prefix
176   end
177 
178   templates
179 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
277 def constant_defined?(name)
278   name.split('::').inject(Object) do |scope, n|
279     if scope.autoload?(n)
280       if !AUTOLOAD_IS_BROKEN
281         return false
282       end
283 
284       if eval("!defined?(scope::#{n})")
285         return false
286       end
287     end
288     return false if !scope.const_defined?(n)
289     scope.const_get(n)
290   end
291 end
lazy?(ext) click to toggle source
    # File lib/tilt/mapping.rb
196 def lazy?(ext)
197   ext = ext.downcase
198   @lazy_map.has_key?(ext) && !@lazy_map[ext].empty?
199 end
lazy_load(pattern) click to toggle source
    # File lib/tilt/mapping.rb
221 def lazy_load(pattern)
222   return unless @lazy_map.has_key?(pattern)
223 
224   LOCK.enter
225   entered = true
226 
227   choices = @lazy_map[pattern]
228 
229   # Check if a template class is already present
230   choices.each do |class_name, file|
231     template_class = constant_defined?(class_name)
232     if template_class
233       register(template_class, pattern)
234       return template_class
235     end
236   end
237 
238   first_failure = nil
239 
240   # Load in order
241   choices.each do |class_name, file|
242     begin
243       require file
244       # It's safe to eval() here because constant_defined? will
245       # raise NameError on invalid constant names
246       template_class = eval(class_name)
247     rescue LoadError => ex
248       first_failure ||= ex
249     else
250       register(template_class, pattern)
251       return template_class
252     end
253   end
254 
255   raise first_failure if first_failure
256 ensure
257   LOCK.exit if entered
258 end
lookup(ext) click to toggle source
    # File lib/tilt/mapping.rb
215 def lookup(ext)
216   @template_map[ext] || lazy_load(ext)
217 end
split(file) click to toggle source
    # File lib/tilt/mapping.rb
201 def split(file)
202   pattern = file.to_s.downcase
203   full_pattern = pattern.dup
204 
205   until registered?(pattern)
206     return if pattern.empty?
207     pattern = File.basename(pattern)
208     pattern.sub!(/^[^.]*\.?/, '')
209   end
210 
211   prefix_size = full_pattern.size - pattern.size
212   [full_pattern[0,prefix_size-1], pattern]
213 end