Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 357 lines (305 sloc) 10.41 kb
137cd54 Ryan Tomayko 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 Scott Taylor Allow Template::register's file extensions to be given as a symbol
smtlaissezfaire authored
6 ext = ext.to_s.sub(/^\./, '')
137cd54 Ryan Tomayko 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 Ryan Tomayko allow templates to be registered for full paths
authored
13 if template_class = self[file]
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
42 class Template
51ef07e Ryan Tomayko Minor doc additions
authored
43 # Template source; loaded from a file or given directly.
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Tilt!
authored
73 if @data.nil?
51ef07e Ryan Tomayko Minor doc additions
authored
74 @data = @reader.call(self)
137cd54 Ryan Tomayko Tilt!
authored
75 compile!
76 end
c1cb0de Ryan Tomayko 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 Ryan Tomayko Tilt!
authored
84 evaluate scope, locals || {}, &block
85 end
86
60c8338 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Fix ERB backtrace line numbers off by 1 under Ruby 1.9
authored
117 scope.instance_eval source, eval_file, line - offset
137cd54 Ryan Tomayko Tilt!
authored
118 end
119
120 # Return a string containing the (Ruby) source code for the template. The
51ef07e Ryan Tomayko Minor doc additions
authored
121 # default Template#evaluate implementation requires this method be
122 # defined.
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Tilt!
authored
139 end
140
d169230 Ryan Tomayko 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 Ryan Tomayko Tilt!
authored
159 # The template source is evaluated as a Ruby string. The #{} interpolation
160 # syntax can be used to generated dynamic output.
93cc8fb Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
161 class StringTemplate < Template
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
177 class ERBTemplate < Template
137cd54 Ryan Tomayko Tilt!
authored
178 def compile!
d0195ed Ryan Tomayko Warn when lazy loading template libraries
authored
179 require_template_library 'erb' unless defined?(::ERB)
ddeb087 Ryan Tomayko Implement ERB @_out_buf hackery
authored
180 @engine = ::ERB.new(data, nil, nil, '@_out_buf')
137cd54 Ryan Tomayko Tilt!
authored
181 end
182
183 def template_source
184 @engine.src
185 end
0d49ccc Ryan Tomayko Fix ERB backtrace line numbers off by 1 under Ruby 1.9
authored
186
ddeb087 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Tilt!
authored
213 end
214 %w[erb rhtml].each { |ext| register ext, ERBTemplate }
215
47a5407 Dylan Egan 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 Ryan Tomayko Tilt!
authored
230 # Haml template implementation. See:
231 # http://haml.hamptoncatlin.com/
d0195ed Ryan Tomayko 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 Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
235 class HamlTemplate < Template
137cd54 Ryan Tomayko Tilt!
authored
236 def compile!
d0195ed Ryan Tomayko Warn when lazy loading template libraries
authored
237 require_template_library 'haml' unless defined?(::Haml::Engine)
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
259 class SassTemplate < Template
137cd54 Ryan Tomayko Tilt!
authored
260 def compile!
d0195ed Ryan Tomayko Warn when lazy loading template libraries
authored
261 require_template_library 'sass' unless defined?(::Sass::Engine)
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
281 class BuilderTemplate < Template
137cd54 Ryan Tomayko Tilt!
authored
282 def compile!
d0195ed Ryan Tomayko Warn when lazy loading template libraries
authored
283 require_template_library 'builder' unless defined?(::Builder)
137cd54 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko 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 Ryan Tomayko Rename "AbstractTemplate" to just "Template"
authored
310 class LiquidTemplate < Template
c3d166f Ryan Tomayko Add Liquid implementation (limited)
authored
311 def compile!
d0195ed Ryan Tomayko Warn when lazy loading template libraries
authored
312 require_template_library 'liquid' unless defined?(::Liquid::Template)
c3d166f Ryan Tomayko 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 Ryan Tomayko Add RDiscountTemplate
authored
322
323 # Discount Markdown implementation.
324 class RDiscountTemplate < Template
325 def compile!
187dd4d Ryan Tomayko Oops. Missed RDiscount in lazy template warning commit
authored
326 require_template_library 'rdiscount' unless defined?(::RDiscount)
8752db2 Ryan Tomayko 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 Dylan Egan 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 Ryan Tomayko Tilt!
authored
356 end
Something went wrong with that request. Please try again.