tilt 0.6 → 1.1

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.1'
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 #precompiled_template
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,193 @@ 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 += 5
286
+ CompileSite.class_eval <<-RUBY, eval_file, line - offset
287
+ def #{method_name}(locals)
288
+ Thread.current[:tilt_vars] = [self, locals]
289
+ class << self
290
+ this, locals = Thread.current[:tilt_vars]
291
+ this.instance_eval do
292
+ #{source}
293
+ end
294
+ end
295
+ end
296
+ RUBY
297
+ ObjectSpace.define_finalizer self,
298
+ Template.compiled_template_method_remover(CompileSite, method_name)
299
+ end
300
+
301
+ def self.compiled_template_method_remover(site, method_name)
302
+ proc { |oid| garbage_collect_compiled_template_method(site, method_name) }
303
+ end
304
+
305
+ def self.garbage_collect_compiled_template_method(site, method_name)
306
+ site.module_eval do
307
+ begin
308
+ remove_method(method_name)
309
+ rescue NameError
310
+ # method was already removed (ruby >= 1.9)
311
+ end
312
+ end
313
+ end
314
+
315
+ # Special case Ruby 1.9.1's broken yield.
316
+ #
317
+ # http://github.com/rtomayko/tilt/commit/20c01a5
318
+ # http://redmine.ruby-lang.org/issues/show/3601
319
+ #
320
+ # Remove when 1.9.2 dominates 1.9.1 installs in the wild.
321
+ if RUBY_VERSION =~ /^1.9.1/
322
+ undef compile_template_method
323
+ def compile_template_method(method_name, locals)
324
+ source, offset = precompiled(locals)
325
+ offset += 1
326
+ CompileSite.module_eval <<-RUBY, eval_file, line - offset
327
+ def #{method_name}(locals)
328
+ #{source}
329
+ end
330
+ RUBY
331
+ ObjectSpace.define_finalizer self,
332
+ Template.compiled_template_method_remover(CompileSite, method_name)
166
333
  end
167
- require name
168
334
  end
169
335
  end
170
336
 
@@ -174,7 +340,7 @@ module Tilt
174
340
  # cache = Tilt::Cache.new
175
341
  # cache.fetch(path, line, options) { Tilt.new(path, line, options) }
176
342
  #
177
- # Subsequent invocations return the already compiled template object.
343
+ # Subsequent invocations return the already loaded template object.
178
344
  class Cache
179
345
  def initialize
180
346
  @cache = {}
@@ -196,11 +362,11 @@ module Tilt
196
362
  # The template source is evaluated as a Ruby string. The #{} interpolation
197
363
  # syntax can be used to generated dynamic output.
198
364
  class StringTemplate < Template
199
- def compile!
365
+ def prepare
200
366
  @code = "%Q{#{data}}"
201
367
  end
202
368
 
203
- def template_source
369
+ def precompiled_template(locals)
204
370
  @code
205
371
  end
206
372
  end
@@ -210,66 +376,99 @@ module Tilt
210
376
  # ERB template implementation. See:
211
377
  # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
212
378
  class ERBTemplate < Template
213
- def initialize_engine
214
- require_template_library 'erb' unless defined? ::ERB
215
- end
379
+ @@default_output_variable = '_erbout'
216
380
 
217
- def compile!
218
- @engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
381
+ def self.default_output_variable
382
+ @@default_output_variable
219
383
  end
220
384
 
221
- def template_source
222
- @engine.src
385
+ def self.default_output_variable=(name)
386
+ @@default_output_variable = name
223
387
  end
224
388
 
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)
389
+ def initialize_engine
390
+ return if defined? ::ERB
391
+ require_template_library 'erb'
392
+ end
232
393
 
233
- scope.instance_eval source, eval_file, line - offset
394
+ def prepare
395
+ @outvar = options[:outvar] || self.class.default_output_variable
396
+ @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
397
+ end
234
398
 
