roda 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +70 -0
- data/README.rdoc +261 -302
- data/Rakefile +1 -1
- data/doc/release_notes/1.2.0.txt +406 -0
- data/lib/roda.rb +206 -124
- data/lib/roda/plugins/all_verbs.rb +11 -10
- data/lib/roda/plugins/assets.rb +5 -5
- data/lib/roda/plugins/backtracking_array.rb +12 -5
- data/lib/roda/plugins/caching.rb +10 -8
- data/lib/roda/plugins/class_level_routing.rb +94 -0
- data/lib/roda/plugins/content_for.rb +6 -0
- data/lib/roda/plugins/default_headers.rb +4 -11
- data/lib/roda/plugins/delay_build.rb +42 -0
- data/lib/roda/plugins/delegate.rb +64 -0
- data/lib/roda/plugins/drop_body.rb +33 -0
- data/lib/roda/plugins/empty_root.rb +48 -0
- data/lib/roda/plugins/environments.rb +68 -0
- data/lib/roda/plugins/error_email.rb +1 -2
- data/lib/roda/plugins/error_handler.rb +1 -1
- data/lib/roda/plugins/halt.rb +7 -5
- data/lib/roda/plugins/head.rb +4 -2
- data/lib/roda/plugins/header_matchers.rb +17 -9
- data/lib/roda/plugins/hooks.rb +16 -32
- data/lib/roda/plugins/json.rb +4 -10
- data/lib/roda/plugins/mailer.rb +233 -0
- data/lib/roda/plugins/match_affix.rb +48 -0
- data/lib/roda/plugins/multi_route.rb +9 -11
- data/lib/roda/plugins/multi_run.rb +81 -0
- data/lib/roda/plugins/named_templates.rb +93 -0
- data/lib/roda/plugins/not_allowed.rb +43 -48
- data/lib/roda/plugins/path.rb +63 -2
- data/lib/roda/plugins/render.rb +79 -48
- data/lib/roda/plugins/render_each.rb +6 -0
- data/lib/roda/plugins/sinatra_helpers.rb +523 -0
- data/lib/roda/plugins/slash_path_empty.rb +25 -0
- data/lib/roda/plugins/static_path_info.rb +64 -0
- data/lib/roda/plugins/streaming.rb +1 -1
- data/lib/roda/plugins/view_subdirs.rb +12 -8
- data/lib/roda/version.rb +1 -1
- data/spec/integration_spec.rb +33 -0
- data/spec/plugin/backtracking_array_spec.rb +24 -18
- data/spec/plugin/class_level_routing_spec.rb +138 -0
- data/spec/plugin/delay_build_spec.rb +23 -0
- data/spec/plugin/delegate_spec.rb +20 -0
- data/spec/plugin/drop_body_spec.rb +20 -0
- data/spec/plugin/empty_root_spec.rb +14 -0
- data/spec/plugin/environments_spec.rb +31 -0
- data/spec/plugin/h_spec.rb +1 -3
- data/spec/plugin/header_matchers_spec.rb +14 -0
- data/spec/plugin/hooks_spec.rb +3 -5
- data/spec/plugin/mailer_spec.rb +191 -0
- data/spec/plugin/match_affix_spec.rb +22 -0
- data/spec/plugin/multi_run_spec.rb +31 -0
- data/spec/plugin/named_templates_spec.rb +65 -0
- data/spec/plugin/path_spec.rb +66 -2
- data/spec/plugin/render_spec.rb +46 -1
- data/spec/plugin/sinatra_helpers_spec.rb +534 -0
- data/spec/plugin/slash_path_empty_spec.rb +22 -0
- data/spec/plugin/static_path_info_spec.rb +50 -0
- data/spec/request_spec.rb +23 -0
- data/spec/response_spec.rb +12 -1
- metadata +48 -6
@@ -0,0 +1,25 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The slash_path_empty plugin considers "/" as an empty path,
|
4
|
+
# in addition to the default of "" being considered an empty
|
5
|
+
# path. This makes it so +r.is+ without an argument will match
|
6
|
+
# a path of "/", and +r.is+ and verb methods such as +r.get+ and
|
7
|
+
# +r.post+ will match if the path is "/" after the arguments
|
8
|
+
# are processed. This can make it easier to handle applications
|
9
|
+
# where a trailing "/" in the path should be ignored.
|
10
|
+
module SlashPathEmpty
|
11
|
+
SLASH = "/".freeze
|
12
|
+
|
13
|
+
module RequestMethods
|
14
|
+
private
|
15
|
+
|
16
|
+
# Consider the path empty if it is "/".
|
17
|
+
def empty_path?
|
18
|
+
super || remaining_path == SLASH
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
register_plugin(:slash_path_empty, SlashPathEmpty)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The static_path_info plugin changes Roda's behavior so that the
|
4
|
+
# SCRIPT_NAME/PATH_INFO environment settings are not modified
|
5
|
+
# while the request is beind routed, improving performance. If
|
6
|
+
# you have any helpers that operate on PATH_INFO or SCRIPT_NAME,
|
7
|
+
# their behavior will not change depending on where they are
|
8
|
+
# called in the routing tree.
|
9
|
+
#
|
10
|
+
# This still updates SCRIPT_NAME/PATH_INFO before dispatching to
|
11
|
+
# another rack app via +r.run+.
|
12
|
+
module StaticPathInfo
|
13
|
+
module RequestMethods
|
14
|
+
PATH_INFO = "PATH_INFO".freeze
|
15
|
+
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
16
|
+
|
17
|
+
# The current path to match requests against. This is initialized
|
18
|
+
# to PATH_INFO when the request is created.
|
19
|
+
attr_reader :remaining_path
|
20
|
+
|
21
|
+
# Set remaining_path when initializing.
|
22
|
+
def initialize(*)
|
23
|
+
super
|
24
|
+
@remaining_path = @env[PATH_INFO]
|
25
|
+
end
|
26
|
+
|
27
|
+
# The already matched part of the path, including the original SCRIPT_NAME.
|
28
|
+
def matched_path
|
29
|
+
e = @env
|
30
|
+
e[SCRIPT_NAME] + e[PATH_INFO].chomp(@remaining_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Update SCRIPT_NAME/PATH_INFO based on the current remaining_path
|
34
|
+
# before dispatching to another rack app, so the app still works as
|
35
|
+
# a URL mapper.
|
36
|
+
def run(_)
|
37
|
+
e = @env
|
38
|
+
path = @remaining_path
|
39
|
+
e[SCRIPT_NAME] += e[PATH_INFO].chomp(path)
|
40
|
+
e[PATH_INFO] = path
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Update remaining_path with the remaining characters
|
47
|
+
def update_remaining_path(remaining)
|
48
|
+
@remaining_path = remaining
|
49
|
+
end
|
50
|
+
|
51
|
+
# Yield to the block, restoring the remaining_path before
|
52
|
+
# the method returns.
|
53
|
+
def keep_remaining_path
|
54
|
+
path = @remaining_path
|
55
|
+
yield
|
56
|
+
ensure
|
57
|
+
@remaining_path = path
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
register_plugin(:static_path_info, StaticPathInfo)
|
63
|
+
end
|
64
|
+
end
|
@@ -23,15 +23,18 @@ class Roda
|
|
23
23
|
# end
|
24
24
|
# end
|
25
25
|
#
|
26
|
-
# This plugin should be loaded after the render plugin, since
|
27
|
-
# it works by overriding parts of the render plugin.
|
28
|
-
#
|
29
26
|
# Note that when a view subdirectory is set, the layout will
|
30
27
|
# also be looked up in the subdirectory unless it contains
|
31
28
|
# a slash. So if you want to use a view subdirectory for
|
32
29
|
# templates but have a shared layout, you should make sure your
|
33
30
|
# layout contains a slash, similar to the example above.
|
34
31
|
module ViewSubdirs
|
32
|
+
# Load the render plugin before this plugin, since this plugin
|
33
|
+
# works by overriding a method in the render plugin.
|
34
|
+
def self.load_dependencies(app)
|
35
|
+
app.plugin :render
|
36
|
+
end
|
37
|
+
|
35
38
|
module InstanceMethods
|
36
39
|
# Set the view subdirectory to use. This can be set to nil
|
37
40
|
# to not use a view subdirectory.
|
@@ -44,12 +47,13 @@ class Roda
|
|
44
47
|
# Override the template name to use the view subdirectory if the
|
45
48
|
# there is a view subdirectory and the template name does not
|
46
49
|
# contain a slash.
|
47
|
-
def
|
48
|
-
|
49
|
-
if (v = @_view_subdir) &&
|
50
|
-
|
50
|
+
def template_name(opts)
|
51
|
+
name = super
|
52
|
+
if (v = @_view_subdir) && name !~ /\//
|
53
|
+
"#{v}/#{name}"
|
54
|
+
else
|
55
|
+
name
|
51
56
|
end
|
52
|
-
super
|
53
57
|
end
|
54
58
|
end
|
55
59
|
end
|
data/lib/roda/version.rb
CHANGED
data/spec/integration_spec.rb
CHANGED
@@ -35,6 +35,25 @@ describe "integration" do
|
|
35
35
|
body('/hello').should == 'D First Second Block'
|
36
36
|
end
|
37
37
|
|
38
|
+
it "should clear middleware when clear_middleware! is called" do
|
39
|
+
c = @c
|
40
|
+
app(:bare) do
|
41
|
+
use c, "First", "Second" do
|
42
|
+
"Block"
|
43
|
+
end
|
44
|
+
|
45
|
+
route do |r|
|
46
|
+
r.get "hello" do
|
47
|
+
"D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
clear_middleware!
|
52
|
+
end
|
53
|
+
|
54
|
+
body('/hello').should == 'D '
|
55
|
+
end
|
56
|
+
|
38
57
|
it "should support adding middleware using use after route block setup" do
|
39
58
|
c = @c
|
40
59
|
app(:bare) do
|
@@ -64,6 +83,20 @@ describe "integration" do
|
|
64
83
|
body('/hello').should == 'D 1 2 3'
|
65
84
|
end
|
66
85
|
|
86
|
+
it "should not inherit middleware in subclass if inhert_middleware = false" do
|
87
|
+
c = @c
|
88
|
+
app(:bare){use(c, '1', '2'){"3"}}
|
89
|
+
@app.inherit_middleware = false
|
90
|
+
@app = Class.new(@app)
|
91
|
+
@app.route do |r|
|
92
|
+
r.get "hello" do
|
93
|
+
"D #{r.env['m.first']} #{r.env['m.second']} #{r.env['m.block']}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
body('/hello').should == 'D '
|
98
|
+
end
|
99
|
+
|
67
100
|
it "should inherit route in subclass" do
|
68
101
|
c = @c
|
69
102
|
app(:bare) do
|
@@ -16,23 +16,29 @@ describe "backtracking_array plugin" do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
19
|
+
tests = lambda do
|
20
|
+
status.should == 404
|
21
|
+
|
22
|
+
body("/a").should == 'a'
|
23
|
+
body("/a/b").should == 'a/b'
|
24
|
+
status("/a/b/").should == 404
|
25
|
+
|
26
|
+
body("/c/d").should == 'c-d'
|
27
|
+
body("/c/e").should == 'c-e'
|
28
|
+
body("/c/d/d").should == 'c/d-d'
|
29
|
+
body("/c/d/e").should == 'c/d-e'
|
30
|
+
status("/c/d/").should == 404
|
31
|
+
|
32
|
+
body("/f").should == 'f'
|
33
|
+
body("/f/g").should == 'f/g'
|
34
|
+
body("/g").should == 'g'
|
35
|
+
body("/g/h").should == 'g/h'
|
36
|
+
status("/f/g/").should == 404
|
37
|
+
status("/g/h/").should == 404
|
38
|
+
end
|
39
|
+
|
40
|
+
tests.call
|
41
|
+
app.plugin(:static_path_info)
|
42
|
+
tests.call
|
37
43
|
end
|
38
44
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "class_level_routing plugin" do
|
4
|
+
before do
|
5
|
+
app(:bare) do
|
6
|
+
plugin :class_level_routing
|
7
|
+
plugin :all_verbs
|
8
|
+
|
9
|
+
root do
|
10
|
+
'root'
|
11
|
+
end
|
12
|
+
|
13
|
+
on "foo" do
|
14
|
+
request.get "bar" do
|
15
|
+
"foobar"
|
16
|
+
end
|
17
|
+
|
18
|
+
"foo"
|
19
|
+
end
|
20
|
+
|
21
|
+
is "d:d" do |x|
|
22
|
+
request.get do
|
23
|
+
"bazget#{x}"
|
24
|
+
end
|
25
|
+
|
26
|
+
request.post do
|
27
|
+
"bazpost#{x}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
meths = %w'get post delete head options patch put trace'
|
32
|
+
meths.concat(%w'link unlink') if ::Rack::Request.method_defined?("link?")
|
33
|
+
meths.each do |meth|
|
34
|
+
send(meth, :d) do |m|
|
35
|
+
"x-#{meth}-#{m}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "adds class methods for setting up routes" do
|
42
|
+
body.should == 'root'
|
43
|
+
body('/foo').should == 'foo'
|
44
|
+
body('/foo/bar').should == 'foobar'
|
45
|
+
body('/dgo').should == 'bazgetgo'
|
46
|
+
body('/dgo', 'REQUEST_METHOD'=>'POST').should == 'bazpostgo'
|
47
|
+
body('/bar').should == "x-get-bar"
|
48
|
+
body('/bar', 'REQUEST_METHOD'=>'POST').should == "x-post-bar"
|
49
|
+
body('/bar', 'REQUEST_METHOD'=>'DELETE').should == "x-delete-bar"
|
50
|
+
body('/bar', 'REQUEST_METHOD'=>'HEAD').should == "x-head-bar"
|
51
|
+
body('/bar', 'REQUEST_METHOD'=>'OPTIONS').should == "x-options-bar"
|
52
|
+
body('/bar', 'REQUEST_METHOD'=>'PATCH').should == "x-patch-bar"
|
53
|
+
body('/bar', 'REQUEST_METHOD'=>'PUT').should == "x-put-bar"
|
54
|
+
body('/bar', 'REQUEST_METHOD'=>'TRACE').should == "x-trace-bar"
|
55
|
+
if ::Rack::Request.method_defined?("link?")
|
56
|
+
body('/bar', 'REQUEST_METHOD'=>'LINK').should == "x-link-bar"
|
57
|
+
body('/bar', 'REQUEST_METHOD'=>'UNLINK').should == "x-unlink-bar"
|
58
|
+
end
|
59
|
+
|
60
|
+
status.should == 200
|
61
|
+
status("/asdfa/asdf").should == 404
|
62
|
+
|
63
|
+
@app = Class.new(app)
|
64
|
+
body.should == 'root'
|
65
|
+
body('/foo').should == 'foo'
|
66
|
+
body('/foo/bar').should == 'foobar'
|
67
|
+
body('/dgo').should == 'bazgetgo'
|
68
|
+
body('/dgo', 'REQUEST_METHOD'=>'POST').should == 'bazpostgo'
|
69
|
+
body('/bar').should == "x-get-bar"
|
70
|
+
body('/bar', 'REQUEST_METHOD'=>'POST').should == "x-post-bar"
|
71
|
+
body('/bar', 'REQUEST_METHOD'=>'DELETE').should == "x-delete-bar"
|
72
|
+
body('/bar', 'REQUEST_METHOD'=>'HEAD').should == "x-head-bar"
|
73
|
+
body('/bar', 'REQUEST_METHOD'=>'OPTIONS').should == "x-options-bar"
|
74
|
+
body('/bar', 'REQUEST_METHOD'=>'PATCH').should == "x-patch-bar"
|
75
|
+
body('/bar', 'REQUEST_METHOD'=>'PUT').should == "x-put-bar"
|
76
|
+
body('/bar', 'REQUEST_METHOD'=>'TRACE').should == "x-trace-bar"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "only calls class level routes if routing tree doesn't handle request" do
|
80
|
+
app.route do |r|
|
81
|
+
r.root do
|
82
|
+
'iroot'
|
83
|
+
end
|
84
|
+
|
85
|
+
r.get 'foo' do
|
86
|
+
'ifoo'
|
87
|
+
end
|
88
|
+
|
89
|
+
r.on 'bar' do
|
90
|
+
r.get true do
|
91
|
+
response.status = 404
|
92
|
+
''
|
93
|
+
end
|
94
|
+
r.post true do
|
95
|
+
'ibar'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
body.should == 'iroot'
|
101
|
+
body('/foo').should == 'ifoo'
|
102
|
+
body('/foo/bar').should == 'foobar'
|
103
|
+
body('/dgo').should == 'bazgetgo'
|
104
|
+
body('/dgo', 'REQUEST_METHOD'=>'POST').should == 'bazpostgo'
|
105
|
+
body('/bar').should == ""
|
106
|
+
body('/bar', 'REQUEST_METHOD'=>'POST').should == "ibar"
|
107
|
+
body('/bar', 'REQUEST_METHOD'=>'DELETE').should == "x-delete-bar"
|
108
|
+
body('/bar', 'REQUEST_METHOD'=>'HEAD').should == "x-head-bar"
|
109
|
+
body('/bar', 'REQUEST_METHOD'=>'OPTIONS').should == "x-options-bar"
|
110
|
+
body('/bar', 'REQUEST_METHOD'=>'PATCH').should == "x-patch-bar"
|
111
|
+
body('/bar', 'REQUEST_METHOD'=>'PUT').should == "x-put-bar"
|
112
|
+
body('/bar', 'REQUEST_METHOD'=>'TRACE').should == "x-trace-bar"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works with the not_found plugin if loaded before" do
|
116
|
+
app.plugin :not_found do
|
117
|
+
"nf"
|
118
|
+
end
|
119
|
+
|
120
|
+
body.should == 'root'
|
121
|
+
body('/foo').should == 'foo'
|
122
|
+
body('/foo/bar').should == 'foobar'
|
123
|
+
body('/dgo').should == 'bazgetgo'
|
124
|
+
body('/dgo', 'REQUEST_METHOD'=>'POST').should == 'bazpostgo'
|
125
|
+
body('/bar').should == "x-get-bar"
|
126
|
+
body('/bar', 'REQUEST_METHOD'=>'POST').should == "x-post-bar"
|
127
|
+
body('/bar', 'REQUEST_METHOD'=>'DELETE').should == "x-delete-bar"
|
128
|
+
body('/bar', 'REQUEST_METHOD'=>'HEAD').should == "x-head-bar"
|
129
|
+
body('/bar', 'REQUEST_METHOD'=>'OPTIONS').should == "x-options-bar"
|
130
|
+
body('/bar', 'REQUEST_METHOD'=>'PATCH').should == "x-patch-bar"
|
131
|
+
body('/bar', 'REQUEST_METHOD'=>'PUT').should == "x-put-bar"
|
132
|
+
body('/bar', 'REQUEST_METHOD'=>'TRACE').should == "x-trace-bar"
|
133
|
+
|
134
|
+
status.should == 200
|
135
|
+
status("/asdfa/asdf").should == 404
|
136
|
+
body("/asdfa/asdf").should == "nf"
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "delay_build plugin" do
|
4
|
+
it "does not build rack app until app is called" do
|
5
|
+
app(:delay_build){"a"}
|
6
|
+
app.instance_variable_get(:@app).should == nil
|
7
|
+
body.should == "a"
|
8
|
+
app.instance_variable_get(:@app).should_not == nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it "only rebuilds the app if build! is called" do
|
12
|
+
app(:delay_build){"a"}
|
13
|
+
body.should == "a"
|
14
|
+
c = Class.new do
|
15
|
+
def initialize(_) end
|
16
|
+
def call(_) [200, {}, ["b"]] end
|
17
|
+
end
|
18
|
+
app.use c
|
19
|
+
body.should == "a"
|
20
|
+
app.build!
|
21
|
+
body.should == "b"
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "delegate plugin" do
|
4
|
+
it "adds request_delegate and response_delegate class methods for delegating" do
|
5
|
+
app(:bare) do
|
6
|
+
plugin :delegate
|
7
|
+
request_delegate :root
|
8
|
+
response_delegate :headers
|
9
|
+
|
10
|
+
route do
|
11
|
+
root do
|
12
|
+
headers['Content-Type'] = 'foo'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
header('Content-Type').should == 'foo'
|
18
|
+
status('/foo').should == 404
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "drop_body plugin" do
|
4
|
+
it "automatically drops body and Content-Type/Content-Length headers for responses without a body" do
|
5
|
+
app(:drop_body) do |r|
|
6
|
+
response.status = r.path.to_i
|
7
|
+
response.write('a')
|
8
|
+
end
|
9
|
+
|
10
|
+
[101, 102, 204, 205, 304].each do |i|
|
11
|
+
body(i.to_s).should == ''
|
12
|
+
header('Content-Type', i.to_s).should == nil
|
13
|
+
header('Content-Length', i.to_s).should == nil
|
14
|
+
end
|
15
|
+
|
16
|
+
body('200').should == 'a'
|
17
|
+
header('Content-Type', '200').should == 'text/html'
|
18
|
+
header('Content-Length', '200').should == '1'
|
19
|
+
end
|
20
|
+
end
|