tilt 0.5 → 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.5'
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,51 +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
-
81
- 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?
82
110
  initialize_engine
83
111
  self.class.engine_initialized = true
84
112
  end
85
- end
86
113
 
87
- # Called once and only once for each template subclass the first time
88
- # the template class is initialized. This should be used to require the
89
- # underlying template library and perform any initial setup.
90
- def initialize_engine
91
- end
92
- @engine_initialized = false
93
- class << self ; attr_accessor :engine_initialized ; end
114
+ # used to generate unique method names for template compilation
115
+ @stamp = (Time.now.to_f * 10000).to_i
116
+ @compiled_method_names = {}
94
117
 
95
-
96
- # Load template source and compile the template. The template is
97
- # loaded and compiled the first time this method is called; subsequent
98
- # calls are no-ops.
99
- def compile
100
- if @data.nil?
101
- @data = @reader.call(self)
102
- compile!
103
- end
118
+ # load template data and prepare
119
+ @reader = block || lambda { |t| File.read(@file) }
120
+ @data = @reader.call(self)
121
+ prepare
104
122
  end
105
123
 
106
124
  # Render the template in the given scope with the locals specified. If a
107
125
  # block is given, it is typically available within the template via
108
126
  # +yield+.
109
127
  def render(scope=Object.new, locals={}, &block)
110
- compile
111
128
  evaluate scope, locals || {}, &block
112
129
  end
113
130
 
@@ -127,43 +144,167 @@ module Tilt
127
144
  end
128
145
 
129
146
  protected
130
- # Do whatever preparation is necessary to "compile" the template.
131
- # Called immediately after template #data is loaded. Instance variables
132
- # 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.
133
166
  #
134
167
  # Subclasses must provide an implementation of this method.
135
- def compile!
136
- 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
137
176
  end
138
177
 
139
- # Process the template and return the result. Subclasses should override
140
- # 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.
141
185
  def evaluate(scope, locals, &block)
142
- source, offset = local_assignment_code(locals)
143
- source = [source, template_source].join("\n")
144
- 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
145
197
  end
146
198
 
147
- # Return a string containing the (Ruby) source code for the template. The
148
- # default Template#evaluate implementation requires this method be
149
- # defined.
150
- 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)
151
225
  raise NotImplementedError
152
226
  end
153
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
+
154
250
  private
155
- def local_assignment_code(locals)
156
- return ['', 1] if locals.empty?
157
- source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
158
- [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)
159
255
  end
160
256
 
161
- def require_template_library(name)
162
- if Thread.list.size > 1
163
- warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
164
- "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
165
307
  end
166
- require name
167
308
  end
168
309
  end
169
310
 
@@ -173,7 +314,7 @@ module Tilt
173
314
  # cache = Tilt::Cache.new
174
315
  # cache.fetch(path, line, options) { Tilt.new(path, line, options) }
175
316
  #
176
- # Subsequent invocations return the already compiled template object.
317
+ # Subsequent invocations return the already loaded template object.
177
318
  class Cache
178
319
  def initialize
179
320
  @cache = {}
@@ -195,11 +336,11 @@ module Tilt
195
336
  # The template source is evaluated as a Ruby string. The #{} interpolation
196
337
  # syntax can be used to generated dynamic output.
197
338
  class StringTemplate < Template
198
- def compile!
339
+ def prepare
199
340
  @code = "%Q{#{data}}"
200
341
  end
201
342
 
202
- def template_source
343
+ def precompiled_template(locals)
203
344
  @code
204
345
  end
205
346
  end
@@ -209,66 +350,99 @@ module Tilt
209
350
  # ERB template implementation. See:
210
351
  # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
211
352
  class ERBTemplate < Template
212
- def initialize_engine
213
- require_template_library 'erb' unless defined? ::ERB
214
- end
353
+ @@default_output_variable = '_erbout'
215
354
 
216
- def compile!
217
- @engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
355
+ def self.default_output_variable
356
+ @@default_output_variable
218
357
  end
219
358
 
220
- def template_source
221
- @engine.src
359
+ def self.default_output_variable=(name)
360
+ @@default_output_variable = name
222
361
  end
223
362
 
224
- def evaluate(scope, locals, &block)
225
- source, offset = local_assignment_code(locals)
226
- source = [source, template_source].join("\n")
227
-
228
- original_out_buf =
229
- scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
230
- scope.instance_variable_get(:@_out_buf)
363
+ def initialize_engine
364
+ return if defined? ::ERB
365
+ require_template_library 'erb'
366
+ end
231
367
 
232
- 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
233
372
 
234
- output = scope.instance_variable_get(:@_out_buf)
235
- scope.instance_variable_set(:@_out_buf, original_out_buf)
373
+ def precompiled_template(locals)
374
+ source = @engine.src
375
+ source
376
+ end
236
377
 
