tilt 0.5 → 0.7

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/README.md CHANGED
@@ -33,6 +33,12 @@ Support for these template engines is included with the package:
33
33
  RDiscount .markdown rdiscount
34
34
  RedCloth .textile redcloth
35
35
 
36
+ See [TEMPLATES.md][t] for detailed information on template engine
37
+ options and supported features.
38
+
39
+ [t]: http://github.com/rtomayko/tilt/blob/master/TEMPLATES.md
40
+ "Tilt Template Engine Documentation"
41
+
36
42
  Basic Usage
37
43
  -----------
38
44
 
data/Rakefile CHANGED
@@ -16,38 +16,39 @@ end
16
16
 
17
17
  # PACKAGING =================================================================
18
18
 
19
- require 'rubygems/specification'
20
- $spec ||= eval(File.read('tilt.gemspec'))
21
-
22
- def package(ext='')
23
- "dist/tilt-#{$spec.version}" + ext
24
- end
25
-
26
- desc 'Build packages'
27
- task :package => %w[.gem .tar.gz].map {|e| package(e)}
28
-
29
- desc 'Build and install as local gem'
30
- task :install => package('.gem') do
31
- sh "gem install #{package('.gem')}"
32
- end
33
-
34
- directory 'dist/'
35
-
36
- file package('.gem') => %w[dist/ tilt.gemspec] + $spec.files do |f|
37
- sh "gem build tilt.gemspec"
38
- mv File.basename(f.name), f.name
39
- end
40
-
41
- file package('.tar.gz') => %w[dist/] + $spec.files do |f|
42
- sh "git archive --format=tar HEAD | gzip > #{f.name}"
43
- end
44
-
45
- desc 'Upload gem and tar.gz distributables to rubyforge'
46
- task :release => [package('.gem'), package('.tar.gz')] do |t|
47
- sh <<-SH
48
- rubyforge add_release sinatra tilt #{$spec.version} #{package('.gem')} &&
49
- rubyforge add_file sinatra tilt #{$spec.version} #{package('.tar.gz')}
50
- SH
19
+ if defined?(Gem)
20
+ SPEC = eval(File.read('tilt.gemspec'))
21
+
22
+ def package(ext='')
23
+ "pkg/tilt-#{SPEC.version}" + ext
24
+ end
25
+
26
+ desc 'Build packages'
27
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
28
+
29
+ desc 'Build and install as local gem'
30
+ task :install => package('.gem') do
31
+ sh "gem install #{package('.gem')}"
32
+ end
33
+
34
+ directory 'pkg/'
35
+
36
+ file package('.gem') => %w[pkg/ tilt.gemspec] + SPEC.files do |f|
37
+ sh "gem build tilt.gemspec"
38
+ mv File.basename(f.name), f.name
39
+ end
40
+
41
+ file package('.tar.gz') => %w[pkg/] + SPEC.files do |f|
42
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
43
+ end
44
+
45
+ desc 'Upload gem and tar.gz distributables to rubyforge'
46
+ task :release => [package('.gem'), package('.tar.gz')] do |t|
47
+ sh <<-SH
48
+ rubyforge add_release sinatra tilt #{SPEC.version} #{package('.gem')} &&
49
+ rubyforge add_file sinatra tilt #{SPEC.version} #{package('.tar.gz')}
50
+ SH
51
+ end
51
52
  end
52
53
 
53
54
  # GEMSPEC ===================================================================
data/TEMPLATES.md CHANGED
@@ -67,6 +67,12 @@ of any combination of the following characters:
67
67
  The `$SAFE` level; when set, ERB code will be run in a
68
68
  separate thread with `$SAFE` set to the provided level.
69
69
 
70
+ #### `:outvar => '_erbout'`
71
+
72
+ The name of the variable used to accumulate template output. This can be
73
+ any valid Ruby expression but must be assignable. By default a local
74
+ variable named `_erbout` is used.
75
+
70
76
  ### See also
71
77
 
72
78
  * [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html)
@@ -98,14 +104,19 @@ Set pattern for embedded Ruby code.
98
104
 
