roda-cj 0.9.2 → 0.9.3
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 +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
|