237
- output
378
+ def precompiled_preamble(locals)
379
+ <<-RUBY
380
+ begin
381
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
382
+ #{super}
383
+ RUBY
238
384
  end
239
385
 
240
- private
386
+ def precompiled_postamble(locals)
387
+ <<-RUBY
388
+ #{super}
389
+ ensure
390
+ #{@outvar} = __original_outvar
391
+ end
392
+ RUBY
393
+ end
241
394
 
242
395
  # ERB generates a line to specify the character coding of the generated
243
396
  # source in 1.9. Account for this in the line offset.
244
397
  if RUBY_VERSION >= '1.9.0'
245
- def local_assignment_code(locals)
398
+ def precompiled(locals)
246
399
  source, offset = super
247
400
  [source, offset + 1]
248
401
  end
249
402
  end
250
403
  end
404
+
251
405
  %w[erb rhtml].each { |ext| register ext, ERBTemplate }
252
406
 
253
407
 
254
408
  # Erubis template implementation. See:
255
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.
256
420
  class ErubisTemplate < ERBTemplate
257
421
  def initialize_engine
258
- require_template_library 'erubis' unless defined? ::Erubis
422
+ return if defined? ::Erubis
423
+ require_template_library 'erubis'
259
424
  end
260
425
 
261
- def compile!
262
- Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
263
- @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)
264
432
  end
265
433
 
266
- private
434
+ def precompiled_preamble(locals)
435
+ [super, "#{@outvar} = _buf = ''"].join("\n")
436
+ end
267
437
 
268
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
269
- # 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.
270
444
  if RUBY_VERSION >= '1.9.0'
271
- def local_assignment_code(locals)
445
+ def precompiled(locals)
272
446
  source, offset = super
273
447
  [source, offset - 1]
274
448
  end
@@ -281,20 +455,54 @@ module Tilt
281
455
  # http://haml.hamptoncatlin.com/
282
456
  class HamlTemplate < Template
283
457
  def initialize_engine
284
- require_template_library 'haml' unless defined? ::Haml::Engine
458
+ return if defined? ::Haml::Engine
459
+ require_template_library 'haml'
285
460
  end
286
461
 
287
- def compile!
288
- @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)
289
465
  end
290
466
 
291
467
  def evaluate(scope, locals, &block)
292
- @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
293
473
  end
294
474
 
295
- private
296
- def haml_options
297
- 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
298
506
  end
299
507
  end
300
508
  register 'haml', HamlTemplate
@@ -306,15 +514,16 @@ module Tilt
306
514
  # Sass templates do not support object scopes, locals, or yield.
307
515
  class SassTemplate < Template
308
516
  def initialize_engine
309
- require_template_library 'sass' unless defined? ::Sass::Engine
517
+ return if defined? ::Sass::Engine
518
+ require_template_library 'sass'
310
519
  end
311
520
 
312
- def compile!
521
+ def prepare
313
522
  @engine = ::Sass::Engine.new(data, sass_options)
314
523
  end
315
524
 
316
525
  def evaluate(scope, locals, &block)
317
- @engine.render
526
+ @output ||= @engine.render
318
527
  end
319
528
 
320
529
  private
@@ -331,10 +540,11 @@ module Tilt
331
540
  # Less templates do not support object scopes, locals, or yield.
332
541
  class LessTemplate < Template
333
542
  def initialize_engine
334
- require_template_library 'less' unless defined? ::Less::Engine
543
+ return if defined? ::Less::Engine
544
+ require_template_library 'less'
335
545
  end
336
546
 
337
- def compile!
547
+ def prepare
338
548
  @engine = ::Less::Engine.new(data)
339
549
  end
340
550
 
@@ -344,14 +554,16 @@ module Tilt
344
554
  end
345
555
  register 'less', LessTemplate
346
556
 
557
+
347
558
  # Builder template implementation. See:
348
559
  # http://builder.rubyforge.org/
349
560
  class BuilderTemplate < Template
350
561
  def initialize_engine
351
- require_template_library 'builder' unless defined?(::Builder)
562
+ return if defined?(::Builder)
563
+ require_template_library 'builder'
352
564
  end
353
565
 
354
- def compile!
566
+ def prepare
355
567
  end
356
568
 
357
569
  def evaluate(scope, locals, &block)
@@ -365,7 +577,7 @@ module Tilt
365
577
  xml.target!
366
578
  end
367
579
 
368
- def template_source
580
+ def precompiled_template(locals)
369
581
  data.to_str
370
582
  end
371
583
  end
@@ -387,10 +599,11 @@ module Tilt
387
599
  # time when using this template engine.
