tilt 0.5 → 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +1 -1
- data/README.md +53 -4
- data/Rakefile +33 -49
- data/TEMPLATES.md +79 -52
- data/bin/tilt +45 -16
- data/lib/tilt.rb +391 -187
- data/test/contest.rb +68 -0
- data/test/tilt_buildertemplate_test.rb +1 -1
- data/test/tilt_compilesite_test.rb +86 -0
- data/test/tilt_erbtemplate_test.rb +121 -2
- data/test/tilt_erubistemplate_test.rb +52 -1
- data/test/tilt_hamltemplate_test.rb +65 -1
- data/test/tilt_liquidtemplate_test.rb +1 -1
- data/test/tilt_radiustemplate_test.rb +66 -0
- data/test/tilt_rdiscounttemplate_test.rb +3 -3
- data/test/tilt_rdoctemplate_test.rb +1 -1
- data/test/tilt_sasstemplate_test.rb +2 -2
- data/test/tilt_stringtemplate_test.rb +87 -1
- data/test/tilt_template_test.rb +47 -24
- data/test/tilt_test.rb +9 -1
- data/tilt.gemspec +12 -5
- metadata +108 -43
- data/test/tilt_mustachetemplate_test.rb +0 -64
data/lib/tilt.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
|
|
1
3
|
module Tilt
|
|
2
|
-
VERSION = '0
|
|
4
|
+
VERSION = '1.0'
|
|
3
5
|
|
|
4
6
|
@template_mappings = {}
|
|
5
7
|
|
|
6
|
-
# Hash of template path pattern => template implementation
|
|
7
|
-
# class mappings.
|
|
8
|
+
# Hash of template path pattern => template implementation class mappings.
|
|
8
9
|
def self.mappings
|
|
9
10
|
@template_mappings
|
|
10
11
|
end
|
|
@@ -15,6 +16,11 @@ module Tilt
|
|
|
15
16
|
mappings[ext.downcase] = template_class
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
# Returns true when a template exists on an exact match of the provided file extension
|
|
20
|
+
def self.registered?(ext)
|
|
21
|
+
mappings.key?(ext.downcase)
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
# Create a new template for the given file using the file's extension
|
|
19
25
|
# to determine the the template mapping.
|
|
20
26
|
def self.new(file, line=nil, options={}, &block)
|
|
@@ -25,28 +31,35 @@ module Tilt
|
|
|
25
31
|
end
|
|
26
32
|
end
|
|
27
33
|
|
|
28
|
-
# Lookup a template class
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 #
|
|
62
|
+
# the #prepare method and one of the #evaluate or #template_source
|
|
50
63
|
# methods.
|
|
51
64
|
class Template
|
|
52
65
|
# Template source; loaded from a file or given directly.
|
|
@@ -63,51 +76,55 @@ module Tilt
|
|
|
63
76
|
# interface.
|
|
64
77
|
attr_reader :options
|
|
65
78
|
|
|
79
|
+
# Used to determine if this class's initialize_engine method has
|
|
80
|
+
# been called yet.
|
|
81
|
+
@engine_initialized = false
|
|
82
|
+
class << self
|
|
83
|
+
attr_accessor :engine_initialized
|
|
84
|
+
alias engine_initialized? engine_initialized
|
|
85
|
+
end
|
|
86
|
+
|
|
66
87
|
# Create a new template with the file, line, and options specified. By
|
|
67
|
-
# default, template data is read from the file
|
|
68
|
-
#
|
|
69
|
-
#
|
|
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
|
-
#
|
|
72
|
-
# time this template subclass has been initialized.
|
|
92
|
+
# All arguments are optional.
|
|
73
93
|
def initialize(file=nil, line=1, options={}, &block)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
@file, @line, @options = nil, 1, {}
|
|
95
|
+
|
|
96
|
+
[options, line, file].compact.each do |arg|
|
|
97
|
+
case
|
|
98
|
+
when arg.respond_to?(:to_str) ; @file = arg.to_str
|
|
99
|
+
when arg.respond_to?(:to_int) ; @line = arg.to_int
|
|
100
|
+
when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
|
|
101
|
+
else raise TypeError
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise ArgumentError, "file or block required" if (@file || block).nil?
|
|
106
|
+
|
|
107
|
+
# call the initialize_engine method if this is the very first time
|
|
108
|
+
# an instance of this class has been created.
|
|
109
|
+
if !self.class.engine_initialized?
|
|
82
110
|
initialize_engine
|
|
83
111
|
self.class.engine_initialized = true
|
|
84
112
|
end
|
|
85
|
-
end
|
|
86
113
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def initialize_engine
|
|
91
|
-
end
|
|
92
|
-
@engine_initialized = false
|
|
93
|
-
class << self ; attr_accessor :engine_initialized ; end
|
|
114
|
+
# used to generate unique method names for template compilation
|
|
115
|
+
@stamp = (Time.now.to_f * 10000).to_i
|
|
116
|
+
@compiled_method_names = {}
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def compile
|
|
100
|
-
if @data.nil?
|
|
101
|
-
@data = @reader.call(self)
|
|
102
|
-
compile!
|
|
103
|
-
end
|
|
118
|
+
# load template data and prepare
|
|
119
|
+
@reader = block || lambda { |t| File.read(@file) }
|
|
120
|
+
@data = @reader.call(self)
|
|
121
|
+
prepare
|
|
104
122
|
end
|
|
105
123
|
|
|
106
124
|
# Render the template in the given scope with the locals specified. If a
|
|
107
125
|
# block is given, it is typically available within the template via
|
|
108
126
|
# +yield+.
|
|
109
127
|
def render(scope=Object.new, locals={}, &block)
|
|
110
|
-
compile
|
|
111
128
|
evaluate scope, locals || {}, &block
|
|
112
129
|
end
|
|
113
130
|
|
|
@@ -127,43 +144,167 @@ module Tilt
|
|
|
127
144
|
end
|
|
128
145
|
|
|
129
146
|
protected
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
147
|
+
# Called once and only once for each template subclass the first time
|
|
148
|
+
# the template class is initialized. This should be used to require the
|
|
149
|
+
# underlying template library and perform any initial setup.
|
|
150
|
+
def initialize_engine
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Like Kernel::require but issues a warning urging a manual require when
|
|
154
|
+
# running under a threaded environment.
|
|
155
|
+
def require_template_library(name)
|
|
156
|
+
if Thread.list.size > 1
|
|
157
|
+
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
|
|
158
|
+
"explicit require '#{name}' suggested."
|
|
159
|
+
end
|
|
160
|
+
require name
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Do whatever preparation is necessary to setup the underlying template
|
|
164
|
+
# engine. Called immediately after template data is loaded. Instance
|
|
165
|
+
# variables set in this method are available when #evaluate is called.
|
|
133
166
|
#
|
|
134
167
|
# Subclasses must provide an implementation of this method.
|
|
135
|
-
def
|
|
136
|
-
|
|
168
|
+
def prepare
|
|
169
|
+
if respond_to?(:compile!)
|
|
170
|
+
# backward compat with tilt < 0.6; just in case
|
|
171
|
+
warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
|
|
172
|
+
compile!
|
|
173
|
+
else
|
|
174
|
+
raise NotImplementedError
|
|
175
|
+
end
|
|
137
176
|
end
|
|
138
177
|
|
|
139
|
-
# Process the template and return the result.
|
|
140
|
-
#
|
|
178
|
+
# Process the template and return the result. When the scope mixes in
|
|
179
|
+
# the Tilt::CompileSite module, the template is compiled to a method and
|
|
180
|
+
# reused given identical locals keys. When the scope object
|
|
181
|
+
# does not mix in the CompileSite module, the template source is
|
|
182
|
+
# evaluated with instance_eval. In any case, template executation
|
|
183
|
+
# is guaranteed to be performed in the scope object with the locals
|
|
184
|
+
# specified and with support for yielding to the block.
|
|
141
185
|
def evaluate(scope, locals, &block)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
186
|
+
if scope.respond_to?(:__tilt__)
|
|
187
|
+
method_name = compiled_method_name(locals.keys)
|
|
188
|
+
if scope.respond_to?(method_name)
|
|
189
|
+
scope.send(method_name, locals, &block)
|
|
190
|
+
else
|
|
191
|
+
compile_template_method(method_name, locals)
|
|
192
|
+
scope.send(method_name, locals, &block)
|
|
193
|
+
end
|
|
194
|
+
else
|
|
195
|
+
evaluate_source(scope, locals, &block)
|
|
196
|
+
end
|
|
145
197
|
end
|
|
146
198
|
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
|
|
199
|
+
# Generates all template source by combining the preamble, template, and
|
|
200
|
+
# postamble and returns a two-tuple of the form: [source, offset], where
|
|
201
|
+
# source is the string containing (Ruby) source code for the template and
|
|
202
|
+
# offset is the integer line offset where line reporting should begin.
|
|
203
|
+
#
|
|
204
|
+
# Template subclasses may override this method when they need complete
|
|
205
|
+
# control over source generation or want to adjust the default line
|
|
206
|
+
# offset. In most cases, overriding the #precompiled_template method is
|
|
207
|
+
# easier and more appropriate.
|
|
208
|
+
def precompiled(locals)
|
|
209
|
+
preamble = precompiled_preamble(locals)
|
|
210
|
+
parts = [
|
|
211
|
+
preamble,
|
|
212
|
+
precompiled_template(locals),
|
|
213
|
+
precompiled_postamble(locals)
|
|
214
|
+
]
|
|
215
|
+
[parts.join("\n"), preamble.count("\n") + 1]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# A string containing the (Ruby) source code for the template. The
|
|
219
|
+
# default Template#evaluate implementation requires either this method
|
|
220
|
+
# or the #precompiled method be overridden. When defined, the base
|
|
221
|
+
# Template guarantees correct file/line handling, locals support, custom
|
|
222
|
+
# scopes, and support for template compilation when the scope object
|
|
223
|
+
# allows it.
|
|
224
|
+
def precompiled_template(locals)
|
|
151
225
|
raise NotImplementedError
|
|
152
226
|
end
|
|
153
227
|
|
|
228
|
+
# Generates preamble code for initializing template state, and performing
|
|
229
|
+
# locals assignment. The default implementation performs locals
|
|
230
|
+
# assignment only. Lines included in the preamble are subtracted from the
|
|
231
|
+
# source line offset, so adding code to the preamble does not effect line
|
|
232
|
+
# reporting in Kernel::caller and backtraces.
|
|
233
|
+
def precompiled_preamble(locals)
|
|
234
|
+
locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Generates postamble code for the precompiled template source. The
|
|
238
|
+
# string returned from this method is appended to the precompiled
|
|
239
|
+
# template source.
|
|
240
|
+
def precompiled_postamble(locals)
|
|
241
|
+
''
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# The unique compiled method name for the locals keys provided.
|
|
245
|
+
def compiled_method_name(locals_keys)
|
|
246
|
+
@compiled_method_names[locals_keys] ||=
|
|
247
|
+
generate_compiled_method_name(locals_keys)
|
|
248
|
+
end
|
|
249
|
+
|
|
154
250
|
private
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
source
|
|
158
|
-
|
|
251
|
+
# Evaluate the template source in the context of the scope object.
|
|
252
|
+
def evaluate_source(scope, locals, &block)
|
|
253
|
+
source, offset = precompiled(locals)
|
|
254
|
+
scope.instance_eval(source, eval_file, line - offset)
|
|
159
255
|
end
|
|
160
256
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
257
|
+
# JRuby doesn't allow Object#instance_eval to yield to the block it's
|
|
258
|
+
# closed over. This is by design and (ostensibly) something that will
|
|
259
|
+
# change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
|
|
260
|
+
# exhibits the behavior. More info here:
|
|
261
|
+
#
|
|
262
|
+
# http://jira.codehaus.org/browse/JRUBY-2599
|
|
263
|
+
#
|
|
264
|
+
# Additionally, JRuby's eval line reporting is off by one compared to
|
|
265
|
+
# all MRI versions tested.
|
|
266
|
+
#
|
|
267
|
+
# We redefine evaluate_source to work around both issues.
|
|
268
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
269
|
+
undef evaluate_source
|
|
270
|
+
def evaluate_source(scope, locals, &block)
|
|
271
|
+
source, offset = precompiled(locals)
|
|
272
|
+
file, lineno = eval_file, (line - offset) - 1
|
|
273
|
+
scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def generate_compiled_method_name(locals_keys)
|
|
278
|
+
parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
|
|
279
|
+
digest = Digest::MD5.hexdigest(parts.join(':'))
|
|
280
|
+
"__tilt_#{digest}"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def compile_template_method(method_name, locals)
|
|
284
|
+
source, offset = precompiled(locals)
|
|
285
|
+
offset += 1
|
|
286
|
+
CompileSite.module_eval <<-RUBY, eval_file, line - offset
|
|
287
|
+
def #{method_name}(locals)
|
|
288
|
+
#{source}
|
|
289
|
+
end
|
|
290
|
+
RUBY
|
|
291
|
+
|
|
292
|
+
ObjectSpace.define_finalizer self,
|
|
293
|
+
Template.compiled_template_method_remover(CompileSite, method_name)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def self.compiled_template_method_remover(site, method_name)
|
|
297
|
+
proc { |oid| garbage_collect_compiled_template_method(site, method_name) }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def self.garbage_collect_compiled_template_method(site, method_name)
|
|
301
|
+
site.module_eval do
|
|
302
|
+
begin
|
|
303
|
+
remove_method(method_name)
|
|
304
|
+
rescue NameError
|
|
305
|
+
# method was already removed (ruby >= 1.9)
|
|
306
|
+
end
|
|
165
307
|
end
|
|
166
|
-
require name
|
|
167
308
|
end
|
|
168
309
|
end
|
|
169
310
|
|
|
@@ -173,7 +314,7 @@ module Tilt
|
|
|
173
314
|
# cache = Tilt::Cache.new
|
|
174
315
|
# cache.fetch(path, line, options) { Tilt.new(path, line, options) }
|
|
175
316
|
#
|
|
176
|
-
# Subsequent invocations return the already
|
|
317
|
+
# Subsequent invocations return the already loaded template object.
|
|
177
318
|
class Cache
|
|
178
319
|
def initialize
|
|
179
320
|
@cache = {}
|
|
@@ -195,11 +336,11 @@ module Tilt
|
|
|
195
336
|
# The template source is evaluated as a Ruby string. The #{} interpolation
|
|
196
337
|
# syntax can be used to generated dynamic output.
|
|
197
338
|
class StringTemplate < Template
|
|
198
|
-
def
|
|
339
|
+
def prepare
|
|
199
340
|
@code = "%Q{#{data}}"
|
|
200
341
|
end
|
|
201
342
|
|
|
202
|
-
def
|
|
343
|
+
def precompiled_template(locals)
|
|
203
344
|
@code
|
|
204
345
|
end
|
|
205
346
|
end
|
|
@@ -209,66 +350,99 @@ module Tilt
|
|
|
209
350
|
# ERB template implementation. See:
|
|
210
351
|
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
|
|
211
352
|
class ERBTemplate < Template
|
|
212
|
-
|
|
213
|
-
require_template_library 'erb' unless defined? ::ERB
|
|
214
|
-
end
|
|
353
|
+
@@default_output_variable = '_erbout'
|
|
215
354
|
|
|
216
|
-
def
|
|
217
|
-
|
|
355
|
+
def self.default_output_variable
|
|
356
|
+
@@default_output_variable
|
|
218
357
|
end
|
|
219
358
|
|
|
220
|
-
def
|
|
221
|
-
|
|
359
|
+
def self.default_output_variable=(name)
|
|
360
|
+
@@default_output_variable = name
|
|
222
361
|
end
|
|
223
362
|
|
|
224
|
-
def
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
original_out_buf =
|
|
229
|
-
scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
|
|
230
|
-
scope.instance_variable_get(:@_out_buf)
|
|
363
|
+
def initialize_engine
|
|
364
|
+
return if defined? ::ERB
|
|
365
|
+
require_template_library 'erb'
|
|
366
|
+
end
|
|
231
367
|
|
|
232
|
-
|
|
368
|
+
def prepare
|
|
369
|
+
@outvar = options[:outvar] || self.class.default_output_variable
|
|
370
|
+
@engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
|
|
371
|
+
end
|
|
233
372
|
|
|
234
|
-
|
|
235
|
-
|
|
373
|
+
def precompiled_template(locals)
|
|
374
|
+
source = @engine.src
|
|
375
|
+
source
|
|
376
|
+
end
|
|
236
377
|
|
|
237
|
-
|
|
378
|
+
def precompiled_preamble(locals)
|
|
379
|
+
<<-RUBY
|
|
380
|
+
begin
|
|
381
|
+
__original_outvar = #{@outvar} if defined?(#{@outvar})
|
|
382
|
+
#{super}
|
|
383
|
+
RUBY
|
|
238
384
|
end
|
|
239
385
|
|
|
240
|
-
|
|
386
|
+
def precompiled_postamble(locals)
|
|
387
|
+
<<-RUBY
|
|
388
|
+
#{super}
|
|
389
|
+
ensure
|
|
390
|
+
#{@outvar} = __original_outvar
|
|
391
|
+
end
|
|
392
|
+
RUBY
|
|
393
|
+
end
|
|
241
394
|
|
|
242
395
|
# ERB generates a line to specify the character coding of the generated
|
|
243
396
|
# source in 1.9. Account for this in the line offset.
|
|
244
397
|
if RUBY_VERSION >= '1.9.0'
|
|
245
|
-
def
|
|
398
|
+
def precompiled(locals)
|
|
246
399
|
source, offset = super
|
|
247
400
|
[source, offset + 1]
|
|
248
401
|
end
|
|
249
402
|
end
|
|
250
403
|
end
|
|
404
|
+
|
|
251
405
|
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
|
252
406
|
|
|
253
407
|
|
|
254
408
|
# Erubis template implementation. See:
|
|
255
409
|
# http://www.kuwata-lab.com/erubis/
|
|
410
|
+
#
|
|
411
|
+
# ErubisTemplate supports the following additional options, which are not
|
|
412
|
+
# passed down to the Erubis engine:
|
|
413
|
+
#
|
|
414
|
+
# :engine_class allows you to specify a custom engine class to use
|
|
415
|
+
# instead of the default (which is ::Erubis::Eruby).
|
|
416
|
+
#
|
|
417
|
+
# :escape_html when true, ::Erubis::EscapedEruby will be used as
|
|
418
|
+
# the engine class instead of the default. All content
|
|
419
|
+
# within <%= %> blocks will be automatically html escaped.
|
|
256
420
|
class ErubisTemplate < ERBTemplate
|
|
257
421
|
def initialize_engine
|
|
258
|
-
|
|
422
|
+
return if defined? ::Erubis
|
|
423
|
+
require_template_library 'erubis'
|
|
259
424
|
end
|
|
260
425
|
|
|
261
|
-
def
|
|
262
|
-
|
|
263
|
-
@
|
|
426
|
+
def prepare
|
|
427
|
+
@options.merge!(:preamble => false, :postamble => false)
|
|
428
|
+
@outvar = options.delete(:outvar) || self.class.default_output_variable
|
|
429
|
+
engine_class = options.delete(:engine_class)
|
|
430
|
+
engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
|
|
431
|
+
@engine = (engine_class || ::Erubis::Eruby).new(data, options)
|
|
264
432
|
end
|
|
265
433
|
|
|
266
|
-
|
|
434
|
+
def precompiled_preamble(locals)
|
|
435
|
+
[super, "#{@outvar} = _buf = ''"].join("\n")
|
|
436
|
+
end
|
|
267
437
|
|
|
268
|
-
|
|
269
|
-
|
|
438
|
+
def precompiled_postamble(locals)
|
|
439
|
+
["_buf", super].join("\n")
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
|
|
443
|
+
# Override and adjust back.
|
|
270
444
|
if RUBY_VERSION >= '1.9.0'
|
|
271
|
-
def
|
|
445
|
+
def precompiled(locals)
|
|
272
446
|
source, offset = super
|
|
273
447
|
[source, offset - 1]
|
|
274
448
|
end
|
|
@@ -281,20 +455,54 @@ module Tilt
|
|
|
281
455
|
# http://haml.hamptoncatlin.com/
|
|
282
456
|
class HamlTemplate < Template
|
|
283
457
|
def initialize_engine
|
|
284
|
-
|
|
458
|
+
return if defined? ::Haml::Engine
|
|
459
|
+
require_template_library 'haml'
|
|
285
460
|
end
|
|
286
461
|
|
|
287
|
-
def
|
|
288
|
-
|
|
462
|
+
def prepare
|
|
463
|
+
options = @options.merge(:filename => eval_file, :line => line)
|
|
464
|
+
@engine = ::Haml::Engine.new(data, options)
|
|
289
465
|
end
|
|
290
466
|
|
|
291
467
|
def evaluate(scope, locals, &block)
|
|
292
|
-
@engine.
|
|
468
|
+
if @engine.respond_to?(:precompiled_method_return_value, true)
|
|
469
|
+
super
|
|
470
|
+
else
|
|
471
|
+
@engine.render(scope, locals, &block)
|
|
472
|
+
end
|
|
293
473
|
end
|
|
294
474
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
475
|
+
# Precompiled Haml source. Taken from the precompiled_with_ambles
|
|
476
|
+
# method in Haml::Precompiler:
|
|
477
|
+
# http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
|
|
478
|
+
def precompiled_template(locals)
|
|
479
|
+
@engine.precompiled
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def precompiled_preamble(locals)
|
|
483
|
+
local_assigns = super
|
|
484
|
+
@engine.instance_eval do
|
|
485
|
+
<<-RUBY
|
|
486
|
+
begin
|
|
487
|
+
extend Haml::Helpers
|
|
488
|
+
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
|
489
|
+
_erbout = _hamlout.buffer
|
|
490
|
+
__in_erb_template = true
|
|
491
|
+
_haml_locals = locals
|
|
492
|
+
#{local_assigns}
|
|
493
|
+
RUBY
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def precompiled_postamble(locals)
|
|
498
|
+
@engine.instance_eval do
|
|
499
|
+
<<-RUBY
|
|
500
|
+
#{precompiled_method_return_value}
|
|
501
|
+
ensure
|
|
502
|
+
@haml_buffer = @haml_buffer.upper
|
|
503
|
+
end
|
|
504
|
+
RUBY
|
|
505
|
+
end
|
|
298
506
|
end
|
|
299
507
|
end
|
|
300
508
|
register 'haml', HamlTemplate
|
|
@@ -306,15 +514,16 @@ module Tilt
|
|
|
306
514
|
# Sass templates do not support object scopes, locals, or yield.
|
|
307
515
|
class SassTemplate < Template
|
|
308
516
|
def initialize_engine
|
|
309
|
-
|
|
517
|
+
return if defined? ::Sass::Engine
|
|
518
|
+
require_template_library 'sass'
|
|
310
519
|
end
|
|
311
520
|
|
|
312
|
-
def
|
|
521
|
+
def prepare
|
|
313
522
|
@engine = ::Sass::Engine.new(data, sass_options)
|
|
314
523
|
end
|
|
315
524
|
|
|
316
525
|
def evaluate(scope, locals, &block)
|
|
317
|
-
@engine.render
|
|
526
|
+
@output ||= @engine.render
|
|
318
527
|
end
|
|
319
528
|
|
|
320
529
|
private
|
|
@@ -331,10 +540,11 @@ module Tilt
|
|
|
331
540
|
# Less templates do not support object scopes, locals, or yield.
|
|
332
541
|
class LessTemplate < Template
|
|
333
542
|
def initialize_engine
|
|
334
|
-
|
|
543
|
+
return if defined? ::Less::Engine
|
|
544
|
+
require_template_library 'less'
|
|
335
545
|
end
|
|
336
546
|
|
|
337
|
-
def
|
|
547
|
+
def prepare
|
|
338
548
|
@engine = ::Less::Engine.new(data)
|
|
339
549
|
end
|
|
340
550
|
|
|
@@ -344,14 +554,16 @@ module Tilt
|
|
|
344
554
|
end
|
|
345
555
|
register 'less', LessTemplate
|
|
346
556
|
|
|
557
|
+
|
|
347
558
|
# Builder template implementation. See:
|
|
348
559
|
# http://builder.rubyforge.org/
|
|
349
560
|
class BuilderTemplate < Template
|
|
350
561
|
def initialize_engine
|
|
351
|
-
|
|
562
|
+
return if defined?(::Builder)
|
|
563
|
+
require_template_library 'builder'
|
|
352
564
|
end
|
|
353
565
|
|
|
354
|
-
def
|
|
566
|
+
def prepare
|
|
355
567
|
end
|
|
356
568
|
|
|
357
569
|
def evaluate(scope, locals, &block)
|
|
@@ -365,7 +577,7 @@ module Tilt
|
|
|
365
577
|
xml.target!
|
|
366
578
|
end
|
|
367
579
|
|
|
368
|
-
def
|
|
580
|
+
def precompiled_template(locals)
|
|
369
581
|
data.to_str
|
|
370
582
|
end
|
|
371
583
|
end
|
|
@@ -387,10 +599,11 @@ module Tilt
|
|
|
387
599
|
# time when using this template engine.
|
|
388
600
|
class LiquidTemplate < Template
|
|
389
601
|
def initialize_engine
|
|
390
|
-
|
|
602
|
+
return if defined? ::Liquid::Template
|
|
603
|
+
require_template_library 'liquid'
|
|
391
604
|
end
|
|
392
605
|
|
|
393
|
-
def
|
|
606
|
+
def prepare
|
|
394
607
|
@engine = ::Liquid::Template.parse(data)
|
|
395
608
|
end
|
|
396
609
|
|
|
@@ -400,9 +613,8 @@ module Tilt
|
|
|
400
613
|
scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
|
|
401
614
|
locals = scope.merge(locals)
|
|
402
615
|
end
|
|
403
|
-
# TODO: Is it possible to lazy yield ?
|
|
404
616
|
locals['yield'] = block.nil? ? '' : yield
|
|
405
|
-
locals['content'] =
|
|
617
|
+
locals['content'] = locals['yield']
|
|
406
618
|
@engine.render(locals)
|
|
407
619
|
end
|
|
408
620
|
end
|
|
@@ -421,15 +633,17 @@ module Tilt
|
|
|
421
633
|
end
|
|
422
634
|
|
|
423
635
|
def initialize_engine
|
|
424
|
-
|
|
636
|
+
return if defined? ::RDiscount
|
|
637
|
+
require_template_library 'rdiscount'
|
|
425
638
|
end
|
|
426
639
|
|
|
427
|
-
def
|
|
640
|
+
def prepare
|
|
428
641
|
@engine = RDiscount.new(data, *flags)
|
|
642
|
+
@output = nil
|
|
429
643
|
end
|
|
430
644
|
|
|
431
645
|
def evaluate(scope, locals, &block)
|
|
432
|
-
@engine.to_html
|
|
646
|
+
@output ||= @engine.to_html
|
|
433
647
|
end
|
|
434
648
|
end
|
|
435
649
|
register 'markdown', RDiscountTemplate
|
|
@@ -437,69 +651,25 @@ module Tilt
|
|
|
437
651
|
register 'md', RDiscountTemplate
|
|
438
652
|
|
|
439
653
|
|
|
440
|
-
# RedCloth implementation. See:
|
|
441
|
-
# http://redcloth.org/
|
|
442
|
-
class RedClothTemplate < Template
|
|
443
|
-
def initialize_engine
|
|
444
|
-
require_template_library 'redcloth' unless defined? ::RedCloth
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
def compile!
|
|
448
|
-
@engine = RedCloth.new(data)
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
def evaluate(scope, locals, &block)
|
|
452
|
-
@engine.to_html
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
register 'textile', RedClothTemplate
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
# Mustache is written and maintained by Chris Wanstrath. See:
|
|
459
|
-
# http://github.com/defunkt/mustache
|
|
460
|
-
#
|
|
461
|
-
# When a scope argument is provided to MustacheTemplate#render, the
|
|
462
|
-
# instance variables are copied from the scope object to the Mustache
|
|
463
|
-
# view.
|
|
464
|
-
class MustacheTemplate < Template
|
|
465
|
-
attr_reader :engine
|
|
466
|
-
|
|
654
|
+
# RedCloth implementation. See:
|
|
655
|
+
# http://redcloth.org/
|
|
656
|
+
class RedClothTemplate < Template
|
|
467
657
|
def initialize_engine
|
|
468
|
-
|
|
658
|
+
return if defined? ::RedCloth
|
|
659
|
+
require_template_library 'redcloth'
|
|
469
660
|
end
|
|
470
661
|
|
|
471
|
-
def
|
|
472
|
-
|
|
473
|
-
@
|
|
474
|
-
options.each do |key, value|
|
|
475
|
-
next if %w[view namespace mustaches].include?(key.to_s)
|
|
476
|
-
@engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
|
|
477
|
-
end
|
|
662
|
+
def prepare
|
|
663
|
+
@engine = RedCloth.new(data)
|
|
664
|
+
@output = nil
|
|
478
665
|
end
|
|
479
666
|
|
|
480
|
-
def evaluate(scope
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
# copy instance variables from scope to the view
|
|
484
|
-
scope.instance_variables.each do |name|
|
|
485
|
-
instance.instance_variable_set(name, scope.instance_variable_get(name))
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
# locals get added to the view's context
|
|
489
|
-
locals.each do |local, value|
|
|
490
|
-
instance[local] = value
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
# if we're passed a block it's a subview. Sticking it in yield
|
|
494
|
-
# lets us use {{yield}} in layout.html to render the actual page.
|
|
495
|
-
instance[:yield] = block.call if block
|
|
496
|
-
|
|
497
|
-
instance.template = data unless instance.compiled?
|
|
498
|
-
|
|
499
|
-
instance.to_html
|
|
667
|
+
def evaluate(scope, locals, &block)
|
|
668
|
+
@output ||= @engine.to_html
|
|
500
669
|
end
|
|
501
670
|
end
|
|
502
|
-
register '
|
|
671
|
+
register 'textile', RedClothTemplate
|
|
672
|
+
|
|
503
673
|
|
|
504
674
|
# RDoc template. See:
|
|
505
675
|
# http://rdoc.rubyforge.org/
|
|
@@ -509,20 +679,54 @@ register 'textile', RedClothTemplate
|
|
|
509
679
|
# engine.
|
|
510
680
|
class RDocTemplate < Template
|
|
511
681
|
def initialize_engine
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
end
|
|
682
|
+
return if defined?(::RDoc::Markup)
|
|
683
|
+
require_template_library 'rdoc/markup'
|
|
684
|
+
require_template_library 'rdoc/markup/to_html'
|
|
516
685
|
end
|
|
517
686
|
|
|
518
|
-
def
|
|
687
|
+
def prepare
|
|
519
688
|
markup = RDoc::Markup::ToHtml.new
|
|
520
689
|
@engine = markup.convert(data)
|
|
690
|
+
@output = nil
|
|
521
691
|
end
|
|
522
692
|
|
|
523
693
|
def evaluate(scope, locals, &block)
|
|
524
|
-
@engine.to_s
|
|
694
|
+
@output ||= @engine.to_s
|
|
525
695
|
end
|
|
526
696
|
end
|
|
527
697
|
register 'rdoc', RDocTemplate
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
# Radius Template
|
|
701
|
+
# http://github.com/jlong/radius/
|
|
702
|
+
class RadiusTemplate < Template
|
|
703
|
+
def initialize_engine
|
|
704
|
+
return if defined? ::Radius
|
|
705
|
+
require_template_library 'radius'
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def prepare
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def evaluate(scope, locals, &block)
|
|
712
|
+
context = Class.new(Radius::Context).new
|
|
713
|
+
context.define_tag("yield") do
|
|
714
|
+
block.call
|
|
715
|
+
end
|
|
716
|
+
locals.each do |tag, value|
|
|
717
|
+
context.define_tag(tag) do
|
|
718
|
+
value
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
(class << context; self; end).class_eval do
|
|
722
|
+
define_method :tag_missing do |tag, attr, &block|
|
|
723
|
+
scope.__send__(tag) # any way to support attr as args?
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
options = {:tag_prefix => 'r'}.merge(@options)
|
|
727
|
+
parser = Radius::Parser.new(context, options)
|
|
728
|
+
parser.parse(data)
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
register 'radius', RadiusTemplate
|
|
528
732
|
end
|