roda-cj 0.9.3 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +13 -1
- data/README.rdoc +14 -9
- data/lib/roda/plugins/all_verbs.rb +6 -8
- data/lib/roda/plugins/backtracking_array.rb +1 -0
- data/lib/roda/plugins/content_for.rb +46 -0
- data/lib/roda/plugins/halt.rb +3 -9
- data/lib/roda/plugins/head.rb +56 -0
- data/lib/roda/plugins/header_matchers.rb +2 -2
- data/lib/roda/plugins/json.rb +1 -1
- data/lib/roda/plugins/not_allowed.rb +133 -0
- data/lib/roda/plugins/pass.rb +13 -6
- data/lib/roda/plugins/render_each.rb +61 -0
- data/lib/roda/plugins/symbol_matchers.rb +8 -4
- data/lib/roda/plugins/symbol_views.rb +1 -1
- data/lib/roda/version.rb +1 -1
- data/lib/roda.rb +80 -55
- data/spec/matchers_spec.rb +4 -15
- data/spec/plugin/content_for_spec.rb +34 -0
- data/spec/plugin/head_spec.rb +35 -0
- data/spec/plugin/not_allowed_spec.rb +42 -0
- data/spec/plugin/pass_spec.rb +7 -1
- data/spec/plugin/render_each_spec.rb +30 -0
- data/spec/plugin/symbol_matchers_spec.rb +6 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0381614904fadaf43af33f351a4edfc17d054c4d
|
4
|
+
data.tar.gz: a1f3866849f764552a2de2f987c636d20cf33099
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2930467e112ec168fd41c4f9005e21984b028ff45aa8fb58d843e4343bb098f888774c94b989b70ce0aea4f6f69e8eae2b2a62fc5c6626816f78eea9257fd58
|
7
|
+
data.tar.gz: 1f506448329172d622492c22167f39b8690ca812c50f734c3dec306fd537855b0d4767a8507c555471963a4f053e4b65874d277cc9d53e44fabe2471c3e9374e
|
data/CHANGELOG
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
= HEAD
|
2
2
|
|
3
|
+
* Add head plugin, for handling HEAD requests like GET requests with an empty body (jeremyevans)
|
4
|
+
|
5
|
+
* Optimize consuming patterns by using a positive lookahead assertion (jeremyevans)
|
6
|
+
|
7
|
+
* Add not_allowed plugin, for automatically returning 405 Method Not Allowed responses (jeremyevans)
|
8
|
+
|
9
|
+
* Optimize match blocks with no arguments (jeremyevans)
|
10
|
+
|
11
|
+
* Add content_for plugin, for storing content in one template and retrieving it in another (jeremyevans)
|
12
|
+
|
13
|
+
* Add render_each plugin, for rendering a template for each value in an enumerable (jeremyevans)
|
14
|
+
|
3
15
|
* Add backtracking_array plugin, allowing array matchers to backtrack if later matchers do not match (jeremyevans)
|
4
16
|
|
5
17
|
* Add :all hash matcher, allowing array matchers to include conditions where you want to match multiple conditions (jeremyevans)
|
@@ -28,7 +40,7 @@
|
|
28
40
|
|
29
41
|
* Optimize matching by caching consume regexp for strings, regexp, symbol, and :extension matchers (jeremyevans)
|
30
42
|
|
31
|
-
* Add r.root for
|
43
|
+
* Add r.root for GET / requests, for easier to read version of r.get "" (jeremyevans)
|
32
44
|
|
33
45
|
* Optimize r.is terminal matcher, remove :term hash matcher (jeremyevans)
|
34
46
|
|
data/README.rdoc
CHANGED
@@ -434,18 +434,17 @@ hash matcher:
|
|
434
434
|
== Root Method
|
435
435
|
|
436
436
|
As displayed above, you can also use +r.root+ as a match method. This
|
437
|
-
method matches
|
438
|
-
is similar to <tt>r.is ""</tt>, except that it does not
|
439
|
-
consume the +/+ from the path.
|
440
|
-
|
441
|
-
Unlike the other matching methods, +r.root+ does not take multiple
|
442
|
-
arguments and pass them to +r.on+. It only accepts an optional request
|
443
|
-
method symbol, so <tt>r.root :get</tt> is similar to <tt>r.get ""</tt>,
|
437
|
+
method matches <tt>GET /</tt> requests. +r.root+ is similar to <tt>r.get ""</tt>,
|
444
438
|
except that it does not consume the +/+ from the path.
|
445
439
|
|
440
|
+
Unlike the other matching methods, +r.root+ takes no arguments.
|
441
|
+
|
446
442
|
Note that +r.root+ does not match if the path is empty, you should use
|
447
|
-
|
448
|
-
the empty path or +/+, you can use <tt>r.
|
443
|
+
<tt>r.get true</tt> for that. If you want to match either the
|
444
|
+
the empty path or +/+, you can use <tt>r.get ["", true]</tt>.
|
445
|
+
|
446
|
+
Note that +r.root+ does not match non-GET requests, so to handle
|
447
|
+
<tt>POST /</tt> requests, use <tt>r.post ''</tt>.
|
449
448
|
|
450
449
|
== Request and Response
|
451
450
|
|
@@ -638,6 +637,8 @@ These plugins ship with roda:
|
|
638
637
|
all_verbs :: Adds routing methods to the request for all http verbs.
|
639
638
|
backtracking_array :: Allows array matchers to backtrack if later matchers
|
640
639
|
do not match.
|
640
|
+
content_for :: Allows storage of content in one template and retrieval of
|
641
|
+
that content in a different template.
|
641
642
|
csrf :: Adds CSRF protection and helper methods using
|
642
643
|
{rack_csrf}[https://github.com/baldowl/rack_csrf].
|
643
644
|
default_headers :: Override the default response headers used.
|
@@ -647,6 +648,7 @@ flash :: Adds a flash handler.
|
|
647
648
|
h :: Adds h method for html escaping.
|
648
649
|
halt :: Augments request#halt method to take status and/or body or status,
|
649
650
|
headers, and body.
|
651
|
+
head :: Treat HEAD requests like GET requests with an empty response body.
|
650
652
|
header_matchers :: Adds host, header, and accept hash matchers.
|
651
653
|
hooks :: Adds before and after methods to run code before and after requests.
|
652
654
|
indifferent_params :: Adds params method with indifferent access to params,
|
@@ -657,6 +659,8 @@ middleware :: Allows the Roda app to be used as a rack middleware, calling the
|
|
657
659
|
next middleware if no route matches.
|
658
660
|
multi_route :: Adds the ability for multiple named route blocks, with the
|
659
661
|
ability to dispatch to them add any point in the main route block.
|
662
|
+
not_allowed :: Adds support for automatically returning 405 Method Not Allowed
|
663
|
+
responses.
|
660
664
|
not_found :: Adds a +not_found+ block that is called for all 404 responses
|
661
665
|
without bodies.
|
662
666
|
pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
|
@@ -664,6 +668,7 @@ pass :: Adds a pass method allowing you to skip the current +r.on+ block as if
|
|
664
668
|
per_thread_caching :: Switches the thread-safe cache from a shared cache to a
|
665
669
|
per-thread cache.
|
666
670
|
render :: Adds support for rendering templates via tilt, as described above.
|
671
|
+
render_each :: Render a template for each value in an enumerable.
|
667
672
|
streaming :: Adds support for streaming responses.
|
668
673
|
symbol_matchers :: Adds support for symbol-specific matching regexps.
|
669
674
|
symbol_views :: Allows match blocks to return template name symbols, uses the
|
@@ -30,14 +30,12 @@ class Roda
|
|
30
30
|
# The verb methods are defined via metaprogramming, so there
|
31
31
|
# isn't documentation for the individual methods created.
|
32
32
|
module AllVerbs
|
33
|
-
|
34
|
-
%w'delete head options link patch put trace unlink'.each do |
|
35
|
-
if ::Rack::Request.method_defined?("#{
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
END
|
33
|
+
def self.configure(app)
|
34
|
+
%w'delete head options link patch put trace unlink'.each do |v|
|
35
|
+
if ::Rack::Request.method_defined?("#{v}?")
|
36
|
+
app.request_module do
|
37
|
+
app::RodaRequest.def_verb_method(self, v)
|
38
|
+
end
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The content_for plugin is designed to be used with the
|
4
|
+
# render plugin, allowing you to store content inside one
|
5
|
+
# template, and retrieve that content inside a separate
|
6
|
+
# template. Most commonly, this is so view templates
|
7
|
+
# can set content for the layout template to display outside
|
8
|
+
# of the normal content pane.
|
9
|
+
#
|
10
|
+
# The content_for template probably only works with erb
|
11
|
+
# templates, and requires that you don't override the
|
12
|
+
# +:outvar+ render option. In the template in which you
|
13
|
+
# want to store content, call content_for with a block:
|
14
|
+
#
|
15
|
+
# <% content_for :foo do %>
|
16
|
+
# Some content here.
|
17
|
+
# <% end %>
|
18
|
+
#
|
19
|
+
# In the template in which you want to retrieve content,
|
20
|
+
# call content_for without the block:
|
21
|
+
#
|
22
|
+
# <%= content_for :foo %>
|
23
|
+
module ContentFor
|
24
|
+
module InstanceMethods
|
25
|
+
# If called with a block, store content enclosed by block
|
26
|
+
# under the given key. If called without a block, retrieve
|
27
|
+
# stored content with the given key, or return nil if there
|
28
|
+
# is no content stored with that key.
|
29
|
+
def content_for(key, &block)
|
30
|
+
if block
|
31
|
+
@_content_for ||= {}
|
32
|
+
buf_was = @_out_buf
|
33
|
+
@_out_buf = ''
|
34
|
+
yield
|
35
|
+
@_content_for[key] = @_out_buf
|
36
|
+
@_out_buf = buf_was
|
37
|
+
elsif @_content_for
|
38
|
+
@_content_for[key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
register_plugin(:content_for, ContentFor)
|
45
|
+
end
|
46
|
+
end
|
data/lib/roda/plugins/halt.rb
CHANGED
@@ -1,18 +1,12 @@
|
|
1
1
|
class Roda
|
2
2
|
module RodaPlugins
|
3
|
-
# The halt plugin augments the standard request +halt+ method to
|
4
|
-
#
|
3
|
+
# The halt plugin augments the standard request +halt+ method to allow the response
|
4
|
+
# status, body, or headers to be changed when halting.
|
5
5
|
#
|
6
6
|
# After loading the halt plugin:
|
7
7
|
#
|
8
8
|
# plugin :halt
|
9
9
|
#
|
10
|
-
# You can call halt with no arguments to immediately stop processing:
|
11
|
-
#
|
12
|
-
# route do |r|
|
13
|
-
# r.halt
|
14
|
-
# end
|
15
|
-
#
|
16
10
|
# You can call the halt method with an integer to set the response status and return:
|
17
11
|
#
|
18
12
|
# route do |r|
|
@@ -38,7 +32,7 @@ class Roda
|
|
38
32
|
# end
|
39
33
|
#
|
40
34
|
# Note that there is a difference between provide status, headers, and body as separate
|
41
|
-
# arguments and providing them as a rack response array. With a rack response array,
|
35
|
+
# arguments and providing them as a single rack response array. With a rack response array,
|
42
36
|
# the values are used directly, while with 3 arguments, the headers given are merged into
|
43
37
|
# the existing headers and the given body is written to the existing response body.
|
44
38
|
module Halt
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The head plugin attempts to automatically handle HEAD requests,
|
4
|
+
# by treating them as GET requests and returning an empty body
|
5
|
+
# without modifying the response status or response headers.
|
6
|
+
#
|
7
|
+
# So for the following routes,
|
8
|
+
#
|
9
|
+
# route do |r|
|
10
|
+
# r.root do
|
11
|
+
# 'root'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# r.get 'a' do
|
15
|
+
# 'a'
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# r.is 'b', :method=>[:get, :post] do
|
19
|
+
# 'b'
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# HEAD requests for +/+, +/a+, and +/b+ will all return 200 status
|
24
|
+
# with an empty body.
|
25
|
+
module Head
|
26
|
+
module InstanceMethods
|
27
|
+
# Always use an empty response body for head requests, with a
|
28
|
+
# content length of 0.
|
29
|
+
def call(*)
|
30
|
+
res = super
|
31
|
+
if request.head?
|
32
|
+
res[2] = []
|
33
|
+
end
|
34
|
+
res
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module RequestMethods
|
39
|
+
# Consider HEAD requests as GET requests.
|
40
|
+
def is_get?
|
41
|
+
super || head?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# If the current request is a HEAD request, match if one of
|
47
|
+
# the given methods is a GET request.
|
48
|
+
def match_method(method)
|
49
|
+
super || (!method.is_a?(Array) && head? && method.to_s.upcase == 'GET')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
register_plugin(:head, Head)
|
55
|
+
end
|
56
|
+
end
|
@@ -35,14 +35,14 @@ class Roda
|
|
35
35
|
|
36
36
|
# Match if the given mimetype is one of the accepted mimetypes.
|
37
37
|
def match_accept(mimetype)
|
38
|
-
if env["HTTP_ACCEPT"].to_s.split(',').any?{|s| s.strip == mimetype}
|
38
|
+
if @env["HTTP_ACCEPT"].to_s.split(',').any?{|s| s.strip == mimetype}
|
39
39
|
response["Content-Type"] = mimetype
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
43
|
# Match if the given uppercase key is present inside the environment.
|
44
44
|
def match_header(key)
|
45
|
-
env[key.upcase.tr("-","_")]
|
45
|
+
@env[key.upcase.tr("-","_")]
|
46
46
|
end
|
47
47
|
|
48
48
|
# Match if the host of the request is the same as the hostname.
|
data/lib/roda/plugins/json.rb
CHANGED
@@ -2,7 +2,7 @@ require 'json'
|
|
2
2
|
|
3
3
|
class Roda
|
4
4
|
module RodaPlugins
|
5
|
-
# The json plugin allows
|
5
|
+
# The json plugin allows match blocks to return
|
6
6
|
# arrays or hashes, and have those arrays or hashes be
|
7
7
|
# converted to json which is used as the response body.
|
8
8
|
# It also sets the response content type to application/json.
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The not_allowed plugin makes Roda attempt to automatically
|
4
|
+
# support the 405 Method Not Allowed response status. The plugin
|
5
|
+
# changes the +r.get+ and +r.post+ verb methods to automatically
|
6
|
+
# return a 405 status if they are called with any arguments, and
|
7
|
+
# the arguments match but the request method does not match. So
|
8
|
+
# this code:
|
9
|
+
#
|
10
|
+
# r.get '' do
|
11
|
+
# "a"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# will return a 200 response for <tt>GET /</tt> and a 405
|
15
|
+
# response for <tt>POST /</tt>.
|
16
|
+
#
|
17
|
+
# This plugin also changes the +r.is+ method so that if you use
|
18
|
+
# a verb method inside +r.is+, it returns a 405 status if none
|
19
|
+
# of the verb methods match. So this code:
|
20
|
+
#
|
21
|
+
# r.is '' do
|
22
|
+
# r.get do
|
23
|
+
# "a"
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# r.post do
|
27
|
+
# "b"
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# will return a 200 response for <tt>GET /</tt> and <tt>POST /</tt>,
|
32
|
+
# but a 405 response for <tt>PUT /</tt>.
|
33
|
+
#
|
34
|
+
# Note that this plugin will probably not do what you want for
|
35
|
+
# code such as:
|
36
|
+
#
|
37
|
+
# r.get '' do
|
38
|
+
# "a"
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# r.post '' do
|
42
|
+
# "b"
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Since for a <tt>POST /</tt> request, when +r.get+ method matches
|
46
|
+
# the path but not the request method, it will return an immediate
|
47
|
+
# 405 response. You must DRY up this code for it work correctly,
|
48
|
+
# like this:
|
49
|
+
#
|
50
|
+
# r.is '' do
|
51
|
+
# r.get do
|
52
|
+
# "a"
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# r.post do
|
56
|
+
# "b"
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# In all cases where it uses a 405 response, it also sets the +Allow+
|
61
|
+
# header in the response to contain the request methods supported.
|
62
|
+
#
|
63
|
+
# To make this affect the verb methods added by the all_verbs plugin,
|
64
|
+
# load this plugin first.
|
65
|
+
module NotAllowed
|
66
|
+
# Redefine the +r.get+ and +r.post+ methods when loading the plugin.
|
67
|
+
def self.configure(app)
|
68
|
+
app.request_module do
|
69
|
+
app::RodaRequest.def_verb_method(self, :get)
|
70
|
+
app::RodaRequest.def_verb_method(self, :post)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module RequestClassMethods
|
75
|
+
# Define a method named +verb+ in the given module which will
|
76
|
+
# return a 405 response if the method is called with any
|
77
|
+
# arguments and the arguments terminally match but the
|
78
|
+
# request method does not.
|
79
|
+
#
|
80
|
+
# If called without any arguments, check to see if the call
|
81
|
+
# is inside a terminal match, and in that case record the
|
82
|
+
# request method used.
|
83
|
+
def def_verb_method(mod, verb)
|
84
|
+
mod.class_eval(<<-END, __FILE__, __LINE__+1)
|
85
|
+
def #{verb}(*args, &block)
|
86
|
+
if args.empty?
|
87
|
+
@_is_verbs << "#{verb.to_s.upcase}" if @_is_verbs
|
88
|
+
always(&block) if #{verb == :get ? :is_get : verb}?
|
89
|
+
else
|
90
|
+
args << ::Roda::RodaPlugins::Base::RequestMethods::TERM
|
91
|
+
if_match(args) do
|
92
|
+
#{verb}(&block)
|
93
|
+
response.status = 405
|
94
|
+
response['Allow'] = '#{verb.to_s.upcase}'
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
END
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module RequestMethods
|
104
|
+
# Keep track of verb calls inside the block. If there are any
|
105
|
+
# verb calls inside the block, but the block returned, assume
|
106
|
+
# that the verb calls inside the block did not match, and
|
107
|
+
# since there was already a successful terminal match, the
|
108
|
+
# request method must not be allowed, so return a 405
|
109
|
+
# response in that case.
|
110
|
+
def is(*verbs)
|
111
|
+
super(*verbs) do
|
112
|
+
begin
|
113
|
+
@_is_verbs = []
|
114
|
+
|
115
|
+
ret = yield
|
116
|
+
|
117
|
+
unless @_is_verbs.empty?
|
118
|
+
response.status = 405
|
119
|
+
response['Allow'] = @_is_verbs.join(', ')
|
120
|
+
end
|
121
|
+
|
122
|
+
ret
|
123
|
+
ensure
|
124
|
+
@_is_verbs = nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
register_plugin(:not_allowed, NotAllowed)
|
132
|
+
end
|
133
|
+
end
|
data/lib/roda/plugins/pass.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class Roda
|
2
2
|
module RodaPlugins
|
3
|
-
# The pass plugin adds a request +pass+ method to skip the current
|
3
|
+
# The pass plugin adds a request +pass+ method to skip the current match
|
4
4
|
# block as if it did not match.
|
5
5
|
#
|
6
6
|
# plugin :pass
|
@@ -17,14 +17,21 @@ class Roda
|
|
17
17
|
# end
|
18
18
|
module Pass
|
19
19
|
module RequestMethods
|
20
|
-
#
|
21
|
-
def
|
20
|
+
# Skip the current match block as if it did not match.
|
21
|
+
def pass
|
22
|
+
throw :pass
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Handle passing inside the match block.
|
28
|
+
def always
|
22
29
|
catch(:pass){super}
|
23
30
|
end
|
24
31
|
|
25
|
-
#
|
26
|
-
def
|
27
|
-
|
32
|
+
# Handle passing inside the match block.
|
33
|
+
def if_match(_)
|
34
|
+
catch(:pass){super}
|
28
35
|
end
|
29
36
|
end
|
30
37
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Roda
|
2
|
+
module RodaPlugins
|
3
|
+
# The render_each plugin allows you to render a template for each
|
4
|
+
# value in an enumerable, returning the concatention of all of the
|
5
|
+
# template renderings. For example:
|
6
|
+
#
|
7
|
+
# render_each([1,2,3], :foo)
|
8
|
+
#
|
9
|
+
# will render the +foo+ template 3 times. Each time the template
|
10
|
+
# is rendered, the local variable +foo+ will contain the given
|
11
|
+
# value (e.g. on the first rendering +foo+ is 1).
|
12
|
+
#
|
13
|
+
# You can pass additional render options via an options hash:
|
14
|
+
#
|
15
|
+
# render_each([1,2,3], :foo, :views=>'partials')
|
16
|
+
#
|
17
|
+
# One additional option supported by is +:local+, which sets the
|
18
|
+
# local variable containing the current value to use. So:
|
19
|
+
#
|
20
|
+
# render_each([1,2,3], :foo, :local=>:bar)
|
21
|
+
#
|
22
|
+
# Will render the +foo+ template, but the local variable used inside
|
23
|
+
# the template will be +bar+. You can use <tt>:local=>nil</tt> to
|
24
|
+
# not set a local variable inside the template.
|
25
|
+
module RenderEach
|
26
|
+
module InstanceMethods
|
27
|
+
EMPTY_STRING = ''.freeze
|
28
|
+
|
29
|
+
# For each value in enum, render the given template using the
|
30
|
+
# given opts. The template and options hash are passed to +render+.
|
31
|
+
# Additional options supported:
|
32
|
+
# :local :: The local variable to use for the current enum value
|
33
|
+
# inside the template. An explicit +nil+ value does not
|
34
|
+
# set a local variable. If not set, uses the template name.
|
35
|
+
def render_each(enum, template, opts={})
|
36
|
+
if as = opts.has_key?(:local)
|
37
|
+
as = opts[:local]
|
38
|
+
else
|
39
|
+
as = template.to_s.to_sym
|
40
|
+
end
|
41
|
+
|
42
|
+
if as
|
43
|
+
opts = opts.dup
|
44
|
+
if locals = opts[:locals]
|
45
|
+
locals = opts[:locals] = locals.dup
|
46
|
+
else
|
47
|
+
locals = opts[:locals] = {}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
enum.map do |v|
|
52
|
+
locals[as] = v if as
|
53
|
+
render(template, opts)
|
54
|
+
end.join
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
register_plugin(:render_each, RenderEach)
|
60
|
+
end
|
61
|
+
end
|
@@ -7,7 +7,7 @@ class Roda
|
|
7
7
|
# # ...
|
8
8
|
# end
|
9
9
|
#
|
10
|
-
# By default this will match all segments. However, if your usernames
|
10
|
+
# By default this will match all nonempty segments. However, if your usernames
|
11
11
|
# must be 6-20 characters, and can only contain +a-z+ and +0-9+, you can do:
|
12
12
|
#
|
13
13
|
# plugin :symbol_matchers
|
@@ -28,11 +28,12 @@ class Roda
|
|
28
28
|
#
|
29
29
|
# By default, this plugin sets up the following symbol matchers:
|
30
30
|
#
|
31
|
-
# :d :: <tt
|
32
|
-
# :format :: <tt>/(?:\.(\w+))?/</tt>, an optional format
|
31
|
+
# :d :: <tt>/(\d+)/</tt>, a decimal segment
|
32
|
+
# :format :: <tt>/(?:\.(\w+))?/</tt>, an optional format/extension
|
33
33
|
# :opt :: <tt>/(?:\/([^\/]+))?</tt>, an optional segment
|
34
34
|
# :optd :: <tt>/(?:\/(\d+))?</tt>, an optional decimal segment
|
35
|
-
# :
|
35
|
+
# :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
|
36
|
+
# :w :: <tt>/(\w+)/</tt>, a alphanumeric segment
|
36
37
|
#
|
37
38
|
# Note that because of how segment matching works, :format, :opt, and :optd
|
38
39
|
# are only going to work inside of a string, like this:
|
@@ -46,6 +47,7 @@ class Roda
|
|
46
47
|
app.symbol_matcher(:format, /(?:\.(\w+))?/)
|
47
48
|
app.symbol_matcher(:opt, /(?:\/([^\/]+))?/)
|
48
49
|
app.symbol_matcher(:optd, /(?:\/(\d+))?/)
|
50
|
+
app.symbol_matcher(:rest, /(.*)/)
|
49
51
|
app.symbol_matcher(:w, /(\w+)/)
|
50
52
|
end
|
51
53
|
|
@@ -57,6 +59,8 @@ class Roda
|
|
57
59
|
end
|
58
60
|
|
59
61
|
module RequestMethods
|
62
|
+
private
|
63
|
+
|
60
64
|
# Allow for symbol specific regexps, by using match_symbol_#{s} if
|
61
65
|
# defined. If not defined, calls super for the default behavior.
|
62
66
|
def _match_symbol_regexp(s)
|
data/lib/roda/version.rb
CHANGED
data/lib/roda.rb
CHANGED
@@ -301,6 +301,17 @@ class Roda
|
|
301
301
|
pattern
|
302
302
|
end
|
303
303
|
|
304
|
+
# Define a verb method in the given that will yield to the match block
|
305
|
+
# if the request method matches and there are either no arguments or
|
306
|
+
# there is a successful terminal match on the arguments.
|
307
|
+
def def_verb_method(mod, verb)
|
308
|
+
mod.class_eval(<<-END, __FILE__, __LINE__+1)
|
309
|
+
def #{verb}(*args, &block)
|
310
|
+
_verb(args, &block) if #{verb == :get ? :is_get : verb}?
|
311
|
+
end
|
312
|
+
END
|
313
|
+
end
|
314
|
+
|
304
315
|
# Since RodaRequest is anonymously subclassed when Roda is subclassed,
|
305
316
|
# and then assigned to a constant of the Roda subclass, make inspect
|
306
317
|
# reflect the likely name for the class.
|
@@ -314,7 +325,7 @@ class Roda
|
|
314
325
|
# pattern requires the path starts with a string and does not match partial
|
315
326
|
# segments.
|
316
327
|
def consume_pattern(pattern)
|
317
|
-
/\A(\/(?:#{pattern}))(
|
328
|
+
/\A(\/(?:#{pattern}))(?=\/|\z)/
|
318
329
|
end
|
319
330
|
end
|
320
331
|
|
@@ -327,8 +338,8 @@ class Roda
|
|
327
338
|
EMPTY_STRING = "".freeze
|
328
339
|
SLASH = "/".freeze
|
329
340
|
SEGMENT = "([^\\/]+)".freeze
|
330
|
-
EMPTY_ARRAY = [].freeze
|
331
341
|
TERM_INSPECT = "TERM".freeze
|
342
|
+
GET_REQUEST_METHOD = 'GET'.freeze
|
332
343
|
|
333
344
|
TERM = Object.new
|
334
345
|
def TERM.inspect
|
@@ -354,14 +365,7 @@ class Roda
|
|
354
365
|
# As request routing modifies SCRIPT_NAME and PATH_INFO, this exists
|
355
366
|
# as a helper method to get the full request of the path info.
|
356
367
|
def full_path_info
|
357
|
-
"#{env[SCRIPT_NAME]}#{env[PATH_INFO]}"
|
358
|
-
end
|
359
|
-
|
360
|
-
# If this is not a GET method, returns immediately. Otherwise, if there
|
361
|
-
# are arguments, do a terminal match on the arguments, otherwise do a
|
362
|
-
# regular match.
|
363
|
-
def get(*args, &block)
|
364
|
-
_verb(args, &block) if get?
|
368
|
+
"#{@env[SCRIPT_NAME]}#{@env[PATH_INFO]}"
|
365
369
|
end
|
366
370
|
|
367
371
|
# Immediately stop execution of the route block and return the given
|
@@ -371,7 +375,14 @@ class Roda
|
|
371
375
|
throw :halt, res
|
372
376
|
end
|
373
377
|
|
374
|
-
#
|
378
|
+
# Whether this request is a get request. Similar to the default
|
379
|
+
# Rack::Request get? method, but can be overridden without changing
|
380
|
+
# rack's behavior.
|
381
|
+
def is_get?
|
382
|
+
@env[REQUEST_METHOD] == GET_REQUEST_METHOD
|
383
|
+
end
|
384
|
+
|
385
|
+
# Handle match block return values. By default, if a string is given
|
375
386
|
# and the response is empty, use the string as the response body.
|
376
387
|
def block_result(result)
|
377
388
|
res = response
|
@@ -383,14 +394,20 @@ class Roda
|
|
383
394
|
# Show information about current request, including request class,
|
384
395
|
# request method and full path.
|
385
396
|
def inspect
|
386
|
-
"#<#{self.class.inspect} #{env[REQUEST_METHOD]} #{full_path_info}>"
|
397
|
+
"#<#{self.class.inspect} #{@env[REQUEST_METHOD]} #{full_path_info}>"
|
387
398
|
end
|
388
399
|
|
389
|
-
#
|
390
|
-
#
|
400
|
+
# Does a terminal match on the input, matching only if the arguments
|
401
|
+
# have fully matched the patch.
|
391
402
|
def is(*args, &block)
|
392
|
-
args
|
393
|
-
|
403
|
+
if args.empty?
|
404
|
+
if @env[PATH_INFO] == EMPTY_STRING
|
405
|
+
always(&block)
|
406
|
+
end
|
407
|
+
else
|
408
|
+
args << TERM
|
409
|
+
if_match(args, &block)
|
410
|
+
end
|
394
411
|
end
|
395
412
|
|
396
413
|
# Attempts to match on all of the arguments. If all of the
|
@@ -399,14 +416,11 @@ class Roda
|
|
399
416
|
# If any of the arguments fails, ensures the request state is
|
400
417
|
# returned to that before matches were attempted.
|
401
418
|
def on(*args, &block)
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
# regular match.
|
408
|
-
def post(*args, &block)
|
409
|
-
_verb(args, &block) if post?
|
419
|
+
if args.empty?
|
420
|
+
always(&block)
|
421
|
+
else
|
422
|
+
if_match(args, &block)
|
423
|
+
end
|
410
424
|
end
|
411
425
|
|
412
426
|
# The response related to the current request.
|
@@ -420,19 +434,17 @@ class Roda
|
|
420
434
|
throw :halt, response.finish
|
421
435
|
end
|
422
436
|
|
423
|
-
# If
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
if env[PATH_INFO] == SLASH && (!request_method || send(:"#{request_method}?"))
|
428
|
-
_on(EMPTY_ARRAY, &block)
|
437
|
+
# If this is a GET request for the root ("/"), yield to the match block.
|
438
|
+
def root(&block)
|
439
|
+
if @env[PATH_INFO] == SLASH && is_get?
|
440
|
+
always(&block)
|
429
441
|
end
|
430
442
|
end
|
431
443
|
|
432
444
|
# Call the given rack app with the environment and immediately return
|
433
445
|
# the response as the response for this request.
|
434
446
|
def run(app)
|
435
|
-
throw :halt, app.call(env)
|
447
|
+
throw :halt, app.call(@env)
|
436
448
|
end
|
437
449
|
|
438
450
|
private
|
@@ -480,31 +492,21 @@ class Roda
|
|
480
492
|
SEGMENT
|
481
493
|
end
|
482
494
|
|
483
|
-
# Internal match method taking array of matchers instead of multiple
|
484
|
-
# arguments.
|
485
|
-
def _on(args)
|
486
|
-
script = env[SCRIPT_NAME]
|
487
|
-
path = env[PATH_INFO]
|
488
|
-
|
489
|
-
# For every block, we make sure to reset captures so that
|
490
|
-
# nesting matchers won't mess with each other's captures.
|
491
|
-
captures.clear
|
492
|
-
|
493
|
-
return unless match_all(args)
|
494
|
-
block_result(yield(*captures))
|
495
|
-
throw :halt, response.finish
|
496
|
-
ensure
|
497
|
-
env[SCRIPT_NAME] = script
|
498
|
-
env[PATH_INFO] = path
|
499
|
-
end
|
500
|
-
|
501
495
|
# Backbone of the verb method support, using a terminal match if
|
502
496
|
# args is not empty, or a regular match if it is empty.
|
503
497
|
def _verb(args, &block)
|
504
|
-
|
498
|
+
if args.empty?
|
499
|
+
always(&block)
|
500
|
+
else
|
505
501
|
args << TERM
|
502
|
+
if_match(args, &block)
|
506
503
|
end
|
507
|
-
|
504
|
+
end
|
505
|
+
|
506
|
+
# Yield to the match block and return rack response after the block returns.
|
507
|
+
def always
|
508
|
+
block_result(yield)
|
509
|
+
throw :halt, response.finish
|
508
510
|
end
|
509
511
|
|
510
512
|
# The body to use for the response if the response does not return
|
@@ -521,17 +523,38 @@ class Roda
|
|
521
523
|
# SCRIPT_NAME to include the matched path, removes the matched
|
522
524
|
# path from PATH_INFO, and updates captures with any regex captures.
|
523
525
|
def consume(pattern)
|
526
|
+
env = @env
|
524
527
|
return unless matchdata = env[PATH_INFO].match(pattern)
|
525
528
|
|
526
529
|
vars = matchdata.captures
|
527
530
|
|
528
531
|
# Don't mutate SCRIPT_NAME, breaks try
|
529
532
|
env[SCRIPT_NAME] += vars.shift
|
530
|
-
env[PATH_INFO] =
|
533
|
+
env[PATH_INFO] = matchdata.post_match
|
531
534
|
|
532
535
|
captures.concat(vars)
|
533
536
|
end
|
534
537
|
|
538
|
+
# If all of the arguments match, yields to the match block and
|
539
|
+
# returns the rack response when the block returns. If any of
|
540
|
+
# the match arguments doesn't match, does nothing.
|
541
|
+
def if_match(args)
|
542
|
+
env = @env
|
543
|
+
script = env[SCRIPT_NAME]
|
544
|
+
path = env[PATH_INFO]
|
545
|
+
|
546
|
+
# For every block, we make sure to reset captures so that
|
547
|
+
# nesting matchers won't mess with each other's captures.
|
548
|
+
captures.clear
|
549
|
+
|
550
|
+
return unless match_all(args)
|
551
|
+
block_result(yield(*captures))
|
552
|
+
throw :halt, response.finish
|
553
|
+
ensure
|
554
|
+
env[SCRIPT_NAME] = script
|
555
|
+
env[PATH_INFO] = path
|
556
|
+
end
|
557
|
+
|
535
558
|
# Attempt to match the argument to the given request, handling
|
536
559
|
# common ruby types.
|
537
560
|
def match(matcher)
|
@@ -543,7 +566,7 @@ class Roda
|
|
543
566
|
when Symbol
|
544
567
|
_match_symbol(matcher)
|
545
568
|
when TERM
|
546
|
-
env[PATH_INFO] == EMPTY_STRING
|
569
|
+
@env[PATH_INFO] == EMPTY_STRING
|
547
570
|
when Hash
|
548
571
|
_match_hash(matcher)
|
549
572
|
when Array
|
@@ -563,7 +586,7 @@ class Roda
|
|
563
586
|
# Match files with the given extension. Requires that the
|
564
587
|
# request path end with the extension.
|
565
588
|
def match_extension(ext)
|
566
|
-
consume(self.class.cached_matcher(ext){"([^\\/]+?)\.#{ext}\\z"})
|
589
|
+
consume(self.class.cached_matcher([:extension, ext]){"([^\\/]+?)\.#{ext}\\z"})
|
567
590
|
end
|
568
591
|
|
569
592
|
# Match by request method. This can be an array if you want
|
@@ -572,7 +595,7 @@ class Roda
|
|
572
595
|
if type.is_a?(Array)
|
573
596
|
type.any?{|t| match_method(t)}
|
574
597
|
else
|
575
|
-
type.to_s.upcase == env[REQUEST_METHOD]
|
598
|
+
type.to_s.upcase == @env[REQUEST_METHOD]
|
576
599
|
end
|
577
600
|
end
|
578
601
|
|
@@ -698,4 +721,6 @@ class Roda
|
|
698
721
|
|
699
722
|
extend RodaPlugins::Base::ClassMethods
|
700
723
|
plugin RodaPlugins::Base
|
724
|
+
RodaRequest.def_verb_method(RodaPlugins::Base::RequestMethods, :get)
|
725
|
+
RodaRequest.def_verb_method(RodaPlugins::Base::RequestMethods, :post)
|
701
726
|
end
|
data/spec/matchers_spec.rb
CHANGED
@@ -487,21 +487,10 @@ describe "path matchers" do
|
|
487
487
|
end
|
488
488
|
|
489
489
|
body.should == 'Home'
|
490
|
+
status('REQUEST_METHOD'=>'POST').should == 404
|
490
491
|
status("//").should == 404
|
491
492
|
status("/foo").should == 404
|
492
493
|
end
|
493
|
-
|
494
|
-
it "matching the root with the root method and request method symbol" do
|
495
|
-
app do |r|
|
496
|
-
r.root(:get) do
|
497
|
-
"Home"
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
body.should == 'Home'
|
502
|
-
status("//").should == 404
|
503
|
-
status('REQUEST_METHOD'=>"POST").should == 404
|
504
|
-
end
|
505
494
|
end
|
506
495
|
|
507
496
|
describe "root/empty segment matching" do
|
@@ -658,15 +647,15 @@ end
|
|
658
647
|
describe "extension matcher" do
|
659
648
|
it "should match given file extensions" do
|
660
649
|
app do |r|
|
661
|
-
r.on "
|
650
|
+
r.on "css" do
|
662
651
|
r.on :extension=>"css" do |file|
|
663
652
|
file
|
664
653
|
end
|
665
654
|
end
|
666
655
|
end
|
667
656
|
|
668
|
-
body("/
|
669
|
-
status("/
|
657
|
+
body("/css/reset.css").should == 'reset'
|
658
|
+
status("/css/reset.bar").should == 404
|
670
659
|
end
|
671
660
|
end
|
672
661
|
|
@@ -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,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,42 @@
|
|
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
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
body.should == 'a'
|
30
|
+
status('REQUEST_METHOD'=>'POST').should == 405
|
31
|
+
header('Allow', 'REQUEST_METHOD'=>'POST').should == 'GET'
|
32
|
+
|
33
|
+
body('/b').should == 'b'
|
34
|
+
status('/b', 'REQUEST_METHOD'=>'POST').should == 404
|
35
|
+
|
36
|
+
body('/c').should == 'cg'
|
37
|
+
body('/c', 'REQUEST_METHOD'=>'POST').should == 'cp'
|
38
|
+
body('/c', 'REQUEST_METHOD'=>'PATCH').should == 'c'
|
39
|
+
status('/c', 'REQUEST_METHOD'=>'PATCH').should == 405
|
40
|
+
header('Allow', '/c', 'REQUEST_METHOD'=>'PATCH').should == 'GET, POST'
|
41
|
+
end
|
42
|
+
end
|
data/spec/plugin/pass_spec.rb
CHANGED
@@ -3,6 +3,11 @@ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
|
3
3
|
describe "pass plugin" do
|
4
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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
describe "render_each plugin" do
|
4
|
+
it "calls render with each argument, returning joined string with all results" do
|
5
|
+
app(:bare) do
|
6
|
+
plugin :render_each
|
7
|
+
def render(t, opts)
|
8
|
+
"r#{t}#{opts[:locals][:foo] if opts[:locals]}#{opts[:bar]} "
|
9
|
+
end
|
10
|
+
|
11
|
+
route do |r|
|
12
|
+
r.root do
|
13
|
+
render_each([1,2,3], :foo)
|
14
|
+
end
|
15
|
+
|
16
|
+
r.is 'a' do
|
17
|
+
render_each([1,2,3], :bar, :local=>:foo, :bar=>4)
|
18
|
+
end
|
19
|
+
|
20
|
+
r.is 'b' do
|
21
|
+
render_each([1,2,3], :bar, :local=>nil)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
body.should == 'rfoo1 rfoo2 rfoo3 '
|
27
|
+
body("/a").should == 'rbar14 rbar24 rbar34 '
|
28
|
+
body("/b").should == 'rbar rbar rbar '
|
29
|
+
end
|
30
|
+
end
|
@@ -27,6 +27,10 @@ describe "symbol_matchers plugin" do
|
|
27
27
|
"f#{f}"
|
28
28
|
end
|
29
29
|
|
30
|
+
r.is 'q:rest' do |r|
|
31
|
+
"rest#{r}"
|
32
|
+
end
|
33
|
+
|
30
34
|
r.is :w do |w|
|
31
35
|
"w#{w}"
|
32
36
|
end
|
@@ -58,5 +62,7 @@ describe "symbol_matchers plugin" do
|
|
58
62
|
body("/1/1a/f").should == 'dwf11af'
|
59
63
|
body("/12/1azy/fffff").should == 'dwf121azyfffff'
|
60
64
|
status("/1/f/a").should == 404
|
65
|
+
body("/qa/b/c/d//f/g").should == 'resta/b/c/d//f/g'
|
66
|
+
body('/q').should == 'rest'
|
61
67
|
end
|
62
68
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda-cj
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -88,12 +88,16 @@ files:
|
|
88
88
|
- lib/roda/plugins/flash.rb
|
89
89
|
- lib/roda/plugins/csrf.rb
|
90
90
|
- lib/roda/plugins/symbol_views.rb
|
91
|
+
- lib/roda/plugins/content_for.rb
|
91
92
|
- lib/roda/plugins/halt.rb
|
93
|
+
- lib/roda/plugins/not_allowed.rb
|
92
94
|
- lib/roda/plugins/header_matchers.rb
|
93
95
|
- lib/roda/plugins/per_thread_caching.rb
|
94
96
|
- lib/roda/plugins/error_handler.rb
|
95
97
|
- lib/roda/plugins/indifferent_params.rb
|
98
|
+
- lib/roda/plugins/head.rb
|
96
99
|
- lib/roda/plugins/json.rb
|
100
|
+
- lib/roda/plugins/render_each.rb
|
97
101
|
- lib/roda/plugins/streaming.rb
|
98
102
|
- lib/roda/plugins/all_verbs.rb
|
99
103
|
- lib/roda/plugins/not_found.rb
|
@@ -118,6 +122,7 @@ files:
|
|
118
122
|
- spec/plugin/error_handler_spec.rb
|
119
123
|
- spec/plugin/json_spec.rb
|
120
124
|
- spec/plugin/symbol_views_spec.rb
|
125
|
+
- spec/plugin/head_spec.rb
|
121
126
|
- spec/plugin/all_verbs_spec.rb
|
122
127
|
- spec/plugin/render_spec.rb
|
123
128
|
- spec/plugin/symbol_matchers_spec.rb
|
@@ -126,12 +131,15 @@ files:
|
|
126
131
|
- spec/plugin/default_headers_spec.rb
|
127
132
|
- spec/plugin/backtracking_array_spec.rb
|
128
133
|
- spec/plugin/per_thread_caching_spec.rb
|
134
|
+
- spec/plugin/content_for_spec.rb
|
129
135
|
- spec/plugin/hooks_spec.rb
|
130
136
|
- spec/plugin/streaming_spec.rb
|
131
137
|
- spec/plugin/flash_spec.rb
|
132
138
|
- spec/plugin/pass_spec.rb
|
139
|
+
- spec/plugin/render_each_spec.rb
|
133
140
|
- spec/plugin/not_found_spec.rb
|
134
141
|
- spec/plugin/middleware_spec.rb
|
142
|
+
- spec/plugin/not_allowed_spec.rb
|
135
143
|
- spec/plugin/h_spec.rb
|
136
144
|
- spec/plugin/halt_spec.rb
|
137
145
|
- spec/plugin/view_subdirs_spec.rb
|