tilt 0.7 → 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,5 +1,7 @@
1
+ require 'digest/md5'
2
+
1
3
  module Tilt
2
- VERSION = '0.7'
4
+ VERSION = '1.1'
3
5
 
4
6
  @template_mappings = {}
5
7
 
@@ -14,6 +16,11 @@ module Tilt
14
16
  mappings[ext.downcase] = template_class
15
17
  end
16
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
+
17
24
  # Create a new template for the given file using the file's extension
18
25
  # to determine the the template mapping.
19
26
  def self.new(file, line=nil, options={}, &block)
@@ -27,20 +34,12 @@ module Tilt
27
34
  # Lookup a template class for the given filename or file
28
35
  # extension. Return nil when no implementation is found.
29
36
  def self.[](file)
30
- if @template_mappings.key?(pattern = file.to_s.downcase)
31
- @template_mappings[pattern]
32
- elsif @template_mappings.key?(pattern = File.basename(pattern))
33
- @template_mappings[pattern]
34
- else
35
- while !pattern.empty?
36
- if @template_mappings.key?(pattern)
37
- return @template_mappings[pattern]
38
- else
39
- pattern = pattern.sub(/^[^.]*\.?/, '')
40
- end
41
- end
42
- nil
37
+ pattern = file.to_s.downcase
38
+ unless registered?(pattern)
39
+ pattern = File.basename(pattern)
40
+ pattern.sub!(/^[^.]*\.?/, '') until (pattern.empty? || registered?(pattern))
43
41
  end
42
+ @template_mappings[pattern]
44
43
  end
45
44
 
46
45
  # Mixin allowing template compilation on scope objects.
@@ -60,7 +59,7 @@ module Tilt
60
59
  end
61
60
 
62
61
  # Base class for template implementations. Subclasses must implement
63
- # the #prepare method and one of the #evaluate or #template_source
62
+ # the #prepare method and one of the #evaluate or #precompiled_template
64
63
  # methods.
65
64
  class Template
66
65
  # Template source; loaded from a file or given directly.
@@ -98,7 +97,7 @@ module Tilt
98
97
  case
99
98
  when arg.respond_to?(:to_str) ; @file = arg.to_str
100
99
  when arg.respond_to?(:to_int) ; @line = arg.to_int
101
- when arg.respond_to?(:to_hash) ; @options = arg.to_hash
100
+ when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
102
101
  else raise TypeError
103
102
  end
104
103
  end
@@ -113,8 +112,8 @@ module Tilt
113
112
  end
114
113
 
115
114
  # used to generate unique method names for template compilation
116
- stamp = (Time.now.to_f * 10000).to_i
117
- @_prefix = "__tilt_O#{object_id.to_s(16)}T#{stamp.to_s(16)}"
115
+ @stamp = (Time.now.to_f * 10000).to_i
116
+ @compiled_method_names = {}
118
117
 
119
118
  # load template data and prepare
120
119
  @reader = block || lambda { |t| File.read(@file) }
@@ -151,6 +150,16 @@ module Tilt
151
150
  def initialize_engine
152
151
  end
153
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
+
154
163
  # Do whatever preparation is necessary to setup the underlying template
155
164
  # engine. Called immediately after template data is loaded. Instance
156
165
  # variables set in this method are available when #evaluate is called.
@@ -175,54 +184,116 @@ module Tilt
175
184
  # specified and with support for yielding to the block.
176
185
  def evaluate(scope, locals, &block)
177
186
  if scope.respond_to?(:__tilt__)
178
- method_name = compiled_method_name(locals.keys.hash)
187
+ method_name = compiled_method_name(locals.keys)
179
188
  if scope.respond_to?(method_name)
180
- # fast path
181
- scope.send method_name, locals, &block
189
+ scope.send(method_name, locals, &block)
182
190
  else
183
- # compile first and then run
184
191
  compile_template_method(method_name, locals)
185
- scope.send method_name, locals, &block
192
+ scope.send(method_name, locals, &block)
186
193
  end
187
194
  else
