frank 0.2.6 → 0.3.0.beta
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/Featurelist +6 -0
- data/README.md +44 -32
- data/Rakefile +3 -3
- data/frank.gemspec +51 -45
- data/lib/frank.rb +1 -0
- data/lib/frank/base.rb +144 -94
- data/lib/frank/lorem.rb +77 -36
- data/lib/frank/middleware/imager.rb +43 -0
- data/lib/frank/middleware/refresh.rb +42 -0
- data/lib/frank/middleware/statik.rb +41 -0
- data/lib/frank/output.rb +25 -37
- data/lib/frank/rescue.rb +13 -6
- data/lib/frank/template_helpers.rb +34 -3
- data/lib/frank/templates/404.haml +1 -0
- data/lib/frank/templates/500.haml +2 -0
- data/lib/frank/tilt.rb +389 -141
- data/lib/template/{dynamic/layout.haml → layouts/default.haml} +0 -0
- data/lib/template/settings.yml +15 -28
- data/spec/base_spec.rb +88 -0
- data/{test → spec}/helper.rb +0 -7
- data/spec/output_spec.rb +220 -0
- data/spec/render_spec.rb +106 -0
- data/spec/template/dynamic/500.haml +1 -0
- data/{test → spec}/template/dynamic/_partial.haml +0 -0
- data/{test → spec}/template/dynamic/builder.builder +0 -0
- data/{test → spec}/template/dynamic/coffee.coffee +0 -0
- data/{test → spec}/template/dynamic/erb.erb +0 -0
- data/{test → spec}/template/dynamic/helper_test.haml +0 -0
- data/spec/template/dynamic/index.haml +5 -0
- data/spec/template/dynamic/layout2_test.haml +4 -0
- data/{test → spec}/template/dynamic/liquid.liquid +0 -0
- data/spec/template/dynamic/lorem_test.haml +7 -0
- data/{test → spec}/template/dynamic/markdown.md +0 -0
- data/spec/template/dynamic/markdown_in_haml.md +4 -0
- data/{test → spec}/template/dynamic/mustache.mustache +0 -0
- data/spec/template/dynamic/nested/child.haml +1 -0
- data/spec/template/dynamic/nested/deeper/deep.haml +1 -0
- data/spec/template/dynamic/no_layout.haml +4 -0
- data/spec/template/dynamic/partial_test.haml +2 -0
- data/{test → spec}/template/dynamic/redcloth.textile +0 -0
- data/spec/template/dynamic/refresh.haml +1 -0
- data/{test → spec}/template/dynamic/sass.sass +0 -0
- data/{test → spec}/template/helpers.rb +0 -0
- data/spec/template/layouts/default.haml +3 -0
- data/{test/template/dynamic → spec/template/layouts/explicit}/layout2.haml +0 -0
- data/spec/template/layouts/nested/default.haml +2 -0
- data/spec/template/settings.yml +45 -0
- data/{test/template/static → spec/template/static/files}/static.html +0 -0
- data/spec/template_helpers_spec.rb +78 -0
- metadata +57 -49
- data/lib/frank/imager.rb +0 -39
- data/lib/frank/statik.rb +0 -39
- data/test/suite.rb +0 -4
- data/test/template/dynamic/index.haml +0 -2
- data/test/template/dynamic/layout.haml +0 -2
- data/test/template/dynamic/layout2_test.haml +0 -1
- data/test/template/dynamic/layout_test.haml +0 -1
- data/test/template/dynamic/lorem_test.haml +0 -7
- data/test/template/dynamic/partial_test.haml +0 -2
- data/test/template/settings.yml +0 -62
- data/test/test_base.rb +0 -81
- data/test/test_helpers.rb +0 -71
- data/test/test_output.rb +0 -160
- data/test/test_render.rb +0 -89
@@ -9,6 +9,7 @@
|
|
9
9
|
h1 { margin: 24px -30px 10px -3px; font: bold 48px Georgia; line-height:48px;}
|
10
10
|
img { margin: 60px 0px 0px -22px; }
|
11
11
|
tt { color: #4D9EEF; color:#999; font-family: Inconsolata, Monaco, monospace }
|
12
|
+
=refresh
|
12
13
|
%body
|
13
14
|
#wrapper
|
14
15
|
%img{:src=>'/__frank__/frank-404.png'}
|
@@ -11,6 +11,8 @@
|
|
11
11
|
tt, pre { color:#666; font-family: Inconsolata, Monaco, monospace; }
|
12
12
|
pre { width:600px; padding-bottom:40px; }
|
13
13
|
p.summary { font-size:18px; border-bottom:1px #ccc solid; margin-bottom:60px; padding-bottom:20px; }
|
14
|
+
=refresh
|
15
|
+
|
14
16
|
%script{:type=>'text/javascript'}
|
15
17
|
%body
|
16
18
|
#wrapper
|
data/lib/frank/tilt.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
1
3
|
module Tilt
|
2
|
-
VERSION = '0.
|
4
|
+
VERSION = '0.9'
|
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,60 @@ 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
|
-
|
100
|
-
if @data.nil?
|
101
|
-
@data = @reader.call(self)
|
102
|
-
compile!
|
118
|
+
# load template data and prepare
|
119
|
+
if @file.match(/^[^\n]+$/) && File.exist?(@file)
|
120
|
+
@reader = block || lambda { |t| File.read(@file) }
|
121
|
+
else
|
122
|
+
@reader = block || lambda { |t| @file }
|
103
123
|
end
|
124
|
+
|
125
|
+
@data = @reader.call(self)
|
126
|
+
prepare
|
104
127
|
end
|
105
128
|
|
106
129
|
# Render the template in the given scope with the locals specified. If a
|
107
130
|
# block is given, it is typically available within the template via
|
108
131
|
# +yield+.
|
109
132
|
def render(scope=Object.new, locals={}, &block)
|
110
|
-
compile
|
111
133
|
evaluate scope, locals || {}, &block
|
112
134
|
end
|
113
135
|
|
@@ -127,43 +149,167 @@ module Tilt
|
|
127
149
|
end
|
128
150
|
|
129
151
|
protected
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
152
|
+
# Called once and only once for each template subclass the first time
|
153
|
+
# the template class is initialized. This should be used to require the
|
154
|
+
# underlying template library and perform any initial setup.
|
155
|
+
def initialize_engine
|
156
|
+
end
|
157
|
+
|
158
|
+
# Like Kernel::require but issues a warning urging a manual require when
|
159
|
+
# running under a threaded environment.
|
160
|
+
def require_template_library(name)
|
161
|
+
# if Thread.list.size > 1
|
162
|
+
# warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
|
163
|
+
# "explicit require '#{name}' suggested."
|
164
|
+
# end
|
165
|
+
require name
|
166
|
+
end
|
167
|
+
|
168
|
+
# Do whatever preparation is necessary to setup the underlying template
|
169
|
+
# engine. Called immediately after template data is loaded. Instance
|
170
|
+
# variables set in this method are available when #evaluate is called.
|
133
171
|
#
|
134
172
|
# Subclasses must provide an implementation of this method.
|
135
|
-
def
|
136
|
-
|
173
|
+
def prepare
|
174
|
+
if respond_to?(:compile!)
|
175
|
+
# backward compat with tilt < 0.6; just in case
|
176
|
+
warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
|
177
|
+
compile!
|
178
|
+
else
|
179
|
+
raise NotImplementedError
|
180
|
+
end
|
137
181
|
end
|
138
182
|
|
139
|
-
# Process the template and return the result.
|
140
|
-
#
|
183
|
+
# Process the template and return the result. When the scope mixes in
|
184
|
+
# the Tilt::CompileSite module, the template is compiled to a method and
|
185
|
+
# reused given identical locals keys. When the scope object
|
186
|
+
# does not mix in the CompileSite module, the template source is
|
187
|
+
# evaluated with instance_eval. In any case, template executation
|
188
|
+
# is guaranteed to be performed in the scope object with the locals
|
189
|
+
# specified and with support for yielding to the block.
|
141
190
|
def evaluate(scope, locals, &block)
|
142
|
-
|
143
|
-
|
144
|
-
|
191
|
+
if scope.respond_to?(:__tilt__)
|
192
|
+
method_name = compiled_method_name(locals.keys)
|
193
|
+
if scope.respond_to?(method_name)
|
194
|
+
scope.send(method_name, locals, &block)
|
195
|
+
else
|
196
|
+
compile_template_method(method_name, locals)
|
197
|
+
scope.send(method_name, locals, &block)
|
198
|
+
end
|
199
|
+
else
|
200
|
+
evaluate_source(scope, locals, &block)
|
201
|
+
end
|
145
202
|
end
|
146
203
|
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
|
204
|
+
# Generates all template source by combining the preamble, template, and
|
205
|
+
# postamble and returns a two-tuple of the form: [source, offset], where
|
206
|
+
# source is the string containing (Ruby) source code for the template and
|
207
|
+
# offset is the integer line offset where line reporting should begin.
|
208
|
+
#
|
209
|
+
# Template subclasses may override this method when they need complete
|
210
|
+
# control over source generation or want to adjust the default line
|
211
|
+
# offset. In most cases, overriding the #precompiled_template method is
|
212
|
+
# easier and more appropriate.
|
213
|
+
def precompiled(locals)
|
214
|
+
preamble = precompiled_preamble(locals)
|
215
|
+
parts = [
|
216
|
+
preamble,
|
217
|
+
precompiled_template(locals),
|
218
|
+
precompiled_postamble(locals)
|
219
|
+
]
|
220
|
+
[parts.join("\n"), preamble.count("\n") + 1]
|
221
|
+
end
|
222
|
+
|
223
|
+
# A string containing the (Ruby) source code for the template. The
|
224
|
+
# default Template#evaluate implementation requires either this method
|
225
|
+
# or the #precompiled method be overridden. When defined, the base
|
226
|
+
# Template guarantees correct file/line handling, locals support, custom
|
227
|
+
# scopes, and support for template compilation when the scope object
|
228
|
+
# allows it.
|
229
|
+
def precompiled_template(locals)
|
151
230
|
raise NotImplementedError
|
152
231
|
end
|
153
232
|
|
233
|
+
# Generates preamble code for initializing template state, and performing
|
234
|
+
# locals assignment. The default implementation performs locals
|
235
|
+
# assignment only. Lines included in the preamble are subtracted from the
|
236
|
+
# source line offset, so adding code to the preamble does not effect line
|
237
|
+
# reporting in Kernel::caller and backtraces.
|
238
|
+
def precompiled_preamble(locals)
|
239
|
+
locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
|
240
|
+
end
|
241
|
+
|
242
|
+
# Generates postamble code for the precompiled template source. The
|
243
|
+
# string returned from this method is appended to the precompiled
|
244
|
+
# template source.
|
245
|
+
def precompiled_postamble(locals)
|
246
|
+
''
|
247
|
+
end
|
248
|
+
|
249
|
+
# The unique compiled method name for the locals keys provided.
|
250
|
+
def compiled_method_name(locals_keys)
|
251
|
+
@compiled_method_names[locals_keys] ||=
|
252
|
+
generate_compiled_method_name(locals_keys)
|
253
|
+
end
|
254
|
+
|
154
255
|
private
|
155
|
-
|
156
|
-
|
157
|
-
source
|
158
|
-
|
256
|
+
# Evaluate the template source in the context of the scope object.
|
257
|
+
def evaluate_source(scope, locals, &block)
|
258
|
+
source, offset = precompiled(locals)
|
259
|
+
scope.instance_eval(source, eval_file, line - offset)
|
159
260
|
end
|
160
261
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
262
|
+
# JRuby doesn't allow Object#instance_eval to yield to the block it's
|
263
|
+
# closed over. This is by design and (ostensibly) something that will
|
264
|
+
# change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
|
265
|
+
# exhibits the behavior. More info here:
|
266
|
+
#
|
267
|
+
# http://jira.codehaus.org/browse/JRUBY-2599
|
268
|
+
#
|
269
|
+
# Additionally, JRuby's eval line reporting is off by one compared to
|
270
|
+
# all MRI versions tested.
|
271
|
+
#
|
272
|
+
# We redefine evaluate_source to work around both issues.
|
273
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
274
|
+
undef evaluate_source
|
275
|
+
def evaluate_source(scope, locals, &block)
|
276
|
+
source, offset = precompiled(locals)
|
277
|
+
file, lineno = eval_file, (line - offset) - 1
|
278
|
+
scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def generate_compiled_method_name(locals_keys)
|
283
|
+
parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
|
284
|
+
digest = Digest::MD5.hexdigest(parts.join(':'))
|
285
|
+
"__tilt_#{digest}"
|
286
|
+
end
|
287
|
+
|
288
|
+
def compile_template_method(method_name, locals)
|
289
|
+
source, offset = precompiled(locals)
|
290
|
+
offset += 1
|
291
|
+
CompileSite.module_eval <<-RUBY, eval_file, line - offset
|
292
|
+
def #{method_name}(locals)
|
293
|
+
#{source}
|
294
|
+
end
|
295
|
+
RUBY
|
296
|
+
|
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
|
165
312
|
end
|
166
|
-
require name
|
167
313
|
end
|
168
314
|
end
|
169
315
|
|
@@ -173,7 +319,7 @@ module Tilt
|
|
173
319
|
# cache = Tilt::Cache.new
|
174
320
|
# cache.fetch(path, line, options) { Tilt.new(path, line, options) }
|
175
321
|
#
|
176
|
-
# Subsequent invocations return the already
|
322
|
+
# Subsequent invocations return the already loaded template object.
|
177
323
|
class Cache
|
178
324
|
def initialize
|
179
325
|
@cache = {}
|
@@ -195,11 +341,11 @@ module Tilt
|
|
195
341
|
# The template source is evaluated as a Ruby string. The #{} interpolation
|
196
342
|
# syntax can be used to generated dynamic output.
|
197
343
|
class StringTemplate < Template
|
198
|
-
def
|
344
|
+
def prepare
|
199
345
|
@code = "%Q{#{data}}"
|
200
346
|
end
|
201
347
|
|
202
|
-
def
|
348
|
+
def precompiled_template(locals)
|
203
349
|
@code
|
204
350
|
end
|
205
351
|
end
|
@@ -210,65 +356,87 @@ module Tilt
|
|
210
356
|
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
|
211
357
|
class ERBTemplate < Template
|
212
358
|
def initialize_engine
|
213
|
-
|
359
|
+
return if defined? ::ERB
|
360
|
+
require_template_library 'erb'
|
214
361
|
end
|
215
362
|
|
216
|
-
def
|
217
|
-
@
|
363
|
+
def prepare
|
364
|
+
@outvar = (options[:outvar] || '_erbout').to_s
|
365
|
+
@engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
|
218
366
|
end
|
219
367
|
|
220
|
-
def
|
368
|
+
def precompiled_template(locals)
|
221
369
|
@engine.src
|
222
370
|
end
|
223
371
|
|
224
|
-
def
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
scope.instance_variable_get(:@_out_buf)
|
231
|
-
|
232
|
-
scope.instance_eval source, eval_file, line - offset
|
233
|
-
|
234
|
-
output = scope.instance_variable_get(:@_out_buf)
|
235
|
-
scope.instance_variable_set(:@_out_buf, original_out_buf)
|
236
|
-
|
237
|
-
output
|
372
|
+
def precompiled_preamble(locals)
|
373
|
+
<<-RUBY
|
374
|
+
begin
|
375
|
+
__original_outvar = #{@outvar} if defined?(#{@outvar})
|
376
|
+
#{super}
|
377
|
+
RUBY
|
238
378
|
end
|
239
379
|
|
240
|
-
|
380
|
+
def precompiled_postamble(locals)
|
381
|
+
<<-RUBY
|
382
|
+
#{super}
|
383
|
+
ensure
|
384
|
+
#{@outvar} = __original_outvar
|
385
|
+
end
|
386
|
+
RUBY
|
387
|
+
end
|
241
388
|
|
242
389
|
# ERB generates a line to specify the character coding of the generated
|
243
390
|
# source in 1.9. Account for this in the line offset.
|
244
391
|
if RUBY_VERSION >= '1.9.0'
|
245
|
-
def
|
392
|
+
def precompiled(locals)
|
246
393
|
source, offset = super
|
247
394
|
[source, offset + 1]
|
248
395
|
end
|
249
396
|
end
|
250
397
|
end
|
398
|
+
|
251
399
|
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
252
400
|
|
253
401
|
|
254
402
|
# Erubis template implementation. See:
|
255
403
|
# http://www.kuwata-lab.com/erubis/
|
404
|
+
#
|
405
|
+
# ErubisTemplate supports the following additional options, which are not
|
406
|
+
# passed down to the Erubis engine:
|
407
|
+
#
|
408
|
+
# :engine_class allows you to specify a custom engine class to use
|
409
|
+
# instead of the default (which is ::Erubis::Eruby).
|
410
|
+
#
|
411
|
+
# :escape_html when true, ::Erubis::EscapedEruby will be used as
|
412
|
+
# the engine class instead of the default. All content
|
413
|
+
# within <%= %> blocks will be automatically html escaped.
|
256
414
|
class ErubisTemplate < ERBTemplate
|
257
415
|
def initialize_engine
|
258
|
-
|
416
|
+
return if defined? ::Erubis
|
417
|
+
require_template_library 'erubis'
|
259
418
|
end
|
260
419
|
|
261
|
-
def
|
262
|
-
|
263
|
-
@
|
420
|
+
def prepare
|
421
|
+
@options.merge!(:preamble => false, :postamble => false)
|
422
|
+
@outvar = (options.delete(:outvar) || '_erbout').to_s
|
423
|
+
engine_class = options.delete(:engine_class)
|
424
|
+
engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html)
|
425
|
+
@engine = (engine_class || ::Erubis::Eruby).new(data, options)
|
264
426
|
end
|
265
427
|
|
266
|
-
|
428
|
+
def precompiled_preamble(locals)
|
429
|
+
[super, "#{@outvar} = _buf = ''"].join("\n")
|
430
|
+
end
|
267
431
|
|
268
|
-
|
269
|
-
|
432
|
+
def precompiled_postamble(locals)
|
433
|
+
["_buf", super].join("\n")
|
434
|
+
end
|
435
|
+
|
436
|
+
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
|
437
|
+
# Override and adjust back.
|
270
438
|
if RUBY_VERSION >= '1.9.0'
|
271
|
-
def
|
439
|
+
def precompiled(locals)
|
272
440
|
source, offset = super
|
273
441
|
[source, offset - 1]
|
274
442
|
end
|
@@ -281,20 +449,54 @@ module Tilt
|
|
281
449
|
# http://haml.hamptoncatlin.com/
|
282
450
|
class HamlTemplate < Template
|
283
451
|
def initialize_engine
|
284
|
-
|
452
|
+
return if defined? ::Haml::Engine
|
453
|
+
require_template_library 'haml'
|
285
454
|
end
|
286
455
|
|
287
|
-
def
|
288
|
-
|
456
|
+
def prepare
|
457
|
+
options = @options.merge(:filename => eval_file, :line => line)
|
458
|
+
@engine = ::Haml::Engine.new(data, options)
|
289
459
|
end
|
290
460
|
|
291
461
|
def evaluate(scope, locals, &block)
|
292
|
-
@engine.
|
462
|
+
if @engine.respond_to?(:precompiled_method_return_value, true)
|
463
|
+
super
|
464
|
+
else
|
465
|
+
@engine.render(scope, locals, &block)
|
466
|
+
end
|
293
467
|
end
|
294
468
|
|
295
|
-
|
296
|
-
|
297
|
-
|
469
|
+
# Precompiled Haml source. Taken from the precompiled_with_ambles
|
470
|
+
# method in Haml::Precompiler:
|
471
|
+
# http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
|
472
|
+
def precompiled_template(locals)
|
473
|
+
@engine.precompiled
|
474
|
+
end
|
475
|
+
|
476
|
+
def precompiled_preamble(locals)
|
477
|
+
local_assigns = super
|
478
|
+
@engine.instance_eval do
|
479
|
+
<<-RUBY
|
480
|
+
begin
|
481
|
+
extend Haml::Helpers
|
482
|
+
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
483
|
+
_erbout = _hamlout.buffer
|
484
|
+
__in_erb_template = true
|
485
|
+
_haml_locals = locals
|
486
|
+
#{local_assigns}
|
487
|
+
RUBY
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
def precompiled_postamble(locals)
|
492
|
+
@engine.instance_eval do
|
493
|
+
<<-RUBY
|
494
|
+
#{precompiled_method_return_value}
|
495
|
+
ensure
|
496
|
+
@haml_buffer = @haml_buffer.upper
|
497
|
+
end
|
498
|
+
RUBY
|
499
|
+
end
|
298
500
|
end
|
299
501
|
end
|
300
502
|
register 'haml', HamlTemplate
|
@@ -306,15 +508,16 @@ module Tilt
|
|
306
508
|
# Sass templates do not support object scopes, locals, or yield.
|
307
509
|
class SassTemplate < Template
|
308
510
|
def initialize_engine
|
309
|
-
|
511
|
+
return if defined? ::Sass::Engine
|
512
|
+
require_template_library 'sass'
|
310
513
|
end
|
311
514
|
|
312
|
-
def
|
515
|
+
def prepare
|
313
516
|
@engine = ::Sass::Engine.new(data, sass_options)
|
314
517
|
end
|
315
518
|
|
316
519
|
def evaluate(scope, locals, &block)
|
317
|
-
@engine.render
|
520
|
+
@output ||= @engine.render
|
318
521
|
end
|
319
522
|
|
320
523
|
private
|
@@ -331,10 +534,11 @@ module Tilt
|
|
331
534
|
# Less templates do not support object scopes, locals, or yield.
|
332
535
|
class LessTemplate < Template
|
333
536
|
def initialize_engine
|
334
|
-
|
537
|
+
return if defined? ::Less::Engine
|
538
|
+
require_template_library 'less'
|
335
539
|
end
|
336
540
|
|
337
|
-
def
|
541
|
+
def prepare
|
338
542
|
@engine = ::Less::Engine.new(data)
|
339
543
|
end
|
340
544
|
|
@@ -344,14 +548,16 @@ module Tilt
|
|
344
548
|
end
|
345
549
|
register 'less', LessTemplate
|
346
550
|
|
551
|
+
|
347
552
|
# Builder template implementation. See:
|
348
553
|
# http://builder.rubyforge.org/
|
349
554
|
class BuilderTemplate < Template
|
350
555
|
def initialize_engine
|
351
|
-
|
556
|
+
return if defined?(::Builder)
|
557
|
+
require_template_library 'builder'
|
352
558
|
end
|
353
559
|
|
354
|
-
def
|
560
|
+
def prepare
|
355
561
|
end
|
356
562
|
|
357
563
|
def evaluate(scope, locals, &block)
|
@@ -365,7 +571,7 @@ module Tilt
|
|
365
571
|
xml.target!
|
366
572
|
end
|
367
573
|
|
368
|
-
def
|
574
|
+
def precompiled_template(locals)
|
369
575
|
data.to_str
|
370
576
|
end
|
371
577
|
end
|
@@ -387,10 +593,11 @@ module Tilt
|
|
387
593
|
# time when using this template engine.
|
388
594
|
class LiquidTemplate < Template
|
389
595
|
def initialize_engine
|
390
|
-
|
596
|
+
return if defined? ::Liquid::Template
|
597
|
+
require_template_library 'liquid'
|
391
598
|
end
|
392
599
|
|
393
|
-
def
|
600
|
+
def prepare
|
394
601
|
@engine = ::Liquid::Template.parse(data)
|
395
602
|
end
|
396
603
|
|
@@ -400,9 +607,8 @@ module Tilt
|
|
400
607
|
scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
|
401
608
|
locals = scope.merge(locals)
|
402
609
|
end
|
403
|
-
# TODO: Is it possible to lazy yield ?
|
404
610
|
locals['yield'] = block.nil? ? '' : yield
|
405
|
-
locals['content'] =
|
611
|
+
locals['content'] = locals['yield']
|
406
612
|
@engine.render(locals)
|
407
613
|
end
|
408
614
|
end
|
@@ -421,15 +627,17 @@ module Tilt
|
|
421
627
|
end
|
422
628
|
|
423
629
|
def initialize_engine
|
424
|
-
|
630
|
+
return if defined? ::RDiscount
|
631
|
+
require_template_library 'rdiscount'
|
425
632
|
end
|
426
633
|
|
427
|
-
def
|
634
|
+
def prepare
|
428
635
|
@engine = RDiscount.new(data, *flags)
|
636
|
+
@output = nil
|
429
637
|
end
|
430
638
|
|
431
639
|
def evaluate(scope, locals, &block)
|
432
|
-
@engine.to_html
|
640
|
+
@output ||= @engine.to_html
|
433
641
|
end
|
434
642
|
end
|
435
643
|
register 'markdown', RDiscountTemplate
|
@@ -441,15 +649,17 @@ module Tilt
|
|
441
649
|
# http://redcloth.org/
|
442
650
|
class RedClothTemplate < Template
|
443
651
|
def initialize_engine
|
444
|
-
|
652
|
+
return if defined? ::RedCloth
|
653
|
+
require_template_library 'redcloth'
|
445
654
|
end
|
446
655
|
|
447
|
-
def
|
656
|
+
def prepare
|
448
657
|
@engine = RedCloth.new(data)
|
658
|
+
@output = nil
|
449
659
|
end
|
450
660
|
|
451
661
|
def evaluate(scope, locals, &block)
|
452
|
-
@engine.to_html
|
662
|
+
@output ||= @engine.to_html
|
453
663
|
end
|
454
664
|
end
|
455
665
|
register 'textile', RedClothTemplate
|
@@ -465,14 +675,16 @@ module Tilt
|
|
465
675
|
attr_reader :engine
|
466
676
|
|
467
677
|
def initialize_engine
|
468
|
-
|
678
|
+
return if defined? ::Mustache
|
679
|
+
require_template_library 'mustache'
|
469
680
|
end
|
470
681
|
|
471
|
-
def
|
682
|
+
def prepare
|
472
683
|
Mustache.view_namespace = options[:namespace]
|
684
|
+
Mustache.view_path = options[:view_path] || options[:mustaches]
|
473
685
|
@engine = options[:view] || Mustache.view_class(name)
|
474
686
|
options.each do |key, value|
|
475
|
-
next if %w[view namespace mustaches].include?(key.to_s)
|
687
|
+
next if %w[view view_path namespace mustaches].include?(key.to_s)
|
476
688
|
@engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
|
477
689
|
end
|
478
690
|
end
|
@@ -501,6 +713,7 @@ module Tilt
|
|
501
713
|
end
|
502
714
|
register 'mustache', MustacheTemplate
|
503
715
|
|
716
|
+
|
504
717
|
# RDoc template. See:
|
505
718
|
# http://rdoc.rubyforge.org/
|
506
719
|
#
|
@@ -509,37 +722,72 @@ module Tilt
|
|
509
722
|
# engine.
|
510
723
|
class RDocTemplate < Template
|
511
724
|
def initialize_engine
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
end
|
725
|
+
return if defined?(::RDoc::Markup)
|
726
|
+
require_template_library 'rdoc/markup'
|
727
|
+
require_template_library 'rdoc/markup/to_html'
|
516
728
|
end
|
517
729
|
|
518
|
-
def
|
730
|
+
def prepare
|
519
731
|
markup = RDoc::Markup::ToHtml.new
|
520
732
|
@engine = markup.convert(data)
|
733
|
+
@output = nil
|
521
734
|
end
|
522
735
|
|
523
736
|
def evaluate(scope, locals, &block)
|
524
|
-
@engine.to_s
|
737
|
+
@output ||= @engine.to_s
|
525
738
|
end
|
526
739
|
end
|
527
740
|
register 'rdoc', RDocTemplate
|
528
|
-
|
741
|
+
|
742
|
+
|
529
743
|
# CoffeeScript info:
|
530
744
|
# http://jashkenas.github.com/coffee-script/
|
531
745
|
class CoffeeTemplate < Template
|
532
746
|
def initialize_engine
|
533
|
-
|
747
|
+
return if defined? ::CoffeeScript
|
748
|
+
require_template_library 'coffee-script'
|
534
749
|
end
|
535
750
|
|
536
|
-
def
|
537
|
-
@
|
751
|
+
def prepare
|
752
|
+
@output = nil
|
538
753
|
end
|
539
754
|
|
540
755
|
def evaluate(scope, locals, &block)
|
541
|
-
@
|
756
|
+
@output ||= ::CoffeeScript::compile(data, options)
|
542
757
|
end
|
543
758
|
end
|
544
759
|
register 'coffee', CoffeeTemplate
|
760
|
+
|
761
|
+
# Radius Template
|
762
|
+
# http://github.com/jlong/radius/
|
763
|
+
class RadiusTemplate < Template
|
764
|
+
def initialize_engine
|
765
|
+
return if defined? ::Radius
|
766
|
+
require_template_library 'radius'
|
767
|
+
end
|
768
|
+
|
769
|
+
def prepare
|
770
|
+
@context = Class.new(Radius::Context).new
|
771
|
+
end
|
772
|
+
|
773
|
+
def evaluate(scope, locals, &block)
|
774
|
+
@context.define_tag("yield") do
|
775
|
+
block.call
|
776
|
+
end
|
777
|
+
(class << @context; self; end).class_eval do
|
778
|
+
define_method :tag_missing do |tag, attr, &block|
|
779
|
+
if locals.key?(tag.to_sym)
|
780
|
+
locals[tag.to_sym]
|
781
|
+
else
|
782
|
+
scope.__send__(tag) # any way to support attr as args?
|
783
|
+
end
|
784
|
+
end
|
785
|
+
end
|
786
|
+
# TODO: how to config tag prefix?
|
787
|
+
parser = Radius::Parser.new(@context, :tag_prefix => 'r')
|
788
|
+
parser.parse(data)
|
789
|
+
end
|
790
|
+
end
|
791
|
+
register 'radius', RadiusTemplate
|
792
|
+
|
545
793
|
end
|