tilt 0.6 → 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/tilt.rb CHANGED
@@ -1,10 +1,11 @@
1
+ require 'digest/md5'
2
+
1
3
  module Tilt
2
- VERSION = '0.6'
4
+ VERSION = '1.0'
3
5
 
4
6
  @template_mappings = {}
5
7
 
6
- # Hash of template path pattern => template implementation
7
- # class mappings.
8
+ # Hash of template path pattern => template implementation class mappings.
8
9
  def self.mappings
9
10
  @template_mappings
10
11
  end
@@ -15,6 +16,11 @@ module Tilt
15
16
  mappings[ext.downcase] = template_class
16
17
  end
17
18
 
19
+ # Returns true when a template exists on an exact match of the provided file extension
20
+ def self.registered?(ext)
21
+ mappings.key?(ext.downcase)
22
+ end
23
+
18
24
  # Create a new template for the given file using the file's extension
19
25
  # to determine the the template mapping.
20
26
  def self.new(file, line=nil, options={}, &block)
@@ -25,28 +31,35 @@ module Tilt
25
31
  end
26
32
  end
27
33
 
28
- # Lookup a template class given for the given filename or file
34
+ # Lookup a template class for the given filename or file
29
35
  # extension. Return nil when no implementation is found.
30
36
  def self.[](file)
31
- if @template_mappings.key?(pattern = file.to_s.downcase)
32
- @template_mappings[pattern]
33
- elsif @template_mappings.key?(pattern = File.basename(pattern))
34
- @template_mappings[pattern]
35
- else
36
- while !pattern.empty?
37
- if @template_mappings.key?(pattern)
38
- return @template_mappings[pattern]
39
- else
40
- pattern = pattern.sub(/^[^.]*\.?/, '')
41
- end
42
- end
43
- nil
37
+ pattern = file.to_s.downcase
38
+ unless registered?(pattern)
39
+ pattern = File.basename(pattern)
40
+ pattern.sub!(/^[^.]*\.?/, '') until (pattern.empty? || registered?(pattern))
44
41
  end
42
+ @template_mappings[pattern]
45
43
  end
46
44
 
45
+ # Mixin allowing template compilation on scope objects.
46
+ #
47
+ # Including this module in scope objects passed to Template#render
48
+ # causes template source to be compiled to methods the first time they're
49
+ # used. This can yield significant (5x-10x) performance increases for
50
+ # templates that support it (ERB, Erubis, Builder).
51
+ #
52
+ # It's also possible (though not recommended) to include this module in
53
+ # Object to enable template compilation globally. The downside is that
54
+ # the template methods will polute the global namespace and could lead to
55
+ # unexpected behavior.
56
+ module CompileSite
57
+ def __tilt__
58
+ end
59
+ end
47
60
 
48
61
  # Base class for template implementations. Subclasses must implement
49
- # the #compile! method and one of the #evaluate or #template_source
62
+ # the #prepare method and one of the #evaluate or #template_source
50
63
  # methods.
51
64
  class Template
52
65
  # Template source; loaded from a file or given directly.
@@ -63,52 +76,55 @@ module Tilt
63
76
  # interface.
64
77
  attr_reader :options
65
78
 
79
+ # Used to determine if this class's initialize_engine method has
80
+ # been called yet.
81
+ @engine_initialized = false
82
+ class << self
83
+ attr_accessor :engine_initialized
84
+ alias engine_initialized? engine_initialized
85
+ end
86
+
66
87
  # Create a new template with the file, line, and options specified. By
67
- # default, template data is read from the file specified. When a block
68
- # is given, it should read template data and return as a String. When
69
- # file is nil, a block is required.
88
+ # default, template data is read from the file. When a block is given,
89
+ # it should read template data and return as a String. When file is nil,
90
+ # a block is required.
70
91
  #
71
- # The #initialize_engine method is called if this is the very first
72
- # time this template subclass has been initialized.
92
+ # All arguments are optional.
73
93
  def initialize(file=nil, line=1, options={}, &block)
