roda-cj 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +26 -0
- data/README.rdoc +46 -12
- data/lib/roda.rb +183 -151
- data/lib/roda/plugins/all_verbs.rb +1 -1
- data/lib/roda/plugins/backtracking_array.rb +91 -0
- data/lib/roda/plugins/csrf.rb +60 -0
- data/lib/roda/plugins/halt.rb +2 -2
- data/lib/roda/plugins/json.rb +84 -0
- data/lib/roda/plugins/pass.rb +1 -1
- data/lib/roda/plugins/per_thread_caching.rb +70 -0
- data/lib/roda/plugins/render.rb +8 -35
- data/lib/roda/plugins/symbol_matchers.rb +75 -0
- data/lib/roda/plugins/symbol_views.rb +40 -0
- data/lib/roda/plugins/view_subdirs.rb +53 -0
- data/lib/roda/version.rb +1 -1
- data/spec/matchers_spec.rb +42 -0
- data/spec/plugin/backtracking_array_spec.rb +38 -0
- data/spec/plugin/csrf_spec.rb +49 -0
- data/spec/plugin/json_spec.rb +50 -0
- data/spec/plugin/pass_spec.rb +1 -1
- data/spec/plugin/per_thread_caching_spec.rb +28 -0
- data/spec/plugin/render_spec.rb +2 -1
- data/spec/plugin/symbol_matchers_spec.rb +62 -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/request_spec.rb +9 -0
- metadata +30 -2
@@ -0,0 +1,40 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The symbol_views plugin allows matching blocks to return
|
4
|
+
# symbols, and consider those symbols as views to use for the
|
5
|
+
# response body. So you can take code like:
|
6
|
+
#
|
7
|
+
# r.root do
|
8
|
+
# view :index
|
9
|
+
# end
|
10
|
+
# r.is "foo" do
|
11
|
+
# view :foo
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# and DRY it up:
|
15
|
+
#
|
16
|
+
# r.root do
|
17
|
+
# :index
|
18
|
+
# end
|
19
|
+
# r.is "foo" do
|
20
|
+
# :foo
|
21
|
+
# end
|
22
|
+
module SymbolViews
|
23
|
+
module RequestMethods
|
24
|
+
private
|
25
|
+
|
26
|
+
# If the block result is a symbol, consider the symbol a
|
27
|
+
# template name and use the template view as the body.
|
28
|
+
def block_result_body(result)
|
29
|
+
if result.is_a?(Symbol)
|
30
|
+
scope.view(result)
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
register_plugin(:symbol_views, SymbolViews)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The view_subdirs plugin is designed for sites that have
|
4
|
+
# outgrown a flat view directory and use subdirectories
|
5
|
+
# for views. It allows you to set the view directory to
|
6
|
+
# use, and template names that do not contain a slash will
|
7
|
+
# automatically use that view subdirectory. Example:
|
8
|
+
#
|
9
|
+
# plugin :render
|
10
|
+
# plugin :view_subdirs
|
11
|
+
#
|
12
|
+
# route do |r|
|
13
|
+
# r.on "users" do
|
14
|
+
# set_view_subdir 'users'
|
15
|
+
#
|
16
|
+
# r.get :id do
|
17
|
+
# view 'profile' # uses ./views/users/profile.erb
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# r.get 'list' do
|
21
|
+
# view 'lists/users' # uses ./views/lists/users.erb
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# This plugin should be loaded after the render plugin, since
|
27
|
+
# it works by overriding parts of the render plugin.
|
28
|
+
module ViewSubdirs
|
29
|
+
module InstanceMethods
|
30
|
+
# Set the view subdirectory to use. This can be set to nil
|
31
|
+
# to not use a view subdirectory.
|
32
|
+
def set_view_subdir(v)
|
33
|
+
@_view_subdir = v
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Override the template name to use the view subdirectory if the
|
39
|
+
# there is a view subdirectory and the template name does not
|
40
|
+
# contain a slash.
|
41
|
+
def template_path(template, opts)
|
42
|
+
t = template.to_s
|
43
|
+
if (v = @_view_subdir) && t !~ /\//
|
44
|
+
template = "#{v}/#{t}"
|
45
|
+
end
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
register_plugin(:view_subdirs, ViewSubdirs)
|
52
|
+
end
|
53
|
+
end
|
data/lib/roda/version.rb
CHANGED
data/spec/matchers_spec.rb
CHANGED
@@ -639,6 +639,22 @@ describe "request verb methods" do
|
|
639
639
|
end
|
640
640
|
end
|
641
641
|
|
642
|
+
describe "all matcher" do
|
643
|
+
it "should match only all all arguments match" do
|
644
|
+
app do |r|
|
645
|
+
r.is :all=>['foo', :y] do |file|
|
646
|
+
file
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
body("/foo/bar").should == 'bar'
|
651
|
+
status.should == 404
|
652
|
+
status("/foo").should == 404
|
653
|
+
status("/foo/").should == 404
|
654
|
+
status("/foo/bar/baz").should == 404
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
642
658
|
describe "extension matcher" do
|
643
659
|
it "should match given file extensions" do
|
644
660
|
app do |r|
|
@@ -681,3 +697,29 @@ describe "route block that returns string" do
|
|
681
697
|
body.should == '+1'
|
682
698
|
end
|
683
699
|
end
|
700
|
+
|
701
|
+
describe "hash_matcher" do
|
702
|
+
it "should enable the handling of arbitrary hash keys" do
|
703
|
+
app(:bare) do
|
704
|
+
hash_matcher(:foos){|v| consume(self.class.cached_matcher(:"foos-#{v}"){/((?:foo){#{v}})/})}
|
705
|
+
route do |r|
|
706
|
+
r.is :foos=>1 do |f|
|
707
|
+
"1#{f}"
|
708
|
+
end
|
709
|
+
r.is :foos=>2 do |f|
|
710
|
+
"2#{f}"
|
711
|
+
end
|
712
|
+
r.is :foos=>3 do |f|
|
713
|
+
"3#{f}"
|
714
|
+
end
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
body("/foo").should == '1foo'
|
719
|
+
body("/foofoo").should == '2foofoo'
|
720
|
+
body("/foofoofoo").should == '3foofoofoo'
|
721
|
+
status("/foofoofoofoo").should == 404
|
722
|
+
status.should == 404
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
@@ -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,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
|
@@ -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
|
data/spec/plugin/pass_spec.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "per_thread_caching plugin" do
|
4
|
+
it "should use a per thread cache instead of a shared cache" do
|
5
|
+
app(:bare) do
|
6
|
+
plugin :per_thread_caching
|
7
|
+
@c = thread_safe_cache
|
8
|
+
def self.c; @c end
|
9
|
+
route do |r|
|
10
|
+
r.on :id do |i|
|
11
|
+
((self.class.c[i] ||= []) << 2).join
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
(0..10).map do |n|
|
17
|
+
Thread.new do
|
18
|
+
Thread.current[:n] = n
|
19
|
+
body('/a').should == '2'
|
20
|
+
body('/a').should == '22'
|
21
|
+
body('/a').should == '222'
|
22
|
+
body('/b').should == '2'
|
23
|
+
body('/b').should == '22'
|
24
|
+
body('/b').should == '222'
|
25
|
+
end
|
26
|
+
end.map{|t| t.join}
|
27
|
+
end
|
28
|
+
end
|
data/spec/plugin/render_spec.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "symbol_matchers plugin" do
|
4
|
+
it "allows symbol specific regexps" do
|
5
|
+
app(:bare) do
|
6
|
+
plugin :symbol_matchers
|
7
|
+
symbol_matcher(:f, /(f+)/)
|
8
|
+
|
9
|
+
route do |r|
|
10
|
+
r.is :d do |d|
|
11
|
+
"d#{d}"
|
12
|
+
end
|
13
|
+
|
14
|
+
r.is "foo:optd" do |o|
|
15
|
+
"foo#{o.inspect}"
|
16
|
+
end
|
17
|
+
|
18
|
+
r.is "bar:opt" do |o|
|
19
|
+
"bar#{o.inspect}"
|
20
|
+
end
|
21
|
+
|
22
|
+
r.is "format:format" do |f|
|
23
|
+
"format#{f.inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
r.is :f do |f|
|
27
|
+
"f#{f}"
|
28
|
+
end
|
29
|
+
|
30
|
+
r.is :w do |w|
|
31
|
+
"w#{w}"
|
32
|
+
end
|
33
|
+
|
34
|
+
r.is ':d/:w/:f' do |d, w, f|
|
35
|
+
"dwf#{d}#{w}#{f}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
status.should == 404
|
41
|
+
body("/1").should == 'd1'
|
42
|
+
body("/11232135").should == 'd11232135'
|
43
|
+
body("/a").should == 'wa'
|
44
|
+
body("/1az0").should == 'w1az0'
|
45
|
+
body("/f").should == 'ff'
|
46
|
+
body("/foo").should == 'foonil'
|
47
|
+
body("/foo/123").should == 'foo"123"'
|
48
|
+
status("/foo/bar").should == 404
|
49
|
+
status("/foo/123/a").should == 404
|
50
|
+
body("/bar").should == 'barnil'
|
51
|
+
body("/bar/foo").should == 'bar"foo"'
|
52
|
+
status("/bar/foo/baz").should == 404
|
53
|
+
body("/format").should == 'formatnil'
|
54
|
+
body("/format.json").should == 'format"json"'
|
55
|
+
status("/format.").should == 404
|
56
|
+
body("/ffffffffffffffff").should == 'fffffffffffffffff'
|
57
|
+
status("/-").should == 404
|
58
|
+
body("/1/1a/f").should == 'dwf11af'
|
59
|
+
body("/12/1azy/fffff").should == 'dwf121azyfffff'
|
60
|
+
status("/1/f/a").should == 404
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "symbol_views plugin" do
|
4
|
+
before do
|
5
|
+
app(:bare) do
|
6
|
+
plugin :symbol_views
|
7
|
+
|
8
|
+
def view(s)
|
9
|
+
"v#{s}"
|
10
|
+
end
|
11
|
+
|
12
|
+
route do |r|
|
13
|
+
r.root do
|
14
|
+
:sym
|
15
|
+
end
|
16
|
+
|
17
|
+
r.is "string" do
|
18
|
+
'string'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should call view with the symbol" do
|
25
|
+
body.should == "vsym"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not affect other return types" do
|
29
|
+
body("/string").should == 'string'
|
30
|
+
body("/foo").should == ''
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,45 @@
|
|
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 view_subdirs plugin test"
|
7
|
+
else
|
8
|
+
describe "view_subdirs plugin" do
|
9
|
+
before do
|
10
|
+
app(:bare) do
|
11
|
+
plugin :render
|
12
|
+
render_opts[:views] = "./spec"
|
13
|
+
plugin :view_subdirs
|
14
|
+
|
15
|
+
route do |r|
|
16
|
+
r.on "home" do
|
17
|
+
set_view_subdir 'views'
|
18
|
+
view("home", :locals=>{:name => "Agent Smith", :title => "Home"}, :layout_opts=>{:locals=>{:title=>"Home"}})
|
19
|
+
end
|
20
|
+
|
21
|
+
r.on "about" do
|
22
|
+
set_view_subdir 'views'
|
23
|
+
render("views/about", :locals=>{:title => "About Roda"})
|
24
|
+
end
|
25
|
+
|
26
|
+
r.on "path" do
|
27
|
+
render('views/about', :locals=>{:title => "Path"}, :layout_opts=>{:locals=>{:title=>"Home"}})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should use set subdir if template name does not contain a slash" do
|
34
|
+
body("/home").strip.should == "<title>Roda: Home</title>\n<h1>Home</h1>\n<p>Hello Agent Smith</p>"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not use set subdir if template name contains a slash" do
|
38
|
+
body("/about").strip.should == "<h1>About Roda</h1>"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not change behavior when subdir is not set" do
|
42
|
+
body("/path").strip.should == "<h1>Path</h1>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|