235
- output = scope.instance_variable_get(:@_out_buf)
236
- scope.instance_variable_set(:@_out_buf, original_out_buf)
399
+ def precompiled_template(locals)
400
+ source = @engine.src
401
+ source
402
+ end
237
403
 
238
- output
404
+ def precompiled_preamble(locals)
405
+ <<-RUBY
406
+ begin
407
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
408
+ #{super}
409
+ RUBY
239
410
  end
240
411
 
241
- private
412
+ def precompiled_postamble(locals)
413
+ <<-RUBY
414
+ #{super}
415
+ ensure
416
+ #{@outvar} = __original_outvar
417
+ end
418
+ RUBY
419
+ end
242
420
 
243
421
  # ERB generates a line to specify the character coding of the generated
244
422
  # source in 1.9. Account for this in the line offset.
245
423
  if RUBY_VERSION >= '1.9.0'
246
- def local_assignment_code(locals)
424
+ def precompiled(locals)
247
425
  source, offset = super
248
426
  [source, offset + 1]
249
427
  end
250
428
  end
251
429
  end
430
+
252
431
  %w[erb rhtml].each { |ext| register ext, ERBTemplate }
253
432
 
254
433
 
255
434
  # Erubis template implementation. See:
256
435
  # http://www.kuwata-lab.com/erubis/
436
+ #
437
+ # ErubisTemplate supports the following additional options, which are not
438
+ # passed down to the Erubis engine:
439
+ #
440
+ # :engine_class allows you to specify a custom engine class to use
441
+ # instead of the default (which is ::Erubis::Eruby).
442
+ #
443
+ # :escape_html when true, ::Erubis::EscapedEruby will be used as
444
+ # the engine class instead of the default. All content
445
+ # within <%= %> blocks will be automatically html escaped.
257
446
  class ErubisTemplate < ERBTemplate
258
447
  def initialize_engine
259
- require_template_library 'erubis' unless defined? ::Erubis
448
+ return if defined? ::Erubis
449
+ require_template_library 'erubis'
260
450
  end
261
451
 
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)
452
+ def prepare
453
+ @options.merge!(:preamble => false, :postamble => false)
454
+ @outvar = options.delete(:outvar) || self.class.default_output_variable
455
+ engine_class = options.delete(:engine_class)
456
+ engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
457
+ @engine = (engine_class || ::Erubis::Eruby).new(data, options)
265
458
  end
266
459
 
267
- private
460
+ def precompiled_preamble(locals)
461
+ [super, "#{@outvar} = _buf = ''"].join("\n")
462
+ end
463
+
464
+ def precompiled_postamble(locals)
465
+ ["_buf", super].join("\n")
466
+ end
268
467
 
269
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
270
- # and adjust back.
468
+ # Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
469
+ # Override and adjust back.
271
470
  if RUBY_VERSION >= '1.9.0'
272
- def local_assignment_code(locals)
471
+ def precompiled(locals)
273
472
  source, offset = super
274
473
  [source, offset - 1]
275
474
  end
@@ -282,20 +481,54 @@ module Tilt
282
481
  # http://haml.hamptoncatlin.com/
283
482
  class HamlTemplate < Template
284
483
  def initialize_engine
285
- require_template_library 'haml' unless defined? ::Haml::Engine
484
+ return if defined? ::Haml::Engine
485
+ require_template_library 'haml'
286
486
  end
287
487
 
288
- def compile!
289
- @engine = ::Haml::Engine.new(data, haml_options)
488
+ def prepare
489
+ options = @options.merge(:filename => eval_file, :line => line)
490
+ @engine = ::Haml::Engine.new(data, options)
290
491
  end
291
492
 
292
493
  def evaluate(scope, locals, &block)
293
- @engine.render(scope, locals, &block)
494
+ if @engine.respond_to?(:precompiled_method_return_value, true)
495
+ super
496
+ else
497
+ @engine.render(scope, locals, &block)
498
+ end
294
499
  end
