brochure 0.1.0 → 0.2.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/LICENSE +1 -0
- data/README.md +5 -3
- data/lib/brochure.rb +18 -103
- data/lib/brochure/application.rb +102 -0
- data/lib/brochure/context.rb +30 -0
- data/lib/brochure/errors.rb +3 -0
- data/lib/brochure/failsafe.rb +26 -0
- metadata +49 -9
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -24,7 +24,7 @@ Sample application structure:
|
|
24
24
|
Sample `config.ru`:
|
25
25
|
|
26
26
|
require "brochure"
|
27
|
-
run Brochure
|
27
|
+
run Brochure.app(File.dirname(__FILE__))
|
28
28
|
|
29
29
|
URLs are automatically mapped to template names. So `/` will render
|
30
30
|
`app/templates/index.html.erb`, `/signup` will render
|
@@ -43,10 +43,12 @@ render `app/templates/shared/_header.html.erb` inline.
|
|
43
43
|
|
44
44
|
$ gem install brochure
|
45
45
|
|
46
|
-
Requires [
|
46
|
+
Requires [Hike](http://github.com/sstephenson/hike),
|
47
|
+
[Rack](http://rack.rubyforge.org/), and
|
48
|
+
[Tilt](http://github.com/rtomayko/tilt).
|
47
49
|
|
48
50
|
# License
|
49
51
|
|
50
|
-
Copyright (c) 2010 Sam Stephenson.
|
52
|
+
Copyright (c) 2010 Sam Stephenson and Josh Peek.
|
51
53
|
|
52
54
|
Released under the MIT license. See `LICENSE` for details.
|
data/lib/brochure.rb
CHANGED
@@ -1,115 +1,30 @@
|
|
1
|
+
require "hike"
|
2
|
+
require "rack"
|
1
3
|
require "tilt"
|
2
4
|
|
3
5
|
module Brochure
|
4
|
-
|
5
|
-
def initialize(root)
|
6
|
-
@app_root = File.expand_path(root)
|
7
|
-
@helper_root = File.join(@app_root, "app", "helpers")
|
8
|
-
@template_root = File.join(@app_root, "app", "templates")
|
9
|
-
@context_class = Context.for(helpers)
|
10
|
-
@templates = {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def helpers
|
14
|
-
@helpers ||= Dir[File.join(@helper_root, "**", "*.rb")].map do |helper_path|
|
15
|
-
base_name = helper_path[(@helper_root.length + 1)..-1][/(.*?)\.rb$/, 1]
|
16
|
-
module_names = base_name.split("/").map { |n| Brochure.camelize(n) }
|
17
|
-
load helper_path
|
18
|
-
module_names.inject(Kernel) { |mod, name| mod.const_get(name) }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def call(env)
|
23
|
-
logical_path = env["PATH_INFO"][/[^.]+/]
|
24
|
-
success render(logical_path)
|
25
|
-
rescue TemplateNotFound => e
|
26
|
-
not_found
|
27
|
-
rescue StandardError => e
|
28
|
-
error e
|
29
|
-
end
|
30
|
-
|
31
|
-
def find_template_path(logical_path, options = {})
|
32
|
-
if options[:partial]
|
33
|
-
path_parts = logical_path.split("/")
|
34
|
-
logical_path = (path_parts[0..-2] + ["_" + path_parts[-1]]).join("/")
|
35
|
-
else
|
36
|
-
return false if File.basename(logical_path)[/^_/]
|
37
|
-
end
|
6
|
+
VERSION = "0.2.0"
|
38
7
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
File.exists?(template_path) && template_path
|
46
|
-
end
|
8
|
+
autoload :Application, "brochure/application"
|
9
|
+
autoload :Context, "brochure/context"
|
10
|
+
autoload :Failsafe, "brochure/failsafe"
|
11
|
+
autoload :TemplateNotFound, "brochure/errors"
|
47
12
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
raise TemplateNotFound, "no such template '#{logical_path}'"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def template_for(template_path)
|
59
|
-
@templates[template_path] ||= Tilt.new(template_path)
|
60
|
-
end
|
61
|
-
|
62
|
-
def respond_with(status, body, content_type = "text/html, charset=utf-8")
|
63
|
-
headers = {
|
64
|
-
"Content-Type" => content_type,
|
65
|
-
"Content-Length" => body.length.to_s
|
66
|
-
}
|
67
|
-
[status, headers, body]
|
68
|
-
end
|
69
|
-
|
70
|
-
def success(body)
|
71
|
-
respond_with 200, body
|
72
|
-
end
|
73
|
-
|
74
|
-
def not_found
|
75
|
-
respond_with 404, <<-HTML
|
76
|
-
<!DOCTYPE html>
|
77
|
-
<html><head><title>Not Found</title></head>
|
78
|
-
<body><h1>404 Not Found</h1></body></html>
|
79
|
-
HTML
|
80
|
-
end
|
81
|
-
|
82
|
-
def error(exception)
|
83
|
-
warn ["#{exception.class.name}: #{exception}", *exception.backtrace].join("\n ")
|
84
|
-
respond_with 500, <<-HTML
|
85
|
-
<!DOCTYPE html>
|
86
|
-
<html><head><title>Internal Server Error</title></head>
|
87
|
-
<body><h1>500 Internal Server Error</h1></body></html>
|
88
|
-
HTML
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
class Context
|
93
|
-
include Tilt::CompileSite
|
94
|
-
|
95
|
-
def self.for(helpers)
|
96
|
-
context = Class.new(self)
|
97
|
-
context.send(:include, *helpers) if helpers.any?
|
98
|
-
context
|
99
|
-
end
|
100
|
-
|
101
|
-
def initialize(application)
|
102
|
-
@application = application
|
103
|
-
end
|
104
|
-
|
105
|
-
def render(logical_path, locals = {})
|
106
|
-
@application.render(logical_path, :partial => true, :locals => locals)
|
13
|
+
def self.app(root)
|
14
|
+
app = Application.new(root)
|
15
|
+
if development?
|
16
|
+
app = Rack::ShowExceptions.new(app)
|
17
|
+
else
|
18
|
+
app = Failsafe.new(app)
|
107
19
|
end
|
20
|
+
app
|
108
21
|
end
|
109
22
|
|
110
|
-
class TemplateNotFound < StandardError; end
|
111
|
-
|
112
23
|
def self.camelize(string)
|
113
24
|
string.gsub(/(^|_)(\w)/) { $2.upcase }
|
114
25
|
end
|
26
|
+
|
27
|
+
def self.development?
|
28
|
+
ENV["RACK_ENV"] == "development"
|
29
|
+
end
|
115
30
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Brochure
|
2
|
+
class Application
|
3
|
+
attr_reader :app_root, :helper_root, :template_root, :asset_root
|
4
|
+
|
5
|
+
def initialize(root)
|
6
|
+
@app_root = File.expand_path(root)
|
7
|
+
@helper_root = File.join(@app_root, "app", "helpers")
|
8
|
+
@template_root = File.join(@app_root, "app", "templates")
|
9
|
+
@asset_root = File.join(@app_root, "public")
|
10
|
+
|
11
|
+
@template_trail = Hike::Trail.new(@app_root)
|
12
|
+
@template_trail.extensions.replace(Tilt.mappings.keys.sort)
|
13
|
+
@template_trail.paths.push(@template_root)
|
14
|
+
|
15
|
+
@context_class = Context.for(helpers)
|
16
|
+
@templates = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def helpers
|
20
|
+
@helpers ||= Dir[File.join(@helper_root, "**", "*.rb")].map do |helper_path|
|
21
|
+
base_name = helper_path[(@helper_root.length + 1)..-1][/(.*?)\.rb$/, 1]
|
22
|
+
module_names = base_name.split("/").map { |n| Brochure.camelize(n) }
|
23
|
+
load helper_path
|
24
|
+
module_names.inject(Kernel) { |mod, name| mod.const_get(name) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(env)
|
29
|
+
if forbidden?(env["PATH_INFO"])
|
30
|
+
forbidden
|
31
|
+
elsif template = find_template(env["PATH_INFO"][/[^.]+/])
|
32
|
+
success render_template(template, env)
|
33
|
+
else
|
34
|
+
not_found
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def forbidden?(path)
|
39
|
+
path[".."] || File.basename(path)[/^_/]
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_template(logical_path)
|
43
|
+
if template_path = find_template_path(logical_path)
|
44
|
+
template_for(template_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_partial(logical_path)
|
49
|
+
if template_path = find_partial_path(logical_path)
|
50
|
+
template_for(template_path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_template_path(logical_path)
|
55
|
+
candidates = [logical_path + ".html", logical_path + "/index.html"]
|
56
|
+
@template_trail.find(*candidates)
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_partial_path(logical_path)
|
60
|
+
path_parts = logical_path.split("/")
|
61
|
+
partial_path = (path_parts[0..-2] + ["_" + path_parts[-1]]).join("/")
|
62
|
+
@template_trail.find(partial_path + ".html")
|
63
|
+
end
|
64
|
+
|
65
|
+
def template_for(template_path)
|
66
|
+
if Brochure.development?
|
67
|
+
Tilt.new(template_path)
|
68
|
+
else
|
69
|
+
@templates[template_path] ||= Tilt.new(template_path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def render_template(template, env, locals = {})
|
74
|
+
context = @context_class.new(self, env)
|
75
|
+
template.render(context, locals)
|
76
|
+
end
|
77
|
+
|
78
|
+
def respond_with(status, body, content_type = "text/html, charset=utf-8")
|
79
|
+
headers = {
|
80
|
+
"Content-Type" => content_type,
|
81
|
+
"Content-Length" => body.length.to_s
|
82
|
+
}
|
83
|
+
[status, headers, [body]]
|
84
|
+
end
|
85
|
+
|
86
|
+
def success(body)
|
87
|
+
respond_with 200, body
|
88
|
+
end
|
89
|
+
|
90
|
+
def not_found
|
91
|
+
respond_with 404, <<-HTML
|
92
|
+
<!DOCTYPE html>
|
93
|
+
<html><head><title>Not Found</title></head>
|
94
|
+
<body><h1>404 Not Found</h1></body></html>
|
95
|
+
HTML
|
96
|
+
end
|
97
|
+
|
98
|
+
def forbidden
|
99
|
+
respond_with 403, "Forbidden"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Brochure
|
2
|
+
class Context
|
3
|
+
include Tilt::CompileSite
|
4
|
+
|
5
|
+
def self.for(helpers)
|
6
|
+
context = Class.new(self)
|
7
|
+
context.send(:include, *helpers) if helpers.any?
|
8
|
+
context
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :application, :env
|
12
|
+
|
13
|
+
def initialize(application, env)
|
14
|
+
self.application = application
|
15
|
+
self.env = env
|
16
|
+
end
|
17
|
+
|
18
|
+
def request
|
19
|
+
@_request ||= Rack::Request.new(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(logical_path, locals = {})
|
23
|
+
if template = @application.find_partial(logical_path)
|
24
|
+
@application.render_template(template, env, locals)
|
25
|
+
else
|
26
|
+
raise TemplateNotFound, "no such template '#{logical_path}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Brochure
|
2
|
+
class Failsafe
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
@app.call(env)
|
9
|
+
rescue Exception => exception
|
10
|
+
backtrace = ["#{exception.class.name}: #{exception}", *exception.backtrace].join("\n ")
|
11
|
+
env["rack.errors"].puts(backtrace)
|
12
|
+
env["rack.errors"].flush
|
13
|
+
|
14
|
+
body = <<-HTML
|
15
|
+
<!DOCTYPE html>
|
16
|
+
<html><head><title>Internal Server Error</title></head>
|
17
|
+
<body><h1>500 Internal Server Error</h1></body></html>
|
18
|
+
HTML
|
19
|
+
|
20
|
+
[500,
|
21
|
+
{ "Content-Type" => "text/html, charset=utf-8",
|
22
|
+
"Content-Length" => Rack::Utils.bytesize(body).to_s },
|
23
|
+
[body]]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,41 +1,76 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brochure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Sam Stephenson
|
14
|
+
- Josh Peek
|
14
15
|
autorequire:
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date: 2010-10-
|
19
|
+
date: 2010-10-16 00:00:00 -05:00
|
19
20
|
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
23
|
+
name: hike
|
23
24
|
prerelease: false
|
24
25
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
26
|
none: false
|
26
27
|
requirements:
|
27
28
|
- - ">="
|
28
29
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
30
|
+
hash: 25
|
30
31
|
segments:
|
31
32
|
- 0
|
32
|
-
|
33
|
+
- 1
|
34
|
+
- 1
|
35
|
+
version: 0.1.1
|
33
36
|
type: :runtime
|
34
37
|
version_requirements: *id001
|
35
38
|
- !ruby/object:Gem::Dependency
|
36
|
-
name: rack
|
39
|
+
name: rack
|
37
40
|
prerelease: false
|
38
41
|
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 23
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 0
|
50
|
+
- 0
|
51
|
+
version: 1.0.0
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: tilt
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 19
|
63
|
+
segments:
|
64
|
+
- 1
|
65
|
+
- 1
|
66
|
+
- 0
|
67
|
+
version: 1.1.0
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rack-test
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
39
74
|
none: false
|
40
75
|
requirements:
|
41
76
|
- - ">="
|
@@ -45,10 +80,11 @@ dependencies:
|
|
45
80
|
- 0
|
46
81
|
version: "0"
|
47
82
|
type: :development
|
48
|
-
version_requirements: *
|
83
|
+
version_requirements: *id004
|
49
84
|
description: A Rack application for serving static sites with ERB templates.
|
50
85
|
email:
|
51
86
|
- sstephenson@gmail.com
|
87
|
+
- josh@joshpeek.com
|
52
88
|
executables: []
|
53
89
|
|
54
90
|
extensions: []
|
@@ -56,6 +92,10 @@ extensions: []
|
|
56
92
|
extra_rdoc_files: []
|
57
93
|
|
58
94
|
files:
|
95
|
+
- lib/brochure/application.rb
|
96
|
+
- lib/brochure/context.rb
|
97
|
+
- lib/brochure/errors.rb
|
98
|
+
- lib/brochure/failsafe.rb
|
59
99
|
- lib/brochure.rb
|
60
100
|
- README.md
|
61
101
|
- LICENSE
|