frank 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ pkg
3
+ lib/frank/output_old.rb
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Travis Dunn
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ Frank
2
+ =========
3
+
4
+ Inspired by [Sinatra][0]'s simplicity and ease of use, Frank lets you build
5
+ static sites using your favorite libs, painlessly. It uses [Tilt][1], so it
6
+ comes with support for [Haml & Sass][2], [Builder][3], [ERB][4],
7
+ [Liquid][5], & [Mustache][6].
8
+
9
+ Frank also supports [CoffeeScript][7] for writing JavaScript in style, and
10
+ [LESS][8] is planned for the near future.
11
+
12
+ Overview
13
+ --------
14
+
15
+ Create a new project with:
16
+
17
+ $ frank <project_name>
18
+
19
+ Then start up the server with:
20
+
21
+ $ frankup
22
+
23
+ -----------------------
24
+ Frank's holdin' it down...
25
+ 0.0.0.0:3601
26
+
27
+ And you're ready to get to work. Feel free to use as much or a little of
28
+ the available libs as you please. Frank works just as well with Mustache /
29
+ CSS / JavasScript as Haml / Sass / CoffeeScript.
30
+
31
+
32
+ Views & Layouts
33
+ -------------------------
34
+
35
+ All of your templates, and less/sass/coffeescript go into `<project>/dynamic`,
36
+ by default. You are more than welcome to organize them into subfolders if you've
37
+ got lots.
38
+
39
+ ### Views
40
+
41
+ Writing views is simple. Say you've got a `blog.haml`, in `<project>/dynamic` just browse to
42
+ `http://0.0.0.0:3601/blog` and your view will be parsed and returned as html.
43
+
44
+ ### Layouts
45
+
46
+ Layouts are also simple with Frank. By default, just create a `layout.haml`
47
+ (or whichever language you like best), that contains a `yield`, and any
48
+ views will be inserted into it at that point.
49
+
50
+ Multiple layouts are also easy. In your `settings.yml`, do something like:
51
+
52
+ layouts:
53
+ - name: blog_layout
54
+ only: [blog]
55
+ - name: normal
56
+ not: [blog, ajax]
57
+ This tells Frank to use `blog_layout.haml` for `/blog`, and `normal.haml`
58
+ for everything but `/blog' and '/ajax`.
59
+
60
+
61
+ Partials & Helpers
62
+ ------------------
63
+
64
+ Frank comes with a helper method, `render_partial`, for including partials
65
+ in your views.
66
+
67
+ In addition, you can also easily add your own helper methods to use.
68
+
69
+ ### Partials
70
+
71
+ To create a partial, make a new file like any of your other views, but
72
+ prefix its name with an underscore.
73
+
74
+ For example, if I have a partial named `_footer.haml`, I can include this
75
+ in my Haml views like this:
76
+
77
+ = render_partial 'footer'
78
+
79
+ ### Helpers
80
+
81
+ Helper methods are also easy. Just open up `helpers.rb` and add your methods
82
+ to the `FrankHelpers` module; that's it. Use them just like `render_partial`.
83
+
84
+
85
+ GET/POST params
86
+ ---------------
87
+
88
+ Sometimes it's nice to include user input in your mock-ups. It's especially
89
+ handy when mocking-up Ajax-driven elements. For this reason, the `request`
90
+ and `params` are available in your templates.
91
+
92
+ For example, to use a person's name submitted through a form you might do:
93
+
94
+ %h1= "Hello, #{params.name}"
95
+
96
+ Configuration
97
+ -------------
98
+
99
+ In `settings.yml`, you can change your folder names, and server port & host name.
100
+ Check the comments there if you need help.
101
+
102
+ Once you've gotten comfortable with Frank, you will probably no longer want
103
+ the example files included whenever you start a new project. You may also have
104
+ preferred folder names, and languages that you always want to start
105
+ projects with.
106
+
107
+ To do this, create a new base project. Then just copy your base project to `~/.frank`.
108
+ This folder will then be copied for you whenever you run the `frank` command.
109
+
110
+ Installation
111
+ ------------
112
+
113
+ ### [Gemcutter](http://gemcutter.org/)
114
+
115
+ $ gem install frank
116
+
117
+
118
+ [0]: http://www.sinatrarb.com/
119
+ [1]: http://github.com/rtomayko/tilt
120
+ [2]: http://haml-lang.com/
121
+ [3]: http://builder.rubyforge.org/
122
+ [4]: http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/
123
+ [5]: http://www.liquidmarkup.org/
124
+ [6]: http://github.com/defunkt/mustache
125
+ [7]: http://jashkenas.github.com/coffee-script/
126
+ [8]: http://lesscss.org/
127
+ [9]: http://rack.rubyforge.org/
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "frank"
5
+ gemspec.summary = "Stupidly Simple Static Slinger"
6
+ gemspec.description = "Create/Dump static builds using whatever templating/helper languages you wish"
7
+ gemspec.email = "travis.dunn@thisismedium.com"
8
+ gemspec.homepage = "http://github.com/blahed/frank"
9
+ gemspec.authors = ["blahed", "nwah"]
10
+ end
11
+ Jeweler::GemcutterTasks.new
12
+ rescue LoadError
13
+ puts "Jeweler not available. Install it with: gem install jeweler"
14
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/frank ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ARGV.empty?
4
+ puts "Usage: frank [PROJECT_NAME]"
5
+ puts "Stubs out a template project; if a .frank folder was found, copies it over as your project base"
6
+ exit
7
+ end
8
+
9
+ puts "\n-----------------------\n Frank:\n"
10
+
11
+ project = ARGV.first
12
+ libdir = File.join(File.dirname(File.expand_path(__FILE__)), '../lib')
13
+ dot_frank = File.join(ENV['HOME'], '.frank')
14
+
15
+ puts " - Creating '#{project}'"
16
+ begin
17
+ Dir.mkdir project
18
+ rescue Errno::EEXIST
19
+ puts " uh oh, #{project} already exists..."
20
+ exit
21
+ end
22
+
23
+ # If they have a .frank, copy contents to new project folder
24
+ if File.directory? dot_frank
25
+ puts " - Copying .frank template"
26
+ FileUtils.cp_r( File.join(dot_frank, '.'), project )
27
+ else # Otherwise just copy over the template project
28
+ puts " - Copying Frank template"
29
+ FileUtils.cp_r( Dir.glob(File.join(libdir, 'template/*')), project )
30
+ end
31
+
32
+ puts "\n Congratulations, '#{project}' is ready to go.\n\n"
33
+
data/bin/frankout ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require 'frank'
3
+
4
+ if ARGV.empty?
5
+ puts "Usage: frankout [DUMP_DIR]"
6
+ puts "Compiles and dumps a project and it\'s static files to given folder"
7
+ exit
8
+ end
9
+
10
+ output_folder = ARGV.first
11
+
12
+ if File.exists? output_folder
13
+ print "Folder: #{output_folder} already exists... do you want to overwrite it? (y|n) "
14
+ resp = STDIN.gets.chomp.strip
15
+ exit if resp.downcase.strip != 'y'
16
+ FileUtils.rm_rf(output_folder)
17
+ end
18
+
19
+ begin
20
+ settings = YAML.load_file('settings.yml')
21
+ proj_dir = Dir.pwd
22
+
23
+ Frank::Output.new do
24
+ settings.each do |name, value|
25
+ set name.to_s, value
26
+ end
27
+ set :output_folder, output_folder
28
+ set :proj_dir, proj_dir
29
+ end.dump
30
+
31
+ rescue Errno::ENOENT
32
+ puts "Frank could not find settings.yml."
33
+ end
data/bin/frankup ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'frank'
3
+
4
+ begin
5
+ settings = YAML.load_file('settings.yml')
6
+ Frank.new do
7
+ settings.each do |name, value|
8
+ set name.to_s, value
9
+ end
10
+ end
11
+ rescue Errno::ENOENT
12
+ puts "Frank could not find settings.yml."
13
+ end
data/frank.gemspec ADDED
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{frank}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["blahed", "nwah"]
12
+ s.date = %q{2010-01-15}
13
+ s.description = %q{Create/Dump static builds using whatever templating/helper languages you wish}
14
+ s.email = %q{travis.dunn@thisismedium.com}
15
+ s.executables = ["frank", "frankout", "frankup"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "bin/frank",
27
+ "bin/frankout",
28
+ "bin/frankup",
29
+ "frank.gemspec",
30
+ "lib/frank.rb",
31
+ "lib/frank/base.rb",
32
+ "lib/frank/output.rb",
33
+ "lib/frank/rescue.rb",
34
+ "lib/frank/statik.rb",
35
+ "lib/frank/template_helpers.rb",
36
+ "lib/frank/templates/404.haml",
37
+ "lib/frank/templates/500.haml",
38
+ "lib/frank/templates/frank-404.png",
39
+ "lib/frank/templates/frank-500.png",
40
+ "lib/frank/tilt.rb",
41
+ "lib/template/dynamic/css/frank.sass",
42
+ "lib/template/dynamic/index.haml",
43
+ "lib/template/dynamic/js/frank.coffee",
44
+ "lib/template/dynamic/layout.haml",
45
+ "lib/template/helpers.rb",
46
+ "lib/template/settings.yml",
47
+ "lib/template/static/images/frank-med.png"
48
+ ]
49
+ s.homepage = %q{http://github.com/blahed/frank}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.5}
53
+ s.summary = %q{Stupidly Simple Static Slinger}
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
60
+ else
61
+ end
62
+ else
63
+ end
64
+ end
65
+
data/lib/frank.rb ADDED
@@ -0,0 +1,7 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require File.join(Dir.pwd, 'helpers')
5
+ require 'rubygems'
6
+ require 'frank/base'
7
+ require 'frank/output'
data/lib/frank/base.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'rack'
2
+ require 'frank/tilt'
3
+ require 'frank/template_helpers'
4
+ require 'frank/rescue'
5
+ require 'frank/statik'
6
+
7
+ module Frank
8
+
9
+ module Render; end
10
+
11
+ class Base
12
+ include Rack::Utils
13
+ include Frank::Rescue
14
+ include Frank::TemplateHelpers
15
+ include Frank::Render
16
+
17
+ attr_accessor :server, :static_folder, :dynamic_folder, :templates
18
+
19
+ def initialize(&block)
20
+ instance_eval &block
21
+ end
22
+
23
+ def call(env)
24
+ dup.call!(env)
25
+ end
26
+
27
+ def call!(env)
28
+ @env = env
29
+ @request = Rack::Request.new(env)
30
+ @response = Rack::Response.new
31
+ process
32
+ @response.close
33
+ @response.finish
34
+ end
35
+
36
+ private
37
+
38
+ # setter for options
39
+ def set(option, value)
40
+ if respond_to?("#{option}=")
41
+ send "#{option}=", value
42
+ end
43
+ end
44
+
45
+ # attempt to render with the request path,
46
+ # if it cannot be found, render error page
47
+ def process
48
+ begin
49
+ ext = File.extname(@request.path.split('/').last || '')
50
+ @response['Content-Type'] = Rack::Mime.mime_type(ext, 'text/html')
51
+ @response.write render_path(@request.path)
52
+ rescue Errno::ENOENT
53
+ render_404
54
+ rescue Exception => e
55
+ render_500 e
56
+ end
57
+ end
58
+
59
+ # prints requests and errors to STDOUT
60
+ def log_request(status, excp=nil)
61
+ out = "[#{Time.now.strftime('%Y-%m-%d %H:%M')}] (#{@server['handler'].capitalize}) http://#{@request.host}:#{@request.port}#{@request.fullpath} [#{@request.request_method}] - #{status}"
62
+ out += "\n\n**QUACK** #{excp.message}\n\n#{excp.backtrace.join("\n")} " if excp
63
+ STDOUT.puts out
64
+ end
65
+
66
+ end
67
+
68
+ module Render
69
+
70
+ def name_ext(path)
71
+ return path.split(/\.(?=[^.]+$)/)
72
+ end
73
+
74
+ def render_path(path)
75
+ path.sub!(/^\//,'')
76
+ template, ext = find_template_ext(path)
77
+ raise Errno::ENOENT if template.nil?
78
+
79
+ if template.match(/^_/) or (ext||'').match(/^(js|css)$/)
80
+ render_template template
81
+ else
82
+ render_with_layout template
83
+ end
84
+ end
85
+
86
+ def render_template(tmpl, *args)
87
+ tilt_with_request(File.join(@dynamic_folder, tmpl), *args) {"CONTENT"}
88
+ end
89
+
90
+ def render_with_layout(tmpl, *args)
91
+ if layout = get_layout_for(tmpl)
92
+ tilt_with_request(File.join(@dynamic_folder, layout), *args) do
93
+ render_template tmpl
94
+ end
95
+ else
96
+ render_template tmpl
97
+ end
98
+ end
99
+
100
+ TMPL_EXTS = { :html => %w[haml erb rhtml builder liquid mustache],
101
+ :css => %w[sass less],
102
+ :js => %w[coffee] }
103
+
104
+ def reverse_ext_lookup(ext)
105
+ TMPL_EXTS.each do |kind, exts|
106
+ return kind.to_s if exts.index(ext)
107
+ end
108
+ nil
109
+ end
110
+
111
+ def find_template_ext(filename)
112
+ name, kind = name_ext(filename)
113
+ kind = reverse_ext_lookup(kind) if kind && TMPL_EXTS[kind.intern].nil?
114
+
115
+ TMPL_EXTS[ kind.nil? ? :html : kind.intern ].each do |ext|
116
+ tmpl = "#{(name||'')}.#{ext}"
117
+ return [tmpl, kind] if File.exists? File.join(@dynamic_folder, tmpl)
118
+ end
119
+
120
+ TMPL_EXTS[ kind.nil? ? :html : kind.intern ].each do |ext|
121
+ default = File.join((name||''), "#{@templates['default']}.#{ext}")
122
+ return [default, kind] if File.exists? File.join(@dynamic_folder, default)
123
+ end
124
+ nil
125
+ rescue
126
+ nil
127
+ end
128
+
129
+ def get_layout_for(view)
130
+ view, ext = name_ext(view)
131
+ layouts = @templates['layouts'] || []
132
+
133
+ onlies = layouts.select {|l| l['only'] }
134
+ nots = layouts.select {|l| l['not'] }
135
+ blanks = layouts - onlies - nots
136
+
137
+ layout = onlies.select {|l| l['only'].index(view) }.first
138
+ layout = nots.reject {|l| l['not'].index(view) }.first unless layout
139
+ layout = blanks.first unless layout
140
+
141
+ layout.nil? ? nil : layout['name'] + '.' + ext
142
+ end
143
+
144
+ def tilt_lang(file, lang, *tilt_args, &block)
145
+ Tilt[lang].new(file, 1).render(*tilt_args, &block)
146
+ end
147
+
148
+ def tilt_with_request(file, *args, &block)
149
+ locals = @request.nil? ? {} : { :request => @env, :params => @request.params }
150
+ obj = Object.new.extend(TemplateHelpers).extend(Render)
151
+ obj.instance_variable_set(:@dynamic_folder, @dynamic_folder)
152
+ obj.instance_variable_set(:@templates, @templates)
153
+ Tilt.new(file, 1).render(obj, locals, &block)
154
+ end
155
+
156
+ def remove_ext(path)
157
+ path.gsub(File.extname(path), '')
158
+ end
159
+
160
+ end
161
+
162
+ def self.new(&block)
163
+ base = Base.new(&block) if block_given?
164
+ server_settings = base.instance_variable_get(:@server)
165
+
166
+ builder = Rack::Builder.new do
167
+ use Rack::Statik, :root => base.instance_variable_get(:@static_folder)
168
+ run base
169
+ end
170
+
171
+ m = "got it under control \n got your back \n holdin' it down
172
+ takin' care of business \n workin' some magic".split("\n").sort_by{rand}.first.strip
173
+ puts "\n-----------------------\n" +
174
+ " Frank's #{ m }...\n" +
175
+ " #{server_settings['hostname']}:#{server_settings['port']} \n\n"
176
+
177
+ server = Rack::Handler.get(server_settings['handler'])
178
+ server.run(builder, :Port => server_settings['port'], :Host => server_settings['hostname']) do
179
+ trap(:INT) { puts "\n\n-----------------------\n Show's over, fellas.\n\n"; exit }
180
+ end
181
+
182
+ base
183
+ end
184
+
185
+ end