295
500
 
296
- private
297
- def haml_options
298
- options.merge(:filename => eval_file, :line => line)
501
+ # Precompiled Haml source. Taken from the precompiled_with_ambles
502
+ # method in Haml::Precompiler:
503
+ # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
504
+ def precompiled_template(locals)
505
+ @engine.precompiled
506
+ end
507
+
508
+ def precompiled_preamble(locals)
509
+ local_assigns = super
510
+ @engine.instance_eval do
511
+ <<-RUBY
512
+ begin
513
+ extend Haml::Helpers
514
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
515
+ _erbout = _hamlout.buffer
516
+ __in_erb_template = true
517
+ _haml_locals = locals
518
+ #{local_assigns}
519
+ RUBY
520
+ end
521
+ end
522
+
523
+ def precompiled_postamble(locals)
524
+ @engine.instance_eval do
525
+ <<-RUBY
526
+ #{precompiled_method_return_value}
527
+ ensure
528
+ @haml_buffer = @haml_buffer.upper
529
+ end
530
+ RUBY
531
+ end
299
532
  end
300
533
  end
301
534
  register 'haml', HamlTemplate
@@ -307,24 +540,33 @@ module Tilt
307
540
  # Sass templates do not support object scopes, locals, or yield.
308
541
  class SassTemplate < Template
309
542
  def initialize_engine
310
- require_template_library 'sass' unless defined? ::Sass::Engine
543
+ return if defined? ::Sass::Engine
544
+ require_template_library 'sass'
311
545
  end
312
546
 
313
- def compile!
547
+ def prepare
314
548
  @engine = ::Sass::Engine.new(data, sass_options)
315
549
  end
316
550
 
317
551
  def evaluate(scope, locals, &block)
318
- @engine.render
552
+ @output ||= @engine.render
319
553
  end
320
554
 
321
555
  private
322
556
  def sass_options
323
- options.merge(:filename => eval_file, :line => line)
557
+ options.merge(:filename => eval_file, :line => line, :syntax => :sass)
324
558
  end
325
559
  end
326
560
  register 'sass', SassTemplate
327
561
 
562
+ # Sass's new .scss type template implementation.
563
+ class ScssTemplate < SassTemplate
564
+ private
565
+ def sass_options
566
+ options.merge(:filename => eval_file, :line => line, :syntax => :scss)
567
+ end
568
+ end
569
+ register 'scss', ScssTemplate
328
570
 
329
571
  # Lessscss template implementation. See:
330
572
  # http://lesscss.org/
@@ -332,10 +574,11 @@ module Tilt
332
574
  # Less templates do not support object scopes, locals, or yield.
333
575
  class LessTemplate < Template
334
576
  def initialize_engine
335
- require_template_library 'less' unless defined? ::Less::Engine
577
+ return if defined? ::Less::Engine
578
+ require_template_library 'less'
336
579
  end
337
580
 
338
- def compile!
581
+ def prepare
339
582
  @engine = ::Less::Engine.new(data)
340
583
  end
341
584
 
@@ -345,14 +588,75 @@ module Tilt
345
588
  end
346
589
  register 'less', LessTemplate
347
590
 
