Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 357 lines (305 sloc) 10.41 kB
137cd54 @rtomayko Tilt!
authored
1 module Tilt
2 @template_mappings = {}
3
4 # Register a template implementation by file extension.
5 def self.register(ext, template_class)
3721ba7 @smtlaissezfaire Allow Template::register's file extensions to be given as a symbol
smtlaissezfaire authored
6 ext = ext.to_s.sub(/^\./, '')
137cd54 @rtomayko Tilt!
authored
7 @template_mappings[ext.downcase] = template_class
8 end
9
10 # Create a new template for the given file using the file's extension
11 # to determine the the template mapping.
12 def self.new(file, line=nil, options={}, &block)
7587c3d @rtomayko allow templates to be registered for full paths
authored
13 if template_class = self[file]
137cd54 @rtomayko Tilt!
authored
14 template_class.new(file, line, options, &block)
15 else
16 fail "No template engine registered for #{File.basename(file)}"
17 end
18 end
19
20 # Lookup a template class given for the given filename or file
21 # extension. Return nil when no implementation is found.
7587c3d @rtomayko allow templates to be registered for full paths
authored
22 def self.[](file)
23 if @template_mappings.key?(pattern = file.to_s.downcase)
24 @template_mappings[pattern]
25 elsif @template_mappings.key?(pattern = File.basename(pattern))
26 @template_mappings[pattern]
27 else
28 while !pattern.empty?
29 if @template_mappings.key?(pattern)
30 return @template_mappings[pattern]
31 else
32 pattern = pattern.sub(/^[^.]*\.?/, '')
33 end
34 end
35 nil
137cd54 @rtomayko Tilt!
authored
36 end
37 end
38
39 # Base class for template implementations. Subclasses must implement
40 # the #compile! method and one of the #evaluate or #template_source
41 # methods.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
42 class Template
51ef07e @rtomayko Minor doc additions
authored
43 # Template source; loaded from a file or given directly.
137cd54 @rtomayko Tilt!
authored
44 attr_reader :data
45
46 # The name of the file where the template data was loaded from.
47 attr_reader :file
48
49 # The line number in #file where template data was loaded from.
50 attr_reader :line
51
52 # A Hash of template engine specific options. This is passed directly
53 # to the underlying engine and is not used by the generic template
54 # interface.
55 attr_reader :options
56
57 # Create a new template with the file, line, and options specified. By
58 # default, template data is read from the file specified. When a block
59 # is given, it should read template data and return as a String. When
60 # file is nil, a block is required.
61 def initialize(file=nil, line=1, options={}, &block)
62 raise ArgumentError, "file or block required" if file.nil? && block.nil?
63 @file = file
64 @line = line || 1
65 @options = options || {}
66 @reader = block || lambda { |t| File.read(file) }
67 end
68
c1cb0de @rtomayko Template#compile method is like #compile! but runs once
authored
69 # Load template source and compile the template. The template is
70 # loaded and compiled the first time this method is called; subsequent
71 # calls are no-ops.
72 def compile
137cd54 @rtomayko Tilt!
authored
73 if @data.nil?
51ef07e @rtomayko Minor doc additions
authored
74 @data = @reader.call(self)
137cd54 @rtomayko Tilt!
authored
75 compile!
76 end
c1cb0de @rtomayko Template#compile method is like #compile! but runs once
authored
77 end
78
79 # Render the template in the given scope with the locals specified. If a
80 # block is given, it is typically available within the template via
81 # +yield+.
82 def render(scope=Object.new, locals={}, &block)
83 compile
137cd54 @rtomayko Tilt!
authored
84 evaluate scope, locals || {}, &block
85 end
86
60c8338 @rtomayko Template#basename and Template#name
authored
87 # The basename of the template file.
88 def basename(suffix='')
89 File.basename(file, suffix) if file
90 end
91
92 # The template file's basename with all extensions chomped off.
93 def name
94 basename.split('.', 2).first if basename
95 end
96
137cd54 @rtomayko Tilt!
authored
97 # The filename used in backtraces to describe the template.
98 def eval_file
99 @file || '(__TEMPLATE__)'
100 end
101
102 protected
51ef07e @rtomayko Minor doc additions
authored
103 # Do whatever preparation is necessary to "compile" the template.
104 # Called immediately after template #data is loaded. Instance variables
105 # set in this method are available when #evaluate is called.
106 #
107 # Subclasses must provide an implementation of this method.
137cd54 @rtomayko Tilt!
authored
108 def compile!
109 raise NotImplementedError
110 end
111
112 # Process the template and return the result. Subclasses should override
113 # this method unless they implement the #template_source.
114 def evaluate(scope, locals, &block)
115 source, offset = local_assignment_code(locals)
116 source = [source, template_source].join("\n")
0d49ccc @rtomayko Fix ERB backtrace line numbers off by 1 under Ruby 1.9
authored
117 scope.instance_eval source, eval_file, line - offset
137cd54 @rtomayko Tilt!
authored
118 end
119
120 # Return a string containing the (Ruby) source code for the template. The
51ef07e @rtomayko Minor doc additions
authored
121 # default Template#evaluate implementation requires this method be
122 # defined.
137cd54 @rtomayko Tilt!
authored
123 def template_source
124 raise NotImplementedError
125 end
126
127 private
128 def local_assignment_code(locals)
129 return ['', 1] if locals.empty?
130 source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
131 [source.join("\n"), source.length]
132 end
b8381e1 @rtomayko require_template_library is private
authored
133
134 def require_template_library(name)
135 warn "WARN: loading '#{name}' library in a non thread-safe way; " +
136 "explicit require '#{name}' suggested."
137 require name
138 end
137cd54 @rtomayko Tilt!
authored
139 end
140
d169230 @rtomayko Dead simple template cache implementation
authored
141 # Extremely simple template cache implementation.
142 class Cache
143 def initialize
144 @cache = {}
145 end
146
147 def fetch(*key)
148 key = key.map { |part| part.to_s }.join(":")
149 @cache[key] ||= yield
150 end
151
152 def clear
153 @cache = {}
154 end
155 end
156
157 # Template Implementations ================================================
158
137cd54 @rtomayko Tilt!
authored
159 # The template source is evaluated as a Ruby string. The #{} interpolation
160 # syntax can be used to generated dynamic output.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
161 class StringTemplate < Template
137cd54 @rtomayko Tilt!
authored
162 def compile!
163 @code = "%Q{#{data}}"
164 end
165
166 def template_source
167 @code
168 end
169 end
170 register 'str', StringTemplate
171
172 # ERB template implementation. See:
173 # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
d0195ed @rtomayko Warn when lazy loading template libraries
authored
174 #
175 # It's suggested that your program require 'erb' at load
176 # time when using this template engine.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
177 class ERBTemplate < Template
137cd54 @rtomayko Tilt!
authored
178 def compile!
d0195ed @rtomayko Warn when lazy loading template libraries
authored
179 require_template_library 'erb' unless defined?(::ERB)
ddeb087 @rtomayko Implement ERB @_out_buf hackery
authored
180 @engine = ::ERB.new(data, nil, nil, '@_out_buf')
137cd54 @rtomayko Tilt!
authored
181 end
182
183 def template_source
184 @engine.src
185 end
0d49ccc @rtomayko Fix ERB backtrace line numbers off by 1 under Ruby 1.9
authored
186
ddeb087 @rtomayko Implement ERB @_out_buf hackery
authored
187 def evaluate(scope, locals, &block)
188 source, offset = local_assignment_code(locals)
189 source = [source, template_source].join("\n")
190
191 original_out_buf =
192 scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
193 scope.instance_variable_get(:@_out_buf)
194
195 scope.instance_eval source, eval_file, line - offset
196
197 output = scope.instance_variable_get(:@_out_buf)
198 scope.instance_variable_set(:@_out_buf, original_out_buf)
199
200 output
201 end
202
0d49ccc @rtomayko Fix ERB backtrace line numbers off by 1 under Ruby 1.9
authored
203 private
204
205 # ERB generates a line to specify the character coding of the generated
206 # source in 1.9. Account for this in the line offset.
207 if RUBY_VERSION >= '1.9.0'
208 def local_assignment_code(locals)
209 source, offset = super
210 [source, offset + 1]
211 end
212 end
137cd54 @rtomayko Tilt!
authored
213 end
214 %w[erb rhtml].each { |ext| register ext, ERBTemplate }
215
47a5407 @dylanegan ERubis template implementation
dylanegan authored
216 # Erubis template implementation. See:
217 # http://www.kuwata-lab.com/erubis/
218 #
219 # It's suggested that your program require 'erubis' at load
220 # time when using this template engine.
221 class ErubisTemplate < ERBTemplate
222 def compile!
223 require_template_library 'erubis' unless defined?(::Erubis)
224 Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
225 @engine = ::Erubis::Eruby.new(data)
226 end
227 end
228 register 'erubis', ErubisTemplate
229
137cd54 @rtomayko Tilt!
authored
230 # Haml template implementation. See:
231 # http://haml.hamptoncatlin.com/
d0195ed @rtomayko Warn when lazy loading template libraries
authored
232 #
233 # It's suggested that your program require 'haml' at load
234 # time when using this template engine.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
235 class HamlTemplate < Template
137cd54 @rtomayko Tilt!
authored
236 def compile!
d0195ed @rtomayko Warn when lazy loading template libraries
authored
237 require_template_library 'haml' unless defined?(::Haml::Engine)
137cd54 @rtomayko Tilt!
authored
238 @engine = ::Haml::Engine.new(data, haml_options)
239 end
240
241 def evaluate(scope, locals, &block)
242 @engine.render(scope, locals, &block)
243 end
244
245 private
246 def haml_options
247 options.merge(:filename => eval_file, :line => line)
248 end
249 end
250 register 'haml', HamlTemplate
251
252 # Sass template implementation. See:
253 # http://haml.hamptoncatlin.com/
254 #
255 # Sass templates do not support object scopes, locals, or yield.
d0195ed @rtomayko Warn when lazy loading template libraries
authored
256 #
257 # It's suggested that your program require 'sass' at load
258 # time when using this template engine.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
259 class SassTemplate < Template
137cd54 @rtomayko Tilt!
authored
260 def compile!
d0195ed @rtomayko Warn when lazy loading template libraries
authored
261 require_template_library 'sass' unless defined?(::Sass::Engine)
137cd54 @rtomayko Tilt!
authored
262 @engine = ::Sass::Engine.new(data, sass_options)
263 end
264
265 def evaluate(scope, locals, &block)
266 @engine.render
267 end
268
269 private
270 def sass_options
271 options.merge(:filename => eval_file, :line => line)
272 end
273 end
274 register 'sass', SassTemplate
275
276 # Builder template implementation. See:
277 # http://builder.rubyforge.org/
d0195ed @rtomayko Warn when lazy loading template libraries
authored
278 #
279 # It's suggested that your program require 'builder' at load
280 # time when using this template engine.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
281 class BuilderTemplate < Template
137cd54 @rtomayko Tilt!
authored
282 def compile!
d0195ed @rtomayko Warn when lazy loading template libraries
authored
283 require_template_library 'builder' unless defined?(::Builder)
137cd54 @rtomayko Tilt!
authored
284 end
285
286 def evaluate(scope, locals, &block)
287 xml = ::Builder::XmlMarkup.new(:indent => 2)
288 if data.respond_to?(:to_str)
289 locals[:xml] = xml
290 super(scope, locals, &block)
291 elsif data.kind_of?(Proc)
292 data.call(xml)
293 end
294 xml.target!
295 end
296
297 def template_source
298 data.to_str
299 end
300 end
301 register 'builder', BuilderTemplate
c3d166f @rtomayko Add Liquid implementation (limited)
authored
302
303 # Liquid template implementation. See:
304 # http://liquid.rubyforge.org/
305 #
306 # LiquidTemplate does not support scopes or yield blocks.
d0195ed @rtomayko Warn when lazy loading template libraries
authored
307 #
308 # It's suggested that your program require 'liquid' at load
309 # time when using this template engine.
93cc8fb @rtomayko Rename "AbstractTemplate" to just "Template"
authored
310 class LiquidTemplate < Template
c3d166f @rtomayko Add Liquid implementation (limited)
authored
311 def compile!
d0195ed @rtomayko Warn when lazy loading template libraries
authored
312 require_template_library 'liquid' unless defined?(::Liquid::Template)
c3d166f @rtomayko Add Liquid implementation (limited)
authored
313 @engine = ::Liquid::Template.parse(data)
314 end
315
316 def evaluate(scope, locals, &block)
317 locals = locals.inject({}) { |hash,(k,v)| hash[k.to_s] = v ; hash }
318 @engine.render(locals)
319 end
320 end
321 register 'liquid', LiquidTemplate
8752db2 @rtomayko Add RDiscountTemplate
authored
322
323 # Discount Markdown implementation.
324 class RDiscountTemplate < Template
325 def compile!
187dd4d @rtomayko Oops. Missed RDiscount in lazy template warning commit
authored
326 require_template_library 'rdiscount' unless defined?(::RDiscount)
8752db2 @rtomayko Add RDiscountTemplate
authored
327 @engine = RDiscount.new(data)
328 end
329
330 def evaluate(scope, locals, &block)
331 @engine.to_html
332 end
333 end
334 register 'markdown', RDiscountTemplate
335
8465ac6 @dylanegan Mustache template implementation
dylanegan authored
336 # Mustache template implementation. See:
337 # http://github.com/defunkt/mustache
338 class MustacheTemplate < Template
339 def compile!
340 require_template_library 'mustache' unless defined?(::Mustache)
341 @engine = Mustache.new
342 @engine.template = data
343 end
344
345 def evaluate(scope, locals, &block)
346 locals.each do |local, value|
347 @engine[local] = value
348 end
349
350 @engine[:yield] = block.call if block
351 @engine.to_html
352 end
353 end
354 register 'mustache', MustacheTemplate
355
137cd54 @rtomayko Tilt!
authored
356 end
Something went wrong with that request. Please try again.