74
- raise ArgumentError, "file or block required" if file.nil? && block.nil?
75
- options, line = line, 1 if line.is_a?(Hash)
76
- @file = file
77
- @line = line || 1
78
- @options = options || {}
79
- @reader = block || lambda { |t| File.read(file) }
80
- @data = nil
81
-
82
- if !self.class.engine_initialized
94
+ @file, @line, @options = nil, 1, {}
95
+
96
+ [options, line, file].compact.each do |arg|
97
+ case
98
+ when arg.respond_to?(:to_str) ; @file = arg.to_str
99
+ when arg.respond_to?(:to_int) ; @line = arg.to_int
100
+ when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
101
+ else raise TypeError
102
+ end
103
+ end
104
+
105
+ raise ArgumentError, "file or block required" if (@file || block).nil?
106
+
107
+ # call the initialize_engine method if this is the very first time
108
+ # an instance of this class has been created.
109
+ if !self.class.engine_initialized?
83
110
  initialize_engine
84
111
  self.class.engine_initialized = true
85
112
  end
86
- end
87
-
88
- # Called once and only once for each template subclass the first time
89
- # the template class is initialized. This should be used to require the
90
- # underlying template library and perform any initial setup.
91
- def initialize_engine
92
- end
93
- @engine_initialized = false
94
- class << self ; attr_accessor :engine_initialized ; end
95
113
 
114
+ # used to generate unique method names for template compilation
115
+ @stamp = (Time.now.to_f * 10000).to_i
116
+ @compiled_method_names = {}
96
117
 
97
- # Load template source and compile the template. The template is
98
- # loaded and compiled the first time this method is called; subsequent
99
- # calls are no-ops.
100
- def compile
101
- if @data.nil?
102
- @data = @reader.call(self)
103
- compile!
104
- end
118
+ # load template data and prepare
119
+ @reader = block || lambda { |t| File.read(@file) }
120
+ @data = @reader.call(self)
121
+ prepare
105
122
  end
106
123
 
107
124
  # Render the template in the given scope with the locals specified. If a
108
125
  # block is given, it is typically available within the template via
109
126
  # +yield+.
110
127
  def render(scope=Object.new, locals={}, &block)
111
- compile
112
128
  evaluate scope, locals || {}, &block
113
129
  end
114
130
 
@@ -128,43 +144,167 @@ module Tilt
128
144
  end
129
145
 
130
146
  protected
131
- # Do whatever preparation is necessary to "compile" the template.
132
- # Called immediately after template #data is loaded. Instance variables
133
- # set in this method are available when #evaluate is called.
147
+ # Called once and only once for each template subclass the first time
148
+ # the template class is initialized. This should be used to require the
149
+ # underlying template library and perform any initial setup.
150
+ def initialize_engine
151
+ end
152
+
153
+ # Like Kernel::require but issues a warning urging a manual require when
154
+ # running under a threaded environment.
155
+ def require_template_library(name)
156
+ if Thread.list.size > 1
157
+ warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
158
+ "explicit require '#{name}' suggested."
159
+ end
160
+ require name
161
+ end
162
+
163
+ # Do whatever preparation is necessary to setup the underlying template
164
+ # engine. Called immediately after template data is loaded. Instance
165
+ # variables set in this method are available when #evaluate is called.
134
166
  #
135
167
  # Subclasses must provide an implementation of this method.
136
- def compile!
137
- raise NotImplementedError
168
+ def prepare
169
+ if respond_to?(:compile!)
170
+ # backward compat with tilt < 0.6; just in case
171
+ warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
172
+ compile!
173
+ else
174
+ raise NotImplementedError
175
+ end
138
176
  end
139
177
 
140
- # Process the template and return the result. Subclasses should override
141
- # this method unless they implement the #template_source.
178
+ # Process the template and return the result. When the scope mixes in
179
+ # the Tilt::CompileSite module, the template is compiled to a method and
180
+ # reused given identical locals keys. When the scope object
181
+ # does not mix in the CompileSite module, the template source is
182
+ # evaluated with instance_eval. In any case, template executation
183
+ # is guaranteed to be performed in the scope object with the locals
184
+ # specified and with support for yielding to the block.
142
185
  def evaluate(scope, locals, &block)