188
- source, offset = local_assignment_code(locals)
189
- source = [source, template_source].join("\n")
190
- scope.instance_eval source, eval_file, line - offset
195
+ evaluate_source(scope, locals, &block)
191
196
  end
192
197
  end
193
198
 
194
- # Return a string containing the (Ruby) source code for the template. The
195
- # default Template#evaluate implementation requires this method be
196
- # defined and guarantees correct file/line handling, custom scopes, and
197
- # support for template compilation when the scope object allows it.
198
- 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)
199
225
  raise NotImplementedError
200
226
  end
201
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
+
202
250
  private
203
- def local_assignment_code(locals)
204
- return ['', 1] if locals.empty?
205
- source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
206
- [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)
255
+ end
256
+
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
207
275
  end
208
276
 
209
- def compiled_method_name(locals_hash)
210
- "#{@_prefix}L#{locals_hash.to_s(16).sub('-', 'n')}"
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}"
211
281
  end
212
282
 
213
283
  def compile_template_method(method_name, locals)
214
- source, offset = local_assignment_code(locals)
215
- source = [source, template_source].join("\n")
216
- offset += 1
217
-
218
- # add the new method
219
- CompileSite.module_eval <<-RUBY, eval_file, line - offset
284
+ source, offset = precompiled(locals)
285
+ offset += 5
286
+ CompileSite.class_eval <<-RUBY, eval_file, line - offset
220
287
  def #{method_name}(locals)
221
- #{source}
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
222
295
  end
223
296
  RUBY
224
-
225
- # setup a finalizer to remove the newly added method
226
297
  ObjectSpace.define_finalizer self,
227
298
  Template.compiled_template_method_remover(CompileSite, method_name)
228
299
  end
@@ -241,12 +312,25 @@ module Tilt
241
312
  end
242
313
  end
243
314
 
244
- def require_template_library(name)
245
- if Thread.list.size > 1
246
- warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
247
- "explicit require '#{name}' suggested."
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)
248
333
  end
249
- require name
250
334
  end
251
335
  end
252
336
 
@@ -282,7 +366,7 @@ module Tilt
282
366
  @code = "%Q{#{data}}"
283
367
  end
284
368
 
285
- def template_source
369
+ def precompiled_template(locals)
286
370
  @code
287
371
  end
288
372
  end
@@ -292,72 +376,99 @@ module Tilt
292
376
  # ERB template implementation. See:
293
377
  # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
294
378
  class ERBTemplate < Template
379
+ @@default_output_variable = '_erbout'
380
+
381
+ def self.default_output_variable
382
+ @@default_output_variable
383
+ end
384
+
385
+ def self.default_output_variable=(name)
386
+ @@default_output_variable = name
387
+ end
388
+
295
389
  def initialize_engine
296
- require_template_library 'erb' unless defined? ::ERB
390
+ return if defined? ::ERB
391
+ require_template_library 'erb'
297
392
  end
298
393
 
299
394
  def prepare
300
- @outvar = (options[:outvar] || '_erbout').to_s
395
+ @outvar = options[:outvar] || self.class.default_output_variable
301
396
  @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
302
397
  end
303
398
 
304
- def template_source
305
- @engine.src
399
+ def precompiled_template(locals)
400
+ source = @engine.src
401
+ source
306
402
  end
307
403
 
308
- def evaluate(scope, locals, &block)
309
- preserve_outvar_value(scope) { super }
404
+ def precompiled_preamble(locals)
405
+ <<-RUBY
406
+ begin
407
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
408
+ #{super}
409
+ RUBY
310
410
  end
311
411
 
312
- private
313
- # Retains the previous value of outvar when configured to use
314
- # an instance variable. This allows multiple templates to be rendered
315
- # within the context of an object without overwriting the outvar.
316
- def preserve_outvar_value(scope)
317
- if @outvar[0] == ?@
318
- previous = scope.instance_variable_get(@outvar)
319
- output = yield
320
- scope.instance_variable_set(@outvar, previous)
321
- output
322
- else
323
- yield
324
- end
412
+ def precompiled_postamble(locals)
413
+ <<-RUBY
414
+ #{super}
415
+ ensure
416
+ #{@outvar} = __original_outvar
417
+ end
418
+ RUBY
325
419
  end
