tilt 0.6 → 0.8

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