143
- source, offset = local_assignment_code(locals)
144
- source = [source, template_source].join("\n")
145
- scope.instance_eval source, eval_file, line - offset
186
+ if scope.respond_to?(:__tilt__)
187
+ method_name = compiled_method_name(locals.keys)
188
+ if scope.respond_to?(method_name)
189
+ scope.send(method_name, locals, &block)
190
+ else
191
+ compile_template_method(method_name, locals)
192
+ scope.send(method_name, locals, &block)
193
+ end
194
+ else
195
+ evaluate_source(scope, locals, &block)
196
+ end
146
197
  end
147
198
 
148
- # Return a string containing the (Ruby) source code for the template. The
149
- # default Template#evaluate implementation requires this method be
150
- # defined.
151
- def template_source
199
+ # Generates all template source by combining the preamble, template, and
200
+ # postamble and returns a two-tuple of the form: [source, offset], where
201
+ # source is the string containing (Ruby) source code for the template and
202
+ # offset is the integer line offset where line reporting should begin.
203
+ #
204
+ # Template subclasses may override this method when they need complete
205
+ # control over source generation or want to adjust the default line
206
+ # offset. In most cases, overriding the #precompiled_template method is
207
+ # easier and more appropriate.
208
+ def precompiled(locals)
209
+ preamble = precompiled_preamble(locals)
210
+ parts = [
211
+ preamble,
212
+ precompiled_template(locals),
213
+ precompiled_postamble(locals)
214
+ ]
215
+ [parts.join("\n"), preamble.count("\n") + 1]
216
+ end
217
+
218
+ # A string containing the (Ruby) source code for the template. The
219
+ # default Template#evaluate implementation requires either this method
220
+ # or the #precompiled method be overridden. When defined, the base
221
+ # Template guarantees correct file/line handling, locals support, custom
222
+ # scopes, and support for template compilation when the scope object
223
+ # allows it.
224
+ def precompiled_template(locals)
152
225
  raise NotImplementedError
153
226
  end
154
227
 
228
+ # Generates preamble code for initializing template state, and performing
229
+ # locals assignment. The default implementation performs locals
230
+ # assignment only. Lines included in the preamble are subtracted from the
231
+ # source line offset, so adding code to the preamble does not effect line
232
+ # reporting in Kernel::caller and backtraces.
233
+ def precompiled_preamble(locals)
234
+ locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
235
+ end
236
+
237
+ # Generates postamble code for the precompiled template source. The
238
+ # string returned from this method is appended to the precompiled
239
+ # template source.
240
+ def precompiled_postamble(locals)
241
+ ''
242
+ end
243
+
244
+ # The unique compiled method name for the locals keys provided.
245
+ def compiled_method_name(locals_keys)
246
+ @compiled_method_names[locals_keys] ||=
247
+ generate_compiled_method_name(locals_keys)
248
+ end
249
+
155
250
  private
156
- def local_assignment_code(locals)
157
- return ['', 1] if locals.empty?
158
- source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
159
- [source.join("\n"), source.length]
251
+ # Evaluate the template source in the context of the scope object.
252
+ def evaluate_source(scope, locals, &block)
253
+ source, offset = precompiled(locals)
254
+ scope.instance_eval(source, eval_file, line - offset)
160
255
  end
161
256
 