388
600
  class LiquidTemplate < Template
389
601
  def initialize_engine
390
- require_template_library 'liquid' unless defined? ::Liquid::Template
602
+ return if defined? ::Liquid::Template
603
+ require_template_library 'liquid'
391
604
  end
392
605
 
393
- def compile!
606
+ def prepare
394
607
  @engine = ::Liquid::Template.parse(data)
395
608
  end
396
609
 
@@ -400,9 +613,8 @@ module Tilt
400
613
  scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
401
614
  locals = scope.merge(locals)
402
615
  end
403
- # TODO: Is it possible to lazy yield ?
404
616
  locals['yield'] = block.nil? ? '' : yield
405
- locals['content'] = block.nil? ? '' : yield
617
+ locals['content'] = locals['yield']
406
618
  @engine.render(locals)
407
619
  end
408
620
  end
@@ -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
@@ -437,69 +651,25 @@ module Tilt
437
651
  register 'md', RDiscountTemplate
438
652
 
439
653
 
440
- # RedCloth implementation. See:
441
- # http://redcloth.org/
442
- class RedClothTemplate < Template
443
- def initialize_engine
444
- require_template_library 'redcloth' unless defined? ::RedCloth
445
- end
446
-
447
- def compile!
448
- @engine = RedCloth.new(data)
449
- end
450
-
451
- def evaluate(scope, locals, &block)
452
- @engine.to_html
453
- end
454
- end
455
- register 'textile', RedClothTemplate
456
-
457
-
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
-
654
+ # RedCloth implementation. See:
655
+ # http://redcloth.org/
656
+ class RedClothTemplate < Template
467
657
  def initialize_engine
468
- require_template_library 'mustache' unless defined? ::Mustache
658
+ return if defined? ::RedCloth
659
+ require_template_library 'redcloth'
469
660
  end
470
661
 
471
- def compile!
472
- Mustache.view_namespace = options[:namespace]
473
- @engine = options[:view] || Mustache.view_class(name)
474
- options.each do |key, value|
475
- next if %w[view namespace mustaches].include?(key.to_s)
476
- @engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
477
- end
662
+ def prepare
663
+ @engine = RedCloth.new(data)
664
+ @output = nil
478
665
  end
479
666
 
480
- def evaluate(scope=nil, locals={}, &block)
481
- instance = @engine.new
482
-
483
- # copy instance variables from scope to the view
484
- scope.instance_variables.each do |name|
485
- instance.instance_variable_set(name, scope.instance_variable_get(name))
486
- end
487
-
488
- # locals get added to the view's context
489
- locals.each do |local, value|
490
- instance[local] = value
491
- end
492
-
493
- # if we're passed a block it's a subview. Sticking it in yield
494
- # lets us use {{yield}} in layout.html to render the actual page.
495
- instance[:yield] = block.call if block
496
-
497
- instance.template = data unless instance.compiled?
498
-
499
- instance.to_html
667
+ def evaluate(scope, locals, &block)
668
+ @output ||= @engine.to_html
500
669
  end
501
670
  end
502
- register 'mustache', MustacheTemplate
671
+ register 'textile', RedClothTemplate
672
+
503
673
 
504
674
  # RDoc template. See:
505
675
  # http://rdoc.rubyforge.org/
@@ -509,20 +679,54 @@ register 'textile', RedClothTemplate
509
679
  # engine.
510
680
  class RDocTemplate < Template
511
681
  def initialize_engine
512
- unless defined?(::RDoc::Markup)
513
- require_template_library 'rdoc/markup'
514
- require_template_library 'rdoc/markup/to_html'
515
- end
682
+ return if defined?(::RDoc::Markup)
683
+ require_template_library 'rdoc/markup'
684
+ require_template_library 'rdoc/markup/to_html'
516
685
  end
517
686
 
518
- def compile!
687
+ def prepare
519
688
  markup = RDoc::Markup::ToHtml.new
520
689
  @engine = markup.convert(data)
690
+ @output = nil
521
691
  end
522
692
 
523
693
  def evaluate(scope, locals, &block)
524
- @engine.to_s
694
+ @output ||= @engine.to_s
525
695
  end
526
696
  end
527
697
  register 'rdoc', RDocTemplate
698
+
699
+
700
+ # Radius Template
701
+ # http://github.com/jlong/radius/
702
+ class RadiusTemplate < Template
703
+ def initialize_engine
704
+ return if defined? ::Radius
705
+ require_template_library 'radius'
706
+ end
707
+
708
+ def prepare
709
+ end
710
+
711
+ def evaluate(scope, locals, &block)
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)
729
+ end
730
+ end
731
+ register 'radius', RadiusTemplate
528
732
  end