tilt 0.7 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Ryan Tomayko <http://tomayko.com/about>
1
+ Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/README.md CHANGED
@@ -13,7 +13,8 @@ feature is relevant to the engine):
13
13
  * Ability to pass locals to template evaluation
14
14
  * Support for passing a block to template evaluation for "yield"
15
15
  * Backtraces with correct filenames and line numbers
16
- * Template compilation caching and reloading
16
+ * Template file caching and reloading
17
+ * Fast, method-based template source compilation
17
18
 
18
19
  The primary goal is to get all of the things listed above right for all
19
20
  template engines included in the distribution.
@@ -145,8 +146,49 @@ Or, use BlueCloth for markdown instead of RDiscount:
145
146
 
146
147
  Tilt.register 'markdown', Tilt::BlueClothTemplate
147
148
 
149
+ Template Compilation
150
+ --------------------
151
+
152
+ Tilt can compile generated Ruby source code produced by template engines and
153
+ reuse on subsequent template invocations. Benchmarks show this yields a 5x-10x
154
+ performance increase over evaluating the Ruby source on each invocation.
155
+
156
+ Template compilation is currently supported for these template engines:
157
+ StringTemplate, ERB, Erubis, Haml, and Builder.
158
+
159
+ To enable template compilation, the `Tilt::CompileSite` module must be mixed in
160
+ to the scope object passed to the template's `#render` method. This can be
161
+ accomplished by including (with `Module#include`) the module in the class used
162
+ for scope objects or by extending (with `Object#extend`) scope objects before
163
+ passing to `Template#render`:
164
+
165
+ require 'tilt'
166
+
167
+ template = Tilt::ERBTemplate.new('foo.erb')
168
+
169
+ # Slow. Uses Object#instance_eval to process template
170
+ class Scope
171
+ end
172
+ scope = Scope.new
173
+ template.render(scope)
174
+
175
+ # Fast. Uses compiled template and Object#send to process template
176
+ class Scope
177
+ include Tilt::CompileSite
178
+ end
179
+ scope = Scope.new
180
+ template.render(scope)
181
+
182
+ # Also fast, though a bit a slower due to having to extend each time
183
+ scope = Object.new
184
+ scope.extend Tilt::CompileSite
185
+ template.render(scope)
186
+
187
+ When the `Tilt::CompileSite` module is not present, template execution falls
188
+ back to evaluating the template from source on each invocation.
189
+
148
190
  LICENSE
149
191
  -------
150
192
 