162
- def require_template_library(name)
163
- if Thread.list.size > 1
164
- warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
165
- "explicit require '#{name}' suggested."
257
+ # JRuby doesn't allow Object#instance_eval to yield to the block it's
258
+ # closed over. This is by design and (ostensibly) something that will
259
+ # change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
260
+ # exhibits the behavior. More info here:
261
+ #
262
+ # http://jira.codehaus.org/browse/JRUBY-2599
263
+ #
264
+ # Additionally, JRuby's eval line reporting is off by one compared to
265
+ # all MRI versions tested.
266
+ #
267
+ # We redefine evaluate_source to work around both issues.
268
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
269
+ undef evaluate_source
270
+ def evaluate_source(scope, locals, &block)
271
+ source, offset = precompiled(locals)
272
+ file, lineno = eval_file, (line - offset) - 1
273
+ scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
274
+ end
275
+ end
276
+
277
+ def generate_compiled_method_name(locals_keys)
278
+ parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
279
+ digest = Digest::MD5.hexdigest(parts.join(':'))
280
+ "__tilt_#{digest}"
281
+ end
282
+
283
+ def compile_template_method(method_name, locals)
284
+ source, offset = precompiled(locals)
285
+ offset += 1
286
+ CompileSite.module_eval <<-RUBY, eval_file, line - offset
287
+ def #{method_name}(locals)
288
+ #{source}
289
+ end
290
+ RUBY
291
+
292
+ ObjectSpace.define_finalizer self,
293
+ Template.compiled_template_method_remover(CompileSite, method_name)
294
+ end
295
+
296
+ def self.compiled_template_method_remover(site, method_name)
297
+ proc { |oid| garbage_collect_compiled_template_method(site, method_name) }
298
+ end
299
+
300
+ def self.garbage_collect_compiled_template_method(site, method_name)
301
+ site.module_eval do
302
+ begin
303
+ remove_method(method_name)
304
+ rescue NameError
305
+ # method was already removed (ruby >= 1.9)
306
+ end
166
307
  end
167
- require name
168
308
  end
169
309
  end
170
310
 
@@ -174,7 +314,7 @@ module Tilt
174
314
  # cache = Tilt::Cache.new
175
315
  # cache.fetch(path, line, options) { Tilt.new(path, line, options) }
176
316
  #
177
- # Subsequent invocations return the already compiled template object.
317
+ # Subsequent invocations return the already loaded template object.
178
318
  class Cache
179
319
  def initialize
180
320
  @cache = {}
@@ -196,11 +336,11 @@ module Tilt
196
336
  # The template source is evaluated as a Ruby string. The #{} interpolation
197
337
  # syntax can be used to generated dynamic output.
198
338
  class StringTemplate < Template
199
- def compile!
339
+ def prepare
200
340
  @code = "%Q{#{data}}"
201
341
  end
202
342
 
203
- def template_source
343
+ def precompiled_template(locals)
204
344
  @code
205
345
  end
206
346
  end
@@ -210,66 +350,99 @@ module Tilt
210
350
  # ERB template implementation. See:
211
351
  # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
212
352
  class ERBTemplate < Template
213
- def initialize_engine
214
- require_template_library 'erb' unless defined? ::ERB
215
- end
353
+ @@default_output_variable = '_erbout'
216
354
 
217
- def compile!
218
- @engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
355
+ def self.default_output_variable
356
+ @@default_output_variable
219
357
  end
220
358
 
221
- def template_source
222
- @engine.src
359
+ def self.default_output_variable=(name)
360
+ @@default_output_variable = name
223
361
  end
224
362
 
225
- def evaluate(scope, locals, &block)
226
- source, offset = local_assignment_code(locals)
227
- source = [source, template_source].join("\n")
228
-
229
- original_out_buf =
230
- scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
231
- scope.instance_variable_get(:@_out_buf)
363
+ def initialize_engine
364
+ return if defined? ::ERB
365
+ require_template_library 'erb'
366
+ end
232
367
 
233
- scope.instance_eval source, eval_file, line - offset
368
+ def prepare
369
+ @outvar = options[:outvar] || self.class.default_output_variable
370
+ @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
371
+ end
234
372
 
235
- output = scope.instance_variable_get(:@_out_buf)
236
- scope.instance_variable_set(:@_out_buf, original_out_buf)
373
+ def precompiled_template(locals)
374
+ source = @engine.src
375
+ source
376
+ end
237
377
 
238
- output
378
+ def precompiled_preamble(locals)
379
+ <<-RUBY
380
+ begin
381
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
382
+ #{super}
383
+ RUBY
239
384
  end
240
385
 
241
- private
386
+ def precompiled_postamble(locals)
387
+ <<-RUBY
388
+ #{super}
389
+ ensure
390
+ #{@outvar} = __original_outvar
391
+ end
392
+ RUBY
393
+ end
242
394
 
243
395
  # ERB generates a line to specify the character coding of the generated
244
396
  # source in 1.9. Account for this in the line offset.
245
397
  if RUBY_VERSION >= '1.9.0'