591
+
592
+ # CoffeeScript template implementation. See:
593
+ # http://coffeescript.org/
594
+ #
595
+ # CoffeeScript templates do not support object scopes, locals, or yield.
596
+ class CoffeeScriptTemplate < Template
597
+ @@default_no_wrap = false
598
+
599
+ def self.default_no_wrap
600
+ @@default_no_wrap
601
+ end
602
+
603
+ def self.default_no_wrap=(value)
604
+ @@default_no_wrap = value
605
+ end
606
+
607
+ def initialize_engine
608
+ return if defined? ::CoffeeScript
609
+ require_template_library 'coffee_script'
610
+ end
611
+
612
+ def prepare
613
+ @no_wrap = options.key?(:no_wrap) ? options[:no_wrap] :
614
+ self.class.default_no_wrap
615
+ end
616
+
617
+ def evaluate(scope, locals, &block)
618
+ @output ||= CoffeeScript.compile(data, :no_wrap => @no_wrap)
619
+ end
620
+ end
621
+ register 'coffee', CoffeeScriptTemplate
622
+
623
+
624
+ # Nokogiri template implementation. See:
625
+ # http://nokogiri.org/
626
+ class NokogiriTemplate < Template
627
+ def initialize_engine
628
+ return if defined?(::Nokogiri)
629
+ require_template_library 'nokogiri'
630
+ end
631
+
632
+ def prepare; end
633
+
634
+ def evaluate(scope, locals, &block)
635
+ xml = ::Nokogiri::XML::Builder.new
636
+ if data.respond_to?(:to_str)
637
+ locals[:xml] = xml
638
+ super(scope, locals, &block)
639
+ elsif data.kind_of?(Proc)
640
+ data.call(xml)
641
+ end
642
+ xml.to_xml
643
+ end
644
+
645
+ def precompiled_template(locals)
646
+ data.to_str
647
+ end
648
+ end
649
+ register 'nokogiri', NokogiriTemplate
650
+
348
651
  # Builder template implementation. See:
349
652
  # http://builder.rubyforge.org/
350
653
  class BuilderTemplate < Template
351
654
  def initialize_engine
352
- require_template_library 'builder' unless defined?(::Builder)
655
+ return if defined?(::Builder)
656
+ require_template_library 'builder'
353
657
  end
354
658
 
355
- def compile!
659
+ def prepare
356
660
  end
357
661
 
358
662
  def evaluate(scope, locals, &block)
@@ -366,7 +670,7 @@ module Tilt
366
670
  xml.target!
367
671
  end
368
672
 
369
- def template_source
673
+ def precompiled_template(locals)
370
674
  data.to_str
371
675
  end
372
676
  end
@@ -388,10 +692,11 @@ module Tilt
388
692
  # time when using this template engine.
389
693
  class LiquidTemplate < Template
390
694
  def initialize_engine
391
- require_template_library 'liquid' unless defined? ::Liquid::Template
695
+ return if defined? ::Liquid::Template
696
+ require_template_library 'liquid'
392
697
  end
393
698
 
394
- def compile!
699
+ def prepare
395
700
  @engine = ::Liquid::Template.parse(data)
396
701
  end
397
702
 
@@ -421,15 +726,17 @@ module Tilt
421
726
  end
422
727
 
423
728
  def initialize_engine
424
- require_template_library 'rdiscount' unless defined? ::RDiscount
729
+ return if defined? ::RDiscount
730
+ require_template_library 'rdiscount'
425
731
  end
426
732
 
427
- def compile!
733
+ def prepare
428
734
  @engine = RDiscount.new(data, *flags)
735
+ @output = nil
429
736
  end
430
737
 
431
738
  def evaluate(scope, locals, &block)
432
- @engine.to_html
739
+ @output ||= @engine.to_html
433
740
  end
434
741
  end
435
742
  register 'markdown', RDiscountTemplate
@@ -441,67 +748,22 @@ module Tilt
441
748
  # http://redcloth.org/
442
749
  class RedClothTemplate < Template
443
750
  def initialize_engine
444
- require_template_library 'redcloth' unless defined? ::RedCloth
751
+ return if defined? ::RedCloth
752
+ require_template_library 'redcloth'
445
753
  end
446
754
 
447
- def compile!
755
+ def prepare
448
756
  @engine = RedCloth.new(data)
757
+ @output = nil
449
758
  end
450
759
 
451
760
  def evaluate(scope, locals, &block)
452
- @engine.to_html
761
+ @output ||= @engine.to_html
453
762
  end