99
105
  See the [ERB](#erb) template documentation for examples, usage, and options.
100
106
 
101
- __NOTE:__ It's suggested that your program `require 'erubis'` at load time when
102
- using this template engine within a threaded environment.
107
+ #### `:outvar => '_erbout'`
108
+
109
+ The name of the variable used to accumulate template output. This can be
110
+ any valid Ruby expression but must be assignable. By default a local
111
+ variable named `_erbout` is used.
103
112
 
104
113
  ### See also
105
114
 
106
115
  * [Erubis Home](http://www.kuwata-lab.com/erubis/)
107
116
  * [Erubis User's Guide](http://www.kuwata-lab.com/erubis/users-guide.html)
108
117
 
118
+ __NOTE:__ It's suggested that your program `require 'erubis'` at load time when
119
+ using this template engine within a threaded environment.
109
120
 
110
121
  <a name='haml'></a>
111
122
  Haml (`haml`)
@@ -308,9 +319,9 @@ The class or module where View classes are located. If you have
308
319
  `Hurl::App::Views`, namespace should be `Hurl:App`. This defaults to `Object`,
309
320
  causing `::Views` to be searched for classes.
310
321
 
311
- #### `:mustaches => nil`
322
+ #### `:mustaches => nil` or `:view_path => nil`
312
323
 
313
- Where mustache views (.rb files) are located, or nil to disable auto-requiring
324
+ Where mustache views (`.rb` files) are located, or `nil` to disable auto-requiring
314
325
  of views based on template names. By default, the view file is assumed to be in
315
326
  the same directory as the template file.
316
327
 
data/lib/tilt.rb CHANGED
@@ -1,10 +1,9 @@
1
1
  module Tilt
2
- VERSION = '0.5'
2
+ VERSION = '0.7'
3
3
 
4
4
  @template_mappings = {}
5
5
 
6
- # Hash of template path pattern => template implementation
7
- # class mappings.
6
+ # Hash of template path pattern => template implementation class mappings.
8
7
  def self.mappings
9
8
  @template_mappings
10
9
  end
@@ -25,7 +24,7 @@ module Tilt
25
24
  end
26
25
  end
27
26
 
28
- # Lookup a template class given for the given filename or file
27
+ # Lookup a template class for the given filename or file
29
28
  # extension. Return nil when no implementation is found.
30
29
  def self.[](file)
31
30
  if @template_mappings.key?(pattern = file.to_s.downcase)
@@ -44,9 +43,24 @@ module Tilt
44
43
  end
45
44
  end
46
45
 
46
+ # Mixin allowing template compilation on scope objects.
47
+ #
48
+ # Including this module in scope objects passed to Template#render
49
+ # causes template source to be compiled to methods the first time they're
50
+ # used. This can yield significant (5x-10x) performance increases for
51
+ # templates that support it (ERB, Erubis, Builder).
52
+ #
53
+ # It's also possible (though not recommended) to include this module in
54
+ # Object to enable template compilation globally. The downside is that
55
+ # the template methods will polute the global namespace and could lead to
56
+ # unexpected behavior.
57
+ module CompileSite
58
+ def __tilt__
59
+ end
60
+ end
47
61
 
48
62
  # Base class for template implementations. Subclasses must implement
49
- # the #compile! method and one of the #evaluate or #template_source
63
+ # the #prepare method and one of the #evaluate or #template_source
50
64
  # methods.
51
65
  class Template
52
66
  # Template source; loaded from a file or given directly.
@@ -63,51 +77,55 @@ module Tilt
63
77
  # interface.
64
78
  attr_reader :options
65
79
 
80
+ # Used to determine if this class's initialize_engine method has
81
+ # been called yet.
82
+ @engine_initialized = false
83
+ class << self
84
+ attr_accessor :engine_initialized
85
+ alias engine_initialized? engine_initialized
86
+ end
87
+
66
88
  # 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.
89
+ # default, template data is read from the file. When a block is given,
90
+ # it should read template data and return as a String. When file is nil,
91
+ # a block is required.
70
92
  #
71
- # The #initialize_engine method is called if this is the very first
72
- # time this template subclass has been initialized.
93
+ # All arguments are optional.
73
94
  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
95
+ @file, @line, @options = nil, 1, {}
96
+
97
+ [options, line, file].compact.each do |arg|
98
+ case
99
+ when arg.respond_to?(:to_str) ; @file = arg.to_str
100
+ when arg.respond_to?(:to_int) ; @line = arg.to_int
101
+ when arg.respond_to?(:to_hash) ; @options = arg.to_hash
102
+ else raise TypeError
103
+ end
104
+ end
105
+
106
+ raise ArgumentError, "file or block required" if (@file || block).nil?
107
+
108
+ # call the initialize_engine method if this is the very first time
109
+ # an instance of this class has been created.
110
+ if !self.class.engine_initialized?
82
111
  initialize_engine
83
112
  self.class.engine_initialized = true
84
113
  end
85
- end
86
114
 
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
115
+ # 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)}"
94
118
 
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
119
+ # load template data and prepare
120
+ @reader = block || lambda { |t| File.read(@file) }
121
+ @data = @reader.call(self)
122
+ prepare
104
123
  end
105
124
 
106
125
  # Render the template in the given scope with the locals specified. If a
107
126
  # block is given, it is typically available within the template via
108
127
  # +yield+.
109
128
  def render(scope=Object.new, locals={}, &block)
110
- compile
111
129
  evaluate scope, locals || {}, &block
112
130
  end
113
131
 
@@ -127,26 +145,56 @@ module Tilt
127
145
  end
128
146
 
129
147
  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.
148
+ # Called once and only once for each template subclass the first time
149
+ # the template class is initialized. This should be used to require the
150
+ # underlying template library and perform any initial setup.
151
+ def initialize_engine
152
+ end
153
+
154
+ # Do whatever preparation is necessary to setup the underlying template
155
+ # engine. Called immediately after template data is loaded. Instance
156
+ # variables set in this method are available when #evaluate is called.
133
157
  #
134
158
  # Subclasses must provide an implementation of this method.
135
- def compile!
136
- raise NotImplementedError
159
+ def prepare
160
+ if respond_to?(:compile!)
161
+ # backward compat with tilt < 0.6; just in case
162
+ warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
163
+ compile!
164
+ else
165
+ raise NotImplementedError
166
+ end
137
167
  end
138
168
 
139
- # Process the template and return the result. Subclasses should override
140
- # this method unless they implement the #template_source.
169
+ # Process the template and return the result. When the scope mixes in
170
+ # the Tilt::CompileSite module, the template is compiled to a method and
171
+ # reused given identical locals keys. When the scope object
172
+ # does not mix in the CompileSite module, the template source is
173
+ # evaluated with instance_eval. In any case, template executation
174
+ # is guaranteed to be performed in the scope object with the locals
175
+ # specified and with support for yielding to the block.
141
176
  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
177
+ if scope.respond_to?(:__tilt__)
178
+ method_name = compiled_method_name(locals.keys.hash)
179
+ if scope.respond_to?(method_name)
180
+ # fast path
181
+ scope.send method_name, locals, &block
182
+ else
183
+ # compile first and then run
184
+ compile_template_method(method_name, locals)
185
+ scope.send method_name, locals, &block
186
+ end
187
+ else
188
+ source, offset = local_assignment_code(locals)
189
+ source = [source, template_source].join("\n")
190
+ scope.instance_eval source, eval_file, line - offset
191
+ end
145
192
  end
146
193
 
147
194
  # Return a string containing the (Ruby) source code for the template. The
148
195
  # default Template#evaluate implementation requires this method be
149
- # defined.
196
+ # defined and guarantees correct file/line handling, custom scopes, and
197
+ # support for template compilation when the scope object allows it.
150
198
  def template_source
151
199
  raise NotImplementedError
152
200
  end
@@ -158,6 +206,41 @@ module Tilt
158
206
  [source.join("\n"), source.length]
159
207
  end
160
208
 
209
+ def compiled_method_name(locals_hash)
210
+ "#{@_prefix}L#{locals_hash.to_s(16).sub('-', 'n')}"
211
+ end
212
+
213
+ 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
220
+ def #{method_name}(locals)
221
+ #{source}
222
+ end
223
+ RUBY
224
+
225
+ # setup a finalizer to remove the newly added method
226
+ ObjectSpace.define_finalizer self,
227
+ Template.compiled_template_method_remover(CompileSite, method_name)
228
+ end
229
+
230
+ def self.compiled_template_method_remover(site, method_name)
231
+ proc { |oid| garbage_collect_compiled_template_method(site, method_name) }
232
+ end
233
+
234
+ def self.garbage_collect_compiled_template_method(site, method_name)
235
+ site.module_eval do
236
+ begin
237
+ remove_method(method_name)
238
+ rescue NameError
239
+ # method was already removed (ruby >= 1.9)
240
+ end
241
+ end
242
+ end
243
+
161
244
  def require_template_library(name)
162
245
  if Thread.list.size > 1
163
246
  warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
@@ -173,7 +256,7 @@ module Tilt
173
256
  # cache = Tilt::Cache.new
174
257
  # cache.fetch(path, line, options) { Tilt.new(path, line, options) }
175
258
  #
176
- # Subsequent invocations return the already compiled template object.
259
+ # Subsequent invocations return the already loaded template object.
177
260
  class Cache
178
261
  def initialize
179
262
  @cache = {}
@@ -195,7 +278,7 @@ module Tilt
195
278
  # The template source is evaluated as a Ruby string. The #{} interpolation
196
279
  # syntax can be used to generated dynamic output.
197
280
  class StringTemplate < Template
198
- def compile!
281
+ def prepare
199
282
  @code = "%Q{#{data}}"
200
283
  end
201
284
 
@@ -213,8 +296,9 @@ module Tilt
213
296
  require_template_library 'erb' unless defined? ::ERB
214
297
  end
215
298
 
216
- def compile!
217
- @engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
299
+ def prepare
300
+ @outvar = (options[:outvar] || '_erbout').to_s
301
+ @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
218
302
  end
219
303
 
220
304
  def template_source
@@ -222,22 +306,23 @@ module Tilt
222
306
  end
223
307
 
224
308
  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)
231
-
232
- scope.instance_eval source, eval_file, line - offset
233
-
234
- output = scope.instance_variable_get(:@_out_buf)
235
- scope.instance_variable_set(:@_out_buf, original_out_buf)
236
-
237
- output
309
+ preserve_outvar_value(scope) { super }
238
310
  end
239
311
 
240
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
325
+ end
241
326
 
242
327
  # ERB generates a line to specify the character coding of the generated
243
328
  # source in 1.9. Account for this in the line offset.
@@ -258,13 +343,17 @@ module Tilt
258
343
  require_template_library 'erubis' unless defined? ::Erubis
259
344
  end
260
345
 
261
- def compile!
262
- Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
346
+ def prepare
347
+ @options.merge!(:preamble => false, :postamble => false)
348
+ @outvar = (options.delete(:outvar) || '_erbout').to_s
263
349
  @engine = ::Erubis::Eruby.new(data, options)
264
350
  end
265
351
 
266
- private
352
+ def template_source
353
+ ["#{@outvar} = _buf = ''", @engine.src, "_buf.to_s"].join(";")
354
+ end
267
355
 
356
+ private
268
357
  # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
269
358
  # and adjust back.
270
359
  if RUBY_VERSION >= '1.9.0'
@@ -284,15 +373,37 @@ module Tilt
284
373
  require_template_library 'haml' unless defined? ::Haml::Engine
285
374
  end
286
375
 
287
- def compile!
376
+ def prepare
288
377
  @engine = ::Haml::Engine.new(data, haml_options)
289
378
  end
290
379
 
291
- def evaluate(scope, locals, &block)
292
- @engine.render(scope, locals, &block)
380
+ # Precompiled Haml source. Taken from the precompiled_with_ambles
381
+ # method in Haml::Precompiler:
382
+ # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
383
+ def template_source
384
+ @engine.instance_eval do
385
+ <<-RUBY
386
+ _haml_locals = locals
387
+ begin
388
+ extend Haml::Helpers
389
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
390
+ _erbout = _hamlout.buffer
391
+ __in_erb_template = true
392
+ #{precompiled}
393
+ #{precompiled_method_return_value}
394
+ ensure
395
+ @haml_buffer = @haml_buffer.upper
396
+ end
397
+ RUBY
398
+ end
293
399
  end
294
400
 
295
401
  private
402
+ def local_assignment_code(locals)
403
+ source, offset = super
404
+ [source, offset + 6]
405
+ end
406
+
296
407
  def haml_options
297
408
  options.merge(:filename => eval_file, :line => line)
298
409
  end
@@ -309,12 +420,12 @@ module Tilt
309
420
  require_template_library 'sass' unless defined? ::Sass::Engine
310
421
  end
311
422
 
312
- def compile!
423
+ def prepare
313
424
  @engine = ::Sass::Engine.new(data, sass_options)
314
425
  end
315
426
 
316
427
  def evaluate(scope, locals, &block)
317
- @engine.render
428
+ @output ||= @engine.render
318
429
  end
319
430
 
320
431
  private
@@ -334,7 +445,7 @@ module Tilt
334
445
  require_template_library 'less' unless defined? ::Less::Engine
335
446
  end
336
447
 
337
- def compile!
448
+ def prepare
338
449
  @engine = ::Less::Engine.new(data)
339
450
  end
340
451
 
@@ -344,6 +455,7 @@ module Tilt
344
455
  end
345
456
  register 'less', LessTemplate
346
457
 
458
+
347
459
  # Builder template implementation. See:
348
460
  # http://builder.rubyforge.org/
349
461
  class BuilderTemplate < Template
@@ -351,7 +463,7 @@ module Tilt
351
463
  require_template_library 'builder' unless defined?(::Builder)
352
464
  end
353
465
 
354
- def compile!
466
+ def prepare
355
467
  end
356
468
 
357
469
  def evaluate(scope, locals, &block)
@@ -390,7 +502,7 @@ module Tilt
390
502
  require_template_library 'liquid' unless defined? ::Liquid::Template
391
503
  end
392
504
 
393
- def compile!
505
+ def prepare
394
506
  @engine = ::Liquid::Template.parse(data)
395
507
  end
396
508
 
@@ -400,9 +512,8 @@ module Tilt
400
512
  scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
401
513
  locals = scope.merge(locals)
402
514
  end
403
- # TODO: Is it possible to lazy yield ?
404
515
  locals['yield'] = block.nil? ? '' : yield
405
- locals['content'] = block.nil? ? '' : yield
516
+ locals['content'] = locals['yield']
406
517
  @engine.render(locals)
407
518
  end
408
519
  end
@@ -424,7 +535,7 @@ module Tilt
424
535
  require_template_library 'rdiscount' unless defined? ::RDiscount
425
536
  end
426
537
 
427
- def compile!
538
+ def prepare
428
539
  @engine = RDiscount.new(data, *flags)
429
540
  end
430
541
 
@@ -437,22 +548,22 @@ module Tilt
437
548
  register 'md', RDiscountTemplate
438
549
 
439
550
 
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
551
+ # RedCloth implementation. See:
552
+ # http://redcloth.org/
553
+ class RedClothTemplate < Template
554
+ def initialize_engine
555
+ require_template_library 'redcloth' unless defined? ::RedCloth
556
+ end
446
557
 
447
- def compile!
448
- @engine = RedCloth.new(data)
449
- end
558
+ def prepare
559
+ @engine = RedCloth.new(data)
560
+ end
450
561
 
451
- def evaluate(scope, locals, &block)
452
- @engine.to_html
562
+ def evaluate(scope, locals, &block)
563
+ @engine.to_html
564
+ end
453
565
  end
454
- end
455
- register 'textile', RedClothTemplate
566
+ register 'textile', RedClothTemplate
456
567
 
457
568
 
458
569
  # Mustache is written and maintained by Chris Wanstrath. See:
@@ -468,11 +579,12 @@ register 'textile', RedClothTemplate
468
579
  require_template_library 'mustache' unless defined? ::Mustache
469
580
  end
470
581
 
471
- def compile!
582
+ def prepare
472
583
  Mustache.view_namespace = options[:namespace]
584
+ Mustache.view_path = options[:view_path] || options[:mustaches]
473
585
  @engine = options[:view] || Mustache.view_class(name)
474
586
  options.each do |key, value|
475
- next if %w[view namespace mustaches].include?(key.to_s)
587
+ next if %w[view view_path namespace mustaches].include?(key.to_s)
476
588
  @engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
477
589
  end
478
590
  end
@@ -501,6 +613,7 @@ register 'textile', RedClothTemplate
501
613
  end
502
614
  register 'mustache', MustacheTemplate
503
615
 
616
+
504
617
  # RDoc template. See:
505
618
  # http://rdoc.rubyforge.org/
506
619
  #
@@ -515,7 +628,7 @@ register 'textile', RedClothTemplate
515
628
  end
516
629
  end
517
630
 
518
- def compile!
631
+ def prepare
519
632
  markup = RDoc::Markup::ToHtml.new
520
633
  @engine = markup.convert(data)
521
634
  end
@@ -525,4 +638,22 @@ register 'textile', RedClothTemplate
525
638
  end
526
639
  end
527
640
  register 'rdoc', RDocTemplate
641
+
642
+
643
+ # CoffeeScript info:
644
+ # http://jashkenas.github.com/coffee-script/
645
+ class CoffeeTemplate < Template
646
+ def initialize_engine
647
+ require_template_library 'coffee-script' unless defined? ::CoffeeScript
648
+ end
649
+
650
+ def prepare
651
+ @engine = ::CoffeeScript::compile(data, options)
652
+ end
653
+
654
+ def evaluate(scope, locals, &block)
655
+ @engine
656
+ end
657
+ end
658
+ register 'coffee', CoffeeTemplate
528
659
  end
@@ -9,7 +9,7 @@ begin
9
9
  assert_equal Tilt::BuilderTemplate, Tilt['test.xml.builder']
10
10
  end
11
11
 
12
- test "compiling and evaluating the template on #render" do
12
+ test "preparing and evaluating the template on #render" do
13
13
  template = Tilt::BuilderTemplate.new { |t| "xml.em 'Hello World!'" }
14
14
  assert_equal "<em>Hello World!</em>\n", template.render
15
15
  end