246
- def local_assignment_code(locals)
398
+ def precompiled(locals)
247
399
  source, offset = super
248
400
  [source, offset + 1]
249
401
  end
250
402
  end
251
403
  end
404
+
252
405
  %w[erb rhtml].each { |ext| register ext, ERBTemplate }
253
406
 
254
407
 
255
408
  # Erubis template implementation. See:
256
409
  # http://www.kuwata-lab.com/erubis/
410
+ #
411
+ # ErubisTemplate supports the following additional options, which are not
412
+ # passed down to the Erubis engine:
413
+ #
414
+ # :engine_class allows you to specify a custom engine class to use
415
+ # instead of the default (which is ::Erubis::Eruby).
416
+ #
417
+ # :escape_html when true, ::Erubis::EscapedEruby will be used as
418
+ # the engine class instead of the default. All content
419
+ # within <%= %> blocks will be automatically html escaped.
257
420
  class ErubisTemplate < ERBTemplate
258
421
  def initialize_engine
259
- require_template_library 'erubis' unless defined? ::Erubis
422
+ return if defined? ::Erubis
423
+ require_template_library 'erubis'
260
424
  end
261
425
 
262
- def compile!
263
- Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
264
- @engine = ::Erubis::Eruby.new(data, options)
426
+ def prepare
427
+ @options.merge!(:preamble => false, :postamble => false)
428
+ @outvar = options.delete(:outvar) || self.class.default_output_variable
429
+ engine_class = options.delete(:engine_class)
430
+ engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
431
+ @engine = (engine_class || ::Erubis::Eruby).new(data, options)
265
432
  end
266
433
 
267
- private
434
+ def precompiled_preamble(locals)
435
+ [super, "#{@outvar} = _buf = ''"].join("\n")
436
+ end
268
437
 
269
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
270
- # and adjust back.
438
+ def precompiled_postamble(locals)
439
+ ["_buf", super].join("\n")
440
+ end
441
+
442
+ # Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
443
+ # Override and adjust back.
271
444
  if RUBY_VERSION >= '1.9.0'
272
- def local_assignment_code(locals)
445
+ def precompiled(locals)
273
446
  source, offset = super
274
447
  [source, offset - 1]
275
448
  end
@@ -282,20 +455,54 @@ module Tilt
282
455
  # http://haml.hamptoncatlin.com/
283
456
  class HamlTemplate < Template
284
457
  def initialize_engine
285
- require_template_library 'haml' unless defined? ::Haml::Engine
458
+ return if defined? ::Haml::Engine
459
+ require_template_library 'haml'
286
460
  end
287
461
 
288
- def compile!
289
- @engine = ::Haml::Engine.new(data, haml_options)
462
+ def prepare
463
+ options = @options.merge(:filename => eval_file, :line => line)
464
+ @engine = ::Haml::Engine.new(data, options)
290
465
  end
291
466
 
292
467
  def evaluate(scope, locals, &block)
293
- @engine.render(scope, locals, &block)
468
+ if @engine.respond_to?(:precompiled_method_return_value, true)
469
+ super
470
+ else
471
+ @engine.render(scope, locals, &block)
472
+ end
294
473
  end
295
474
 
296
- private
297
- def haml_options
298
- options.merge(:filename => eval_file, :line => line)
475
+ # Precompiled Haml source. Taken from the precompiled_with_ambles
476
+ # method in Haml::Precompiler:
477
+ # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
478
+ def precompiled_template(locals)
479
+ @engine.precompiled
480
+ end
481
+
482
+ def precompiled_preamble(locals)
483
+ local_assigns = super
484
+ @engine.instance_eval do
485
+ <<-RUBY
486
+ begin
487
+ extend Haml::Helpers
488
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
489
+ _erbout = _hamlout.buffer
490
+ __in_erb_template = true
491
+ _haml_locals = locals
492
+ #{local_assigns}
493
+ RUBY
494
+ end
495
+ end
496
+
497
+ def precompiled_postamble(locals)
498
+ @engine.instance_eval do
499
+ <<-RUBY
500
+ #{precompiled_method_return_value}
501
+ ensure
502
+ @haml_buffer = @haml_buffer.upper
503
+ end
504
+ RUBY
505
+ end
299
506
  end
