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.
@@ -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