454
763
  end
455
764
  register 'textile', RedClothTemplate
456
765
 
457
766
 
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
767
  # RDoc template. See:
506
768
  # http://rdoc.rubyforge.org/
507
769
  #
@@ -510,37 +772,97 @@ module Tilt
510
772
  # engine.
511
773
  class RDocTemplate < Template
512
774
  def initialize_engine
513
- unless defined?(::RDoc::Markup)
514
- require_template_library 'rdoc/markup'
515
- require_template_library 'rdoc/markup/to_html'
516
- end
775
+ return if defined?(::RDoc::Markup)
776
+ require_template_library 'rdoc/markup'
777
+ require_template_library 'rdoc/markup/to_html'
517
778
  end
518
779
 
519
- def compile!
780
+ def prepare
520
781
  markup = RDoc::Markup::ToHtml.new
521
782
  @engine = markup.convert(data)
783
+ @output = nil
522
784
  end
523
785
 
524
786
  def evaluate(scope, locals, &block)
525
- @engine.to_s
787
+ @output ||= @engine.to_s
526
788
  end
527
789
  end
528
790
  register 'rdoc', RDocTemplate
529
791
 
530
- # CoffeeScript info:
531
- # http://jashkenas.github.com/coffee-script/
532
- class CoffeeTemplate < Template
792
+
793
+ # Radius Template
794
+ # http://github.com/jlong/radius/
795
+ class RadiusTemplate < Template
796
+ def initialize_engine
797
+ return if defined? ::Radius
798
+ require_template_library 'radius'
799
+ end
800
+
801
+ def prepare
802
+ end
803
+
804
+ def evaluate(scope, locals, &block)
805
+ context = Class.new(Radius::Context).new
806
+ context.define_tag("yield") do
807
+ block.call
808
+ end
809
+ locals.each do |tag, value|
810
+ context.define_tag(tag) do
811
+ value
812
+ end
813
+ end
814
+ (class << context; self; end).class_eval do
815
+ define_method :tag_missing do |tag, attr|
816
+ scope.__send__(tag) # any way to support attr as args?
817
+ end
818
+ end
819
+ options = {:tag_prefix => 'r'}.merge(@options)
820
+ parser = Radius::Parser.new(context, options)
821
+ parser.parse(data)
822
+ end
823
+ end
824
+ register 'radius', RadiusTemplate
825
+
826
+
827
+ # Markaby
828
+ # http://github.com/markaby/markaby
829
+ class MarkabyTemplate < Template
830
+ def self.builder_class
831
+ @builder_class ||= Class.new(Markaby::Builder) do
832
+ def __capture_markaby_tilt__(&block)
833
+ __run_markaby_tilt__ do
834
+ text capture(&block)
835
+ end
836
+ end
837
+ end
838
+ end
839
+
533
840
  def initialize_engine
534
- require_template_library 'coffee-script' unless defined? ::CoffeeScript
841
+ return if defined? ::Markaby
842
+ require_template_library 'markaby'
535
843
  end
536
844
 
537
- def compile!
538
- @engine = ::CoffeeScript::compile(data, options)
845
+ def prepare
539
846
  end
540
847
 
541
848
  def evaluate(scope, locals, &block)
542
- @engine
849
+ builder = self.class.builder_class.new({}, scope)
850
+ builder.locals = locals
851
+
852
+ if block
853
+ builder.instance_eval <<-CODE, __FILE__, __LINE__
854
+ def __run_markaby_tilt__
855
+ #{data}
856
+ end
857
+ CODE
858
+
859
+ builder.__capture_markaby_tilt__(&block)
860
+ else
861
+ builder.instance_eval(data, __FILE__, __LINE__)
862
+ end
863
+
864
+ builder.to_s
543
865
  end
544
866
  end
545
- register 'coffee', CoffeeTemplate
867
+ register 'mab', MarkabyTemplate
546
868
  end