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
@private
@private
Public Class Methods
# 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
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
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
@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
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
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
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
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
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
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
# 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
# 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
# File lib/tilt/mapping.rb 215 def lookup(ext) 216 @template_map[ext] || lazy_load(ext) 217 end
# 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