151
- Tilt is Copyright (c) 2009 [Ryan Tomayko](http://tomayko.com/about) and
152
- distributed under the MIT license. See the COPYING file for more info.
193
+ Tilt is Copyright (c) 2010 [Ryan Tomayko](http://tomayko.com/about) and
194
+ distributed under the MIT license. See the `COPYING` file for more info.
data/bin/tilt CHANGED
@@ -3,17 +3,21 @@ require 'ostruct'
3
3
  require 'optparse'
4
4
  require 'tilt'
5
5
 
6
- usage = <<EOF
7
- Usage: tilt [OPTIONS] [FILE]
8
- Process template FILE and generate output. With no FILE or when FILE
9
- is '-', read template from stdin and use the --type option to determine
10
- the template's type.
6
+ usage = <<USAGE
7
+ Usage: tilt <options> <file>
8
+ Process template <file> and write output to stdout. With no <file> or
9
+ when <file> is '-', read template from stdin and use the --type option
10
+ to determine the template's type.
11
11
 
12
12
  Options
13
- -l, --list List supported template engines + file patterns and exit.
14
- -t, --type=PATTERN Use this template engine; required when FILE is stdin.
13
+ -l, --list List template engines + file patterns and exit
14
+ -t, --type=<pattern> Use this template engine; required if no <file>
15
+ -y, --layout=<file> Use <file> as a layout template
15
16
 
16
- -h, --help Show this help message.
17
+ -D<name>=<value> Define variable <name> as <value>
18
+ -o, --vars=<ruby> Evaluate <ruby> to Hash and use for variables
19
+
20
+ -h, --help Show this help message
17
21
 
18
22
  Convert markdown to HTML:
19
23
  $ tilt foo.markdown > foo.html
@@ -21,11 +25,18 @@ Convert markdown to HTML:
21
25
  Process ERB template:
22
26
  $ echo "Answer: <%= 2 + 2 %>" | tilt -t erb
23
27
  Answer: 4
24
- EOF
28
+
29
+ Define variables:
30
+ $ echo "Answer: <%= 2 + n %>" | tilt --locals="{:n=>40, :x=>0}"
31
+ Answer: 42
32
+ $ echo "Answer: <%= 2 + n %>" | tilt -Dn=40 -Dx=0
33
+ Answer: 42
34
+ USAGE
25
35
 
26
36
  script_name = File.basename($0)
27
37
  pattern = nil
28
38
  layout = nil
39
+ locals = {}
29
40
 
30
41
  ARGV.options do |o|
31
42
  o.program_name = script_name
@@ -44,18 +55,31 @@ ARGV.options do |o|
44
55
  end
45
56
 
46
57
  # the template type / pattern
47
- o.on("-t", "--type=PATTERN") do |val|
48
- fail "unknown template type: #{val}" if Tilt[val].nil?
58
+ o.on("-t", "--type=PATTERN", String) do |val|
59
+ abort "unknown template type: #{val}" if Tilt[val].nil?
49
60
  pattern = val
50
61
  end
51
62
 
52
63
  # pass template output into the specified layout template
53
- o.on("-y", "--layout=FILE") do |file|
64
+ o.on("-y", "--layout=FILE", String) do |file|
54
65
  paths = [file, "~/.tilt/#{file}", "/etc/tilt/#{file}"]
55
66
  layout = paths.
56
67
  map { |p| File.expand_path(p) }.
57
68
  find { |p| File.exist?(p) }
58
- fail "no such layout: #{file}" if layout.nil?
69
+ abort "no such layout: #{file}" if layout.nil?
70
+ end
71
+
72
+ # define a local variable
73
+ o.on("-D", "--define=PAIR", String) do |pair|
74
+ key, value = pair.split(/[=:]/, 2)
75
+ locals[key.to_sym] = value
76
+ end
77
+
78
+ # define local variables using a Ruby hash
79
+ o.on("--vars=RUBY") do |ruby|
80
+ hash = eval(ruby)
81
+ abort "vars must be a Hash, not #{hash.inspect}" if !hash.is_a?(Hash)
82
+ hash.each { |key, value| locals[key.to_sym] = value }
59
83
  end
60
84
 
61
85
  o.on_tail("-h", "--help") { puts usage; exit }
@@ -65,17 +89,22 @@ end
65
89
 
66
90
  file = ARGV.first || '-'
67
91
  pattern = file if pattern.nil?
92
+ abort "template type not given. see: #{$0} --help" if ['-', ''].include?(pattern)
93
+
94
+ engine = Tilt[pattern]
95
+ abort "template engine not found for: #{pattern}" if engine.nil?
96
+
68
97
  template =
69
- Tilt[pattern].new(file) {
98
+ engine.new(file) {
70
99
  if file == '-'
71
100
  $stdin.read
72
101
  else
73
102
  File.read(file)
74
103
  end
75
104
  }
76
- output = template.render
105
+ output = template.render(self, locals)
77
106
 
78
107
  # process layout
79
- output = Tilt.new(layout).render { output } if layout
108
+ output = Tilt.new(layout).render(self, locals) { output } if layout
80
109
 
81
110
  $stdout.write(output)
data/lib/tilt.rb CHANGED
@@ -1,5 +1,7 @@
1
+ require 'digest/md5'
2
+
1
3
  module Tilt
2
- VERSION = '0.7'
4
+ VERSION = '0.8'
3
5
 
4
6
  @template_mappings = {}
5
7
 
@@ -113,8 +115,8 @@ module Tilt
113
115
  end
114
116
 
115
117
  # 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)}"
118
+ @stamp = (Time.now.to_f * 10000).to_i
119
+ @compiled_method_names = {}
118
120
 
119
121
  # load template data and prepare
120
122
  @reader = block || lambda { |t| File.read(@file) }
@@ -151,6 +153,16 @@ module Tilt
151
153
  def initialize_engine
152
154
  end
153
155
 
156
+ # Like Kernel::require but issues a warning urging a manual require when
157
+ # running under a threaded environment.
158
+ def require_template_library(name)
159
+ if Thread.list.size > 1
160
+ warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
161
+ "explicit require '#{name}' suggested."
162
+ end
163
+ require name
164
+ end
165
+
154
166
  # Do whatever preparation is necessary to setup the underlying template
155
167
  # engine. Called immediately after template data is loaded. Instance
156
168
  # variables set in this method are available when #evaluate is called.
@@ -175,54 +187,111 @@ module Tilt
175
187
  # specified and with support for yielding to the block.
176
188
  def evaluate(scope, locals, &block)
177
189
  if scope.respond_to?(:__tilt__)
178
- method_name = compiled_method_name(locals.keys.hash)
190
+ method_name = compiled_method_name(locals.keys)
179
191
  if scope.respond_to?(method_name)
180
- # fast path
181
- scope.send method_name, locals, &block
192
+ scope.send(method_name, locals, &block)
182
193
  else
183
- # compile first and then run
184
194
  compile_template_method(method_name, locals)
185
- scope.send method_name, locals, &block
195
+ scope.send(method_name, locals, &block)
186
196
  end
187
197
  else
188
- source, offset = local_assignment_code(locals)
189
- source = [source, template_source].join("\n")
190
- scope.instance_eval source, eval_file, line - offset
198
+ evaluate_source(scope, locals, &block)
191
199
  end
192
200
  end
193
201
 
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
202
+ # Generates all template source by combining the preamble, template, and
203
+ # postamble and returns a two-tuple of the form: [source, offset], where
204
+ # source is the string containing (Ruby) source code for the template and
205
+ # offset is the integer line offset where line reporting should begin.
206
+ #
207
+ # Template subclasses may override this method when they need complete
208
+ # control over source generation or want to adjust the default line
209
+ # offset. In most cases, overriding the #precompiled_template method is
210
+ # easier and more appropriate.
211
+ def precompiled(locals)
212
+ preamble = precompiled_preamble(locals)
213
+ parts = [
214
+ preamble,
215
+ precompiled_template(locals),
216
+ precompiled_postamble(locals)
217
+ ]
218
+ [parts.join("\n"), preamble.count("\n") + 1]
219
+ end
220
+
221
+ # A string containing the (Ruby) source code for the template. The
222
+ # default Template#evaluate implementation requires either this method
223
+ # or the #precompiled method be overridden. When defined, the base
224
+ # Template guarantees correct file/line handling, locals support, custom
225
+ # scopes, and support for template compilation when the scope object
226
+ # allows it.
227
+ def precompiled_template(locals)
199
228
  raise NotImplementedError
200
229
  end
201
230
 
231
+ # Generates preamble code for initializing template state, and performing
232
+ # locals assignment. The default implementation performs locals
233
+ # assignment only. Lines included in the preamble are subtracted from the
234
+ # source line offset, so adding code to the preamble does not effect line
235
+ # reporting in Kernel::caller and backtraces.
236
+ def precompiled_preamble(locals)
237
+ locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
238
+ end
239
+
240
+ # Generates postamble code for the precompiled template source. The
241
+ # string returned from this method is appended to the precompiled
242
+ # template source.
243
+ def precompiled_postamble(locals)
244
+ ''
245
+ end
246
+
247
+ # The unique compiled method name for the locals keys provided.
248
+ def compiled_method_name(locals_keys)
249
+ @compiled_method_names[locals_keys] ||=
250
+ generate_compiled_method_name(locals_keys)
251
+ end
252
+
202
253
  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]
254
+ # Evaluate the template source in the context of the scope object.
255
+ def evaluate_source(scope, locals, &block)
256
+ source, offset = precompiled(locals)
257
+ scope.instance_eval(source, eval_file, line - offset)
207
258
  end
208
259
 
209
- def compiled_method_name(locals_hash)
210
- "#{@_prefix}L#{locals_hash.to_s(16).sub('-', 'n')}"
260
+ # JRuby doesn't allow Object#instance_eval to yield to the block it's
261
+ # closed over. This is by design and (ostensibly) something that will
262
+ # change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
263
+ # exhibits the behavior. More info here:
264
+ #
265
+ # http://jira.codehaus.org/browse/JRUBY-2599
266
+ #
267
+ # Additionally, JRuby's eval line reporting is off by one compared to
268
+ # all MRI versions tested.
269
+ #
270
+ # We redefine evaluate_source to work around both issues.
271
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
272
+ undef evaluate_source
273
+ def evaluate_source(scope, locals, &block)
274
+ source, offset = precompiled(locals)
275
+ file, lineno = eval_file, (line - offset) - 1
276
+ scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
277
+ end
278
+ end
279
+
280
+ def generate_compiled_method_name(locals_keys)
281
+ parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
282
+ digest = Digest::MD5.hexdigest(parts.join(':'))
283
+ "__tilt_#{digest}"
211
284
  end
212
285
 
213
286
  def compile_template_method(method_name, locals)
214
- source, offset = local_assignment_code(locals)
215
- source = [source, template_source].join("\n")
287
+ source, offset = precompiled(locals)
216
288
  offset += 1
217
-
218
- # add the new method
219
289
  CompileSite.module_eval <<-RUBY, eval_file, line - offset
220
290
  def #{method_name}(locals)
221
291
  #{source}
222
292
  end
223
293
  RUBY
224
294
 
225
- # setup a finalizer to remove the newly added method
226
295
  ObjectSpace.define_finalizer self,
227
296
  Template.compiled_template_method_remover(CompileSite, method_name)
228
297
  end
@@ -240,14 +309,6 @@ module Tilt
240
309
  end
241
310
  end
242
311
  end
243
-
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."
248
- end
249
- require name
250
- end
251
312
  end
252
313
 
253
314
  # Extremely simple template cache implementation. Calling applications
@@ -282,7 +343,7 @@ module Tilt
282
343
  @code = "%Q{#{data}}"
283
344
  end
284
345
 
285
- def template_source
346
+ def precompiled_template(locals)
286
347
  @code
287
348
  end
288
349
  end
@@ -293,7 +354,8 @@ module Tilt
293
354
  # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
294
355
  class ERBTemplate < Template
295
356
  def initialize_engine
296
- require_template_library 'erb' unless defined? ::ERB
357
+ return if defined? ::ERB
358
+ require_template_library 'erb'
297
359
  end
298
360
 
299
361
  def prepare
@@ -301,38 +363,37 @@ module Tilt
301
363
  @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
302
364
  end
303
365
 
304
- def template_source
366
+ def precompiled_template(locals)
305
367
  @engine.src
306
368
  end
307
369
 
308
- def evaluate(scope, locals, &block)
309
- preserve_outvar_value(scope) { super }
370
+ def precompiled_preamble(locals)
371
+ <<-RUBY
372
+ begin
373
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
374
+ #{super}
375
+ RUBY
310
376
  end
311
377
 
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
378
+ def precompiled_postamble(locals)
379
+ <<-RUBY
380
+ #{super}
381
+ ensure
382
+ #{@outvar} = __original_outvar
383
+ end
384
+ RUBY
325
385
  end
326
386
 
327
387
  # ERB generates a line to specify the character coding of the generated
328
388
  # source in 1.9. Account for this in the line offset.
329
389
  if RUBY_VERSION >= '1.9.0'
330
- def local_assignment_code(locals)
390
+ def precompiled(locals)
331
391
  source, offset = super
332
392
  [source, offset + 1]
333
393
  end
334
394
  end
335
395
  end
396
+
336
397
  %w[erb rhtml].each { |ext| register ext, ERBTemplate }
337
398
 
338
399
 
@@ -340,7 +401,8 @@ module Tilt
340
401
  # http://www.kuwata-lab.com/erubis/
341
402
  class ErubisTemplate < ERBTemplate
342
403
  def initialize_engine
343
- require_template_library 'erubis' unless defined? ::Erubis
404
+ return if defined? ::Erubis
405
+ require_template_library 'erubis'
344
406
  end
345
407
 
346
408
  def prepare
@@ -349,15 +411,18 @@ module Tilt
349
411
  @engine = ::Erubis::Eruby.new(data, options)
350
412
  end
351
413
 
352
- def template_source
353
- ["#{@outvar} = _buf = ''", @engine.src, "_buf.to_s"].join(";")
414
+ def precompiled_preamble(locals)
415
+ [super, "#{@outvar} = _buf = ''"].join("\n")
354
416
  end
355
417
 
356
- private
357
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
358
- # and adjust back.
418
+ def precompiled_postamble(locals)
419
+ ["_buf", super].join("\n")
420
+ end
421
+
422
+ # Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
423
+ # Override and adjust back.
359
424
  if RUBY_VERSION >= '1.9.0'
360
- def local_assignment_code(locals)
425
+ def precompiled(locals)
361
426
  source, offset = super
362
427
  [source, offset - 1]
363
428
  end
@@ -370,26 +435,48 @@ module Tilt
370
435
  # http://haml.hamptoncatlin.com/
371
436
  class HamlTemplate < Template
372
437
  def initialize_engine
373
- require_template_library 'haml' unless defined? ::Haml::Engine
438
+ return if defined? ::Haml::Engine
439
+ require_template_library 'haml'
374
440
  end
375
441
 
376
442
  def prepare
377
- @engine = ::Haml::Engine.new(data, haml_options)
443
+ options = @options.merge(:filename => eval_file, :line => line)
444
+ @engine = ::Haml::Engine.new(data, options)
445
+ end
446
+
447
+ def evaluate(scope, locals, &block)
448
+ if @engine.respond_to?(:precompiled_method_return_value, true)
449
+ super
450
+ else
451
+ @engine.render(scope, locals, &block)
452
+ end
378
453
  end
379
454
 
380
455
  # Precompiled Haml source. Taken from the precompiled_with_ambles
381
456
  # method in Haml::Precompiler:
382
457
  # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
383
- def template_source
458
+ def precompiled_template(locals)
459
+ @engine.precompiled
460
+ end
461
+
462
+ def precompiled_preamble(locals)
463
+ local_assigns = super
384
464
  @engine.instance_eval do
385
465
  <<-RUBY
386
- _haml_locals = locals
387
466
  begin
388
467
  extend Haml::Helpers
389
468
  _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
390
469
  _erbout = _hamlout.buffer
391
470
  __in_erb_template = true
392
- #{precompiled}
471
+ _haml_locals = locals
472
+ #{local_assigns}
473
+ RUBY
474
+ end
475
+ end
476
+
477
+ def precompiled_postamble(locals)
478
+ @engine.instance_eval do
479
+ <<-RUBY
393
480
  #{precompiled_method_return_value}
394
481
  ensure
395
482
  @haml_buffer = @haml_buffer.upper
@@ -397,16 +484,6 @@ module Tilt
397
484
  RUBY
398
485
  end
399
486
  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
487
  end
411
488
  register 'haml', HamlTemplate
412
489
 
@@ -417,7 +494,8 @@ module Tilt
417
494
  # Sass templates do not support object scopes, locals, or yield.
418
495
  class SassTemplate < Template
419
496
  def initialize_engine
420
- require_template_library 'sass' unless defined? ::Sass::Engine
497
+ return if defined? ::Sass::Engine
498
+ require_template_library 'sass'
421
499
  end
422
500
 
423
501
  def prepare
@@ -442,7 +520,8 @@ module Tilt
442
520
  # Less templates do not support object scopes, locals, or yield.
443
521
  class LessTemplate < Template
444
522
  def initialize_engine
445
- require_template_library 'less' unless defined? ::Less::Engine
523
+ return if defined? ::Less::Engine
524
+ require_template_library 'less'
446
525
  end
447
526
 
448
527
  def prepare
@@ -460,7 +539,8 @@ module Tilt
460
539
  # http://builder.rubyforge.org/
461
540
  class BuilderTemplate < Template
462
541
  def initialize_engine
463
- require_template_library 'builder' unless defined?(::Builder)
542
+ return if defined?(::Builder)
543
+ require_template_library 'builder'
464
544
  end
465
545
 
466
546
  def prepare
@@ -477,7 +557,7 @@ module Tilt
477
557
  xml.target!
478
558
  end
479
559
 
480
- def template_source
560
+ def precompiled_template(locals)
481
561
  data.to_str
482
562
  end
483
563
  end
@@ -499,7 +579,8 @@ module Tilt
499
579
  # time when using this template engine.
500
580
  class LiquidTemplate < Template
501
581
  def initialize_engine
502
- require_template_library 'liquid' unless defined? ::Liquid::Template
582
+ return if defined? ::Liquid::Template
583
+ require_template_library 'liquid'
503
584
  end
504
585
 
505
586
  def prepare
@@ -532,15 +613,17 @@ module Tilt
532
613
  end
533
614
 
534
615
  def initialize_engine
535
- require_template_library 'rdiscount' unless defined? ::RDiscount
616
+ return if defined? ::RDiscount
617
+ require_template_library 'rdiscount'
536
618
  end
537
619
 
538
620
  def prepare
539
621
  @engine = RDiscount.new(data, *flags)
622
+ @output = nil
540
623
  end
541
624
 
542
625
  def evaluate(scope, locals, &block)
543
- @engine.to_html
626
+ @output ||= @engine.to_html
544
627
  end
545
628
  end
546
629
  register 'markdown', RDiscountTemplate
@@ -552,15 +635,17 @@ module Tilt
552
635
  # http://redcloth.org/
553
636
  class RedClothTemplate < Template
554
637
  def initialize_engine
555
- require_template_library 'redcloth' unless defined? ::RedCloth
638
+ return if defined? ::RedCloth
639
+ require_template_library 'redcloth'
556
640
  end
557
641
 
558
642
  def prepare
559
643
  @engine = RedCloth.new(data)
644
+ @output = nil
560
645
  end
561
646
 
562
647
  def evaluate(scope, locals, &block)
563
- @engine.to_html
648
+ @output ||= @engine.to_html
564
649
  end
565
650
  end
566
651
  register 'textile', RedClothTemplate
@@ -576,7 +661,8 @@ module Tilt
576
661
  attr_reader :engine
577
662
 
578
663
  def initialize_engine
579
- require_template_library 'mustache' unless defined? ::Mustache
664
+ return if defined? ::Mustache
665
+ require_template_library 'mustache'
580
666
  end
581
667
 
582
668
  def prepare
@@ -622,19 +708,19 @@ module Tilt
622
708
  # engine.
623
709
  class RDocTemplate < Template
624
710
  def initialize_engine
625
- unless defined?(::RDoc::Markup)
626
- require_template_library 'rdoc/markup'
627
- require_template_library 'rdoc/markup/to_html'
628
- end
711
+ return if defined?(::RDoc::Markup)
712
+ require_template_library 'rdoc/markup'
713
+ require_template_library 'rdoc/markup/to_html'
629
714
  end
630
715
 
631
716
  def prepare
632
717
  markup = RDoc::Markup::ToHtml.new
633
718
  @engine = markup.convert(data)
719
+ @output = nil
634
720
  end
635
721
 
636
722
  def evaluate(scope, locals, &block)
637
- @engine.to_s
723
+ @output ||= @engine.to_s
638
724
  end
639
725
  end
640
726
  register 'rdoc', RDocTemplate
@@ -644,15 +730,16 @@ module Tilt
644
730
  # http://jashkenas.github.com/coffee-script/
645
731
  class CoffeeTemplate < Template
646
732
  def initialize_engine
647
- require_template_library 'coffee-script' unless defined? ::CoffeeScript
733
+ return if defined? ::CoffeeScript
734
+ require_template_library 'coffee-script'
648
735
  end
649
736
 
650
737
  def prepare
651
- @engine = ::CoffeeScript::compile(data, options)
738
+ @output = nil
652
739
  end
653
740
 
654
741
  def evaluate(scope, locals, &block)
655
- @engine
742
+ @output ||= ::CoffeeScript::compile(data, options)
656
743
  end
657
744
  end
658
745
  register 'coffee', CoffeeTemplate
@@ -1,5 +1,6 @@
1
1
  require 'contest'
2
2
  require 'tilt'
3
+ require 'thread'
3
4
 
4
5
  class CompileSiteTest < Test::Unit::TestCase
5
6
  def setup
@@ -10,7 +11,7 @@ class CompileSiteTest < Test::Unit::TestCase
10
11
  def prepare
11
12
  end
12
13
 
13
- def template_source
14
+ def precompiled_template(locals)
14
15
  @data.inspect
15
16
  end
16
17
  end
@@ -22,7 +23,7 @@ class CompileSiteTest < Test::Unit::TestCase
22
23
  test "compiling template source to a method" do
23
24
  template = CompilingTemplate.new { |t| "Hello World!" }
24
25
  template.render(Scope.new)
25
- method_name = template.send(:compiled_method_name, [].hash)
26
+ method_name = template.send(:compiled_method_name, [])
26
27
  method_name = method_name.to_sym if Symbol === Kernel.methods.first
27
28
  assert Tilt::CompileSite.instance_methods.include?(method_name),
28
29
  "CompileSite.instance_methods.include?(#{method_name.inspect})"
@@ -32,7 +33,7 @@ class CompileSiteTest < Test::Unit::TestCase
32
33
 
33
34
  test 'garbage collecting compiled methods' do
34
35
  template = CompilingTemplate.new { '' }
35
- method_name = template.send(:compiled_method_name, [].hash)
36
+ method_name = template.send(:compiled_method_name, [])
36
37
  template.render(Scope.new)
37
38
  assert Scope.new.respond_to?(method_name)
38
39
  Tilt::Template.send(
@@ -46,7 +47,7 @@ class CompileSiteTest < Test::Unit::TestCase
46
47
  def self.create_and_destroy_template
47
48
  template = CompilingTemplate.new { 'Hello World' }
48
49
  template.render(Scope.new)
49
- method_name = template.send(:compiled_method_name, [].hash)
50
+ method_name = template.send(:compiled_method_name, [])
50
51
  method_name = method_name.to_sym if Symbol === Kernel.methods.first
51
52
  [template.object_id, method_name]
52
53
  end
@@ -59,4 +60,27 @@ class CompileSiteTest < Test::Unit::TestCase
59
60
  assert !Scope.new.respond_to?(finalized_method_name),
60
61
  "Scope.new.respond_to?(#{finalized_method_name.inspect})"
61
62
  end
63
+
64
+ # This test attempts to surface issues with compiling templates from
65
+ # multiple threads.
66
+ test "using compiled templates from multiple threads" do
67
+ template = CompilingTemplate.new { 'template' }
68
+ main_thread = Thread.current
69
+ 10.times do |i|
70
+ threads =
71
+ (1..50).map do |j|
72
+ Thread.new {
73
+ begin
74
+ locals = { "local#{i}" => 'value' }
75
+ res = template.render(self, locals)
76
+ thread_id = Thread.current.object_id
77
+ res = template.render(self, "local#{thread_id.to_s}" => 'value')
78
+ rescue => boom
79
+ main_thread.raise(boom)
80
+ end
81
+ }
82
+ end
83
+ threads.each { |t| t.join }
84
+ end
85
+ end
62
86
  end
@@ -102,7 +102,7 @@ class CompiledERBTemplateTest < Test::Unit::TestCase
102
102
  test "compiling template source to a method" do
103
103
  template = Tilt::ERBTemplate.new { |t| "Hello World!" }
104
104
  template.render(Scope.new)
105
- method_name = template.send(:compiled_method_name, [].hash)
105
+ method_name = template.send(:compiled_method_name, [])
106
106
  method_name = method_name.to_sym if Symbol === Kernel.methods.first
107
107
  assert Tilt::CompileSite.instance_methods.include?(method_name),
108
108
  "CompileSite.instance_methods.include?(#{method_name.inspect})"
@@ -71,6 +71,17 @@ begin
71
71
  include Tilt::CompileSite
72
72
  end
73
73
 
74
+ test "compiling template source to a method" do
75
+ template = Tilt::HamlTemplate.new { |t| "Hello World!" }
76
+ template.render(Scope.new)
77
+ method_name = template.send(:compiled_method_name, [])
78
+ method_name = method_name.to_sym if Symbol === Kernel.methods.first
79
+ assert Tilt::CompileSite.instance_methods.include?(method_name),
80
+ "CompileSite.instance_methods.include?(#{method_name.inspect})"
81
+ assert Scope.new.respond_to?(method_name),
82
+ "scope.respond_to?(#{method_name.inspect})"
83
+ end
84
+
74
85
  test "passing locals" do
75
86
  template = Tilt::HamlTemplate.new { "%p= 'Hey ' + name + '!'" }
76
87
  assert_equal "<p>Hey Joe!</p>\n", template.render(Scope.new, :name => 'Joe')
@@ -80,7 +80,7 @@ class CompiledStringTemplateTest < Test::Unit::TestCase
80
80
  test "compiling template source to a method" do
81
81
  template = Tilt::StringTemplate.new { |t| "Hello World!" }
82
82
  template.render(Scope.new)
83
- method_name = template.send(:compiled_method_name, [].hash)
83
+ method_name = template.send(:compiled_method_name, [])
84
84
  method_name = method_name.to_sym if Symbol === Kernel.methods.first
85
85
  assert Tilt::CompileSite.instance_methods.include?(method_name),
86
86
  "CompileSite.instance_methods.include?(#{method_name.inspect})"
@@ -110,7 +110,7 @@ class TiltTemplateTest < Test::Unit::TestCase
110
110
  end
111
111
 
112
112
  class SourceGeneratingMockTemplate < PreparingMockTemplate
113
- def template_source
113
+ def precompiled_template(locals)
114
114
  "foo = [] ; foo << %Q{#{data}} ; foo.join"
115
115
  end
116
116
  end
data/tilt.gemspec CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'tilt'
6
- s.version = '0.7'
7
- s.date = '2010-03-04'
6
+ s.version = '0.8'
7
+ s.date = '2010-03-07'
8
8
 
9
9
  s.description = "Generic interface to multiple Ruby template engines"
10
10
  s.summary = s.description
@@ -42,11 +42,11 @@ Gem::Specification.new do |s|
42
42
  ]
43
43
  # = MANIFEST =
44
44
 
45
- s.test_files = s.files.select {|path| path =~ /^test\/spec_.*.rb/}
45
+ s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
46
46
  s.add_development_dependency 'contest'
47
47
  s.add_development_dependency 'builder'
48
48
  s.add_development_dependency 'erubis'
49
- s.add_development_dependency 'haml'
49
+ s.add_development_dependency 'haml', '>= 2.2.11'
50
50
  s.add_development_dependency 'mustache'
51
51
  s.add_development_dependency 'rdiscount'
52
52
  s.add_development_dependency 'liquid'
metadata CHANGED
@@ -4,8 +4,8 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 7
8
- version: "0.7"
7
+ - 8
8
+ version: "0.8"
9
9
  platform: ruby
10
10
  authors:
11
11
  - Ryan Tomayko
@@ -13,7 +13,7 @@ autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
15
 
16
- date: 2010-03-04 00:00:00 -08:00
16
+ date: 2010-03-07 00:00:00 -08:00
17
17
  default_executable:
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
@@ -60,8 +60,10 @@ dependencies:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
62
  segments:
63
- - 0
64
- version: "0"
63
+ - 2
64
+ - 2
65
+ - 11
66
+ version: 2.2.11
65
67
  type: :development
66
68
  version_requirements: *id004
67
69
  - !ruby/object:Gem::Dependency
@@ -193,5 +195,21 @@ rubygems_version: 1.3.6
193
195
  signing_key:
194
196
  specification_version: 2
195
197
  summary: Generic interface to multiple Ruby template engines
196
- test_files: []
197
-
198
+ test_files:
199
+ - test/tilt_buildertemplate_test.rb
200
+ - test/tilt_cache_test.rb
201
+ - test/tilt_coffeetemplate_test.rb
202
+ - test/tilt_compilesite_test.rb
203
+ - test/tilt_erbtemplate_test.rb
204
+ - test/tilt_erubistemplate_test.rb
205
+ - test/tilt_hamltemplate_test.rb
206
+ - test/tilt_lesstemplate_test.rb
207
+ - test/tilt_liquidtemplate_test.rb
208
+ - test/tilt_mustachetemplate_test.rb
209
+ - test/tilt_rdiscounttemplate_test.rb
210
+ - test/tilt_rdoctemplate_test.rb
211
+ - test/tilt_redclothtemplate_test.rb
212
+ - test/tilt_sasstemplate_test.rb
213
+ - test/tilt_stringtemplate_test.rb
214
+ - test/tilt_template_test.rb
215
+ - test/tilt_test.rb