300
507
  end
301
508
  register 'haml', HamlTemplate
@@ -307,15 +514,16 @@ module Tilt
307
514
  # Sass templates do not support object scopes, locals, or yield.
308
515
  class SassTemplate < Template
309
516
  def initialize_engine
310
- require_template_library 'sass' unless defined? ::Sass::Engine
517
+ return if defined? ::Sass::Engine
518
+ require_template_library 'sass'
311
519
  end
312
520
 
313
- def compile!
521
+ def prepare
314
522
  @engine = ::Sass::Engine.new(data, sass_options)
315
523
  end
316
524
 
317
525
  def evaluate(scope, locals, &block)
318
- @engine.render
526
+ @output ||= @engine.render
319
527
  end
320
528
 
321
529
  private
@@ -332,10 +540,11 @@ module Tilt
332
540
  # Less templates do not support object scopes, locals, or yield.
333
541
  class LessTemplate < Template
334
542
  def initialize_engine
335
- require_template_library 'less' unless defined? ::Less::Engine
543
+ return if defined? ::Less::Engine
544
+ require_template_library 'less'
336
545
  end
337
546
 
338
- def compile!
547
+ def prepare
339
548
  @engine = ::Less::Engine.new(data)
340
549
  end
341
550
 
@@ -345,14 +554,16 @@ module Tilt
345
554
  end
346
555
  register 'less', LessTemplate
347
556
 
557
+
348
558
  # Builder template implementation. See:
349
559
  # http://builder.rubyforge.org/
350
560
  class BuilderTemplate < Template
351
561
  def initialize_engine
352
- require_template_library 'builder' unless defined?(::Builder)
562
+ return if defined?(::Builder)
563
+ require_template_library 'builder'
353
564
  end
354
565
 
355
- def compile!
566
+ def prepare
356
567
  end
357
568
 
358
569
  def evaluate(scope, locals, &block)
@@ -366,7 +577,7 @@ module Tilt
366
577
  xml.target!
367
578
  end
368
579
 
369
- def template_source
580
+ def precompiled_template(locals)
370
581
  data.to_str
371
582
  end
372
583
  end
@@ -388,10 +599,11 @@ module Tilt
388
599
  # time when using this template engine.
389
600
  class LiquidTemplate < Template
390
601
  def initialize_engine
391
- require_template_library 'liquid' unless defined? ::Liquid::Template
602
+ return if defined? ::Liquid::Template
603
+ require_template_library 'liquid'
392
604
  end
393
605
 
394
- def compile!
606
+ def prepare
395
607
  @engine = ::Liquid::Template.parse(data)
396
608
  end
397
609
 
@@ -421,15 +633,17 @@ module Tilt
421
633
  end
422
634
 
423
635
  def initialize_engine
424
- require_template_library 'rdiscount' unless defined? ::RDiscount
636
+ return if defined? ::RDiscount
637
+ require_template_library 'rdiscount'
425
638
  end
426
639
 
427
- def compile!
640
+ def prepare
428
641
  @engine = RDiscount.new(data, *flags)
642
+ @output = nil
429
643
  end
430
644
 
431
645
  def evaluate(scope, locals, &block)
432
- @engine.to_html
646
+ @output ||= @engine.to_html
433
647
  end
434
648
  end
435
649
  register 'markdown', RDiscountTemplate
@@ -441,67 +655,22 @@ module Tilt
441
655
  # http://redcloth.org/
442
656
  class RedClothTemplate < Template
443
657
  def initialize_engine
444
- require_template_library 'redcloth' unless defined? ::RedCloth
658
+ return if defined? ::RedCloth
659
+ require_template_library 'redcloth'
445
660
  end
446
661
 
447
- def compile!
662
+ def prepare
448
663
  @engine = RedCloth.new(data)
664
+ @output = nil
449
665
  end
450
666
 
451
667
  def evaluate(scope, locals, &block)
452
- @engine.to_html
668
+ @output ||= @engine.to_html
453
669
  end
454
670
  end
455
671
  register 'textile', RedClothTemplate
456
672
 