326
420
 
327
421
  # ERB generates a line to specify the character coding of the generated
328
422
  # source in 1.9. Account for this in the line offset.
329
423
  if RUBY_VERSION >= '1.9.0'
330
- def local_assignment_code(locals)
424
+ def precompiled(locals)
331
425
  source, offset = super
332
426
  [source, offset + 1]
333
427
  end
334
428
  end
335
429
  end
430
+
336
431
  %w[erb rhtml].each { |ext| register ext, ERBTemplate }
337
432
 
338
433
 
339
434
  # Erubis template implementation. See:
340
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.
341
446
  class ErubisTemplate < ERBTemplate
342
447
  def initialize_engine
343
- require_template_library 'erubis' unless defined? ::Erubis
448
+ return if defined? ::Erubis
449
+ require_template_library 'erubis'
344
450
  end
345
451
 
346
452
  def prepare
347
453
  @options.merge!(:preamble => false, :postamble => false)
348
- @outvar = (options.delete(:outvar) || '_erbout').to_s
349
- @engine = ::Erubis::Eruby.new(data, options)
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)
350
458
  end
351
459
 
352
- def template_source
353
- ["#{@outvar} = _buf = ''", @engine.src, "_buf.to_s"].join(";")
460
+ def precompiled_preamble(locals)
461
+ [super, "#{@outvar} = _buf = ''"].join("\n")
354
462
  end
355
463
 
356
- private
357
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
358
- # and adjust back.
464
+ def precompiled_postamble(locals)
465
+ ["_buf", super].join("\n")
466
+ end
467
+
468
+ # Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
469
+ # Override and adjust back.
359
470
  if RUBY_VERSION >= '1.9.0'
360
- def local_assignment_code(locals)
471
+ def precompiled(locals)
361
472
  source, offset = super
362
473
  [source, offset - 1]
363
474
  end
@@ -370,26 +481,48 @@ module Tilt
370
481
  # http://haml.hamptoncatlin.com/
371
482
  class HamlTemplate < Template
372
483
  def initialize_engine
373
- require_template_library 'haml' unless defined? ::Haml::Engine
484
+ return if defined? ::Haml::Engine
485
+ require_template_library 'haml'
374
486
  end
375
487
 
376
488
  def prepare
