tilt 1.2 → 1.3.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/README.md +54 -24
- data/TEMPLATES.md +250 -175
- data/lib/tilt/builder.rb +40 -0
- data/lib/tilt/coffee.rb +50 -0
- data/lib/tilt/creole.rb +28 -0
- data/lib/tilt/css.rb +68 -0
- data/lib/tilt/erb.rb +110 -0
- data/lib/tilt/haml.rb +64 -0
- data/lib/tilt/liquid.rb +41 -0
- data/lib/tilt/markaby.rb +52 -0
- data/lib/tilt/markdown.rb +131 -0
- data/lib/tilt/nokogiri.rb +43 -0
- data/lib/tilt/radius.rb +51 -0
- data/lib/tilt/rdoc.rb +32 -0
- data/lib/tilt/string.rb +21 -0
- data/lib/tilt/template.rb +285 -0
- data/lib/tilt/textile.rb +25 -0
- data/lib/tilt.rb +102 -805
- data/test/tilt_blueclothtemplate_test.rb +11 -25
- data/test/tilt_buildertemplate_test.rb +16 -1
- data/test/tilt_coffeescripttemplate_test.rb +37 -1
- data/test/tilt_creoletemplate_test.rb +24 -0
- data/test/tilt_erbtemplate_test.rb +23 -19
- data/test/tilt_erubistemplate_test.rb +17 -2
- data/test/tilt_fallback_test.rb +122 -0
- data/test/tilt_hamltemplate_test.rb +9 -4
- data/test/tilt_kramdown_test.rb +42 -0
- data/test/tilt_lesstemplate_test.rb +5 -0
- data/test/tilt_liquidtemplate_test.rb +5 -0
- data/test/tilt_markaby_test.rb +5 -0
- data/test/tilt_markdown_test.rb +149 -0
- data/test/tilt_marukutemplate_test.rb +48 -0
- data/test/tilt_nokogiritemplate_test.rb +20 -7
- data/test/tilt_radiustemplate_test.rb +5 -0
- data/test/tilt_rdiscounttemplate_test.rb +21 -6
- data/test/tilt_rdoctemplate_test.rb +6 -1
- data/test/tilt_redcarpettemplate_test.rb +55 -0
- data/test/tilt_redclothtemplate_test.rb +4 -0
- data/test/tilt_sasstemplate_test.rb +21 -0
- data/test/tilt_stringtemplate_test.rb +15 -3
- data/test/tilt_template_test.rb +6 -0
- data/tilt.gemspec +28 -7
- metadata +105 -7
data/lib/tilt.rb
CHANGED
|
@@ -1,23 +1,57 @@
|
|
|
1
1
|
module Tilt
|
|
2
|
-
|
|
3
|
-
VERSION = '1.2'
|
|
2
|
+
VERSION = '1.3.1'
|
|
4
3
|
|
|
5
|
-
@
|
|
4
|
+
@preferred_mappings = Hash.new
|
|
5
|
+
@template_mappings = Hash.new { |h, k| h[k] = [] }
|
|
6
6
|
|
|
7
7
|
# Hash of template path pattern => template implementation class mappings.
|
|
8
8
|
def self.mappings
|
|
9
9
|
@template_mappings
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def self.normalize(ext)
|
|
13
|
+
ext.to_s.downcase.sub(/^\./, '')
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
# Register a template implementation by file extension.
|
|
13
|
-
def self.register(
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
def self.register(template_class, *extensions)
|
|
18
|
+
if template_class.respond_to?(:to_str)
|
|
19
|
+
# Support register(ext, template_class) too
|
|
20
|
+
extensions, template_class = [template_class], extensions[0]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
extensions.each do |ext|
|
|
24
|
+
ext = normalize(ext)
|
|
25
|
+
mappings[ext].unshift(template_class).uniq!
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Makes a template class preferred for the given file extensions. If you
|
|
30
|
+
# don't provide any extensions, it will be preferred for all its already
|
|
31
|
+
# registered extensions:
|
|
32
|
+
#
|
|
33
|
+
# # Prefer RDiscount for its registered file extensions:
|
|
34
|
+
# Tilt.prefer(Tilt::RDiscountTemplate)
|
|
35
|
+
#
|
|
36
|
+
# # Prefer RDiscount only for the .md extensions:
|
|
37
|
+
# Tilt.prefer(Tilt::RDiscountTemplate, '.md')
|
|
38
|
+
def self.prefer(template_class, *extensions)
|
|
39
|
+
if extensions.empty?
|
|
40
|
+
mappings.each do |ext, klasses|
|
|
41
|
+
@preferred_mappings[ext] = template_class if klasses.include? template_class
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
extensions.each do |ext|
|
|
45
|
+
ext = normalize(ext)
|
|
46
|
+
register(template_class, ext)
|
|
47
|
+
@preferred_mappings[ext] = template_class
|
|
48
|
+
end
|
|
49
|
+
end
|
|
16
50
|
end
|
|
17
51
|
|
|
18
52
|
# Returns true when a template exists on an exact match of the provided file extension
|
|
19
53
|
def self.registered?(ext)
|
|
20
|
-
mappings.key?(ext.downcase)
|
|
54
|
+
mappings.key?(ext.downcase) && !mappings[ext.downcase].empty?
|
|
21
55
|
end
|
|
22
56
|
|
|
23
57
|
# Create a new template for the given file using the file's extension
|
|
@@ -34,297 +68,47 @@ module Tilt
|
|
|
34
68
|
# extension. Return nil when no implementation is found.
|
|
35
69
|
def self.[](file)
|
|
36
70
|
pattern = file.to_s.downcase
|
|
37
|
-
|
|
71
|
+
until pattern.empty? || registered?(pattern)
|
|
38
72
|
pattern = File.basename(pattern)
|
|
39
|
-
pattern.sub!(/^[^.]*\.?/, '')
|
|
73
|
+
pattern.sub!(/^[^.]*\.?/, '')
|
|
40
74
|
end
|
|
41
|
-
@template_mappings[pattern]
|
|
42
|
-
end
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
warn "WARNING: Tilt::CompileSite is deprecated and will be removed in Tilt 2.0 (#{caller.first})."
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Base class for template implementations. Subclasses must implement
|
|
52
|
-
# the #prepare method and one of the #evaluate or #precompiled_template
|
|
53
|
-
# methods.
|
|
54
|
-
class Template
|
|
55
|
-
# Template source; loaded from a file or given directly.
|
|
56
|
-
attr_reader :data
|
|
57
|
-
|
|
58
|
-
# The name of the file where the template data was loaded from.
|
|
59
|
-
attr_reader :file
|
|
60
|
-
|
|
61
|
-
# The line number in #file where template data was loaded from.
|
|
62
|
-
attr_reader :line
|
|
63
|
-
|
|
64
|
-
# A Hash of template engine specific options. This is passed directly
|
|
65
|
-
# to the underlying engine and is not used by the generic template
|
|
66
|
-
# interface.
|
|
67
|
-
attr_reader :options
|
|
68
|
-
|
|
69
|
-
# Used to determine if this class's initialize_engine method has
|
|
70
|
-
# been called yet.
|
|
71
|
-
@engine_initialized = false
|
|
72
|
-
class << self
|
|
73
|
-
attr_accessor :engine_initialized
|
|
74
|
-
alias engine_initialized? engine_initialized
|
|
75
|
-
end
|
|
76
|
+
# Try to find a preferred engine.
|
|
77
|
+
preferred_klass = @preferred_mappings[pattern]
|
|
78
|
+
return preferred_klass if preferred_klass
|
|
76
79
|
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
# it should read template data and return as a String. When file is nil,
|
|
80
|
-
# a block is required.
|
|
81
|
-
#
|
|
82
|
-
# All arguments are optional.
|
|
83
|
-
def initialize(file=nil, line=1, options={}, &block)
|
|
84
|
-
@file, @line, @options = nil, 1, {}
|
|
85
|
-
|
|
86
|
-
[options, line, file].compact.each do |arg|
|
|
87
|
-
case
|
|
88
|
-
when arg.respond_to?(:to_str) ; @file = arg.to_str
|
|
89
|
-
when arg.respond_to?(:to_int) ; @line = arg.to_int
|
|
90
|
-
when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
|
|
91
|
-
else raise TypeError
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
raise ArgumentError, "file or block required" if (@file || block).nil?
|
|
80
|
+
# Fall back to the general list of mappings.
|
|
81
|
+
klasses = @template_mappings[pattern]
|
|
96
82
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
self.class.engine_initialized = true
|
|
83
|
+
# Try to find an engine which is already loaded.
|
|
84
|
+
template = klasses.detect do |klass|
|
|
85
|
+
if klass.respond_to?(:engine_initialized?)
|
|
86
|
+
klass.engine_initialized?
|
|
102
87
|
end
|
|
103
|
-
|
|
104
|
-
# used to hold compiled template methods
|
|
105
|
-
@compiled_method = {}
|
|
106
|
-
|
|
107
|
-
# used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment)
|
|
108
|
-
# currently only used if template compiles to ruby
|
|
109
|
-
@default_encoding = @options.delete :default_encoding
|
|
110
|
-
|
|
111
|
-
# load template data and prepare (uses binread to avoid encoding issues)
|
|
112
|
-
@reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : File.read(@file) }
|
|
113
|
-
@data = @reader.call(self)
|
|
114
|
-
prepare
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Render the template in the given scope with the locals specified. If a
|
|
118
|
-
# block is given, it is typically available within the template via
|
|
119
|
-
# +yield+.
|
|
120
|
-
def render(scope=Object.new, locals={}, &block)
|
|
121
|
-
evaluate scope, locals || {}, &block
|
|
122
88
|
end
|
|
123
89
|
|
|
124
|
-
|
|
125
|
-
def basename(suffix='')
|
|
126
|
-
File.basename(file, suffix) if file
|
|
127
|
-
end
|
|
90
|
+
return template if template
|
|
128
91
|
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# The filename used in backtraces to describe the template.
|
|
135
|
-
def eval_file
|
|
136
|
-
file || '(__TEMPLATE__)'
|
|
137
|
-
end
|
|
92
|
+
# Try each of the classes until one succeeds. If all of them fails,
|
|
93
|
+
# we'll raise the error of the first class.
|
|
94
|
+
first_failure = nil
|
|
138
95
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# Like Kernel::require but issues a warning urging a manual require when
|
|
147
|
-
# running under a threaded environment.
|
|
148
|
-
def require_template_library(name)
|
|
149
|
-
if Thread.list.size > 1
|
|
150
|
-
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
|
|
151
|
-
"explicit require '#{name}' suggested."
|
|
152
|
-
end
|
|
153
|
-
require name
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Do whatever preparation is necessary to setup the underlying template
|
|
157
|
-
# engine. Called immediately after template data is loaded. Instance
|
|
158
|
-
# variables set in this method are available when #evaluate is called.
|
|
159
|
-
#
|
|
160
|
-
# Subclasses must provide an implementation of this method.
|
|
161
|
-
def prepare
|
|
162
|
-
if respond_to?(:compile!)
|
|
163
|
-
# backward compat with tilt < 0.6; just in case
|
|
164
|
-
warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
|
|
165
|
-
compile!
|
|
96
|
+
klasses.each do |klass|
|
|
97
|
+
begin
|
|
98
|
+
klass.new { '' }
|
|
99
|
+
rescue Exception => ex
|
|
100
|
+
first_failure ||= ex
|
|
101
|
+
next
|
|
166
102
|
else
|
|
167
|
-
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Process the template and return the result. The first time this
|
|
172
|
-
# method is called, the template source is evaluated with instance_eval.
|
|
173
|
-
# On the sequential method calls it will compile the template to an
|
|
174
|
-
# unbound method which will lead to better performance. In any case,
|
|
175
|
-
# template executation is guaranteed to be performed in the scope object
|
|
176
|
-
# with the locals specified and with support for yielding to the block.
|
|
177
|
-
def evaluate(scope, locals, &block)
|
|
178
|
-
# Redefine itself to use method compilation the next time:
|
|
179
|
-
def self.evaluate(scope, locals, &block)
|
|
180
|
-
method = compiled_method(locals.keys)
|
|
181
|
-
method.bind(scope).call(locals, &block)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# Use instance_eval the first time:
|
|
185
|
-
evaluate_source(scope, locals, &block)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Generates all template source by combining the preamble, template, and
|
|
189
|
-
# postamble and returns a two-tuple of the form: [source, offset], where
|
|
190
|
-
# source is the string containing (Ruby) source code for the template and
|
|
191
|
-
# offset is the integer line offset where line reporting should begin.
|
|
192
|
-
#
|
|
193
|
-
# Template subclasses may override this method when they need complete
|
|
194
|
-
# control over source generation or want to adjust the default line
|
|
195
|
-
# offset. In most cases, overriding the #precompiled_template method is
|
|
196
|
-
# easier and more appropriate.
|
|
197
|
-
def precompiled(locals)
|
|
198
|
-
preamble = precompiled_preamble(locals)
|
|
199
|
-
template = precompiled_template(locals)
|
|
200
|
-
magic_comment = extract_magic_comment(template)
|
|
201
|
-
if magic_comment
|
|
202
|
-
# Magic comment e.g. "# coding: utf-8" has to be in the first line.
|
|
203
|
-
# So we copy the magic comment to the first line.
|
|
204
|
-
preamble = magic_comment + "\n" + preamble
|
|
205
|
-
end
|
|
206
|
-
parts = [
|
|
207
|
-
preamble,
|
|
208
|
-
template,
|
|
209
|
-
precompiled_postamble(locals)
|
|
210
|
-
]
|
|
211
|
-
[parts.join("\n"), preamble.count("\n") + 1]
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# A string containing the (Ruby) source code for the template. The
|
|
215
|
-
# default Template#evaluate implementation requires either this method
|
|
216
|
-
# or the #precompiled method be overridden. When defined, the base
|
|
217
|
-
# Template guarantees correct file/line handling, locals support, custom
|
|
218
|
-
# scopes, and support for template compilation when the scope object
|
|
219
|
-
# allows it.
|
|
220
|
-
def precompiled_template(locals)
|
|
221
|
-
raise NotImplementedError
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# Generates preamble code for initializing template state, and performing
|
|
225
|
-
# locals assignment. The default implementation performs locals
|
|
226
|
-
# assignment only. Lines included in the preamble are subtracted from the
|
|
227
|
-
# source line offset, so adding code to the preamble does not effect line
|
|
228
|
-
# reporting in Kernel::caller and backtraces.
|
|
229
|
-
def precompiled_preamble(locals)
|
|
230
|
-
locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# Generates postamble code for the precompiled template source. The
|
|
234
|
-
# string returned from this method is appended to the precompiled
|
|
235
|
-
# template source.
|
|
236
|
-
def precompiled_postamble(locals)
|
|
237
|
-
''
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# The compiled method for the locals keys provided.
|
|
241
|
-
def compiled_method(locals_keys)
|
|
242
|
-
@compiled_method[locals_keys] ||=
|
|
243
|
-
compile_template_method(locals_keys)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
private
|
|
247
|
-
# Evaluate the template source in the context of the scope object.
|
|
248
|
-
def evaluate_source(scope, locals, &block)
|
|
249
|
-
source, offset = precompiled(locals)
|
|
250
|
-
scope.instance_eval(source, eval_file, line - offset)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# JRuby doesn't allow Object#instance_eval to yield to the block it's
|
|
254
|
-
# closed over. This is by design and (ostensibly) something that will
|
|
255
|
-
# change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
|
|
256
|
-
# exhibits the behavior. More info here:
|
|
257
|
-
#
|
|
258
|
-
# http://jira.codehaus.org/browse/JRUBY-2599
|
|
259
|
-
#
|
|
260
|
-
# Additionally, JRuby's eval line reporting is off by one compared to
|
|
261
|
-
# all MRI versions tested.
|
|
262
|
-
#
|
|
263
|
-
# We redefine evaluate_source to work around both issues.
|
|
264
|
-
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
265
|
-
undef evaluate_source
|
|
266
|
-
def evaluate_source(scope, locals, &block)
|
|
267
|
-
source, offset = precompiled(locals)
|
|
268
|
-
file, lineno = eval_file, (line - offset) - 1
|
|
269
|
-
scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
|
|
103
|
+
return klass
|
|
270
104
|
end
|
|
271
105
|
end
|
|
272
106
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
offset += 5
|
|
276
|
-
method_name = "__tilt_#{Thread.current.object_id.abs}"
|
|
277
|
-
Object.class_eval <<-RUBY, eval_file, line - offset
|
|
278
|
-
#{extract_magic_comment source}
|
|
279
|
-
TOPOBJECT.class_eval do
|
|
280
|
-
def #{method_name}(locals)
|
|
281
|
-
Thread.current[:tilt_vars] = [self, locals]
|
|
282
|
-
class << self
|
|
283
|
-
this, locals = Thread.current[:tilt_vars]
|
|
284
|
-
this.instance_eval do
|
|
285
|
-
#{source}
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
RUBY
|
|
291
|
-
unbind_compiled_method(method_name)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def unbind_compiled_method(method_name)
|
|
295
|
-
method = TOPOBJECT.instance_method(method_name)
|
|
296
|
-
TOPOBJECT.class_eval { remove_method(method_name) }
|
|
297
|
-
method
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
def extract_magic_comment(script)
|
|
301
|
-
comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/)
|
|
302
|
-
return comment if comment and not %w[ascii-8bit binary].include?($1.downcase)
|
|
303
|
-
"# coding: #{@default_encoding}" if @default_encoding
|
|
304
|
-
end
|
|
107
|
+
raise first_failure if first_failure
|
|
108
|
+
end
|
|
305
109
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
# http://github.com/rtomayko/tilt/commit/20c01a5
|
|
309
|
-
# http://redmine.ruby-lang.org/issues/show/3601
|
|
310
|
-
#
|
|
311
|
-
# Remove when 1.9.2 dominates 1.9.1 installs in the wild.
|
|
312
|
-
if RUBY_VERSION =~ /^1.9.1/
|
|
313
|
-
undef compile_template_method
|
|
314
|
-
def compile_template_method(locals)
|
|
315
|
-
source, offset = precompiled(locals)
|
|
316
|
-
offset += 1
|
|
317
|
-
method_name = "__tilt_#{Thread.current.object_id}"
|
|
318
|
-
Object.class_eval <<-RUBY, eval_file, line - offset
|
|
319
|
-
TOPOBJECT.class_eval do
|
|
320
|
-
def #{method_name}(locals)
|
|
321
|
-
#{source}
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
RUBY
|
|
325
|
-
unbind_compiled_method(method_name)
|
|
326
|
-
end
|
|
327
|
-
end
|
|
110
|
+
# Deprecated module.
|
|
111
|
+
module CompileSite
|
|
328
112
|
end
|
|
329
113
|
|
|
330
114
|
# Extremely simple template cache implementation. Calling applications
|
|
@@ -351,539 +135,52 @@ module Tilt
|
|
|
351
135
|
|
|
352
136
|
# Template Implementations ================================================
|
|
353
137
|
|
|
138
|
+
require 'tilt/string'
|
|
139
|
+
register StringTemplate, 'str'
|
|
354
140
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
def prepare
|
|
359
|
-
@code = "%Q{#{data}}"
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def precompiled_template(locals)
|
|
363
|
-
@code
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
register 'str', StringTemplate
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
# ERB template implementation. See:
|
|
370
|
-
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
|
|
371
|
-
class ERBTemplate < Template
|
|
372
|
-
@@default_output_variable = '_erbout'
|
|
373
|
-
|
|
374
|
-
def self.default_output_variable
|
|
375
|
-
@@default_output_variable
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
def self.default_output_variable=(name)
|
|
379
|
-
@@default_output_variable = name
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
def initialize_engine
|
|
383
|
-
return if defined? ::ERB
|
|
384
|
-
require_template_library 'erb'
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
def prepare
|
|
388
|
-
@outvar = options[:outvar] || self.class.default_output_variable
|
|
389
|
-
@engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
def precompiled_template(locals)
|
|
393
|
-
source = @engine.src
|
|
394
|
-
source
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
def precompiled_preamble(locals)
|
|
398
|
-
<<-RUBY
|
|
399
|
-
begin
|
|
400
|
-
__original_outvar = #{@outvar} if defined?(#{@outvar})
|
|
401
|
-
#{super}
|
|
402
|
-
RUBY
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
def precompiled_postamble(locals)
|
|
406
|
-
<<-RUBY
|
|
407
|
-
#{super}
|
|
408
|
-
ensure
|
|
409
|
-
#{@outvar} = __original_outvar
|
|
410
|
-
end
|
|
411
|
-
RUBY
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
# ERB generates a line to specify the character coding of the generated
|
|
415
|
-
# source in 1.9. Account for this in the line offset.
|
|
416
|
-
if RUBY_VERSION >= '1.9.0'
|
|
417
|
-
def precompiled(locals)
|
|
418
|
-
source, offset = super
|
|
419
|
-
[source, offset + 1]
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
# Erubis template implementation. See:
|
|
428
|
-
# http://www.kuwata-lab.com/erubis/
|
|
429
|
-
#
|
|
430
|
-
# ErubisTemplate supports the following additional options, which are not
|
|
431
|
-
# passed down to the Erubis engine:
|
|
432
|
-
#
|
|
433
|
-
# :engine_class allows you to specify a custom engine class to use
|
|
434
|
-
# instead of the default (which is ::Erubis::Eruby).
|
|
435
|
-
#
|
|
436
|
-
# :escape_html when true, ::Erubis::EscapedEruby will be used as
|
|
437
|
-
# the engine class instead of the default. All content
|
|
438
|
-
# within <%= %> blocks will be automatically html escaped.
|
|
439
|
-
class ErubisTemplate < ERBTemplate
|
|
440
|
-
def initialize_engine
|
|
441
|
-
return if defined? ::Erubis
|
|
442
|
-
require_template_library 'erubis'
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
def prepare
|
|
446
|
-
@options.merge!(:preamble => false, :postamble => false)
|
|
447
|
-
@outvar = options.delete(:outvar) || self.class.default_output_variable
|
|
448
|
-
engine_class = options.delete(:engine_class)
|
|
449
|
-
engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
|
|
450
|
-
@engine = (engine_class || ::Erubis::Eruby).new(data, options)
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
def precompiled_preamble(locals)
|
|
454
|
-
[super, "#{@outvar} = _buf = ''"].join("\n")
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
def precompiled_postamble(locals)
|
|
458
|
-
["_buf", super].join("\n")
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
|
|
462
|
-
# Override and adjust back.
|
|
463
|
-
if RUBY_VERSION >= '1.9.0'
|
|
464
|
-
def precompiled(locals)
|
|
465
|
-
source, offset = super
|
|
466
|
-
[source, offset - 1]
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
register 'erubis', ErubisTemplate
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
# Haml template implementation. See:
|
|
474
|
-
# http://haml.hamptoncatlin.com/
|
|
475
|
-
class HamlTemplate < Template
|
|
476
|
-
def initialize_engine
|
|
477
|
-
return if defined? ::Haml::Engine
|
|
478
|
-
require_template_library 'haml'
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
def prepare
|
|
482
|
-
options = @options.merge(:filename => eval_file, :line => line)
|
|
483
|
-
@engine = ::Haml::Engine.new(data, options)
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
def evaluate(scope, locals, &block)
|
|
487
|
-
if @engine.respond_to?(:precompiled_method_return_value, true)
|
|
488
|
-
super
|
|
489
|
-
else
|
|
490
|
-
@engine.render(scope, locals, &block)
|
|
491
|
-
end
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# Precompiled Haml source. Taken from the precompiled_with_ambles
|
|
495
|
-
# method in Haml::Precompiler:
|
|
496
|
-
# http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
|
|
497
|
-
def precompiled_template(locals)
|
|
498
|
-
@engine.precompiled
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
def precompiled_preamble(locals)
|
|
502
|
-
local_assigns = super
|
|
503
|
-
@engine.instance_eval do
|
|
504
|
-
<<-RUBY
|
|
505
|
-
begin
|
|
506
|
-
extend Haml::Helpers
|
|
507
|
-
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
|
508
|
-
_erbout = _hamlout.buffer
|
|
509
|
-
__in_erb_template = true
|
|
510
|
-
_haml_locals = locals
|
|
511
|
-
#{local_assigns}
|
|
512
|
-
RUBY
|
|
513
|
-
end
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
def precompiled_postamble(locals)
|
|
517
|
-
@engine.instance_eval do
|
|
518
|
-
<<-RUBY
|
|
519
|
-
#{precompiled_method_return_value}
|
|
520
|
-
ensure
|
|
521
|
-
@haml_buffer = @haml_buffer.upper
|
|
522
|
-
end
|
|
523
|
-
RUBY
|
|
524
|
-
end
|
|
525
|
-
end
|
|
526
|
-
end
|
|
527
|
-
register 'haml', HamlTemplate
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
# Sass template implementation. See:
|
|
531
|
-
# http://haml.hamptoncatlin.com/
|
|
532
|
-
#
|
|
533
|
-
# Sass templates do not support object scopes, locals, or yield.
|
|
534
|
-
class SassTemplate < Template
|
|
535
|
-
def initialize_engine
|
|
536
|
-
return if defined? ::Sass::Engine
|
|
537
|
-
require_template_library 'sass'
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
def prepare
|
|
541
|
-
@engine = ::Sass::Engine.new(data, sass_options)
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
def evaluate(scope, locals, &block)
|
|
545
|
-
@output ||= @engine.render
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
private
|
|
549
|
-
def sass_options
|
|
550
|
-
options.merge(:filename => eval_file, :line => line, :syntax => :sass)
|
|
551
|
-
end
|
|
552
|
-
end
|
|
553
|
-
register 'sass', SassTemplate
|
|
554
|
-
|
|
555
|
-
# Sass's new .scss type template implementation.
|
|
556
|
-
class ScssTemplate < SassTemplate
|
|
557
|
-
private
|
|
558
|
-
def sass_options
|
|
559
|
-
options.merge(:filename => eval_file, :line => line, :syntax => :scss)
|
|
560
|
-
end
|
|
561
|
-
end
|
|
562
|
-
register 'scss', ScssTemplate
|
|
563
|
-
|
|
564
|
-
# Lessscss template implementation. See:
|
|
565
|
-
# http://lesscss.org/
|
|
566
|
-
#
|
|
567
|
-
# Less templates do not support object scopes, locals, or yield.
|
|
568
|
-
class LessTemplate < Template
|
|
569
|
-
def initialize_engine
|
|
570
|
-
return if defined? ::Less::Engine
|
|
571
|
-
require_template_library 'less'
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
def prepare
|
|
575
|
-
@engine = ::Less::Engine.new(data)
|
|
576
|
-
end
|
|
577
|
-
|
|
578
|
-
def evaluate(scope, locals, &block)
|
|
579
|
-
@engine.to_css
|
|
580
|
-
end
|
|
581
|
-
end
|
|
582
|
-
register 'less', LessTemplate
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
# CoffeeScript template implementation. See:
|
|
586
|
-
# http://coffeescript.org/
|
|
587
|
-
#
|
|
588
|
-
# CoffeeScript templates do not support object scopes, locals, or yield.
|
|
589
|
-
class CoffeeScriptTemplate < Template
|
|
590
|
-
@@default_no_wrap = false
|
|
591
|
-
|
|
592
|
-
def self.default_no_wrap
|
|
593
|
-
@@default_no_wrap
|
|
594
|
-
end
|
|
595
|
-
|
|
596
|
-
def self.default_no_wrap=(value)
|
|
597
|
-
@@default_no_wrap = value
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
def initialize_engine
|
|
601
|
-
return if defined? ::CoffeeScript
|
|
602
|
-
require_template_library 'coffee_script'
|
|
603
|
-
end
|
|
604
|
-
|
|
605
|
-
def prepare
|
|
606
|
-
@no_wrap = options.key?(:no_wrap) ? options[:no_wrap] :
|
|
607
|
-
self.class.default_no_wrap
|
|
608
|
-
end
|
|
609
|
-
|
|
610
|
-
def evaluate(scope, locals, &block)
|
|
611
|
-
@output ||= CoffeeScript.compile(data, :no_wrap => @no_wrap)
|
|
612
|
-
end
|
|
613
|
-
end
|
|
614
|
-
register 'coffee', CoffeeScriptTemplate
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
# Nokogiri template implementation. See:
|
|
618
|
-
# http://nokogiri.org/
|
|
619
|
-
class NokogiriTemplate < Template
|
|
620
|
-
def initialize_engine
|
|
621
|
-
return if defined?(::Nokogiri)
|
|
622
|
-
require_template_library 'nokogiri'
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
def prepare; end
|
|
626
|
-
|
|
627
|
-
def evaluate(scope, locals, &block)
|
|
628
|
-
xml = ::Nokogiri::XML::Builder.new
|
|
629
|
-
if data.respond_to?(:to_str)
|
|
630
|
-
locals[:xml] = xml
|
|
631
|
-
block &&= proc { yield.gsub(/^<\?xml version=\"1\.0\"\?>\n?/, "") }
|
|
632
|
-
super(scope, locals, &block)
|
|
633
|
-
elsif data.kind_of?(Proc)
|
|
634
|
-
data.call(xml)
|
|
635
|
-
end
|
|
636
|
-
xml.to_xml
|
|
637
|
-
end
|
|
638
|
-
|
|
639
|
-
def precompiled_template(locals)
|
|
640
|
-
data.to_str
|
|
641
|
-
end
|
|
642
|
-
end
|
|
643
|
-
register 'nokogiri', NokogiriTemplate
|
|
644
|
-
|
|
645
|
-
# Builder template implementation. See:
|
|
646
|
-
# http://builder.rubyforge.org/
|
|
647
|
-
class BuilderTemplate < Template
|
|
648
|
-
def initialize_engine
|
|
649
|
-
return if defined?(::Builder)
|
|
650
|
-
require_template_library 'builder'
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
def prepare
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
def evaluate(scope, locals, &block)
|
|
657
|
-
xml = ::Builder::XmlMarkup.new(:indent => 2)
|
|
658
|
-
if data.respond_to?(:to_str)
|
|
659
|
-
locals[:xml] = xml
|
|
660
|
-
super(scope, locals, &block)
|
|
661
|
-
elsif data.kind_of?(Proc)
|
|
662
|
-
data.call(xml)
|
|
663
|
-
end
|
|
664
|
-
xml.target!
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
def precompiled_template(locals)
|
|
668
|
-
data.to_str
|
|
669
|
-
end
|
|
670
|
-
end
|
|
671
|
-
register 'builder', BuilderTemplate
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
# Liquid template implementation. See:
|
|
675
|
-
# http://liquid.rubyforge.org/
|
|
676
|
-
#
|
|
677
|
-
# Liquid is designed to be a *safe* template system and threfore
|
|
678
|
-
# does not provide direct access to execuatable scopes. In order to
|
|
679
|
-
# support a +scope+, the +scope+ must be able to represent itself
|
|
680
|
-
# as a hash by responding to #to_h. If the +scope+ does not respond
|
|
681
|
-
# to #to_h it will be ignored.
|
|
682
|
-
#
|
|
683
|
-
# LiquidTemplate does not support yield blocks.
|
|
684
|
-
#
|
|
685
|
-
# It's suggested that your program require 'liquid' at load
|
|
686
|
-
# time when using this template engine.
|
|
687
|
-
class LiquidTemplate < Template
|
|
688
|
-
def initialize_engine
|
|
689
|
-
return if defined? ::Liquid::Template
|
|
690
|
-
require_template_library 'liquid'
|
|
691
|
-
end
|
|
692
|
-
|
|
693
|
-
def prepare
|
|
694
|
-
@engine = ::Liquid::Template.parse(data)
|
|
695
|
-
end
|
|
141
|
+
require 'tilt/erb'
|
|
142
|
+
register ERBTemplate, 'erb', 'rhtml'
|
|
143
|
+
register ErubisTemplate, 'erb', 'rhtml', 'erubis'
|
|
696
144
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if scope.respond_to?(:to_h)
|
|
700
|
-
scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
|
|
701
|
-
locals = scope.merge(locals)
|
|
702
|
-
end
|
|
703
|
-
locals['yield'] = block.nil? ? '' : yield
|
|
704
|
-
locals['content'] = locals['yield']
|
|
705
|
-
@engine.render(locals)
|
|
706
|
-
end
|
|
707
|
-
end
|
|
708
|
-
register 'liquid', LiquidTemplate
|
|
145
|
+
require 'tilt/haml'
|
|
146
|
+
register HamlTemplate, 'haml'
|
|
709
147
|
|
|
148
|
+
require 'tilt/css'
|
|
149
|
+
register SassTemplate, 'sass'
|
|
150
|
+
register ScssTemplate, 'scss'
|
|
151
|
+
register LessTemplate, 'less'
|
|
710
152
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
#
|
|
714
|
-
# RDiscount is a simple text filter. It does not support +scope+ or
|
|
715
|
-
# +locals+. The +:smart+ and +:filter_html+ options may be set true
|
|
716
|
-
# to enable those flags on the underlying RDiscount object.
|
|
717
|
-
class RDiscountTemplate < Template
|
|
718
|
-
def flags
|
|
719
|
-
[:smart, :filter_html].select { |flag| options[flag] }
|
|
720
|
-
end
|
|
153
|
+
require 'tilt/coffee'
|
|
154
|
+
register CoffeeScriptTemplate, 'coffee'
|
|
721
155
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
require_template_library 'rdiscount'
|
|
725
|
-
end
|
|
156
|
+
require 'tilt/nokogiri'
|
|
157
|
+
register NokogiriTemplate, 'nokogiri'
|
|
726
158
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
@output = nil
|
|
730
|
-
end
|
|
159
|
+
require 'tilt/builder'
|
|
160
|
+
register BuilderTemplate, 'builder'
|
|
731
161
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
end
|
|
735
|
-
end
|
|
736
|
-
register 'markdown', RDiscountTemplate
|
|
737
|
-
register 'mkd', RDiscountTemplate
|
|
738
|
-
register 'md', RDiscountTemplate
|
|
162
|
+
require 'tilt/markaby'
|
|
163
|
+
register MarkabyTemplate, 'mab'
|
|
739
164
|
|
|
165
|
+
require 'tilt/liquid'
|
|
166
|
+
register LiquidTemplate, 'liquid'
|
|
740
167
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
#
|
|
744
|
-
# RDiscount is a simple text filter. It does not support +scope+ or
|
|
745
|
-
# +locals+. The +:smartypants+ and +:escape_html+ options may be set true
|
|
746
|
-
# to enable those flags on the underlying BlueCloth object.
|
|
747
|
-
class BlueClothTemplate < Template
|
|
748
|
-
def initialize_engine
|
|
749
|
-
return if defined? ::BlueCloth
|
|
750
|
-
require_template_library 'bluecloth'
|
|
751
|
-
end
|
|
168
|
+
require 'tilt/radius'
|
|
169
|
+
register RadiusTemplate, 'radius'
|
|
752
170
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
@output ||= @engine.to_html
|
|
760
|
-
end
|
|
761
|
-
end
|
|
171
|
+
require 'tilt/markdown'
|
|
172
|
+
register MarukuTemplate, 'markdown', 'mkd', 'md'
|
|
173
|
+
register KramdownTemplate, 'markdown', 'mkd', 'md'
|
|
174
|
+
register BlueClothTemplate, 'markdown', 'mkd', 'md'
|
|
175
|
+
register RedcarpetTemplate, 'markdown', 'mkd', 'md'
|
|
176
|
+
register RDiscountTemplate, 'markdown', 'mkd', 'md'
|
|
762
177
|
|
|
178
|
+
require 'tilt/textile'
|
|
179
|
+
register RedClothTemplate, 'textile'
|
|
763
180
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
class RedClothTemplate < Template
|
|
767
|
-
def initialize_engine
|
|
768
|
-
return if defined? ::RedCloth
|
|
769
|
-
require_template_library 'redcloth'
|
|
770
|
-
end
|
|
771
|
-
|
|
772
|
-
def prepare
|
|
773
|
-
@engine = RedCloth.new(data)
|
|
774
|
-
@output = nil
|
|
775
|
-
end
|
|
181
|
+
require 'tilt/rdoc'
|
|
182
|
+
register RDocTemplate, 'rdoc'
|
|
776
183
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
end
|
|
780
|
-
end
|
|
781
|
-
register 'textile', RedClothTemplate
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
# RDoc template. See:
|
|
785
|
-
# http://rdoc.rubyforge.org/
|
|
786
|
-
#
|
|
787
|
-
# It's suggested that your program require 'rdoc/markup' and
|
|
788
|
-
# 'rdoc/markup/to_html' at load time when using this template
|
|
789
|
-
# engine.
|
|
790
|
-
class RDocTemplate < Template
|
|
791
|
-
def initialize_engine
|
|
792
|
-
return if defined?(::RDoc::Markup)
|
|
793
|
-
require_template_library 'rdoc/markup'
|
|
794
|
-
require_template_library 'rdoc/markup/to_html'
|
|
795
|
-
end
|
|
796
|
-
|
|
797
|
-
def prepare
|
|
798
|
-
markup = RDoc::Markup::ToHtml.new
|
|
799
|
-
@engine = markup.convert(data)
|
|
800
|
-
@output = nil
|
|
801
|
-
end
|
|
802
|
-
|
|
803
|
-
def evaluate(scope, locals, &block)
|
|
804
|
-
@output ||= @engine.to_s
|
|
805
|
-
end
|
|
806
|
-
end
|
|
807
|
-
register 'rdoc', RDocTemplate
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
# Radius Template
|
|
811
|
-
# http://github.com/jlong/radius/
|
|
812
|
-
class RadiusTemplate < Template
|
|
813
|
-
def initialize_engine
|
|
814
|
-
return if defined? ::Radius
|
|
815
|
-
require_template_library 'radius'
|
|
816
|
-
end
|
|
817
|
-
|
|
818
|
-
def prepare
|
|
819
|
-
end
|
|
820
|
-
|
|
821
|
-
def evaluate(scope, locals, &block)
|
|
822
|
-
context = Class.new(Radius::Context).new
|
|
823
|
-
context.define_tag("yield") do
|
|
824
|
-
block.call
|
|
825
|
-
end
|
|
826
|
-
locals.each do |tag, value|
|
|
827
|
-
context.define_tag(tag) do
|
|
828
|
-
value
|
|
829
|
-
end
|
|
830
|
-
end
|
|
831
|
-
(class << context; self; end).class_eval do
|
|
832
|
-
define_method :tag_missing do |tag, attr|
|
|
833
|
-
scope.__send__(tag) # any way to support attr as args?
|
|
834
|
-
end
|
|
835
|
-
end
|
|
836
|
-
options = {:tag_prefix => 'r'}.merge(@options)
|
|
837
|
-
parser = Radius::Parser.new(context, options)
|
|
838
|
-
parser.parse(data)
|
|
839
|
-
end
|
|
840
|
-
end
|
|
841
|
-
register 'radius', RadiusTemplate
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
# Markaby
|
|
845
|
-
# http://github.com/markaby/markaby
|
|
846
|
-
class MarkabyTemplate < Template
|
|
847
|
-
def self.builder_class
|
|
848
|
-
@builder_class ||= Class.new(Markaby::Builder) do
|
|
849
|
-
def __capture_markaby_tilt__(&block)
|
|
850
|
-
__run_markaby_tilt__ do
|
|
851
|
-
text capture(&block)
|
|
852
|
-
end
|
|
853
|
-
end
|
|
854
|
-
end
|
|
855
|
-
end
|
|
856
|
-
|
|
857
|
-
def initialize_engine
|
|
858
|
-
return if defined? ::Markaby
|
|
859
|
-
require_template_library 'markaby'
|
|
860
|
-
end
|
|
861
|
-
|
|
862
|
-
def prepare
|
|
863
|
-
end
|
|
864
|
-
|
|
865
|
-
def evaluate(scope, locals, &block)
|
|
866
|
-
builder = self.class.builder_class.new({}, scope)
|
|
867
|
-
builder.locals = locals
|
|
868
|
-
|
|
869
|
-
if data.kind_of? Proc
|
|
870
|
-
(class << builder; self end).send(:define_method, :__run_markaby_tilt__, &data)
|
|
871
|
-
else
|
|
872
|
-
builder.instance_eval <<-CODE, __FILE__, __LINE__
|
|
873
|
-
def __run_markaby_tilt__
|
|
874
|
-
#{data}
|
|
875
|
-
end
|
|
876
|
-
CODE
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
if block
|
|
880
|
-
builder.__capture_markaby_tilt__(&block)
|
|
881
|
-
else
|
|
882
|
-
builder.__run_markaby_tilt__
|
|
883
|
-
end
|
|
884
|
-
|
|
885
|
-
builder.to_s
|
|
886
|
-
end
|
|
887
|
-
end
|
|
888
|
-
register 'mab', MarkabyTemplate
|
|
184
|
+
require 'tilt/creole'
|
|
185
|
+
register CreoleTemplate, 'creole'
|
|
889
186
|
end
|