457
673
 
458
- # Mustache is written and maintained by Chris Wanstrath. See:
459
- # http://github.com/defunkt/mustache
460
- #
461
- # When a scope argument is provided to MustacheTemplate#render, the
462
- # instance variables are copied from the scope object to the Mustache
463
- # view.
464
- class MustacheTemplate < Template
465
- attr_reader :engine
466
-
467
- def initialize_engine
468
- require_template_library 'mustache' unless defined? ::Mustache
469
- end
470
-
471
- def compile!
472
- Mustache.view_namespace = options[:namespace]
473
- Mustache.view_path = options[:view_path] || options[:mustaches]
474
- @engine = options[:view] || Mustache.view_class(name)
475
- options.each do |key, value|
476
- next if %w[view view_path namespace mustaches].include?(key.to_s)
477
- @engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
478
- end
479
- end
480
-
481
- def evaluate(scope=nil, locals={}, &block)
482
- instance = @engine.new
483
-
484
- # copy instance variables from scope to the view
485
- scope.instance_variables.each do |name|
486
- instance.instance_variable_set(name, scope.instance_variable_get(name))
487
- end
488
-
489
- # locals get added to the view's context
490
- locals.each do |local, value|
491
- instance[local] = value
492
- end
493
-
494
- # if we're passed a block it's a subview. Sticking it in yield
495
- # lets us use {{yield}} in layout.html to render the actual page.
496
- instance[:yield] = block.call if block
497
-
498
- instance.template = data unless instance.compiled?
499
-
500
- instance.to_html
501
- end
502
- end
503
- register 'mustache', MustacheTemplate
504
-
505
674
  # RDoc template. See:
506
675
  # http://rdoc.rubyforge.org/
507
676
  #
@@ -510,37 +679,54 @@ module Tilt
510
679
  # engine.
511
680
  class RDocTemplate < Template
512
681
  def initialize_engine
513
- unless defined?(::RDoc::Markup)
514
- require_template_library 'rdoc/markup'
515
- require_template_library 'rdoc/markup/to_html'
516
- end
682
+ return if defined?(::RDoc::Markup)
683
+ require_template_library 'rdoc/markup'
684
+ require_template_library 'rdoc/markup/to_html'
517
685
  end
518
686
 
519
- def compile!
687
+ def prepare
520
688
  markup = RDoc::Markup::ToHtml.new
521
689
  @engine = markup.convert(data)
690
+ @output = nil
522
691
  end
523
692
 
524
693
  def evaluate(scope, locals, &block)
525
- @engine.to_s
694
+ @output ||= @engine.to_s
526
695
  end
527
696
  end
528
697
  register 'rdoc', RDocTemplate
529
698
 
530
- # CoffeeScript info:
531
- # http://jashkenas.github.com/coffee-script/
532
- class CoffeeTemplate < Template
699
+
700
+ # Radius Template
701
+ # http://github.com/jlong/radius/
702
+ class RadiusTemplate < Template
533
703
  def initialize_engine
534
- require_template_library 'coffee-script' unless defined? ::CoffeeScript
704
+ return if defined? ::Radius
705
+ require_template_library 'radius'
535
706
  end
536
707
 
537
- def compile!
538
- @engine = ::CoffeeScript::compile(data, options)
708
+ def prepare
539
709
  end
540
710
 
541
711
  def evaluate(scope, locals, &block)
542
- @engine
712
+ context = Class.new(Radius::Context).new
713
+ context.define_tag("yield") do
714
+ block.call
715
+ end
716
+ locals.each do |tag, value|
717
+ context.define_tag(tag) do
718
+ value
719
+ end
720
+ end
721
+ (class << context; self; end).class_eval do
722
+ define_method :tag_missing do |tag, attr, &block|
723
+ scope.__send__(tag) # any way to support attr as args?
724
+ end
725
+ end
726
+ options = {:tag_prefix => 'r'}.merge(@options)
727
+ parser = Radius::Parser.new(context, options)
728
+ parser.parse(data)
543
729
  end
544
730
  end
545
- register 'coffee', CoffeeTemplate
731
+ register 'radius', RadiusTemplate
546
732
  end