Skip to content
This repository
Newer
Older
100644 339 lines (291 sloc) 9.946 kb
137cd540 »
2009-01-25 Tilt!
1 module Tilt
2 @template_mappings = {}
3
4 # Register a template implementation by file extension.
5 def self.register(ext, template_class)
3721ba70 » smtlaissezfaire
2009-10-07 Allow Template::register's file extensions to be given as a symbol
6 ext = ext.to_s.sub(/^\./, '')
137cd540 »
2009-01-25 Tilt!
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)
7587c3d1 »
2009-10-14 allow templates to be registered for full paths
13 if template_class = self[file]
137cd540 »
2009-01-25 Tilt!
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.
7587c3d1 »
2009-10-14 allow templates to be registered for full paths
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
137cd540 »
2009-01-25 Tilt!
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.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
42 class Template
51ef07e8 »
2009-03-29 Minor doc additions
43 # Template source; loaded from a file or given directly.
137cd540 »
2009-01-25 Tilt!
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
69 # Render the template in the given scope with the locals specified. If a
70 # block is given, it is typically available within the template via
71 # +yield+.
72 def render(scope=Object.new, locals={}, &block)
73 if @data.nil?
51ef07e8 »
2009-03-29 Minor doc additions
74 @data = @reader.call(self)
137cd540 »
2009-01-25 Tilt!
75 compile!
76 end
77 evaluate scope, locals || {}, &block
78 end
79
80 # The filename used in backtraces to describe the template.
81 def eval_file
82 @file || '(__TEMPLATE__)'
83 end
84
85 protected
51ef07e8 »
2009-03-29 Minor doc additions
86 # Do whatever preparation is necessary to "compile" the template.
87 # Called immediately after template #data is loaded. Instance variables
88 # set in this method are available when #evaluate is called.
89 #
90 # Subclasses must provide an implementation of this method.
137cd540 »
2009-01-25 Tilt!
91 def compile!
92 raise NotImplementedError
93 end
94
95 # Process the template and return the result. Subclasses should override
96 # this method unless they implement the #template_source.
97 def evaluate(scope, locals, &block)
98 source, offset = local_assignment_code(locals)
99 source = [source, template_source].join("\n")
0d49ccc9 »
2009-02-03 Fix ERB backtrace line numbers off by 1 under Ruby 1.9
100 scope.instance_eval source, eval_file, line - offset
137cd540 »
2009-01-25 Tilt!
101 end
102
103 # Return a string containing the (Ruby) source code for the template. The
51ef07e8 »
2009-03-29 Minor doc additions
104 # default Template#evaluate implementation requires this method be
105 # defined.
137cd540 »
2009-01-25 Tilt!
106 def template_source
107 raise NotImplementedError
108 end
109
110 private
111 def local_assignment_code(locals)
112 return ['', 1] if locals.empty?
113 source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
114 [source.join("\n"), source.length]
115 end
b8381e19 »
2009-06-07 require_template_library is private
116
117 def require_template_library(name)
118 warn "WARN: loading '#{name}' library in a non thread-safe way; " +
119 "explicit require '#{name}' suggested."
120 require name
121 end
137cd540 »
2009-01-25 Tilt!
122 end
123
d169230e »
2009-06-07 Dead simple template cache implementation
124 # Extremely simple template cache implementation.
125 class Cache
126 def initialize
127 @cache = {}
128 end
129
130 def fetch(*key)
131 key = key.map { |part| part.to_s }.join(":")
132 @cache[key] ||= yield
133 end
134
135 def clear
136 @cache = {}
137 end
138 end
139
140 # Template Implementations ================================================
141
137cd540 »
2009-01-25 Tilt!
142 # The template source is evaluated as a Ruby string. The #{} interpolation
143 # syntax can be used to generated dynamic output.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
144 class StringTemplate < Template
137cd540 »
2009-01-25 Tilt!
145 def compile!
146 @code = "%Q{#{data}}"
147 end
148
149 def template_source
150 @code
151 end
152 end
153 register 'str', StringTemplate
154
155 # ERB template implementation. See:
156 # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
d0195edc »
2009-06-07 Warn when lazy loading template libraries
157 #
158 # It's suggested that your program require 'erb' at load
159 # time when using this template engine.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
160 class ERBTemplate < Template
137cd540 »
2009-01-25 Tilt!
161 def compile!
d0195edc »
2009-06-07 Warn when lazy loading template libraries
162 require_template_library 'erb' unless defined?(::ERB)
ddeb0878 »
2009-06-07 Implement ERB @_out_buf hackery
163 @engine = ::ERB.new(data, nil, nil, '@_out_buf')
137cd540 »
2009-01-25 Tilt!
164 end
165
166 def template_source
167 @engine.src
168 end
0d49ccc9 »
2009-02-03 Fix ERB backtrace line numbers off by 1 under Ruby 1.9
169
ddeb0878 »
2009-06-07 Implement ERB @_out_buf hackery
170 def evaluate(scope, locals, &block)
171 source, offset = local_assignment_code(locals)
172 source = [source, template_source].join("\n")
173
174 original_out_buf =
175 scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
176 scope.instance_variable_get(:@_out_buf)
177
178 scope.instance_eval source, eval_file, line - offset
179
180 output = scope.instance_variable_get(:@_out_buf)
181 scope.instance_variable_set(:@_out_buf, original_out_buf)
182
183 output
184 end
185
0d49ccc9 »
2009-02-03 Fix ERB backtrace line numbers off by 1 under Ruby 1.9
186 private
187
188 # ERB generates a line to specify the character coding of the generated
189 # source in 1.9. Account for this in the line offset.
190 if RUBY_VERSION >= '1.9.0'
191 def local_assignment_code(locals)
192 source, offset = super
193 [source, offset + 1]
194 end
195 end
137cd540 »
2009-01-25 Tilt!
196 end
197 %w[erb rhtml].each { |ext| register ext, ERBTemplate }
198
47a54071 » dylanegan
2009-10-05 ERubis template implementation
199 # Erubis template implementation. See:
200 # http://www.kuwata-lab.com/erubis/
201 #
202 # It's suggested that your program require 'erubis' at load
203 # time when using this template engine.
204 class ErubisTemplate < ERBTemplate
205 def compile!
206 require_template_library 'erubis' unless defined?(::Erubis)
207 Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
208 @engine = ::Erubis::Eruby.new(data)
209 end
210 end
211 register 'erubis', ErubisTemplate
212
137cd540 »
2009-01-25 Tilt!
213 # Haml template implementation. See:
214 # http://haml.hamptoncatlin.com/
d0195edc »
2009-06-07 Warn when lazy loading template libraries
215 #
216 # It's suggested that your program require 'haml' at load
217 # time when using this template engine.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
218 class HamlTemplate < Template
137cd540 »
2009-01-25 Tilt!
219 def compile!
d0195edc »
2009-06-07 Warn when lazy loading template libraries
220 require_template_library 'haml' unless defined?(::Haml::Engine)
137cd540 »
2009-01-25 Tilt!
221 @engine = ::Haml::Engine.new(data, haml_options)
222 end
223
224 def evaluate(scope, locals, &block)
225 @engine.render(scope, locals, &block)
226 end
227
228 private
229 def haml_options
230 options.merge(:filename => eval_file, :line => line)
231 end
232 end
233 register 'haml', HamlTemplate
234
235 # Sass template implementation. See:
236 # http://haml.hamptoncatlin.com/
237 #
238 # Sass templates do not support object scopes, locals, or yield.
d0195edc »
2009-06-07 Warn when lazy loading template libraries
239 #
240 # It's suggested that your program require 'sass' at load
241 # time when using this template engine.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
242 class SassTemplate < Template
137cd540 »
2009-01-25 Tilt!
243 def compile!
d0195edc »
2009-06-07 Warn when lazy loading template libraries
244 require_template_library 'sass' unless defined?(::Sass::Engine)
137cd540 »
2009-01-25 Tilt!
245 @engine = ::Sass::Engine.new(data, sass_options)
246 end
247
248 def evaluate(scope, locals, &block)
249 @engine.render
250 end
251
252 private
253 def sass_options
254 options.merge(:filename => eval_file, :line => line)
255 end
256 end
257 register 'sass', SassTemplate
258
259 # Builder template implementation. See:
260 # http://builder.rubyforge.org/
d0195edc »
2009-06-07 Warn when lazy loading template libraries
261 #
262 # It's suggested that your program require 'builder' at load
263 # time when using this template engine.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
264 class BuilderTemplate < Template
137cd540 »
2009-01-25 Tilt!
265 def compile!
d0195edc »
2009-06-07 Warn when lazy loading template libraries
266 require_template_library 'builder' unless defined?(::Builder)
137cd540 »
2009-01-25 Tilt!
267 end
268
269 def evaluate(scope, locals, &block)
270 xml = ::Builder::XmlMarkup.new(:indent => 2)
271 if data.respond_to?(:to_str)
272 locals[:xml] = xml
273 super(scope, locals, &block)
274 elsif data.kind_of?(Proc)
275 data.call(xml)
276 end
277 xml.target!
278 end
279
280 def template_source
281 data.to_str
282 end
283 end
284 register 'builder', BuilderTemplate
c3d166f5 »
2009-01-26 Add Liquid implementation (limited)
285
286 # Liquid template implementation. See:
287 # http://liquid.rubyforge.org/
288 #
289 # LiquidTemplate does not support scopes or yield blocks.
d0195edc »
2009-06-07 Warn when lazy loading template libraries
290 #
291 # It's suggested that your program require 'liquid' at load
292 # time when using this template engine.
93cc8fb9 »
2009-01-26 Rename "AbstractTemplate" to just "Template"
293 class LiquidTemplate < Template
c3d166f5 »
2009-01-26 Add Liquid implementation (limited)
294 def compile!
d0195edc »
2009-06-07 Warn when lazy loading template libraries
295 require_template_library 'liquid' unless defined?(::Liquid::Template)
c3d166f5 »
2009-01-26 Add Liquid implementation (limited)
296 @engine = ::Liquid::Template.parse(data)
297 end
298
299 def evaluate(scope, locals, &block)
300 locals = locals.inject({}) { |hash,(k,v)| hash[k.to_s] = v ; hash }
301 @engine.render(locals)
302 end
303 end
304 register 'liquid', LiquidTemplate
8752db28 »
2009-02-03 Add RDiscountTemplate
305
306 # Discount Markdown implementation.
307 class RDiscountTemplate < Template
308 def compile!
187dd4de »
2009-06-07 Oops. Missed RDiscount in lazy template warning commit
309 require_template_library 'rdiscount' unless defined?(::RDiscount)
8752db28 »
2009-02-03 Add RDiscountTemplate
310 @engine = RDiscount.new(data)
311 end
312
313 def evaluate(scope, locals, &block)
314 @engine.to_html
315 end
316 end
317 register 'markdown', RDiscountTemplate
318
8465ac68 » dylanegan
2009-10-05 Mustache template implementation
319 # Mustache template implementation. See:
320 # http://github.com/defunkt/mustache
321 class MustacheTemplate < Template
322 def compile!
323 require_template_library 'mustache' unless defined?(::Mustache)
324 @engine = Mustache.new
325 @engine.template = data
326 end
327
328 def evaluate(scope, locals, &block)
329 locals.each do |local, value|
330 @engine[local] = value
331 end
332
333 @engine[:yield] = block.call if block
334 @engine.to_html
335 end
336 end
337 register 'mustache', MustacheTemplate
338
137cd540 »
2009-01-25 Tilt!
339 end
Something went wrong with that request. Please try again.