roda 0.9.0 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG +62 -0
- data/README.rdoc +362 -167
- data/Rakefile +2 -2
- data/doc/release_notes/1.0.0.txt +329 -0
- data/lib/roda.rb +553 -180
- data/lib/roda/plugins/_erubis_escaping.rb +28 -0
- data/lib/roda/plugins/all_verbs.rb +7 -9
- data/lib/roda/plugins/backtracking_array.rb +92 -0
- data/lib/roda/plugins/content_for.rb +46 -0
- data/lib/roda/plugins/csrf.rb +60 -0
- data/lib/roda/plugins/flash.rb +53 -7
- data/lib/roda/plugins/halt.rb +8 -14
- data/lib/roda/plugins/head.rb +56 -0
- data/lib/roda/plugins/header_matchers.rb +2 -2
- data/lib/roda/plugins/json.rb +84 -0
- data/lib/roda/plugins/multi_route.rb +50 -10
- data/lib/roda/plugins/not_allowed.rb +140 -0
- data/lib/roda/plugins/pass.rb +13 -6
- data/lib/roda/plugins/per_thread_caching.rb +70 -0
- data/lib/roda/plugins/render.rb +20 -33
- data/lib/roda/plugins/render_each.rb +61 -0
- data/lib/roda/plugins/symbol_matchers.rb +79 -0
- data/lib/roda/plugins/symbol_views.rb +40 -0
- data/lib/roda/plugins/view_subdirs.rb +53 -0
- data/lib/roda/version.rb +3 -0
- data/spec/matchers_spec.rb +61 -5
- data/spec/plugin/_erubis_escaping_spec.rb +29 -0
- data/spec/plugin/backtracking_array_spec.rb +38 -0
- data/spec/plugin/content_for_spec.rb +34 -0
- data/spec/plugin/csrf_spec.rb +49 -0
- data/spec/plugin/flash_spec.rb +69 -5
- data/spec/plugin/head_spec.rb +35 -0
- data/spec/plugin/json_spec.rb +50 -0
- data/spec/plugin/multi_route_spec.rb +22 -6
- data/spec/plugin/not_allowed_spec.rb +55 -0
- data/spec/plugin/pass_spec.rb +8 -2
- data/spec/plugin/per_thread_caching_spec.rb +28 -0
- data/spec/plugin/render_each_spec.rb +30 -0
- data/spec/plugin/render_spec.rb +7 -1
- data/spec/plugin/symbol_matchers_spec.rb +68 -0
- data/spec/plugin/symbol_views_spec.rb +32 -0
- data/spec/plugin/view_subdirs_spec.rb +45 -0
- data/spec/plugin_spec.rb +11 -1
- data/spec/redirect_spec.rb +21 -4
- data/spec/request_spec.rb +9 -0
- metadata +49 -5
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "backtracking_array plugin" do
|
4
|
+
it "backtracks to next entry in array if later matcher fails" do
|
5
|
+
app(:backtracking_array) do |r|
|
6
|
+
r.is %w'a a/b' do |id|
|
7
|
+
id
|
8
|
+
end
|
9
|
+
|
10
|
+
r.is %w'c c/d', %w'd e' do |a, b|
|
11
|
+
"#{a}-#{b}"
|
12
|
+
end
|
13
|
+
|
14
|
+
r.is [%w'f f/g', %w'g g/h'] do |id|
|
15
|
+
id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
status.should == 404
|
20
|
+
|
21
|
+
body("/a").should == 'a'
|
22
|
+
body("/a/b").should == 'a/b'
|
23
|
+
status("/a/b/").should == 404
|
24
|
+
|
25
|
+
body("/c/d").should == 'c-d'
|
26
|
+
body("/c/e").should == 'c-e'
|
27
|
+
body("/c/d/d").should == 'c/d-d'
|
28
|
+
body("/c/d/e").should == 'c/d-e'
|
29
|
+
status("/c/d/").should == 404
|
30
|
+
|
31
|
+
body("/f").should == 'f'
|
32
|
+
body("/f/g").should == 'f/g'
|
33
|
+
body("/g").should == 'g'
|
34
|
+
body("/g/h").should == 'g/h'
|
35
|
+
status("/f/g/").should == 404
|
36
|
+
status("/g/h/").should == 404
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'tilt/erb'
|
5
|
+
rescue LoadError
|
6
|
+
warn "tilt not installed, skipping content_for plugin test"
|
7
|
+
else
|
8
|
+
describe "content_for plugin" do
|
9
|
+
before do
|
10
|
+
app(:bare) do
|
11
|
+
plugin :render
|
12
|
+
render_opts[:views] = "./spec/views"
|
13
|
+
plugin :content_for
|
14
|
+
|
15
|
+
route do |r|
|
16
|
+
r.root do
|
17
|
+
view(:inline=>"<% content_for :foo do %>foo<% end %>bar", :layout=>{:inline=>'<%= yield %> <%= content_for(:foo) %>'})
|
18
|
+
end
|
19
|
+
r.get 'a' do
|
20
|
+
view(:inline=>"bar", :layout=>{:inline=>'<%= content_for(:foo) %> <%= yield %>'})
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be able to set content in template and get that content in the layout" do
|
27
|
+
body.strip.should == "bar foo"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should work if content is not set by the template" do
|
31
|
+
body('/a').strip.should == "bar"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rack/csrf'
|
5
|
+
rescue LoadError
|
6
|
+
warn "rack_csrf not installed, skipping csrf plugin test"
|
7
|
+
else
|
8
|
+
describe "csrf plugin" do
|
9
|
+
it "adds csrf protection and csrf helper methods" do
|
10
|
+
app(:bare) do
|
11
|
+
use Rack::Session::Cookie, :secret=>'1'
|
12
|
+
plugin :csrf, :skip=>['POST:/foo']
|
13
|
+
|
14
|
+
route do |r|
|
15
|
+
r.get do
|
16
|
+
response['TAG'] = csrf_tag
|
17
|
+
response['METATAG'] = csrf_metatag
|
18
|
+
response['TOKEN'] = csrf_token
|
19
|
+
response['FIELD'] = csrf_field
|
20
|
+
response['HEADER'] = csrf_header
|
21
|
+
'g'
|
22
|
+
end
|
23
|
+
r.post 'foo' do
|
24
|
+
'bar'
|
25
|
+
end
|
26
|
+
r.post do
|
27
|
+
'p'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
io = StringIO.new
|
33
|
+
status('REQUEST_METHOD'=>'POST', 'rack.input'=>io).should == 403
|
34
|
+
body('/foo', 'REQUEST_METHOD'=>'POST', 'rack.input'=>io).should == 'bar'
|
35
|
+
|
36
|
+
env = proc{|h| h['Set-Cookie'] ? {'HTTP_COOKIE'=>h['Set-Cookie'].sub("; path=/; HttpOnly", '')} : {}}
|
37
|
+
s, h, b = req
|
38
|
+
s.should == 200
|
39
|
+
field = h['FIELD']
|
40
|
+
token = Regexp.escape(h['TOKEN'])
|
41
|
+
h['TAG'].should =~ /\A<input type="hidden" name="#{field}" value="#{token}" \/>\z/
|
42
|
+
h['METATAG'].should =~ /\A<meta name="#{field}" content="#{token}" \/>\z/
|
43
|
+
b.should == ['g']
|
44
|
+
s, _, b = req('/', env[h].merge('REQUEST_METHOD'=>'POST', 'rack.input'=>io, "HTTP_#{h['HEADER']}"=>h['TOKEN']))
|
45
|
+
s.should == 200
|
46
|
+
b.should == ['p']
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/spec/plugin/flash_spec.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
2
|
|
3
|
-
begin
|
4
|
-
require 'sinatra/flash/hash'
|
5
|
-
rescue LoadError
|
6
|
-
warn "sinatra-flash not installed, skipping flash plugin test"
|
7
|
-
else
|
8
3
|
describe "flash plugin" do
|
9
4
|
it "flash.now[] sets flash for current page" do
|
10
5
|
app(:bare) do
|
@@ -56,4 +51,73 @@ describe "flash plugin" do
|
|
56
51
|
b.join.should == 'bb'
|
57
52
|
end
|
58
53
|
end
|
54
|
+
|
55
|
+
describe "FlashHash" do
|
56
|
+
before do
|
57
|
+
@h = Roda::RodaPlugins::Flash::FlashHash.new
|
58
|
+
end
|
59
|
+
|
60
|
+
it ".new should accept nil for empty hash" do
|
61
|
+
@h = Roda::RodaPlugins::Flash::FlashHash.new(nil)
|
62
|
+
@h.now.should == {}
|
63
|
+
@h.next.should == {}
|
64
|
+
end
|
65
|
+
|
66
|
+
it ".new should accept a hash" do
|
67
|
+
@h = Roda::RodaPlugins::Flash::FlashHash.new(1=>2)
|
68
|
+
@h.now.should == {1=>2}
|
69
|
+
@h.next.should == {}
|
70
|
+
end
|
71
|
+
|
72
|
+
it "#[]= assigns to next flash" do
|
73
|
+
@h[1] = 2
|
74
|
+
@h.now.should == {}
|
75
|
+
@h.next.should == {1=>2}
|
76
|
+
end
|
77
|
+
|
78
|
+
it "#discard removes given key from next hash" do
|
79
|
+
@h[1] = 2
|
80
|
+
@h[nil] = 3
|
81
|
+
@h.next.should == {1=>2, nil=>3}
|
82
|
+
@h.discard(nil)
|
83
|
+
@h.next.should == {1=>2}
|
84
|
+
@h.discard(1)
|
85
|
+
@h.next.should == {}
|
86
|
+
end
|
87
|
+
|
88
|
+
it "#discard removes all entries from next hash with no arguments" do
|
89
|
+
@h[1] = 2
|
90
|
+
@h[nil] = 3
|
91
|
+
@h.next.should == {1=>2, nil=>3}
|
92
|
+
@h.discard
|
93
|
+
@h.next.should == {}
|
94
|
+
end
|
95
|
+
|
96
|
+
it "#keep copies entry for key from current hash to next hash" do
|
97
|
+
@h.now[1] = 2
|
98
|
+
@h.now[nil] = 3
|
99
|
+
@h.next.should == {}
|
100
|
+
@h.keep(nil)
|
101
|
+
@h.next.should == {nil=>3}
|
102
|
+
@h.keep(1)
|
103
|
+
@h.next.should == {1=>2, nil=>3}
|
104
|
+
end
|
105
|
+
|
106
|
+
it "#keep copies all entries from current hash to next hash" do
|
107
|
+
@h.now[1] = 2
|
108
|
+
@h.now[nil] = 3
|
109
|
+
@h.next.should == {}
|
110
|
+
@h.keep
|
111
|
+
@h.next.should == {1=>2, nil=>3}
|
112
|
+
end
|
113
|
+
|
114
|
+
it "#sweep replaces current hash with next hash" do
|
115
|
+
@h[1] = 2
|
116
|
+
@h[nil] = 3
|
117
|
+
@h.next.should == {1=>2, nil=>3}
|
118
|
+
@h.now.should == {}
|
119
|
+
@h.sweep.should == {1=>2, nil=>3}
|
120
|
+
@h.next.should == {}
|
121
|
+
@h.now.should == {1=>2, nil=>3}
|
122
|
+
end
|
59
123
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "head plugin" do
|
4
|
+
it "considers HEAD requests as GET requests which return no body" do
|
5
|
+
app(:head) do |r|
|
6
|
+
r.root do
|
7
|
+
'root'
|
8
|
+
end
|
9
|
+
|
10
|
+
r.get 'a' do
|
11
|
+
'a'
|
12
|
+
end
|
13
|
+
|
14
|
+
r.is 'b', :method=>[:get, :post] do
|
15
|
+
'b'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
s, h, b = req
|
20
|
+
s.should == 200
|
21
|
+
h['Content-Length'].should == '4'
|
22
|
+
b.should == ['root']
|
23
|
+
|
24
|
+
s, h, b = req('REQUEST_METHOD' => 'HEAD')
|
25
|
+
s.should == 200
|
26
|
+
h['Content-Length'].should == '4'
|
27
|
+
b.should == []
|
28
|
+
|
29
|
+
body('/a').should == 'a'
|
30
|
+
status('/a', 'REQUEST_METHOD' => 'HEAD').should == 200
|
31
|
+
|
32
|
+
body('/b').should == 'b'
|
33
|
+
status('/b', 'REQUEST_METHOD' => 'HEAD').should == 200
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "json plugin" do
|
4
|
+
before do
|
5
|
+
c = Class.new do
|
6
|
+
def to_json
|
7
|
+
'[1]'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
app(:bare) do
|
12
|
+
plugin :json
|
13
|
+
json_result_classes << c
|
14
|
+
|
15
|
+
route do |r|
|
16
|
+
r.is 'array' do
|
17
|
+
[1, 2, 3]
|
18
|
+
end
|
19
|
+
|
20
|
+
r.is "hash" do
|
21
|
+
{'a'=>'b'}
|
22
|
+
end
|
23
|
+
|
24
|
+
r.is 'c' do
|
25
|
+
c.new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should use a json content type for a json response" do
|
32
|
+
header('Content-Type', "/array").should == 'application/json'
|
33
|
+
header('Content-Type', "/hash").should == 'application/json'
|
34
|
+
header('Content-Type', "/c").should == 'application/json'
|
35
|
+
header('Content-Type').should == 'text/html'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should convert objects to json" do
|
39
|
+
body('/array').gsub(/\s/, '').should == '[1,2,3]'
|
40
|
+
body('/hash').gsub(/\s/, '').should == '{"a":"b"}'
|
41
|
+
body('/c').should == '[1]'
|
42
|
+
body.should == ''
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should work when subclassing" do
|
46
|
+
@app = Class.new(app)
|
47
|
+
app.route{[1]}
|
48
|
+
body.should == '[1]'
|
49
|
+
end
|
50
|
+
end
|
@@ -5,7 +5,7 @@ describe "multi_route plugin" do
|
|
5
5
|
app(:bare) do
|
6
6
|
plugin :multi_route
|
7
7
|
|
8
|
-
route(
|
8
|
+
route("get") do |r|
|
9
9
|
r.is "" do
|
10
10
|
"get"
|
11
11
|
end
|
@@ -15,7 +15,7 @@ describe "multi_route plugin" do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
route(
|
18
|
+
route("post") do |r|
|
19
19
|
r.is "" do
|
20
20
|
"post"
|
21
21
|
end
|
@@ -26,15 +26,22 @@ describe "multi_route plugin" do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
route do |r|
|
29
|
+
r.on 'foo' do
|
30
|
+
r.multi_route do
|
31
|
+
"foo"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
29
35
|
r.get do
|
30
|
-
route(
|
36
|
+
r.route("get")
|
31
37
|
|
32
38
|
r.is "b" do
|
33
39
|
"getb"
|
34
40
|
end
|
35
41
|
end
|
42
|
+
|
36
43
|
r.post do
|
37
|
-
route(
|
44
|
+
r.route("post")
|
38
45
|
|
39
46
|
r.is "b" do
|
40
47
|
"postb"
|
@@ -55,6 +62,15 @@ describe "multi_route plugin" do
|
|
55
62
|
status('/c', 'REQUEST_METHOD'=>'POST').should == 404
|
56
63
|
end
|
57
64
|
|
65
|
+
it "uses multi_route to dispatch to any named route" do
|
66
|
+
status('/foo').should == 404
|
67
|
+
body('/foo/get/').should == 'get'
|
68
|
+
body('/foo/get/a').should == 'geta'
|
69
|
+
body('/foo/post/').should == 'post'
|
70
|
+
body('/foo/post/a').should == 'posta'
|
71
|
+
body('/foo/post/b').should == 'foo'
|
72
|
+
end
|
73
|
+
|
58
74
|
it "handles loading the plugin multiple times correctly" do
|
59
75
|
app.plugin :multi_route
|
60
76
|
body.should == 'get'
|
@@ -71,14 +87,14 @@ describe "multi_route plugin" do
|
|
71
87
|
@app = Class.new(@app)
|
72
88
|
@app.route do |r|
|
73
89
|
r.get do
|
74
|
-
route(
|
90
|
+
r.route("post")
|
75
91
|
|
76
92
|
r.is "b" do
|
77
93
|
"1b"
|
78
94
|
end
|
79
95
|
end
|
80
96
|
r.post do
|
81
|
-
route(
|
97
|
+
r.route("get")
|
82
98
|
|
83
99
|
r.is "b" do
|
84
100
|
"2b"
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "not_allowed plugin" do
|
4
|
+
it "skips the current block if pass is called" do
|
5
|
+
app(:not_allowed) do |r|
|
6
|
+
r.get '' do
|
7
|
+
'a'
|
8
|
+
end
|
9
|
+
|
10
|
+
r.is "c" do
|
11
|
+
r.get do
|
12
|
+
"cg"
|
13
|
+
end
|
14
|
+
|
15
|
+
r.post do
|
16
|
+
"cp"
|
17
|
+
end
|
18
|
+
|
19
|
+
"c"
|
20
|
+
end
|
21
|
+
|
22
|
+
r.get do
|
23
|
+
r.is 'b' do
|
24
|
+
'b'
|
25
|
+
end
|
26
|
+
r.is /(d)/ do |s|
|
27
|
+
s
|
28
|
+
end
|
29
|
+
r.get /(e)/ do |s|
|
30
|
+
s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
body.should == 'a'
|
36
|
+
status('REQUEST_METHOD'=>'POST').should == 405
|
37
|
+
header('Allow', 'REQUEST_METHOD'=>'POST').should == 'GET'
|
38
|
+
|
39
|
+
body('/b').should == 'b'
|
40
|
+
status('/b', 'REQUEST_METHOD'=>'POST').should == 404
|
41
|
+
|
42
|
+
body('/d').should == 'd'
|
43
|
+
status('/d', 'REQUEST_METHOD'=>'POST').should == 404
|
44
|
+
|
45
|
+
body('/e').should == 'e'
|
46
|
+
status('/d', 'REQUEST_METHOD'=>'POST').should == 404
|
47
|
+
|
48
|
+
body('/c').should == 'cg'
|
49
|
+
body('/c').should == 'cg'
|
50
|
+
body('/c', 'REQUEST_METHOD'=>'POST').should == 'cp'
|
51
|
+
body('/c', 'REQUEST_METHOD'=>'PATCH').should == 'c'
|
52
|
+
status('/c', 'REQUEST_METHOD'=>'PATCH').should == 405
|
53
|
+
header('Allow', '/c', 'REQUEST_METHOD'=>'PATCH').should == 'GET, POST'
|
54
|
+
end
|
55
|
+
end
|
data/spec/plugin/pass_spec.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
2
|
|
3
3
|
describe "pass plugin" do
|
4
|
-
it "
|
4
|
+
it "skips the current block if pass is called" do
|
5
5
|
app(:pass) do |r|
|
6
|
+
r.root do
|
7
|
+
r.pass if env['FOO']
|
8
|
+
'root'
|
9
|
+
end
|
10
|
+
|
6
11
|
r.on :id do |id|
|
7
12
|
r.pass if id == 'foo'
|
8
13
|
id
|
@@ -13,11 +18,12 @@ describe "pass plugin" do
|
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
21
|
+
body.should == 'root'
|
22
|
+
status('FOO'=>true).should == 404
|
16
23
|
body("/a").should == 'a'
|
17
24
|
body("/a/b").should == 'a'
|
18
25
|
body("/foo/a").should == 'fooa'
|
19
26
|
body("/foo/a/b").should == 'fooa'
|
20
27
|
status("/foo").should == 404
|
21
|
-
status.should == 404
|
22
28
|
end
|
23
29
|
end
|