frank 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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,12 @@
1
+ module Frank
2
+ module TemplateHelpers
3
+ include FrankHelpers
4
+
5
+ def render_partial(path)
6
+ pieces = path.split("/")
7
+ partial = '_' + pieces.pop
8
+ render_path File.join(pieces.join('/'), partial)
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ !!! Strict
2
+ %html
3
+ %head
4
+ %title= "404 &ndash; 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&mdash;"
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 &ndash; 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&#146;s Wrong&mdash;"
19
+ %p.summary= "The error, &laquo; <tt>#{exception.message.gsub('<','&lt;')}</tt> &raquo;<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