377
- @engine = ::Haml::Engine.new(data, haml_options)
489
+ options = @options.merge(:filename => eval_file, :line => line)
490
+ @engine = ::Haml::Engine.new(data, options)
491
+ end
492
+
493
+ def evaluate(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
378
499
  end
379
500
 
380
501
  # Precompiled Haml source. Taken from the precompiled_with_ambles
381
502
  # method in Haml::Precompiler:
382
503
  # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
383
- def template_source
504
+ def precompiled_template(locals)
505
+ @engine.precompiled
506
+ end
507
+
508
+ def precompiled_preamble(locals)
509
+ local_assigns = super
384
510
  @engine.instance_eval do
385
511
  <<-RUBY
386
- _haml_locals = locals
387
512
  begin
388
513
  extend Haml::Helpers
389
514
  _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
390
515
  _erbout = _hamlout.buffer
391
516
  __in_erb_template = true
392
- #{precompiled}
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
393
526
  #{precompiled_method_return_value}
394
527
  ensure
395
528
  @haml_buffer = @haml_buffer.upper
@@ -397,16 +530,6 @@ module Tilt
397
530
  RUBY
398
531
  end
399
532
  end
400
-
401
- private
402
- def local_assignment_code(locals)
403
- source, offset = super
404
- [source, offset + 6]
405
- end
406
-
407
- def haml_options
408
- options.merge(:filename => eval_file, :line => line)
409
- end
410
533
  end
411
534
  register 'haml', HamlTemplate
412
535
 
@@ -417,7 +540,8 @@ module Tilt
417
540
  # Sass templates do not support object scopes, locals, or yield.
418
541
  class SassTemplate < Template
419
542
  def initialize_engine
420
- require_template_library 'sass' unless defined? ::Sass::Engine
543
+ return if defined? ::Sass::Engine
544
+ require_template_library 'sass'
421
545
  end
422
546
 
423
547
  def prepare
@@ -430,11 +554,19 @@ module Tilt
430
554
 
431
555
  private
432
556
  def sass_options
433
- options.merge(:filename => eval_file, :line => line)
557
+ options.merge(:filename => eval_file, :line => line, :syntax => :sass)
434
558
  end
435
559
  end
436
560
  register 'sass', SassTemplate
437
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
438
570
 
439
571
  # Lessscss template implementation. See:
440
572
  # http://lesscss.org/
@@ -442,7 +574,8 @@ module Tilt
442
574
  # Less templates do not support object scopes, locals, or yield.
443
575
  class LessTemplate < Template
444
576
  def initialize_engine
445
- require_template_library 'less' unless defined? ::Less::Engine
577
+ return if defined? ::Less::Engine
578
+ require_template_library 'less'
446
579
  end
447
580
 
448
581
  def prepare
@@ -456,11 +589,71 @@ module Tilt
456
589
  register 'less', LessTemplate
457
590
 
458
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
+
459
651
  # Builder template implementation. See:
460
652
  # http://builder.rubyforge.org/
461
653
  class BuilderTemplate < Template
462
654
  def initialize_engine
463
- require_template_library 'builder' unless defined?(::Builder)
655
+ return if defined?(::Builder)
656
+ require_template_library 'builder'
464
657
  end
465
658
 
466
659
  def prepare
@@ -477,7 +670,7 @@ module Tilt
477
670
  xml.target!
478
671
  end
479
672
 
480
- def template_source
673
+ def precompiled_template(locals)
481
674
  data.to_str
482
675
  end
483
676
  end
@@ -499,7 +692,8 @@ module Tilt
499
692
  # time when using this template engine.
500
693
  class LiquidTemplate < Template
501
694
  def initialize_engine
502
- require_template_library 'liquid' unless defined? ::Liquid::Template
695
+ return if defined? ::Liquid::Template
696
+ require_template_library 'liquid'
503
697
  end
504
698
 
505
699
  def prepare
@@ -532,15 +726,17 @@ module Tilt
532
726
  end
533
727
 
534
728
  def initialize_engine
535
- require_template_library 'rdiscount' unless defined? ::RDiscount
729
+ return if defined? ::RDiscount
730
+ require_template_library 'rdiscount'
536
731
  end
537
732
 
538
733
  def prepare
539
734
  @engine = RDiscount.new(data, *flags)
735
+ @output = nil
540
736
  end
541
737
 
542
738
  def evaluate(scope, locals, &block)
543
- @engine.to_html
739
+ @output ||= @engine.to_html
544
740
  end
545
741
  end
546
742
  register 'markdown', RDiscountTemplate
@@ -552,108 +748,121 @@ module Tilt
552
748
  # http://redcloth.org/
553
749
  class RedClothTemplate < Template
554
750
  def initialize_engine
555
- require_template_library 'redcloth' unless defined? ::RedCloth
751
+ return if defined? ::RedCloth
752
+ require_template_library 'redcloth'
556
753
  end
557
754
 
558
755
  def prepare
559
756
  @engine = RedCloth.new(data)
757
+ @output = nil
560
758
  end
561
759
 
562
760
  def evaluate(scope, locals, &block)
563
- @engine.to_html
761
+ @output ||= @engine.to_html
564
762
  end
565
763
  end
566
764
  register 'textile', RedClothTemplate
567
765
 
568
766
 
569
- # Mustache is written and maintained by Chris Wanstrath. See:
570
- # http://github.com/defunkt/mustache
767
+ # RDoc template. See:
768
+ # http://rdoc.rubyforge.org/
571
769
  #
572
- # When a scope argument is provided to MustacheTemplate#render, the
573
- # instance variables are copied from the scope object to the Mustache
574
- # view.
575
- class MustacheTemplate < Template
576
- attr_reader :engine
577
-
770
+ # It's suggested that your program require 'rdoc/markup' and
771
+ # 'rdoc/markup/to_html' at load time when using this template
772
+ # engine.
773
+ class RDocTemplate < Template
578
774
  def initialize_engine
579
- require_template_library 'mustache' unless defined? ::Mustache
775
+ return if defined?(::RDoc::Markup)
776
+ require_template_library 'rdoc/markup'
777
+ require_template_library 'rdoc/markup/to_html'
580
778
  end
581
779
 
582
780
  def prepare
583
- Mustache.view_namespace = options[:namespace]
584
- Mustache.view_path = options[:view_path] || options[:mustaches]
585
- @engine = options[:view] || Mustache.view_class(name)
586
- options.each do |key, value|
587
- next if %w[view view_path namespace mustaches].include?(key.to_s)
588
- @engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
589
- end
781
+ markup = RDoc::Markup::ToHtml.new
782
+ @engine = markup.convert(data)
783
+ @output = nil
590
784
  end
591
785
 
592
- def evaluate(scope=nil, locals={}, &block)
593
- instance = @engine.new
594
-
595
- # copy instance variables from scope to the view
596
- scope.instance_variables.each do |name|
597
- instance.instance_variable_set(name, scope.instance_variable_get(name))
598
- end
599
-
600
- # locals get added to the view's context
601
- locals.each do |local, value|
602
- instance[local] = value
603
- end
604
-
605
- # if we're passed a block it's a subview. Sticking it in yield
606
- # lets us use {{yield}} in layout.html to render the actual page.
607
- instance[:yield] = block.call if block
608
-
609
- instance.template = data unless instance.compiled?
610
-
611
- instance.to_html
786
+ def evaluate(scope, locals, &block)
787
+ @output ||= @engine.to_s
612
788
  end
613
789
  end
614
- register 'mustache', MustacheTemplate
790
+ register 'rdoc', RDocTemplate
615
791
 
616
792
 
617
- # RDoc template. See:
618
- # http://rdoc.rubyforge.org/
619
- #
620
- # It's suggested that your program require 'rdoc/markup' and
621
- # 'rdoc/markup/to_html' at load time when using this template
622
- # engine.
623
- class RDocTemplate < Template
793
+ # Radius Template
794
+ # http://github.com/jlong/radius/
795
+ class RadiusTemplate < Template
624
796
  def initialize_engine
625
- unless defined?(::RDoc::Markup)
626
- require_template_library 'rdoc/markup'
627
- require_template_library 'rdoc/markup/to_html'
628
- end
797
+ return if defined? ::Radius
798
+ require_template_library 'radius'
629
799
  end
630
800
 
631
801
  def prepare
632
- markup = RDoc::Markup::ToHtml.new
633
- @engine = markup.convert(data)
634
802
  end
635
803
 
636
804
  def evaluate(scope, locals, &block)
637
- @engine.to_s
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)
638
822
  end
639
823
  end
640
- register 'rdoc', RDocTemplate
824
+ register 'radius', RadiusTemplate
825
+
641
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
642
839
 
643
- # CoffeeScript info:
644
- # http://jashkenas.github.com/coffee-script/
645
- class CoffeeTemplate < Template
646
840
  def initialize_engine
647
- require_template_library 'coffee-script' unless defined? ::CoffeeScript
841
+ return if defined? ::Markaby
842
+ require_template_library 'markaby'
648
843
  end
649
844
 
650
845
  def prepare
651
- @engine = ::CoffeeScript::compile(data, options)
652
846
  end
653
847
 
654
848
  def evaluate(scope, locals, &block)
655
- @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
656
865
  end
657
866
  end
658
- register 'coffee', CoffeeTemplate
867
+ register 'mab', MarkabyTemplate
659
868
  end