tilt 0.6 → 1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +1 -1
- data/README.md +56 -4
- data/Rakefile +33 -49
- data/TEMPLATES.md +80 -53
- data/bin/tilt +45 -16
- data/lib/tilt.rb +511 -189
- data/test/contest.rb +68 -0
- data/test/markaby/locals.mab +1 -0
- data/test/markaby/markaby.mab +1 -0
- data/test/markaby/markaby_other_static.mab +1 -0
- data/test/markaby/render_twice.mab +1 -0
- data/test/markaby/scope.mab +1 -0
- data/test/markaby/yielding.mab +2 -0
- data/test/tilt_buildertemplate_test.rb +1 -1
- data/test/tilt_cache_test.rb +2 -3
- data/test/tilt_coffeescripttemplate_test.rb +25 -0
- 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_markaby_test.rb +73 -0
- data/test/tilt_nokogiritemplate_test.rb +54 -0
- data/test/tilt_radiustemplate_test.rb +70 -0
- data/test/tilt_rdiscounttemplate_test.rb +3 -3
- data/test/tilt_rdoctemplate_test.rb +1 -1
- data/test/tilt_sasstemplate_test.rb +13 -2
- data/test/tilt_stringtemplate_test.rb +87 -1
- data/test/tilt_template_test.rb +46 -24
- data/test/tilt_test.rb +9 -1
- data/tilt.gemspec +23 -8
- metadata +111 -19
- data/test/tilt_coffeetemplate_test.rb +0 -20
- data/test/tilt_mustache_views/external.rb +0 -9
- data/test/tilt_mustachetemplate_test.rb +0 -73
data/lib/tilt.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
|
|
1
3
|
module Tilt
|
|
2
|
-
VERSION = '
|
|
4
|
+
VERSION = '1.1'
|
|
3
5
|
|
|
4
6
|
@template_mappings = {}
|
|
5
7
|
|
|
6
|
-
# Hash of template path pattern => template implementation
|
|
7
|
-
# class mappings.
|
|
8
|
+
# Hash of template path pattern => template implementation class mappings.
|
|
8
9
|
def self.mappings
|
|
9
10
|
@template_mappings
|
|
10
11
|
end
|
|
@@ -15,6 +16,11 @@ module Tilt
|
|
|
15
16
|
mappings[ext.downcase] = template_class
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
# Returns true when a template exists on an exact match of the provided file extension
|
|
20
|
+
def self.registered?(ext)
|
|
21
|
+
mappings.key?(ext.downcase)
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
# Create a new template for the given file using the file's extension
|
|
19
25
|
# to determine the the template mapping.
|
|
20
26
|
def self.new(file, line=nil, options={}, &block)
|
|
@@ -25,28 +31,35 @@ module Tilt
|
|
|
25
31
|
end
|
|
26
32
|
end
|
|
27
33
|
|
|
28
|
-
# Lookup a template class
|
|
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 #precompiled_template
|
|
50
63
|
# methods.
|
|
51
64
|
class Template
|
|
52
65
|
# Template source; loaded from a file or given directly.
|
|
@@ -63,52 +76,55 @@ module Tilt
|
|
|
63
76
|
# interface.
|
|
64
77
|
attr_reader :options
|
|
65
78
|
|
|
79
|
+
# Used to determine if this class's initialize_engine method has
|
|
80
|
+
# been called yet.
|
|
81
|
+
@engine_initialized = false
|
|
82
|
+
class << self
|
|
83
|
+
attr_accessor :engine_initialized
|
|
84
|
+
alias engine_initialized? engine_initialized
|
|
85
|
+
end
|
|
86
|
+
|
|
66
87
|
# Create a new template with the file, line, and options specified. By
|
|
67
|
-
# default, template data is read from the file
|
|
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
|
-
|
|
82
|
-
|
|
94
|
+
@file, @line, @options = nil, 1, {}
|
|
95
|
+
|
|
96
|
+
[options, line, file].compact.each do |arg|
|
|
97
|
+
case
|
|
98
|
+
when arg.respond_to?(:to_str) ; @file = arg.to_str
|
|
99
|
+
when arg.respond_to?(:to_int) ; @line = arg.to_int
|
|
100
|
+
when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
|
|
101
|
+
else raise TypeError
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise ArgumentError, "file or block required" if (@file || block).nil?
|
|
106
|
+
|
|
107
|
+
# call the initialize_engine method if this is the very first time
|
|
108
|
+
# an instance of this class has been created.
|
|
109
|
+
if !self.class.engine_initialized?
|
|
83
110
|
initialize_engine
|
|
84
111
|
self.class.engine_initialized = true
|
|
85
112
|
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Called once and only once for each template subclass the first time
|
|
89
|
-
# the template class is initialized. This should be used to require the
|
|
90
|
-
# underlying template library and perform any initial setup.
|
|
91
|
-
def initialize_engine
|
|
92
|
-
end
|
|
93
|
-
@engine_initialized = false
|
|
94
|
-
class << self ; attr_accessor :engine_initialized ; end
|
|
95
113
|
|
|
114
|
+
# used to generate unique method names for template compilation
|
|
115
|
+
@stamp = (Time.now.to_f * 10000).to_i
|
|
116
|
+
@compiled_method_names = {}
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if @data.nil?
|
|
102
|
-
@data = @reader.call(self)
|
|
103
|
-
compile!
|
|
104
|
-
end
|
|
118
|
+
# load template data and prepare
|
|
119
|
+
@reader = block || lambda { |t| File.read(@file) }
|
|
120
|
+
@data = @reader.call(self)
|
|
121
|
+
prepare
|
|
105
122
|
end
|
|
106
123
|
|
|
107
124
|
# Render the template in the given scope with the locals specified. If a
|
|
108
125
|
# block is given, it is typically available within the template via
|
|
109
126
|
# +yield+.
|
|
110
127
|
def render(scope=Object.new, locals={}, &block)
|
|
111
|
-
compile
|
|
112
128
|
evaluate scope, locals || {}, &block
|
|
113
129
|
end
|
|
114
130
|
|
|
@@ -128,43 +144,193 @@ module Tilt
|
|
|
128
144
|
end
|
|
129
145
|
|
|
130
146
|
protected
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
147
|
+
# Called once and only once for each template subclass the first time
|
|
148
|
+
# the template class is initialized. This should be used to require the
|
|
149
|
+
# underlying template library and perform any initial setup.
|
|
150
|
+
def initialize_engine
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Like Kernel::require but issues a warning urging a manual require when
|
|
154
|
+
# running under a threaded environment.
|
|
155
|
+
def require_template_library(name)
|
|
156
|
+
if Thread.list.size > 1
|
|
157
|
+
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
|
|
158
|
+
"explicit require '#{name}' suggested."
|
|
159
|
+
end
|
|
160
|
+
require name
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Do whatever preparation is necessary to setup the underlying template
|
|
164
|
+
# engine. Called immediately after template data is loaded. Instance
|
|
165
|
+
# variables set in this method are available when #evaluate is called.
|
|
134
166
|
#
|
|
135
167
|
# Subclasses must provide an implementation of this method.
|
|
136
|
-
def
|
|
137
|
-
|
|
168
|
+
def prepare
|
|
169
|
+
if respond_to?(:compile!)
|
|
170
|
+
# backward compat with tilt < 0.6; just in case
|
|
171
|
+
warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
|
|
172
|
+
compile!
|
|
173
|
+
else
|
|
174
|
+
raise NotImplementedError
|
|
175
|
+
end
|
|
138
176
|
end
|
|
139
177
|
|
|
140
|
-
# Process the template and return the result.
|
|
141
|
-
#
|
|
178
|
+
# Process the template and return the result. When the scope mixes in
|
|
179
|
+
# the Tilt::CompileSite module, the template is compiled to a method and
|
|
180
|
+
# reused given identical locals keys. When the scope object
|
|
181
|
+
# does not mix in the CompileSite module, the template source is
|
|
182
|
+
# evaluated with instance_eval. In any case, template executation
|
|
183
|
+
# is guaranteed to be performed in the scope object with the locals
|
|
184
|
+
# specified and with support for yielding to the block.
|
|
142
185
|
def evaluate(scope, locals, &block)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
186
|
+
if scope.respond_to?(:__tilt__)
|
|
187
|
+
method_name = compiled_method_name(locals.keys)
|
|
188
|
+
if scope.respond_to?(method_name)
|
|
189
|
+
scope.send(method_name, locals, &block)
|
|
190
|
+
else
|
|
191
|
+
compile_template_method(method_name, locals)
|
|
192
|
+
scope.send(method_name, locals, &block)
|
|
193
|
+
end
|
|
194
|
+
else
|
|
195
|
+
evaluate_source(scope, locals, &block)
|
|
196
|
+
end
|
|
146
197
|
end
|
|
147
198
|
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
|
|
199
|
+
# Generates all template source by combining the preamble, template, and
|
|
200
|
+
# postamble and returns a two-tuple of the form: [source, offset], where
|
|
201
|
+
# source is the string containing (Ruby) source code for the template and
|
|
202
|
+
# offset is the integer line offset where line reporting should begin.
|
|
203
|
+
#
|
|
204
|
+
# Template subclasses may override this method when they need complete
|
|
205
|
+
# control over source generation or want to adjust the default line
|
|
206
|
+
# offset. In most cases, overriding the #precompiled_template method is
|
|
207
|
+
# easier and more appropriate.
|
|
208
|
+
def precompiled(locals)
|
|
209
|
+
preamble = precompiled_preamble(locals)
|
|
210
|
+
parts = [
|
|
211
|
+
preamble,
|
|
212
|
+
precompiled_template(locals),
|
|
213
|
+
precompiled_postamble(locals)
|
|
214
|
+
]
|
|
215
|
+
[parts.join("\n"), preamble.count("\n") + 1]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# A string containing the (Ruby) source code for the template. The
|
|
219
|
+
# default Template#evaluate implementation requires either this method
|
|
220
|
+
# or the #precompiled method be overridden. When defined, the base
|
|
221
|
+
# Template guarantees correct file/line handling, locals support, custom
|
|
222
|
+
# scopes, and support for template compilation when the scope object
|
|
223
|
+
# allows it.
|
|
224
|
+
def precompiled_template(locals)
|
|
152
225
|
raise NotImplementedError
|
|
153
226
|
end
|
|
154
227
|
|
|
228
|
+
# Generates preamble code for initializing template state, and performing
|
|
229
|
+
# locals assignment. The default implementation performs locals
|
|
230
|
+
# assignment only. Lines included in the preamble are subtracted from the
|
|
231
|
+
# source line offset, so adding code to the preamble does not effect line
|
|
232
|
+
# reporting in Kernel::caller and backtraces.
|
|
233
|
+
def precompiled_preamble(locals)
|
|
234
|
+
locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Generates postamble code for the precompiled template source. The
|
|
238
|
+
# string returned from this method is appended to the precompiled
|
|
239
|
+
# template source.
|
|
240
|
+
def precompiled_postamble(locals)
|
|
241
|
+
''
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# The unique compiled method name for the locals keys provided.
|
|
245
|
+
def compiled_method_name(locals_keys)
|
|
246
|
+
@compiled_method_names[locals_keys] ||=
|
|
247
|
+
generate_compiled_method_name(locals_keys)
|
|
248
|
+
end
|
|
249
|
+
|
|
155
250
|
private
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
source
|
|
159
|
-
|
|
251
|
+
# Evaluate the template source in the context of the scope object.
|
|
252
|
+
def evaluate_source(scope, locals, &block)
|
|
253
|
+
source, offset = precompiled(locals)
|
|
254
|
+
scope.instance_eval(source, eval_file, line - offset)
|
|
160
255
|
end
|
|
161
256
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
257
|
+
# JRuby doesn't allow Object#instance_eval to yield to the block it's
|
|
258
|
+
# closed over. This is by design and (ostensibly) something that will
|
|
259
|
+
# change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
|
|
260
|
+
# exhibits the behavior. More info here:
|
|
261
|
+
#
|
|
262
|
+
# http://jira.codehaus.org/browse/JRUBY-2599
|
|
263
|
+
#
|
|
264
|
+
# Additionally, JRuby's eval line reporting is off by one compared to
|
|
265
|
+
# all MRI versions tested.
|
|
266
|
+
#
|
|
267
|
+
# We redefine evaluate_source to work around both issues.
|
|
268
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
269
|
+
undef evaluate_source
|
|
270
|
+
def evaluate_source(scope, locals, &block)
|
|
271
|
+
source, offset = precompiled(locals)
|
|
272
|
+
file, lineno = eval_file, (line - offset) - 1
|
|
273
|
+
scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def generate_compiled_method_name(locals_keys)
|
|
278
|
+
parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
|
|
279
|
+
digest = Digest::MD5.hexdigest(parts.join(':'))
|
|
280
|
+
"__tilt_#{digest}"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def compile_template_method(method_name, locals)
|
|
284
|
+
source, offset = precompiled(locals)
|
|
285
|
+
offset += 5
|
|
286
|
+
CompileSite.class_eval <<-RUBY, eval_file, line - offset
|
|
287
|
+
def #{method_name}(locals)
|
|
288
|
+
Thread.current[:tilt_vars] = [self, locals]
|
|
289
|
+
class << self
|
|
290
|
+
this, locals = Thread.current[:tilt_vars]
|
|
291
|
+
this.instance_eval do
|
|
292
|
+
#{source}
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
RUBY
|
|
297
|
+
ObjectSpace.define_finalizer self,
|
|
298
|
+
Template.compiled_template_method_remover(CompileSite, method_name)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def self.compiled_template_method_remover(site, method_name)
|
|
302
|
+
proc { |oid| garbage_collect_compiled_template_method(site, method_name) }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def self.garbage_collect_compiled_template_method(site, method_name)
|
|
306
|
+
site.module_eval do
|
|
307
|
+
begin
|
|
308
|
+
remove_method(method_name)
|
|
309
|
+
rescue NameError
|
|
310
|
+
# method was already removed (ruby >= 1.9)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Special case Ruby 1.9.1's broken yield.
|
|
316
|
+
#
|
|
317
|
+
# http://github.com/rtomayko/tilt/commit/20c01a5
|
|
318
|
+
# http://redmine.ruby-lang.org/issues/show/3601
|
|
319
|
+
#
|
|
320
|
+
# Remove when 1.9.2 dominates 1.9.1 installs in the wild.
|
|
321
|
+
if RUBY_VERSION =~ /^1.9.1/
|
|
322
|
+
undef compile_template_method
|
|
323
|
+
def compile_template_method(method_name, locals)
|
|
324
|
+
source, offset = precompiled(locals)
|
|
325
|
+
offset += 1
|
|
326
|
+
CompileSite.module_eval <<-RUBY, eval_file, line - offset
|
|
327
|
+
def #{method_name}(locals)
|
|
328
|
+
#{source}
|
|
329
|
+
end
|
|
330
|
+
RUBY
|
|
331
|
+
ObjectSpace.define_finalizer self,
|
|
332
|
+
Template.compiled_template_method_remover(CompileSite, method_name)
|
|
166
333
|
end
|
|
167
|
-
require name
|
|
168
334
|
end
|
|
169
335
|
end
|
|
170
336
|
|
|
@@ -174,7 +340,7 @@ module Tilt
|
|
|
174
340
|
# cache = Tilt::Cache.new
|
|
175
341
|
# cache.fetch(path, line, options) { Tilt.new(path, line, options) }
|
|
176
342
|
#
|
|
177
|
-
# Subsequent invocations return the already
|
|
343
|
+
# Subsequent invocations return the already loaded template object.
|
|
178
344
|
class Cache
|
|
179
345
|
def initialize
|
|
180
346
|
@cache = {}
|
|
@@ -196,11 +362,11 @@ module Tilt
|
|
|
196
362
|
# The template source is evaluated as a Ruby string. The #{} interpolation
|
|
197
363
|
# syntax can be used to generated dynamic output.
|
|
198
364
|
class StringTemplate < Template
|
|
199
|
-
def
|
|
365
|
+
def prepare
|
|
200
366
|
@code = "%Q{#{data}}"
|
|
201
367
|
end
|
|
202
368
|
|
|
203
|
-
def
|
|
369
|
+
def precompiled_template(locals)
|
|
204
370
|
@code
|
|
205
371
|
end
|
|
206
372
|
end
|
|
@@ -210,66 +376,99 @@ module Tilt
|
|
|
210
376
|
# ERB template implementation. See:
|
|
211
377
|
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
|
|
212
378
|
class ERBTemplate < Template
|
|
213
|
-
|
|
214
|
-
require_template_library 'erb' unless defined? ::ERB
|
|
215
|
-
end
|
|
379
|
+
@@default_output_variable = '_erbout'
|
|
216
380
|
|
|
217
|
-
def
|
|
218
|
-
|
|
381
|
+
def self.default_output_variable
|
|
382
|
+
@@default_output_variable
|
|
219
383
|
end
|
|
220
384
|
|
|
221
|
-
def
|
|
222
|
-
|
|
385
|
+
def self.default_output_variable=(name)
|
|
386
|
+
@@default_output_variable = name
|
|
223
387
|
end
|
|
224
388
|
|
|
225
|
-
def
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
original_out_buf =
|
|
230
|
-
scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
|
|
231
|
-
scope.instance_variable_get(:@_out_buf)
|
|
389
|
+
def initialize_engine
|
|
390
|
+
return if defined? ::ERB
|
|
391
|
+
require_template_library 'erb'
|
|
392
|
+
end
|
|
232
393
|
|
|
233
|
-
|
|
394
|
+
def prepare
|
|
395
|
+
@outvar = options[:outvar] || self.class.default_output_variable
|
|
396
|
+
@engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
|
|
397
|
+
end
|
|
234
398
|
|
|
235
|
-
|
|
236
|
-
|
|
399
|
+
def precompiled_template(locals)
|
|
400
|
+
source = @engine.src
|
|
401
|
+
source
|
|
402
|
+
end
|
|
237
403
|
|
|
238
|
-
|
|
404
|
+
def precompiled_preamble(locals)
|
|
405
|
+
<<-RUBY
|
|
406
|
+
begin
|
|
407
|
+
__original_outvar = #{@outvar} if defined?(#{@outvar})
|
|
408
|
+
#{super}
|
|
409
|
+
RUBY
|
|
239
410
|
end
|
|
240
411
|
|
|
241
|
-
|
|
412
|
+
def precompiled_postamble(locals)
|
|
413
|
+
<<-RUBY
|
|
414
|
+
#{super}
|
|
415
|
+
ensure
|
|
416
|
+
#{@outvar} = __original_outvar
|
|
417
|
+
end
|
|
418
|
+
RUBY
|
|
419
|
+
end
|
|
242
420
|
|
|
243
421
|
# ERB generates a line to specify the character coding of the generated
|
|
244
422
|
# source in 1.9. Account for this in the line offset.
|
|
245
423
|
if RUBY_VERSION >= '1.9.0'
|
|
246
|
-
def
|
|
424
|
+
def precompiled(locals)
|
|
247
425
|
source, offset = super
|
|
248
426
|
[source, offset + 1]
|
|
249
427
|
end
|
|
250
428
|
end
|
|
251
429
|
end
|
|
430
|
+
|
|
252
431
|
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
|
253
432
|
|
|
254
433
|
|
|
255
434
|
# Erubis template implementation. See:
|
|
256
435
|
# http://www.kuwata-lab.com/erubis/
|
|
436
|
+
#
|
|
437
|
+
# ErubisTemplate supports the following additional options, which are not
|
|
438
|
+
# passed down to the Erubis engine:
|
|
439
|
+
#
|
|
440
|
+
# :engine_class allows you to specify a custom engine class to use
|
|
441
|
+
# instead of the default (which is ::Erubis::Eruby).
|
|
442
|
+
#
|
|
443
|
+
# :escape_html when true, ::Erubis::EscapedEruby will be used as
|
|
444
|
+
# the engine class instead of the default. All content
|
|
445
|
+
# within <%= %> blocks will be automatically html escaped.
|
|
257
446
|
class ErubisTemplate < ERBTemplate
|
|
258
447
|
def initialize_engine
|
|
259
|
-
|
|
448
|
+
return if defined? ::Erubis
|
|
449
|
+
require_template_library 'erubis'
|
|
260
450
|
end
|
|
261
451
|
|
|
262
|
-
def
|
|
263
|
-
|
|
264
|
-
@
|
|
452
|
+
def prepare
|
|
453
|
+
@options.merge!(:preamble => false, :postamble => false)
|
|
454
|
+
@outvar = options.delete(:outvar) || self.class.default_output_variable
|
|
455
|
+
engine_class = options.delete(:engine_class)
|
|
456
|
+
engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
|
|
457
|
+
@engine = (engine_class || ::Erubis::Eruby).new(data, options)
|
|
265
458
|
end
|
|
266
459
|
|
|
267
|
-
|
|
460
|
+
def precompiled_preamble(locals)
|
|
461
|
+
[super, "#{@outvar} = _buf = ''"].join("\n")
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def precompiled_postamble(locals)
|
|
465
|
+
["_buf", super].join("\n")
|
|
466
|
+
end
|
|
268
467
|
|
|
269
|
-
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
|
|
270
|
-
# and adjust back.
|
|
468
|
+
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
|
|
469
|
+
# Override and adjust back.
|
|
271
470
|
if RUBY_VERSION >= '1.9.0'
|
|
272
|
-
def
|
|
471
|
+
def precompiled(locals)
|
|
273
472
|
source, offset = super
|
|
274
473
|
[source, offset - 1]
|
|
275
474
|
end
|
|
@@ -282,20 +481,54 @@ module Tilt
|
|
|
282
481
|
# http://haml.hamptoncatlin.com/
|
|
283
482
|
class HamlTemplate < Template
|
|
284
483
|
def initialize_engine
|
|
285
|
-
|
|
484
|
+
return if defined? ::Haml::Engine
|
|
485
|
+
require_template_library 'haml'
|
|
286
486
|
end
|
|
287
487
|
|
|
288
|
-
def
|
|
289
|
-
|
|
488
|
+
def prepare
|
|
489
|
+
options = @options.merge(:filename => eval_file, :line => line)
|
|
490
|
+
@engine = ::Haml::Engine.new(data, options)
|
|
290
491
|
end
|
|
291
492
|
|
|
292
493
|
def evaluate(scope, locals, &block)
|
|
293
|
-
@engine.
|
|
494
|
+
if @engine.respond_to?(:precompiled_method_return_value, true)
|
|
495
|
+
super
|
|
496
|
+
else
|
|
497
|
+
@engine.render(scope, locals, &block)
|
|
498
|
+
end
|
|
294
499
|
end
|
|
295
500
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
501
|
+
# Precompiled Haml source. Taken from the precompiled_with_ambles
|
|
502
|
+
# method in Haml::Precompiler:
|
|
503
|
+
# http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
|
|
504
|
+
def precompiled_template(locals)
|
|
505
|
+
@engine.precompiled
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def precompiled_preamble(locals)
|
|
509
|
+
local_assigns = super
|
|
510
|
+
@engine.instance_eval do
|
|
511
|
+
<<-RUBY
|
|
512
|
+
begin
|
|
513
|
+
extend Haml::Helpers
|
|
514
|
+
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
|
515
|
+
_erbout = _hamlout.buffer
|
|
516
|
+
__in_erb_template = true
|
|
517
|
+
_haml_locals = locals
|
|
518
|
+
#{local_assigns}
|
|
519
|
+
RUBY
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def precompiled_postamble(locals)
|
|
524
|
+
@engine.instance_eval do
|
|
525
|
+
<<-RUBY
|
|
526
|
+
#{precompiled_method_return_value}
|
|
527
|
+
ensure
|
|
528
|
+
@haml_buffer = @haml_buffer.upper
|
|
529
|
+
end
|
|
530
|
+
RUBY
|
|
531
|
+
end
|
|
299
532
|
end
|
|
300
533
|
end
|
|
301
534
|
register 'haml', HamlTemplate
|
|
@@ -307,24 +540,33 @@ module Tilt
|
|
|
307
540
|
# Sass templates do not support object scopes, locals, or yield.
|
|
308
541
|
class SassTemplate < Template
|
|
309
542
|
def initialize_engine
|
|
310
|
-
|
|
543
|
+
return if defined? ::Sass::Engine
|
|
544
|
+
require_template_library 'sass'
|
|
311
545
|
end
|
|
312
546
|
|
|
313
|
-
def
|
|
547
|
+
def prepare
|
|
314
548
|
@engine = ::Sass::Engine.new(data, sass_options)
|
|
315
549
|
end
|
|
316
550
|
|
|
317
551
|
def evaluate(scope, locals, &block)
|
|
318
|
-
@engine.render
|
|
552
|
+
@output ||= @engine.render
|
|
319
553
|
end
|
|
320
554
|
|
|
321
555
|
private
|
|
322
556
|
def sass_options
|
|
323
|
-
options.merge(:filename => eval_file, :line => line)
|
|
557
|
+
options.merge(:filename => eval_file, :line => line, :syntax => :sass)
|
|
324
558
|
end
|
|
325
559
|
end
|
|
326
560
|
register 'sass', SassTemplate
|
|
327
561
|
|
|
562
|
+
# Sass's new .scss type template implementation.
|
|
563
|
+
class ScssTemplate < SassTemplate
|
|
564
|
+
private
|
|
565
|
+
def sass_options
|
|
566
|
+
options.merge(:filename => eval_file, :line => line, :syntax => :scss)
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
register 'scss', ScssTemplate
|
|
328
570
|
|
|
329
571
|
# Lessscss template implementation. See:
|
|
330
572
|
# http://lesscss.org/
|
|
@@ -332,10 +574,11 @@ module Tilt
|
|
|
332
574
|
# Less templates do not support object scopes, locals, or yield.
|
|
333
575
|
class LessTemplate < Template
|
|
334
576
|
def initialize_engine
|
|
335
|
-
|
|
577
|
+
return if defined? ::Less::Engine
|
|
578
|
+
require_template_library 'less'
|
|
336
579
|
end
|
|
337
580
|
|
|
338
|
-
def
|
|
581
|
+
def prepare
|
|
339
582
|
@engine = ::Less::Engine.new(data)
|
|
340
583
|
end
|
|
341
584
|
|
|
@@ -345,14 +588,75 @@ module Tilt
|
|
|
345
588
|
end
|
|
346
589
|
register 'less', LessTemplate
|
|
347
590
|
|
|
591
|
+
|
|
592
|
+
# CoffeeScript template implementation. See:
|
|
593
|
+
# http://coffeescript.org/
|
|
594
|
+
#
|
|
595
|
+
# CoffeeScript templates do not support object scopes, locals, or yield.
|
|
596
|
+
class CoffeeScriptTemplate < Template
|
|
597
|
+
@@default_no_wrap = false
|
|
598
|
+
|
|
599
|
+
def self.default_no_wrap
|
|
600
|
+
@@default_no_wrap
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def self.default_no_wrap=(value)
|
|
604
|
+
@@default_no_wrap = value
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def initialize_engine
|
|
608
|
+
return if defined? ::CoffeeScript
|
|
609
|
+
require_template_library 'coffee_script'
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def prepare
|
|
613
|
+
@no_wrap = options.key?(:no_wrap) ? options[:no_wrap] :
|
|
614
|
+
self.class.default_no_wrap
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def evaluate(scope, locals, &block)
|
|
618
|
+
@output ||= CoffeeScript.compile(data, :no_wrap => @no_wrap)
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
register 'coffee', CoffeeScriptTemplate
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
# Nokogiri template implementation. See:
|
|
625
|
+
# http://nokogiri.org/
|
|
626
|
+
class NokogiriTemplate < Template
|
|
627
|
+
def initialize_engine
|
|
628
|
+
return if defined?(::Nokogiri)
|
|
629
|
+
require_template_library 'nokogiri'
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def prepare; end
|
|
633
|
+
|
|
634
|
+
def evaluate(scope, locals, &block)
|
|
635
|
+
xml = ::Nokogiri::XML::Builder.new
|
|
636
|
+
if data.respond_to?(:to_str)
|
|
637
|
+
locals[:xml] = xml
|
|
638
|
+
super(scope, locals, &block)
|
|
639
|
+
elsif data.kind_of?(Proc)
|
|
640
|
+
data.call(xml)
|
|
641
|
+
end
|
|
642
|
+
xml.to_xml
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def precompiled_template(locals)
|
|
646
|
+
data.to_str
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
register 'nokogiri', NokogiriTemplate
|
|
650
|
+
|
|
348
651
|
# Builder template implementation. See:
|
|
349
652
|
# http://builder.rubyforge.org/
|
|
350
653
|
class BuilderTemplate < Template
|
|
351
654
|
def initialize_engine
|
|
352
|
-
|
|
655
|
+
return if defined?(::Builder)
|
|
656
|
+
require_template_library 'builder'
|
|
353
657
|
end
|
|
354
658
|
|
|
355
|
-
def
|
|
659
|
+
def prepare
|
|
356
660
|
end
|
|
357
661
|
|
|
358
662
|
def evaluate(scope, locals, &block)
|
|
@@ -366,7 +670,7 @@ module Tilt
|
|
|
366
670
|
xml.target!
|
|
367
671
|
end
|
|
368
672
|
|
|
369
|
-
def
|
|
673
|
+
def precompiled_template(locals)
|
|
370
674
|
data.to_str
|
|
371
675
|
end
|
|
372
676
|
end
|
|
@@ -388,10 +692,11 @@ module Tilt
|
|
|
388
692
|
# time when using this template engine.
|
|
389
693
|
class LiquidTemplate < Template
|
|
390
694
|
def initialize_engine
|
|
391
|
-
|
|
695
|
+
return if defined? ::Liquid::Template
|
|
696
|
+
require_template_library 'liquid'
|
|
392
697
|
end
|
|
393
698
|
|
|
394
|
-
def
|
|
699
|
+
def prepare
|
|
395
700
|
@engine = ::Liquid::Template.parse(data)
|
|
396
701
|
end
|
|
397
702
|
|
|
@@ -421,15 +726,17 @@ module Tilt
|
|
|
421
726
|
end
|
|
422
727
|
|
|
423
728
|
def initialize_engine
|
|
424
|
-
|
|
729
|
+
return if defined? ::RDiscount
|
|
730
|
+
require_template_library 'rdiscount'
|
|
425
731
|
end
|
|
426
732
|
|
|
427
|
-
def
|
|
733
|
+
def prepare
|
|
428
734
|
@engine = RDiscount.new(data, *flags)
|
|
735
|
+
@output = nil
|
|
429
736
|
end
|
|
430
737
|
|
|
431
738
|
def evaluate(scope, locals, &block)
|
|
432
|
-
@engine.to_html
|
|
739
|
+
@output ||= @engine.to_html
|
|
433
740
|
end
|
|
434
741
|
end
|
|
435
742
|
register 'markdown', RDiscountTemplate
|
|
@@ -441,67 +748,22 @@ module Tilt
|
|
|
441
748
|
# http://redcloth.org/
|
|
442
749
|
class RedClothTemplate < Template
|
|
443
750
|
def initialize_engine
|
|
444
|
-
|
|
751
|
+
return if defined? ::RedCloth
|
|
752
|
+
require_template_library 'redcloth'
|
|
445
753
|
end
|
|
446
754
|
|
|
447
|
-
def
|
|
755
|
+
def prepare
|
|
448
756
|
@engine = RedCloth.new(data)
|
|
757
|
+
@output = nil
|
|
449
758
|
end
|
|
450
759
|
|
|
451
760
|
def evaluate(scope, locals, &block)
|
|
452
|
-
@engine.to_html
|
|
761
|
+
@output ||= @engine.to_html
|
|
453
762
|
end
|
|
454
763
|
end
|
|
455
764
|
register 'textile', RedClothTemplate
|
|
456
765
|
|
|
457
766
|
|
|
458
|
-
# Mustache is written and maintained by Chris Wanstrath. See:
|
|
459
|
-
# http://github.com/defunkt/mustache
|
|
460
|
-
#
|
|
461
|
-
# When a scope argument is provided to MustacheTemplate#render, the
|
|
462
|
-
# instance variables are copied from the scope object to the Mustache
|
|
463
|
-
# view.
|
|
464
|
-
class MustacheTemplate < Template
|
|
465
|
-
attr_reader :engine
|
|
466
|
-
|
|
467
|
-
def initialize_engine
|
|
468
|
-
require_template_library 'mustache' unless defined? ::Mustache
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
def compile!
|
|
472
|
-
Mustache.view_namespace = options[:namespace]
|
|
473
|
-
Mustache.view_path = options[:view_path] || options[:mustaches]
|
|
474
|
-
@engine = options[:view] || Mustache.view_class(name)
|
|
475
|
-
options.each do |key, value|
|
|
476
|
-
next if %w[view view_path namespace mustaches].include?(key.to_s)
|
|
477
|
-
@engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
def evaluate(scope=nil, locals={}, &block)
|
|
482
|
-
instance = @engine.new
|
|
483
|
-
|
|
484
|
-
# copy instance variables from scope to the view
|
|
485
|
-
scope.instance_variables.each do |name|
|
|
486
|
-
instance.instance_variable_set(name, scope.instance_variable_get(name))
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
# locals get added to the view's context
|
|
490
|
-
locals.each do |local, value|
|
|
491
|
-
instance[local] = value
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# if we're passed a block it's a subview. Sticking it in yield
|
|
495
|
-
# lets us use {{yield}} in layout.html to render the actual page.
|
|
496
|
-
instance[:yield] = block.call if block
|
|
497
|
-
|
|
498
|
-
instance.template = data unless instance.compiled?
|
|
499
|
-
|
|
500
|
-
instance.to_html
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
register 'mustache', MustacheTemplate
|
|
504
|
-
|
|
505
767
|
# RDoc template. See:
|
|
506
768
|
# http://rdoc.rubyforge.org/
|
|
507
769
|
#
|
|
@@ -510,37 +772,97 @@ module Tilt
|
|
|
510
772
|
# engine.
|
|
511
773
|
class RDocTemplate < Template
|
|
512
774
|
def initialize_engine
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
end
|
|
775
|
+
return if defined?(::RDoc::Markup)
|
|
776
|
+
require_template_library 'rdoc/markup'
|
|
777
|
+
require_template_library 'rdoc/markup/to_html'
|
|
517
778
|
end
|
|
518
779
|
|
|
519
|
-
def
|
|
780
|
+
def prepare
|
|
520
781
|
markup = RDoc::Markup::ToHtml.new
|
|
521
782
|
@engine = markup.convert(data)
|
|
783
|
+
@output = nil
|
|
522
784
|
end
|
|
523
785
|
|
|
524
786
|
def evaluate(scope, locals, &block)
|
|
525
|
-
@engine.to_s
|
|
787
|
+
@output ||= @engine.to_s
|
|
526
788
|
end
|
|
527
789
|
end
|
|
528
790
|
register 'rdoc', RDocTemplate
|
|
529
791
|
|
|
530
|
-
|
|
531
|
-
#
|
|
532
|
-
|
|
792
|
+
|
|
793
|
+
# Radius Template
|
|
794
|
+
# http://github.com/jlong/radius/
|
|
795
|
+
class RadiusTemplate < Template
|
|
796
|
+
def initialize_engine
|
|
797
|
+
return if defined? ::Radius
|
|
798
|
+
require_template_library 'radius'
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def prepare
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def evaluate(scope, locals, &block)
|
|
805
|
+
context = Class.new(Radius::Context).new
|
|
806
|
+
context.define_tag("yield") do
|
|
807
|
+
block.call
|
|
808
|
+
end
|
|
809
|
+
locals.each do |tag, value|
|
|
810
|
+
context.define_tag(tag) do
|
|
811
|
+
value
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
(class << context; self; end).class_eval do
|
|
815
|
+
define_method :tag_missing do |tag, attr|
|
|
816
|
+
scope.__send__(tag) # any way to support attr as args?
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
options = {:tag_prefix => 'r'}.merge(@options)
|
|
820
|
+
parser = Radius::Parser.new(context, options)
|
|
821
|
+
parser.parse(data)
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
register 'radius', RadiusTemplate
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
# Markaby
|
|
828
|
+
# http://github.com/markaby/markaby
|
|
829
|
+
class MarkabyTemplate < Template
|
|
830
|
+
def self.builder_class
|
|
831
|
+
@builder_class ||= Class.new(Markaby::Builder) do
|
|
832
|
+
def __capture_markaby_tilt__(&block)
|
|
833
|
+
__run_markaby_tilt__ do
|
|
834
|
+
text capture(&block)
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
|
|
533
840
|
def initialize_engine
|
|
534
|
-
|
|
841
|
+
return if defined? ::Markaby
|
|
842
|
+
require_template_library 'markaby'
|
|
535
843
|
end
|
|
536
844
|
|
|
537
|
-
def
|
|
538
|
-
@engine = ::CoffeeScript::compile(data, options)
|
|
845
|
+
def prepare
|
|
539
846
|
end
|
|
540
847
|
|
|
541
848
|
def evaluate(scope, locals, &block)
|
|
542
|
-
|
|
849
|
+
builder = self.class.builder_class.new({}, scope)
|
|
850
|
+
builder.locals = locals
|
|
851
|
+
|
|
852
|
+
if block
|
|
853
|
+
builder.instance_eval <<-CODE, __FILE__, __LINE__
|
|
854
|
+
def __run_markaby_tilt__
|
|
855
|
+
#{data}
|
|
856
|
+
end
|
|
857
|
+
CODE
|
|
858
|
+
|
|
859
|
+
builder.__capture_markaby_tilt__(&block)
|
|
860
|
+
else
|
|
861
|
+
builder.instance_eval(data, __FILE__, __LINE__)
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
builder.to_s
|
|
543
865
|
end
|
|
544
866
|
end
|
|
545
|
-
register '
|
|
867
|
+
register 'mab', MarkabyTemplate
|
|
546
868
|
end
|