frank 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/LICENSE +22 -0
- data/README.md +127 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/bin/frank +33 -0
- data/bin/frankout +33 -0
- data/bin/frankup +13 -0
- data/frank.gemspec +65 -0
- data/lib/frank.rb +7 -0
- data/lib/frank/base.rb +185 -0
- data/lib/frank/output.rb +46 -0
- data/lib/frank/rescue.rb +24 -0
- data/lib/frank/statik.rb +27 -0
- data/lib/frank/template_helpers.rb +12 -0
- data/lib/frank/templates/404.haml +16 -0
- data/lib/frank/templates/500.haml +22 -0
- data/lib/frank/templates/frank-404.png +0 -0
- data/lib/frank/templates/frank-500.png +0 -0
- data/lib/frank/tilt.rb +526 -0
- data/lib/template/dynamic/css/frank.sass +72 -0
- data/lib/template/dynamic/index.haml +15 -0
- data/lib/template/dynamic/js/frank.coffee +49 -0
- data/lib/template/dynamic/layout.haml +9 -0
- data/lib/template/helpers.rb +3 -0
- data/lib/template/settings.yml +68 -0
- data/lib/template/static/images/frank-med.png +0 -0
- metadata +85 -0
data/lib/frank/output.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module Frank
|
4
|
+
class Output < Frank::Base
|
5
|
+
include Frank::Render
|
6
|
+
|
7
|
+
attr_accessor :static_folder, :dynamic_folder, :templates, :output_folder, :proj_dir
|
8
|
+
|
9
|
+
def initialize(&block)
|
10
|
+
instance_eval &block
|
11
|
+
@output_path = File.join(@proj_dir, @output_folder)
|
12
|
+
end
|
13
|
+
|
14
|
+
def compile_templates
|
15
|
+
dir = File.join( @proj_dir, @dynamic_folder )
|
16
|
+
|
17
|
+
Find.find(dir) do |path|
|
18
|
+
if FileTest.file?(path) and !File.basename(path).match(/^\./)
|
19
|
+
path = path[ dir.size + 1 ..-1 ]
|
20
|
+
name, ext = name_ext(path)
|
21
|
+
new_ext = reverse_ext_lookup(ext)
|
22
|
+
new_file = File.join( @output_folder, "#{name}.#{new_ext}")
|
23
|
+
FileUtils.makedirs(new_file.split('/').reverse[1..-1].reverse.join('/'))
|
24
|
+
|
25
|
+
File.open(new_file, 'w') {|f| f.write render_path(path) }
|
26
|
+
puts "Create #{name}.#{new_ext}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def copy_static
|
32
|
+
puts "Copying over your static content"
|
33
|
+
static_folder = File.join(@proj_dir, @static_folder)
|
34
|
+
FileUtils.cp_r(static_folder, @output_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def dump
|
38
|
+
FileUtils.mkdir(@output_path)
|
39
|
+
puts "Create #{@output_folder}"
|
40
|
+
|
41
|
+
compile_templates
|
42
|
+
copy_static
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/frank/rescue.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Frank
|
2
|
+
module Rescue
|
3
|
+
|
4
|
+
def render_404
|
5
|
+
template = File.expand_path(File.dirname(__FILE__)) + '/templates/404.haml'
|
6
|
+
|
7
|
+
@response['Content-Type'] = 'text/html'
|
8
|
+
@response.status = 404
|
9
|
+
@response.body = tilt_lang(template, 'haml', Object.new, locals = { :request => @env, :params => @request.params })
|
10
|
+
|
11
|
+
log_request('404')
|
12
|
+
end
|
13
|
+
|
14
|
+
def render_500(excp)
|
15
|
+
template = File.expand_path(File.dirname(__FILE__)) + '/templates/500.haml'
|
16
|
+
|
17
|
+
@response['Content-Type'] = 'text/html'
|
18
|
+
@response.status = 500
|
19
|
+
@response.body = tilt_lang(template, 'haml', Object.new, locals = { :request => @env, :params => @request.params, :exception => excp })
|
20
|
+
|
21
|
+
log_request('500', excp)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/frank/statik.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Rack::Statik
|
2
|
+
|
3
|
+
def initialize(app, options={})
|
4
|
+
@app = app
|
5
|
+
frank_root = File.expand_path(File.dirname(__FILE__)) + '/templates'
|
6
|
+
root = options[:root] || Dir.pwd
|
7
|
+
@frank_server = Rack::File.new(frank_root)
|
8
|
+
@static_server = Rack::File.new(root)
|
9
|
+
end
|
10
|
+
|
11
|
+
# handles serving from __frank__
|
12
|
+
# looks for static access, if not found,
|
13
|
+
# passes request to frank
|
14
|
+
def call(env)
|
15
|
+
path = env['PATH_INFO']
|
16
|
+
|
17
|
+
if path.include? '__frank__'
|
18
|
+
env['PATH_INFO'].gsub!('/__frank__', '')
|
19
|
+
result = @frank_server.call(env)
|
20
|
+
elsif path.index('/') == 0
|
21
|
+
result = @static_server.call(env)
|
22
|
+
end
|
23
|
+
return result if result[0] == 200
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
!!! Strict
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title= "404 – Not Found"
|
5
|
+
%style{:type=>'text/css'}
|
6
|
+
:plain
|
7
|
+
body { color: #222; font:14px "Helvetica", "Arial"; line-height: 20px; }
|
8
|
+
#wrapper { margin:0px auto; width:300px; }
|
9
|
+
h1 { margin: 24px -30px 10px -3px; font: bold 48px Georgia; line-height:48px;}
|
10
|
+
img { margin: 60px 0px 0px -22px; }
|
11
|
+
tt { color: #4D9EEF; color:#999; font-family: Inconsolata, Monaco, monospace }
|
12
|
+
%body
|
13
|
+
#wrapper
|
14
|
+
%img{:src=>'/__frank__/frank-404.png'}
|
15
|
+
%h1= "Not Found—"
|
16
|
+
%p= "Try creating <tt>#{request['REQUEST_PATH'][1..-1]}.haml</tt> in the <tt>views</tt> folder."
|
@@ -0,0 +1,22 @@
|
|
1
|
+
!!! Strict
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title= "500 – Internal Server Error"
|
5
|
+
%style{:type=>'text/css'}
|
6
|
+
:plain
|
7
|
+
body { color: #222; font:14px "Helvetica", "Arial"; line-height: 20px; }
|
8
|
+
#wrapper { margin:0px auto; width:600px; }
|
9
|
+
h1 { margin: 0px -30px 10px -3px; font: bold 48px Georgia; line-height:48px;}
|
10
|
+
img { margin: 60px 0px 0px -51px; }
|
11
|
+
tt, pre { color:#666; font-family: Inconsolata, Monaco, monospace; }
|
12
|
+
pre { width:600px; padding-bottom:40px; }
|
13
|
+
p.summary { font-size:18px; border-bottom:1px #ccc solid; margin-bottom:60px; padding-bottom:20px; }
|
14
|
+
%script{:type=>'text/javascript'}
|
15
|
+
%body
|
16
|
+
#wrapper
|
17
|
+
%img{:src=>'/__frank__/frank-500.png'}
|
18
|
+
%h1= "Something’s Wrong—"
|
19
|
+
%p.summary= "The error, « <tt>#{exception.message.gsub('<','<')}</tt> »<br /> occurred in <tt>#{exception.backtrace.first.split(':')[0..1].join('</tt> on line <tt>')}</tt>."
|
20
|
+
%pre
|
21
|
+
:preserve
|
22
|
+
#{ exception.backtrace.join("\n") }
|
Binary file
|
Binary file
|
data/lib/frank/tilt.rb
ADDED
@@ -0,0 +1,526 @@
|
|
1
|
+
module Tilt
|
2
|
+
VERSION = '0.4'
|
3
|
+
|
4
|
+
@template_mappings = {}
|
5
|
+
|
6
|
+
# Hash of template path pattern => template implementation
|
7
|
+
# class mappings.
|
8
|
+
def self.mappings
|
9
|
+
@template_mappings
|
10
|
+
end
|
11
|
+
|
12
|
+
# Register a template implementation by file extension.
|
13
|
+
def self.register(ext, template_class)
|
14
|
+
ext = ext.to_s.sub(/^\./, '')
|
15
|
+
mappings[ext.downcase] = template_class
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a new template for the given file using the file's extension
|
19
|
+
# to determine the the template mapping.
|
20
|
+
def self.new(file, line=nil, options={}, &block)
|
21
|
+
if template_class = self[file]
|
22
|
+
template_class.new(file, line, options, &block)
|
23
|
+
else
|
24
|
+
fail "No template engine registered for #{File.basename(file)}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Lookup a template class given for the given filename or file
|
29
|
+
# extension. Return nil when no implementation is found.
|
30
|
+
def self.[](file)
|
31
|
+
if @template_mappings.key?(pattern = file.to_s.downcase)
|
32
|
+
@template_mappings[pattern]
|
33
|
+
elsif @template_mappings.key?(pattern = File.basename(pattern))
|
34
|
+
@template_mappings[pattern]
|
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
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Base class for template implementations. Subclasses must implement
|
49
|
+
# the #compile! method and one of the #evaluate or #template_source
|
50
|
+
# methods.
|
51
|
+
class Template
|
52
|
+
# Template source; loaded from a file or given directly.
|
53
|
+
attr_reader :data
|
54
|
+
|
55
|
+
# The name of the file where the template data was loaded from.
|
56
|
+
attr_reader :file
|
57
|
+
|
58
|
+
# The line number in #file where template data was loaded from.
|
59
|
+
attr_reader :line
|
60
|
+
|
61
|
+
# A Hash of template engine specific options. This is passed directly
|
62
|
+
# to the underlying engine and is not used by the generic template
|
63
|
+
# interface.
|
64
|
+
attr_reader :options
|
65
|
+
|
66
|
+
# Create a new template with the file, line, and options specified. By
|
67
|
+
# default, template data is read from the file specified. When a block
|
68
|
+
# is given, it should read template data and return as a String. When
|
69
|
+
# file is nil, a block is required.
|
70
|
+
#
|
71
|
+
# The #initialize_engine method is called if this is the very first
|
72
|
+
# time this template subclass has been initialized.
|
73
|
+
def initialize(file=nil, line=1, options={}, &block)
|
74
|
+
raise ArgumentError, "file or block required" if file.nil? && block.nil?
|
75
|
+
options, line = line, 1 if line.is_a?(Hash)
|
76
|
+
@file = file
|
77
|
+
@line = line || 1
|
78
|
+
@options = options || {}
|
79
|
+
@reader = block || lambda { |t| File.read(file) }
|
80
|
+
|
81
|
+
if !self.class.engine_initialized
|
82
|
+
initialize_engine
|
83
|
+
self.class.engine_initialized = true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Called once and only once for each template subclass the first time
|
88
|
+
# the template class is initialized. This should be used to require the
|
89
|
+
# underlying template library and perform any initial setup.
|
90
|
+
def initialize_engine
|
91
|
+
end
|
92
|
+
@engine_initialized = false
|
93
|
+
class << self ; attr_accessor :engine_initialized ; end
|
94
|
+
|
95
|
+
|
96
|
+
# Load template source and compile the template. The template is
|
97
|
+
# loaded and compiled the first time this method is called; subsequent
|
98
|
+
# calls are no-ops.
|
99
|
+
def compile
|
100
|
+
if @data.nil?
|
101
|
+
@data = @reader.call(self)
|
102
|
+
compile!
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Render the template in the given scope with the locals specified. If a
|
107
|
+
# block is given, it is typically available within the template via
|
108
|
+
# +yield+.
|
109
|
+
def render(scope=Object.new, locals={}, &block)
|
110
|
+
compile
|
111
|
+
evaluate scope, locals || {}, &block
|
112
|
+
end
|
113
|
+
|
114
|
+
# The basename of the template file.
|
115
|
+
def basename(suffix='')
|
116
|
+
File.basename(file, suffix) if file
|
117
|
+
end
|
118
|
+
|
119
|
+
# The template file's basename with all extensions chomped off.
|
120
|
+
def name
|
121
|
+
basename.split('.', 2).first if basename
|
122
|
+
end
|
123
|
+
|
124
|
+
# The filename used in backtraces to describe the template.
|
125
|
+
def eval_file
|
126
|
+
file || '(__TEMPLATE__)'
|
127
|
+
end
|
128
|
+
|
129
|
+
protected
|
130
|
+
# Do whatever preparation is necessary to "compile" the template.
|
131
|
+
# Called immediately after template #data is loaded. Instance variables
|
132
|
+
# set in this method are available when #evaluate is called.
|
133
|
+
#
|
134
|
+
# Subclasses must provide an implementation of this method.
|
135
|
+
def compile!
|
136
|
+
raise NotImplementedError
|
137
|
+
end
|
138
|
+
|
139
|
+
# Process the template and return the result. Subclasses should override
|
140
|
+
# this method unless they implement the #template_source.
|
141
|
+
def evaluate(scope, locals, &block)
|
142
|
+
source, offset = local_assignment_code(locals)
|
143
|
+
source = [source, template_source].join("\n")
|
144
|
+
scope.instance_eval source, eval_file, line - offset
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return a string containing the (Ruby) source code for the template. The
|
148
|
+
# default Template#evaluate implementation requires this method be
|
149
|
+
# defined.
|
150
|
+
def template_source
|
151
|
+
raise NotImplementedError
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def local_assignment_code(locals)
|
156
|
+
return ['', 1] if locals.empty?
|
157
|
+
source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
|
158
|
+
[source.join("\n"), source.length]
|
159
|
+
end
|
160
|
+
|
161
|
+
def require_template_library(name)
|
162
|
+
if Thread.list.size > 1
|
163
|
+
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
|
164
|
+
"explicit require '#{name}' suggested."
|
165
|
+
end
|
166
|
+
require name
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Extremely simple template cache implementation. Calling applications
|
171
|
+
# create a Tilt::Cache instance and use #fetch with any set of hashable
|
172
|
+
# arguments (such as those to Tilt.new):
|
173
|
+
# cache = Tilt::Cache.new
|
174
|
+
# cache.fetch(path, line, options) { Tilt.new(path, line, options) }
|
175
|
+
#
|
176
|
+
# Subsequent invocations return the already compiled template object.
|
177
|
+
class Cache
|
178
|
+
def initialize
|
179
|
+
@cache = {}
|
180
|
+
end
|
181
|
+
|
182
|
+
def fetch(*key)
|
183
|
+
@cache[key] ||= yield
|
184
|
+
end
|
185
|
+
|
186
|
+
def clear
|
187
|
+
@cache = {}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
# Template Implementations ================================================
|
193
|
+
|
194
|
+
|
195
|
+
# The template source is evaluated as a Ruby string. The #{} interpolation
|
196
|
+
# syntax can be used to generated dynamic output.
|
197
|
+
class StringTemplate < Template
|
198
|
+
def compile!
|
199
|
+
@code = "%Q{#{data}}"
|
200
|
+
end
|
201
|
+
|
202
|
+
def template_source
|
203
|
+
@code
|
204
|
+
end
|
205
|
+
end
|
206
|
+
register 'str', StringTemplate
|
207
|
+
|
208
|
+
|
209
|
+
# ERB template implementation. See:
|
210
|
+
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
|
211
|
+
class ERBTemplate < Template
|
212
|
+
def initialize_engine
|
213
|
+
require_template_library 'erb' unless defined? ::ERB
|
214
|
+
end
|
215
|
+
|
216
|
+
def compile!
|
217
|
+
@engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
|
218
|
+
end
|
219
|
+
|
220
|
+
def template_source
|
221
|
+
@engine.src
|
222
|
+
end
|
223
|
+
|
224
|
+
def evaluate(scope, locals, &block)
|
225
|
+
source, offset = local_assignment_code(locals)
|
226
|
+
source = [source, template_source].join("\n")
|
227
|
+
|
228
|
+
original_out_buf =
|
229
|
+
scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
|
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
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
# ERB generates a line to specify the character coding of the generated
|
243
|
+
# source in 1.9. Account for this in the line offset.
|
244
|
+
if RUBY_VERSION >= '1.9.0'
|
245
|
+
def local_assignment_code(locals)
|
246
|
+
source, offset = super
|
247
|
+
[source, offset + 1]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
252
|
+
|
253
|
+
|
254
|
+
# Erubis template implementation. See:
|
255
|
+
# http://www.kuwata-lab.com/erubis/
|
256
|
+
class ErubisTemplate < ERBTemplate
|
257
|
+
def initialize_engine
|
258
|
+
require_template_library 'erubis' unless defined? ::Erubis
|
259
|
+
end
|
260
|
+
|
261
|
+
def compile!
|
262
|
+
Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
|
263
|
+
@engine = ::Erubis::Eruby.new(data, options)
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
# Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
|
269
|
+
# and adjust back.
|
270
|
+
if RUBY_VERSION >= '1.9.0'
|
271
|
+
def local_assignment_code(locals)
|
272
|
+
source, offset = super
|
273
|
+
[source, offset - 1]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
register 'erubis', ErubisTemplate
|
278
|
+
|
279
|
+
|
280
|
+
# Haml template implementation. See:
|
281
|
+
# http://haml.hamptoncatlin.com/
|
282
|
+
class HamlTemplate < Template
|
283
|
+
def initialize_engine
|
284
|
+
require_template_library 'haml' unless defined? ::Haml::Engine
|
285
|
+
end
|
286
|
+
|
287
|
+
def compile!
|
288
|
+
@engine = ::Haml::Engine.new(data, haml_options)
|
289
|
+
end
|
290
|
+
|
291
|
+
def evaluate(scope, locals, &block)
|
292
|
+
@engine.render(scope, locals, &block)
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
def haml_options
|
297
|
+
options.merge(:filename => eval_file, :line => line)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
register 'haml', HamlTemplate
|
301
|
+
|
302
|
+
|
303
|
+
# Sass template implementation. See:
|
304
|
+
# http://haml.hamptoncatlin.com/
|
305
|
+
#
|
306
|
+
# Sass templates do not support object scopes, locals, or yield.
|
307
|
+
class SassTemplate < Template
|
308
|
+
def initialize_engine
|
309
|
+
require_template_library 'sass' unless defined? ::Sass::Engine
|
310
|
+
end
|
311
|
+
|
312
|
+
def compile!
|
313
|
+
@engine = ::Sass::Engine.new(data, sass_options)
|
314
|
+
end
|
315
|
+
|
316
|
+
def evaluate(scope, locals, &block)
|
317
|
+
@engine.render
|
318
|
+
end
|
319
|
+
|
320
|
+
private
|
321
|
+
def sass_options
|
322
|
+
options.merge(:filename => eval_file, :line => line)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
register 'sass', SassTemplate
|
326
|
+
|
327
|
+
|
328
|
+
# Builder template implementation. See:
|
329
|
+
# http://builder.rubyforge.org/
|
330
|
+
class BuilderTemplate < Template
|
331
|
+
def initialize_engine
|
332
|
+
require_template_library 'builder' unless defined?(::Builder)
|
333
|
+
end
|
334
|
+
|
335
|
+
def compile!
|
336
|
+
end
|
337
|
+
|
338
|
+
def evaluate(scope, locals, &block)
|
339
|
+
xml = ::Builder::XmlMarkup.new(:indent => 2)
|
340
|
+
if data.respond_to?(:to_str)
|
341
|
+
locals[:xml] = xml
|
342
|
+
super(scope, locals, &block)
|
343
|
+
elsif data.kind_of?(Proc)
|
344
|
+
data.call(xml)
|
345
|
+
end
|
346
|
+
xml.target!
|
347
|
+
end
|
348
|
+
|
349
|
+
def template_source
|
350
|
+
data.to_str
|
351
|
+
end
|
352
|
+
end
|
353
|
+
register 'builder', BuilderTemplate
|
354
|
+
|
355
|
+
|
356
|
+
# Liquid template implementation. See:
|
357
|
+
# http://liquid.rubyforge.org/
|
358
|
+
#
|
359
|
+
# Liquid is designed to be a *safe* template system and threfore
|
360
|
+
# does not provide direct access to execuatable scopes. In order to
|
361
|
+
# support a +scope+, the +scope+ must be able to represent itself
|
362
|
+
# as a hash by responding to #to_h. If the +scope+ does not respond
|
363
|
+
# to #to_h it will be ignored.
|
364
|
+
#
|
365
|
+
# LiquidTemplate does not support yield blocks.
|
366
|
+
#
|
367
|
+
# It's suggested that your program require 'liquid' at load
|
368
|
+
# time when using this template engine.
|
369
|
+
class LiquidTemplate < Template
|
370
|
+
def initialize_engine
|
371
|
+
require_template_library 'liquid' unless defined? ::Liquid::Template
|
372
|
+
end
|
373
|
+
|
374
|
+
def compile!
|
375
|
+
@engine = ::Liquid::Template.parse(data)
|
376
|
+
end
|
377
|
+
|
378
|
+
def evaluate(scope, locals, &block)
|
379
|
+
locals = locals.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
|
380
|
+
if scope.respond_to?(:to_h)
|
381
|
+
scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
|
382
|
+
locals = scope.merge(locals)
|
383
|
+
end
|
384
|
+
# TODO: Is it possible to lazy yield ?
|
385
|
+
locals['yield'] = block.nil? ? '' : yield
|
386
|
+
locals['content'] = block.nil? ? '' : yield
|
387
|
+
@engine.render(locals)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
register 'liquid', LiquidTemplate
|
391
|
+
|
392
|
+
|
393
|
+
# Discount Markdown implementation. See:
|
394
|
+
# http://github.com/rtomayko/rdiscount
|
395
|
+
#
|
396
|
+
# RDiscount is a simple text filter. It does not support +scope+ or
|
397
|
+
# +locals+. The +:smart+ and +:filter_html+ options may be set true
|
398
|
+
# to enable those flags on the underlying RDiscount object.
|
399
|
+
class RDiscountTemplate < Template
|
400
|
+
def flags
|
401
|
+
[:smart, :filter_html].select { |flag| options[flag] }
|
402
|
+
end
|
403
|
+
|
404
|
+
def initialize_engine
|
405
|
+
require_template_library 'rdiscount' unless defined? ::RDiscount
|
406
|
+
end
|
407
|
+
|
408
|
+
def compile!
|
409
|
+
@engine = RDiscount.new(data, *flags)
|
410
|
+
end
|
411
|
+
|
412
|
+
def evaluate(scope, locals, &block)
|
413
|
+
@engine.to_html
|
414
|
+
end
|
415
|
+
end
|
416
|
+
register 'markdown', RDiscountTemplate
|
417
|
+
register 'mkd', RDiscountTemplate
|
418
|
+
register 'md', RDiscountTemplate
|
419
|
+
|
420
|
+
|
421
|
+
# RedCloth implementation. See:
|
422
|
+
# http://redcloth.org/
|
423
|
+
class RedClothTemplate < Template
|
424
|
+
def initialize_engine
|
425
|
+
require_template_library 'redcloth' unless defined? ::RedCloth
|
426
|
+
end
|
427
|
+
|
428
|
+
def compile!
|
429
|
+
@engine = RedCloth.new(data)
|
430
|
+
end
|
431
|
+
|
432
|
+
def evaluate(scope, locals, &block)
|
433
|
+
@engine.to_html
|
434
|
+
end
|
435
|
+
end
|
436
|
+
register 'textile', RedClothTemplate
|
437
|
+
|
438
|
+
|
439
|
+
# Mustache is written and maintained by Chris Wanstrath. See:
|
440
|
+
# http://github.com/defunkt/mustache
|
441
|
+
#
|
442
|
+
# When a scope argument is provided to MustacheTemplate#render, the
|
443
|
+
# instance variables are copied from the scope object to the Mustache
|
444
|
+
# view.
|
445
|
+
class MustacheTemplate < Template
|
446
|
+
attr_reader :engine
|
447
|
+
|
448
|
+
def initialize_engine
|
449
|
+
require_template_library 'mustache' unless defined? ::Mustache
|
450
|
+
end
|
451
|
+
|
452
|
+
def compile!
|
453
|
+
Mustache.view_namespace = options[:namespace]
|
454
|
+
@engine = options[:view] || Mustache.view_class(name)
|
455
|
+
options.each do |key, value|
|
456
|
+
next if %w[view namespace mustaches].include?(key.to_s)
|
457
|
+
@engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def evaluate(scope=nil, locals={}, &block)
|
462
|
+
instance = @engine.new
|
463
|
+
|
464
|
+
# copy instance variables from scope to the view
|
465
|
+
scope.instance_variables.each do |name|
|
466
|
+
instance.instance_variable_set(name, scope.instance_variable_get(name))
|
467
|
+
end
|
468
|
+
|
469
|
+
# locals get added to the view's context
|
470
|
+
locals.each do |local, value|
|
471
|
+
instance[local] = value
|
472
|
+
end
|
473
|
+
|
474
|
+
# if we're passed a block it's a subview. Sticking it in yield
|
475
|
+
# lets us use {{yield}} in layout.html to render the actual page.
|
476
|
+
instance[:yield] = block.call if block
|
477
|
+
|
478
|
+
instance.template = data unless instance.compiled?
|
479
|
+
|
480
|
+
instance.to_html
|
481
|
+
end
|
482
|
+
end
|
483
|
+
register 'mustache', MustacheTemplate
|
484
|
+
|
485
|
+
# RDoc template. See:
|
486
|
+
# http://rdoc.rubyforge.org/
|
487
|
+
#
|
488
|
+
# It's suggested that your program require 'rdoc/markup' and
|
489
|
+
# 'rdoc/markup/to_html' at load time when using this template
|
490
|
+
# engine.
|
491
|
+
class RDocTemplate < Template
|
492
|
+
def initialize_engine
|
493
|
+
unless defined?(::RDoc::Markup)
|
494
|
+
require_template_library 'rdoc/markup'
|
495
|
+
require_template_library 'rdoc/markup/to_html'
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def compile!
|
500
|
+
markup = RDoc::Markup::ToHtml.new
|
501
|
+
@engine = markup.convert(data)
|
502
|
+
end
|
503
|
+
|
504
|
+
def evaluate(scope, locals, &block)
|
505
|
+
@engine.to_s
|
506
|
+
end
|
507
|
+
end
|
508
|
+
register 'rdoc', RDocTemplate
|
509
|
+
|
510
|
+
# CoffeeScript info:
|
511
|
+
# http://jashkenas.github.com/coffee-script/
|
512
|
+
class CoffeeTemplate < Template
|
513
|
+
def initialize_engine
|
514
|
+
require_template_library 'coffee-script' unless defined? ::CoffeeScript
|
515
|
+
end
|
516
|
+
|
517
|
+
def compile!
|
518
|
+
@engine = ::CoffeeScript::compile(data, options)
|
519
|
+
end
|
520
|
+
|
521
|
+
def evaluate(scope, locals, &block)
|
522
|
+
@engine
|
523
|
+
end
|
524
|
+
end
|
525
|
+
register 'coffee', CoffeeTemplate
|
526
|
+
end
|