roda 2.21.0 → 2.22.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e6b3cac6d9da3a5df41af141588ad921d4708d8
4
- data.tar.gz: 345a82534b2994081e0fde6e7e9552d1d28af295
3
+ metadata.gz: 6bf01ef06d46bc9ee153b2b737b44a19b6dc8e3f
4
+ data.tar.gz: 32979a928ec7cc15994b5c1a937b8601946f6451
5
5
  SHA512:
6
- metadata.gz: 44fa6264003e578c1d694737d9658d9b78a7d26274542ddc1fe8057e42684cd2f6dae884cccda81ddcebf0e76310f3ee9b5f283fe48cafd4c5256bb529b0a0b7
7
- data.tar.gz: d3b536a7846bd36832d58968192f2bc031215dae669eb09b8351d135aa3ccaffa195032dd429315d40b564c5b61f1e9540678637c6ef57db351e9a8b1c5cfc3c
6
+ metadata.gz: d730f1511a4c437b8f5bc8e3df705a2286703277ab22a985e9d331de934c8c30810f657fe06510b928f731086d824fdbd2b1996833751085fef898ecd540507d
7
+ data.tar.gz: 39d8d2b3c9b26f610d212b00ce087973030d17a1aadc0867baf0a613a1dfb9d1f54a8bc1792dad30802dcc305b1850cfab8a67052591c5699926389ab90ca591
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 2.22.0 (2017-01-20)
2
+
3
+ * Add support for :verbatim_string_matcher option, for making all string matchers match verbatim (jeremyevans)
4
+
5
+ * Add support for :unsupported_matcher => :raise option, for raising on unsupported matcher values (jeremyevans)
6
+
7
+ * Add support for :unsupported_block_result => :raise option, for raising on unsupported route/match block return values (jeremyevans)
8
+
1
9
  = 2.21.0 (2016-12-16)
2
10
 
3
11
  * Add handle_stream_error method to streaming plugin, for handling errors when using stream(:loop=>true) (jeremyevans)
data/README.rdoc CHANGED
@@ -56,8 +56,6 @@ Here's a simple application, showing how the routing tree works:
56
56
  require "roda"
57
57
 
58
58
  class App < Roda
59
- use Rack::Session::Cookie, :secret => ENV['SECRET']
60
-
61
59
  route do |r|
62
60
  # GET / request
63
61
  r.root do
@@ -95,9 +93,6 @@ Here's a simple application, showing how the routing tree works:
95
93
 
96
94
  Here's a breakdown of what is going on in the block above:
97
95
 
98
- After requiring the library and subclassing Roda, the +use+ method is called.
99
- This loads a Rack middleware into the current application.
100
-
101
96
  The +route+ block is called whenever a new request comes in.
102
97
  It is yielded an instance of a subclass of <tt>Rack::Request</tt>
103
98
  with some additional methods for matching routes.
@@ -232,12 +227,12 @@ Here's an example showcasing how different matchers work:
232
227
  end
233
228
 
234
229
  # GET /post/2011/02/16/hello
235
- r.get "post/:y/:m/:d/:slug" do |y, m, d, slug|
230
+ r.get "post", :y, :m, :d, :slug do |y, m, d, slug|
236
231
  "#{y}-#{m}-#{d} #{slug}" #=> "2011-02-16 hello"
237
232
  end
238
233
 
239
234
  # GET /username/foobar branch
240
- r.on "username/:username", :method=>:get do |username|
235
+ r.on "username", :username, :method=>:get do |username|
241
236
  user = User.find_by_username(username)
242
237
 
243
238
  # GET /username/foobar/posts
@@ -290,9 +285,9 @@ If a string contains any slashes, it matches one additional segment for each sla
290
285
  "foo/bar" # matches "/foo/bar"
291
286
  "foo/bar" # does not match "/foo/bard"
292
287
 
293
- If a string contains a colon followed by any <tt>\\w</tt> characters,
294
- the colon and remaining <tt>\\w</tt> characters match any nonempty segment
295
- that contains at least one character:
288
+ For backwards compatibility, if a string contains a colon followed by any
289
+ <tt>\\w</tt> characters, the colon and remaining <tt>\\w</tt> characters match any
290
+ nonempty segment that contains at least one character:
296
291
 
297
292
  "foo/:id" # matches "/foo/bar", "/foo/baz", etc.
298
293
  "foo/:id" # does not match "/fo/bar"
@@ -302,22 +297,17 @@ You can use multiple colons in a string:
302
297
  ":x/:y" # matches "/foo/bar", "/bar/foo" etc.
303
298
  ":x/:y" # does not match "/foo", "/bar/"
304
299
 
305
- You can prefix colons:
306
-
307
- "foo:x/bar:y" # matches "/food/bard", "/fool/bart", etc.
308
- "foo:x/bar:y" # does not match "/foo/bart", "/fool/bar", etc.
309
-
310
- If any colons are used, the block will yield one argument
311
- for each segment matched containing the matched text, so:
312
-
313
- "foo:x/:y" # matching "/fool/bar" yields "l", "bar"
300
+ Note that instead of using colons in strings, it is recommended to use separate
301
+ symbol arguments, as it is faster and simpler:
314
302
 
315
- Colons that are not followed by a <tt>\\w</tt> character are matched literally:
303
+ "foo", :id # instead of "foo/:id"
304
+ :x, :y # instead of ":x/:y"
316
305
 
317
- ":/a" # matches "/:/a"
306
+ It is possible in future versions of Roda, colons will not be treated specially
307
+ in strings, and will just match a literal colon character.
318
308
 
319
- Note that other than colons, strings do no handle regular expression syntax, the
320
- string is matched verbatim:
309
+ Note that other than colons followed by a <tt>\\w</tt> character, strings do no
310
+ handle regular expression syntax, they are matched verbatim:
321
311
 
322
312
  "\\d+(/\\w+)?" # matches "/\d+(/\w+)?"
323
313
  "\\d+(/\\w+)?" # does not match "/123/abc"
@@ -641,12 +631,28 @@ The plugins that ship with Roda freeze their settings and only allow modificatio
641
631
  to their settings by reloading the plugin, and external plugins are encouraged
642
632
  to follow this approach.
643
633
 
644
- The following options are respected by multiple plugins:
634
+ The following options are respected by the default library or multiple plugins:
645
635
 
646
636
  :add_script_name :: Prepend the SCRIPT_NAME for the request to paths. This is
647
637
  useful if you mount the app as a path under another app.
648
638
  :root :: Set the root path for the app. This defaults to the current working
649
639
  directory of the process.
640
+ :unsupported_block_result :: If set to :raise, raises an error if a match or
641
+ route block returns an object that is not handled.
642
+ By default, String, nil, and false are handled,
643
+ and other types can be handled via plugins. Setting
644
+ this option can alert you to possible issues in your
645
+ application.
646
+ :unsupported_matcher :: If set to :raise, raises an error if a matcher is used that
647
+ is not handled. By default, String, Symbol, Regexp, Hash,
648
+ Array, Proc, true, false, and nil are handled. Setting
649
+ this option can alert you to possible issues in your
650
+ application.
651
+ :verbatim_string_matcher :: If set to true, makes all string matchers match
652
+ verbatim strings, disallowing the use of colons
653
+ for placeholders. In general, it is recommended
654
+ to use separate symbol matchers instead of
655
+ embedding placeholders in string matchers.
650
656
 
651
657
  There may be other options supported by individual plugins, if so it will be
652
658
  mentioned in the documentation for the plugin.
@@ -0,0 +1,41 @@
1
+ = New Features
2
+
3
+ * An :unsupported_block_result => :raise option is now supported
4
+ for Roda applications. This will raise a RodaError if an
5
+ unsupported value is returned from a match block or the
6
+ route block. This can make it easier to discover potential
7
+ problems in the routing tree. This option may become the
8
+ default behavior in Roda 3.
9
+
10
+ * An :unsupported_matcher => :raise option is now supported for
11
+ Roda applications. This will raise a RodaError if you use an
12
+ unsupported value as a matcher. This can make it easier to
13
+ discover potential problems in the routing tree. This option
14
+ may become the default behavior in Roda 3.
15
+
16
+ * A :verbatim_string_matcher option is now supported for Roda
17
+ applications. This will make all string matchers only match
18
+ the path verbatim, disallowing the use of a colon for
19
+ placeholders in the string. It's recommended that users
20
+ switch to using separate symbol arguments for placeholders.
21
+
22
+ If you enable this option, you need to change code such as:
23
+
24
+ r.is "foo/:bar" do |bar|
25
+ end
26
+
27
+ to:
28
+
29
+ r.is "foo", :bar do |bar|
30
+ end
31
+
32
+ If you are looking to convert an existing routing tree
33
+ from using placeholders in strings to separate symbol
34
+ arguments, you can scan your routing tree for potential
35
+ usage of placeholders in strings:
36
+
37
+ grep ' r\..*['\''"].*:.*['\''"]' app.rb
38
+
39
+ This option may become the default behavior in Roda 3, with
40
+ a plugin added to support the current default behavior of
41
+ allowing placeholders in string matchers.
data/lib/roda.rb CHANGED
@@ -683,7 +683,7 @@ class Roda
683
683
  # string so that regexp metacharacters are not matched, and recognizes
684
684
  # colon tokens for placeholders.
685
685
  def _match_string(str)
686
- if str.index(COLON)
686
+ if str.index(COLON) && placeholder_string_matcher?
687
687
  consume(self.class.cached_matcher(str){Regexp.escape(str).gsub(/:(\w+)/){|m| _match_symbol_regexp($1)}})
688
688
  else
689
689
  rp = @remaining_path
@@ -750,12 +750,19 @@ class Roda
750
750
  throw :halt, response.finish
751
751
  end
752
752
 
753
- # The body to use for the response if the response does not return
753
+ # The body to use for the response if the response does not already have
754
754
  # a body. By default, a String is returned directly, and nil is
755
755
  # returned otherwise.
756
756
  def block_result_body(result)
757
- if result.is_a?(String)
757
+ case result
758
+ when String
758
759
  result
760
+ when nil, false
761
+ # nothing
762
+ else
763
+ if roda_class.opts[:unsupported_block_result] == :raise
764
+ raise RodaError, "unsupported block result: #{result.inspect}"
765
+ end
759
766
  end
760
767
  end
761
768
 
@@ -830,7 +837,12 @@ class Roda
830
837
  _match_array(matcher)
831
838
  when Proc
832
839
  matcher.call
840
+ when true, false, nil
841
+ matcher
833
842
  else
843
+ if roda_class.opts[:unsupported_matcher] == :raise
844
+ raise RodaError, "unsupported matcher: #{matcher.inspect}"
845
+ end
834
846
  matcher
835
847
  end
836
848
  end
@@ -849,6 +861,12 @@ class Roda
849
861
  type.to_s.upcase == @env[REQUEST_METHOD]
850
862
  end
851
863
  end
864
+
865
+ # Whether string matchers are used verbatim, without handling
866
+ # placeholders via colons.
867
+ def placeholder_string_matcher?
868
+ !roda_class.opts[:verbatim_string_matcher]
869
+ end
852
870
  end
853
871
 
854
872
  # Class methods for RodaResponse
@@ -976,6 +994,7 @@ class Roda
976
994
  def redirect(path, status = 302)
977
995
  @headers[LOCATION] = path
978
996
  @status = status
997
+ nil
979
998
  end
980
999
 
981
1000
  # Return the Roda class related to this response.
@@ -8,7 +8,7 @@ class Roda
8
8
  # For proper caching, you should use either the +last_modified+ or
9
9
  # +etag+ request methods.
10
10
  #
11
- # r.get 'albums/:d' do |album_id|
11
+ # r.get 'albums', :d do |album_id|
12
12
  # @album = Album[album_id]
13
13
  # r.last_modified @album.updated_at
14
14
  # view('album')
@@ -16,7 +16,7 @@ class Roda
16
16
  #
17
17
  # # or
18
18
  #
19
- # r.get 'albums/:d' do |album_id|
19
+ # r.get 'albums', :d do |album_id|
20
20
  # @album = Album[album_id]
21
21
  # r.etag @album.sha1
22
22
  # view('album')
@@ -44,7 +44,7 @@ class Roda
44
44
  # block will be delayed until rendering the content template. This is
45
45
  # useful if you want to delay execution for all routes under a branch:
46
46
  #
47
- # r.on 'albums/:d' do |album_id|
47
+ # r.on 'albums', :d do |album_id|
48
48
  # delay do
49
49
  # @album = Album[album_id]
50
50
  # end
@@ -94,6 +94,7 @@ class Roda
94
94
  instance_exec(*a, &blk)
95
95
  end
96
96
  end
97
+ nil
97
98
  end
98
99
  else
99
100
  result
@@ -60,7 +60,7 @@ class Roda
60
60
  # email bodies, you can use all of Roda's usual routing tree features
61
61
  # to DRY up your code:
62
62
  #
63
- # r.on "albums/:d" do |album_id|
63
+ # r.on "albums", :d do |album_id|
64
64
  # @album = Album[album_id.to_i]
65
65
  # from 'from@example.com'
66
66
  # to 'to@example.com'
@@ -92,7 +92,7 @@ class Roda
92
92
  # If while preparing the email you figure out you don't want to send an
93
93
  # email, call +no_mail!+:
94
94
  #
95
- # r.mail '/welcome/:d' do |id|
95
+ # r.mail 'welcome', :d do |id|
96
96
  # no_mail! unless user = User[id]
97
97
  # # ...
98
98
  # end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  class Roda
5
5
  module RodaPlugins
6
- # The params_capturing plugin makes string and symbol matchers
6
+ # The params_capturing plugin makes symbol matchers
7
7
  # update the request params with the value of the captured segments,
8
8
  # using the matcher as the key:
9
9
  #
@@ -11,7 +11,7 @@ class Roda
11
11
  #
12
12
  # route do |r|
13
13
  # # GET /foo/123/abc/67
14
- # r.on("foo/:bar/:baz", :quux) do
14
+ # r.on("foo", :bar, :baz, :quux) do
15
15
  # r[:bar] #=> '123'
16
16
  # r[:baz] #=> 'abc'
17
17
  # r[:quux] #=> '67'
@@ -23,10 +23,9 @@ class Roda
23
23
  # or strings.
24
24
  #
25
25
  # All matchers will update the request params by adding all
26
- # captured segments to the +captures+ key, including
27
- # symbol and string matchers:
26
+ # captured segments to the +captures+ key:
28
27
  #
29
- # r.on(:x, /(\d+)\/(\w+)/, ':y') do
28
+ # r.on(:x, /(\d+)\/(\w+)/, :y) do
30
29
  # r[:x] #=> nil
31
30
  # r[:y] #=> nil
32
31
  # r[:captures] #=> ["foo", "123", "abc", "67"]
@@ -53,6 +52,9 @@ class Roda
53
52
  # strings and not symbols (<tt>r[]</tt> converts the argument
54
53
  # to a string before looking it up in +r.params+).
55
54
  #
55
+ # This plugin will also handle string matchers if placeholders in
56
+ # string matchers are supported.
57
+ #
56
58
  # Also note that this plugin will not work correctly if you are using
57
59
  # the symbol_matchers plugin with custom symbol matching and are using
58
60
  # symbols that capture multiple values or no values.
@@ -73,7 +75,7 @@ class Roda
73
75
  # Add the capture names from this string to list of param
74
76
  # capture names if param capturing.
75
77
  def _match_string(str)
76
- if pc = @_params_captures
78
+ if (pc = @_params_captures) && placeholder_string_matcher?
77
79
  pc.concat(str.scan(STRING_PARAM_CAPTURE_REGEXP))
78
80
  end
79
81
  super
@@ -86,7 +88,7 @@ class Roda
86
88
  STRING_PARAM_CAPTURE_RANGE = 1..-1
87
89
 
88
90
  def _match_string(str)
89
- if pc = @_params_captures
91
+ if (pc = @_params_captures) && placeholder_string_matcher?
90
92
  pc.concat(str.scan(/:\w+/).map{|s| s[STRING_PARAM_CAPTURE_RANGE]})
91
93
  end
92
94
  super
@@ -9,7 +9,7 @@ class Roda
9
9
  # plugin :pass
10
10
  #
11
11
  # route do |r|
12
- # r.on "foo/:bar" do |bar|
12
+ # r.on "foo", :bar do |bar|
13
13
  # r.pass if bar == 'baz'
14
14
  # "/foo/#{bar} (not baz)"
15
15
  # end
@@ -19,7 +19,13 @@ class Roda
19
19
  # Then the route will only if the path is +/foobar123+, but not if it is
20
20
  # +/foo+, +/FooBar123+, or +/foobar_123+.
21
21
  #
22
- # Note that this feature does not apply to just symbols, but also to
22
+ # By default, this plugin sets up the following symbol matchers:
23
+ #
24
+ # :d :: <tt>/(\d+)/</tt>, a decimal segment
25
+ # :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
26
+ # :w :: <tt>/(\w+)/</tt>, a alphanumeric segment
27
+ #
28
+ # If placeholder string matchers are supported, this feature also applies to
23
29
  # embedded colons in strings, so the following:
24
30
  #
25
31
  # r.on "users/:username" do
@@ -29,17 +35,16 @@ class Roda
29
35
  # Would match +/users/foobar123+, but not +/users/foo+, +/users/FooBar123+,
30
36
  # or +/users/foobar_123+.
31
37
  #
32
- # By default, this plugin sets up the following symbol matchers:
38
+ # If placeholder string matchers are supported, it also adds the following
39
+ # symbol matchers:
33
40
  #
34
- # :d :: <tt>/(\d+)/</tt>, a decimal segment
35
41
  # :format :: <tt>/(?:\.(\w+))?/</tt>, an optional format/extension
36
42
  # :opt :: <tt>/(?:\/([^\/]+))?</tt>, an optional segment
37
43
  # :optd :: <tt>/(?:\/(\d+))?</tt>, an optional decimal segment
38
- # :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
39
- # :w :: <tt>/(\w+)/</tt>, a alphanumeric segment
40
44
  #
41
- # Note that because of how segment matching works, :format, :opt, and :optd
42
- # are only going to work inside of a string, like this:
45
+ # These are only added when placeholder string matchers are supported,
46
+ # because they only make sense when used inside of a string, due to how
47
+ # segment matching works. Example:
43
48
  #
44
49
  # r.is "album:opt" do |id| end
45
50
  # # matches /album (yielding nil) and /album/foo (yielding "foo")
@@ -50,11 +55,14 @@ class Roda
50
55
  module SymbolMatchers
51
56
  def self.configure(app)
52
57
  app.symbol_matcher(:d, /(\d+)/)
53
- app.symbol_matcher(:format, /(?:\.(\w+))?/)
54
- app.symbol_matcher(:opt, /(?:\/([^\/]+))?/)
55
- app.symbol_matcher(:optd, /(?:\/(\d+))?/)
56
- app.symbol_matcher(:rest, /(.*)/)
57
58
  app.symbol_matcher(:w, /(\w+)/)
59
+ app.symbol_matcher(:rest, /(.*)/)
60
+
61
+ if !app.opts[:verbatim_string_matcher]
62
+ app.symbol_matcher(:format, /(?:\.(\w+))?/)
63
+ app.symbol_matcher(:opt, /(?:\/([^\/]+))?/)
64
+ app.symbol_matcher(:optd, /(?:\/(\d+))?/)
65
+ end
58
66
  end
59
67
 
60
68
  module ClassMethods
@@ -27,7 +27,7 @@ class Roda
27
27
  # end
28
28
  #
29
29
  # route do |r|
30
- # r.get "room/:d" do |room_id|
30
+ # r.get "room", :d do |room_id|
31
31
  # room = sync{ROOMS[room_id] ||= []}
32
32
  #
33
33
  # r.websocket do |ws|
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 21
7
+ RodaMinorVersion = 22
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -3,7 +3,7 @@ require File.expand_path("spec_helper", File.dirname(__FILE__))
3
3
  describe "r.run" do
4
4
  it "should allow composition of apps" do
5
5
  a = app do |r|
6
- r.on "services/:id" do |id|
6
+ r.on "services", :id do |id|
7
7
  "View #{id}"
8
8
  end
9
9
  end
@@ -136,18 +136,39 @@ describe "matchers" do
136
136
  id
137
137
  end
138
138
  end
139
+ app.opts[:verbatim_string_matcher] = false
139
140
 
140
141
  body('/posts/123').must_equal '123'
141
142
  status('/post/123').must_equal 404
142
143
  body('/responses-123').must_equal '123'
143
144
  end
144
145
 
146
+ it "should not handle string with embedded param if :verbatim_string_matcher option is set" do
147
+ app do |r|
148
+ r.on "posts/:id" do
149
+ '1'
150
+ end
151
+
152
+ r.on "responses-:id" do
153
+ '2'
154
+ end
155
+ end
156
+ app.opts[:verbatim_string_matcher] = true
157
+
158
+ status('/post/123').must_equal 404
159
+ status('/posts/123').must_equal 404
160
+ body('/posts/:id').must_equal '1'
161
+ status('/responses-123').must_equal 404
162
+ body('/responses-:id').must_equal '2'
163
+ end
164
+
145
165
  it "should handle multiple params in single string" do
146
166
  app do |r|
147
167
  r.on "u/:uid/posts/:id" do |uid, id|
148
168
  uid + id
149
169
  end
150
170
  end
171
+ app.opts[:verbatim_string_matcher] = false
151
172
 
152
173
  body("/u/jdoe/posts/123").must_equal 'jdoe123'
153
174
  status("/u/jdoe/pots/123").must_equal 404
@@ -159,6 +180,7 @@ describe "matchers" do
159
180
  uid + id
160
181
  end
161
182
  end
183
+ app.opts[:verbatim_string_matcher] = false
162
184
 
163
185
  body("/u/jdoe/posts?/123").must_equal 'jdoe123'
164
186
  status("/u/jdoe/post/123").must_equal 404
@@ -170,6 +192,7 @@ describe "matchers" do
170
192
  uid + id
171
193
  end
172
194
  end
195
+ app.opts[:verbatim_string_matcher] = false
173
196
 
174
197
  body("/u/:/jdoe/posts/:123").must_equal 'jdoe123'
175
198
  status("/u/a/jdoe/post/b123").must_equal 404
@@ -286,6 +309,49 @@ describe "r.on" do
286
309
  body.must_equal '+1'
287
310
  end
288
311
 
312
+ it "does not execute on false" do
313
+ app do |r|
314
+ r.on false do
315
+ "+1"
316
+ end
317
+ end
318
+
319
+ status.must_equal 404
320
+ end
321
+
322
+ it "does not execute on nil" do
323
+ app do |r|
324
+ r.on nil do
325
+ "+1"
326
+ end
327
+ end
328
+
329
+ status.must_equal 404
330
+ end
331
+
332
+ it "executes on arbitrary object" do
333
+ app do |r|
334
+ r.on Object.new do
335
+ "+1"
336
+ end
337
+ end
338
+
339
+ body.must_equal '+1'
340
+ end
341
+
342
+ it "raises on arbitrary object if :unsupported_matcher => :raise" do
343
+ app(:bare) do
344
+ opts[:unsupported_matcher] = :raise
345
+ route do |r|
346
+ r.on Object.new do
347
+ "+1"
348
+ end
349
+ end
350
+ end
351
+
352
+ proc{body}.must_raise Roda::RodaError
353
+ end
354
+
289
355
  it "executes on non-false" do
290
356
  app do |r|
291
357
  r.on "123" do
@@ -700,3 +766,45 @@ describe "route block that returns string" do
700
766
  body.must_equal '+1'
701
767
  end
702
768
  end
769
+
770
+ describe "app with :unsupported_block_result => :raise option" do
771
+ def app_value(v)
772
+ app(:bare) do
773
+ opts[:unsupported_block_result] = :raise
774
+ route do |r|
775
+ r.is 'a' do v end
776
+ v
777
+ end
778
+ end
779
+ end
780
+
781
+ it "should handle String as body" do
782
+ app_value '1'
783
+ status.must_equal 200
784
+ body.must_equal '1'
785
+ status('/a').must_equal 200
786
+ body('/a').must_equal '1'
787
+ end
788
+
789
+ it "should handle nil and false as not found" do
790
+ app_value nil
791
+ status.must_equal 404
792
+ body.must_equal ''
793
+ status('/a').must_equal 404
794
+ body('/a').must_equal ''
795
+ end
796
+
797
+ it "should handle false as not found" do
798
+ app_value false
799
+ status.must_equal 404
800
+ body.must_equal ''
801
+ status('/a').must_equal 404
802
+ body('/a').must_equal ''
803
+ end
804
+
805
+ it "should raise RodaError for other types" do
806
+ app_value Object.new
807
+ proc{body}.must_raise Roda::RodaError
808
+ proc{body('/a')}.must_raise Roda::RodaError
809
+ end
810
+ end
@@ -38,6 +38,7 @@ describe 'response.finish' do
38
38
  it 'removes Content-Type and Content-Length for 304 responses' do
39
39
  app(:caching) do |r|
40
40
  response.status = 304
41
+ nil
41
42
  end
42
43
  header('Content-Type').must_be_nil
43
44
  header('Content-Length').must_be_nil
@@ -46,6 +47,7 @@ describe 'response.finish' do
46
47
  it 'does not change non-304 responses' do
47
48
  app(:caching) do |r|
48
49
  response.status = 200
50
+ nil
49
51
  end
50
52
  header('Content-Type').must_equal 'text/html'
51
53
  header('Content-Length').must_equal '0'
@@ -18,7 +18,7 @@ describe "class_level_routing plugin" do
18
18
  "foo"
19
19
  end
20
20
 
21
- is "d:d" do |x|
21
+ is "d", :d do |x|
22
22
  request.get do
23
23
  "bazget#{x}"
24
24
  end
@@ -42,8 +42,8 @@ describe "class_level_routing plugin" do
42
42
  body.must_equal 'root'
43
43
  body('/foo').must_equal 'foo'
44
44
  body('/foo/bar').must_equal 'foobar'
45
- body('/dgo').must_equal 'bazgetgo'
46
- body('/dgo', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
45
+ body('/d/go').must_equal 'bazgetgo'
46
+ body('/d/go', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
47
47
  body('/bar').must_equal "x-get-bar"
48
48
  body('/bar', 'REQUEST_METHOD'=>'POST').must_equal "x-post-bar"
49
49
  body('/bar', 'REQUEST_METHOD'=>'DELETE').must_equal "x-delete-bar"
@@ -64,8 +64,8 @@ describe "class_level_routing plugin" do
64
64
  body.must_equal 'root'
65
65
  body('/foo').must_equal 'foo'
66
66
  body('/foo/bar').must_equal 'foobar'
67
- body('/dgo').must_equal 'bazgetgo'
68
- body('/dgo', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
67
+ body('/d/go').must_equal 'bazgetgo'
68
+ body('/d/go', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
69
69
  body('/bar').must_equal "x-get-bar"
70
70
  body('/bar', 'REQUEST_METHOD'=>'POST').must_equal "x-post-bar"
71
71
  body('/bar', 'REQUEST_METHOD'=>'DELETE').must_equal "x-delete-bar"
@@ -100,8 +100,8 @@ describe "class_level_routing plugin" do
100
100
  body.must_equal 'iroot'
101
101
  body('/foo').must_equal 'ifoo'
102
102
  body('/foo/bar').must_equal 'foobar'
103
- body('/dgo').must_equal 'bazgetgo'
104
- body('/dgo', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
103
+ body('/d/go').must_equal 'bazgetgo'
104
+ body('/d/go', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
105
105
  body('/bar').must_equal ""
106
106
  body('/bar', 'REQUEST_METHOD'=>'POST').must_equal "ibar"
107
107
  body('/bar', 'REQUEST_METHOD'=>'DELETE').must_equal "x-delete-bar"
@@ -120,8 +120,8 @@ describe "class_level_routing plugin" do
120
120
  body.must_equal 'root'
121
121
  body('/foo').must_equal 'foo'
122
122
  body('/foo/bar').must_equal 'foobar'
123
- body('/dgo').must_equal 'bazgetgo'
124
- body('/dgo', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
123
+ body('/d/go').must_equal 'bazgetgo'
124
+ body('/d/go', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
125
125
  body('/bar').must_equal "x-get-bar"
126
126
  body('/bar', 'REQUEST_METHOD'=>'POST').must_equal "x-post-bar"
127
127
  body('/bar', 'REQUEST_METHOD'=>'DELETE').must_equal "x-delete-bar"
@@ -141,8 +141,8 @@ describe "class_level_routing plugin" do
141
141
  body.must_equal 'root'
142
142
  body('/foo').must_equal 'foo'
143
143
  body('/foo/bar').must_equal 'foobar'
144
- body('/dgo').must_equal 'bazgetgo'
145
- body('/dgo', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
144
+ body('/d/go').must_equal 'bazgetgo'
145
+ body('/d/go', 'REQUEST_METHOD'=>'POST').must_equal 'bazpostgo'
146
146
  body('/bar').must_equal "x-get-bar"
147
147
  body('/bar', 'REQUEST_METHOD'=>'POST').must_equal "x-post-bar"
148
148
  body('/bar', 'REQUEST_METHOD'=>'DELETE').must_equal "x-delete-bar"
@@ -145,7 +145,7 @@ describe "mailer plugin" do
145
145
 
146
146
  it "supports regular web requests in same application" do
147
147
  app(:mailer) do |r|
148
- r.get "foo/:bar" do |bar|
148
+ r.get "foo", :bar do |bar|
149
149
  "foo#{bar}"
150
150
  end
151
151
  r.mail "bar" do
@@ -11,25 +11,28 @@ describe "optimized_string_matchers plugin" do
11
11
  "ee"
12
12
  end
13
13
 
14
- r.on_branch "a/:b" do |*b|
15
- r.is_exactly ":c" do |*c|
16
- "c-#{c.length}"
14
+ r.on_branch "a" do
15
+ r.on_branch "b" do
16
+ r.is_exactly "c" do
17
+ "c"
18
+ end
19
+
20
+ "b"
17
21
  end
18
22
 
19
- "a-#{b.length}"
23
+ "a"
20
24
  end
21
25
 
22
26
  "cc"
23
27
  end
24
28
 
25
29
  body.must_equal 'cc'
26
- body('/a').must_equal 'cc'
27
- body('/a/').must_equal 'cc'
28
- body('/a/b/').must_equal 'a-0'
29
- body('/a/b/').must_equal 'a-0'
30
- body('/a/b/c').must_equal 'c-0'
31
- body('/a/b/c/').must_equal 'a-0'
32
- body('/a/b/c/d').must_equal 'a-0'
30
+ body('/a').must_equal 'a'
31
+ body('/a/').must_equal 'a'
32
+ body('/a/b/').must_equal 'b'
33
+ body('/a/b/c').must_equal 'c'
34
+ body('/a/b/c/').must_equal 'b'
35
+ body('/a/b/c/d').must_equal 'b'
33
36
  body('/e').must_equal 'ee'
34
37
  body('/eb').must_equal 'cc'
35
38
  body('/e/').must_equal 'ee'
@@ -1,16 +1,12 @@
1
1
  require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
2
 
3
3
  describe "params_capturing plugin" do
4
- it "should add captures to r.params" do
4
+ it "should add captures to r.params for symbol matchers" do
5
5
  app(:params_capturing) do |r|
6
- r.on('foo/:y/:z', :w) do |y, z, w|
6
+ r.on('foo', :y, :z, :w) do |y, z, w|
7
7
  (r.params.values_at('y', 'z', 'w') + [y, z, w, r[:captures].length]).join('-')
8
8
  end
9
9
 
10
- r.on("bar/:foo") do |foo|
11
- "b-#{foo}-#{r[:foo]}-#{r[:captures].length}"
12
- end
13
-
14
10
  r.on(/(quux)/, /(foo)(bar)/) do |q, foo, bar|
15
11
  "y-#{r[:captures].join}-#{q}-#{foo}-#{bar}"
16
12
  end
@@ -30,9 +26,24 @@ describe "params_capturing plugin" do
30
26
 
31
27
  body('/blarg', 'rack.input'=>StringIO.new).must_equal 'x-blarg-blarg-1'
32
28
  body('/foo/1/2/3', 'rack.input'=>StringIO.new).must_equal '1-2-3-1-2-3-3'
33
- body('/bar/banana', 'rack.input'=>StringIO.new).must_equal 'b-banana-banana-1'
34
29
  body('/quux/foobar', 'rack.input'=>StringIO.new).must_equal 'y-quuxfoobar-quux-foo-bar'
35
30
  body('/quux/asdf', 'rack.input'=>StringIO.new).must_equal 'y--quux-asdf-2'
36
31
  body('/quux/asdf/890', 'rack.input'=>StringIO.new).must_equal 'y--890-quux-asdf-890-3'
37
32
  end
33
+
34
+ it "should add captures to r.params for string matchers" do
35
+ app(:params_capturing) do |r|
36
+ r.on("bar/:foo") do |foo|
37
+ "b-#{foo}-#{r[:foo]}-#{r[:captures].length}"
38
+ end
39
+
40
+ r.on("baz/:bar", :foo) do |bar, foo|
41
+ "b-#{bar}-#{foo}-#{r[:bar]}-#{r[:foo]}-#{r[:captures].length}"
42
+ end
43
+ end
44
+ app.opts[:verbatim_string_matcher] = false
45
+
46
+ body('/bar/banana', 'rack.input'=>StringIO.new).must_equal 'b-banana-banana-1'
47
+ body('/baz/ban/ana', 'rack.input'=>StringIO.new).must_equal 'b-ban-ana-ban-ana-2'
48
+ end
38
49
  end
@@ -13,7 +13,7 @@ describe "pass plugin" do
13
13
  id
14
14
  end
15
15
 
16
- r.on ":x/:y" do |x, y|
16
+ r.on :x, :y do |x, y|
17
17
  x + y
18
18
  end
19
19
  end
@@ -507,7 +507,7 @@ describe "sinatra_helpers plugin" do
507
507
  end
508
508
 
509
509
  it 'logger logs to rack.logger' do
510
- sin_app{logger.info "foo"}
510
+ sin_app{logger.info "foo"; nil}
511
511
  o = Object.new
512
512
  def o.method_missing(*a)
513
513
  (@a ||= []) << a
@@ -1,7 +1,7 @@
1
1
  require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
2
 
3
3
  describe "symbol_matchers plugin" do
4
- it "allows symbol specific regexps" do
4
+ it "allows symbol specific regexps for symbol matchers" do
5
5
  app(:bare) do
6
6
  plugin :symbol_matchers
7
7
  symbol_matcher(:f, /(f+)/)
@@ -11,6 +11,55 @@ describe "symbol_matchers plugin" do
11
11
  "d#{d}"
12
12
  end
13
13
 
14
+ r.is "thing2", :thing do |d|
15
+ "thing2#{d}"
16
+ end
17
+
18
+ r.is :f do |f|
19
+ "f#{f}"
20
+ end
21
+
22
+ r.is 'q', :rest do |rest|
23
+ "rest#{rest}"
24
+ end
25
+
26
+ r.is :w do |w|
27
+ "w#{w}"
28
+ end
29
+
30
+ r.is :d, :w, :f do |d, w, f|
31
+ "dwf#{d}#{w}#{f}"
32
+ end
33
+ end
34
+ end
35
+
36
+ status.must_equal 404
37
+ body("/1").must_equal 'd1'
38
+ body("/11232135").must_equal 'd11232135'
39
+ body("/a").must_equal 'wa'
40
+ body("/1az0").must_equal 'w1az0'
41
+ body("/f").must_equal 'ff'
42
+ body("/ffffffffffffffff").must_equal 'fffffffffffffffff'
43
+ status("/-").must_equal 404
44
+ body("/1/1a/f").must_equal 'dwf11af'
45
+ body("/12/1azy/fffff").must_equal 'dwf121azyfffff'
46
+ status("/1/f/a").must_equal 404
47
+ body("/q/a/b/c/d//f/g").must_equal 'resta/b/c/d//f/g'
48
+ body('/q/').must_equal 'rest'
49
+ body('/thing2/q').must_equal 'thing2q'
50
+ end
51
+
52
+ it "works with placeholder string matchers" do
53
+ app(:bare) do
54
+ opts[:verbatim_string_matcher] = false
55
+ plugin :symbol_matchers
56
+ symbol_matcher(:f, /(f+)/)
57
+
58
+ route do |r|
59
+ r.is ":d" do |d|
60
+ "d#{d}"
61
+ end
62
+
14
63
  r.is "foo:optd" do |o|
15
64
  "foo#{o.inspect}"
16
65
  end
@@ -27,11 +76,11 @@ describe "symbol_matchers plugin" do
27
76
  "thing#{d}"
28
77
  end
29
78
 
30
- r.is "thing2", :thing do |d|
79
+ r.is "thing2", ":thing" do |d|
31
80
  "thing2#{d}"
32
81
  end
33
82
 
34
- r.is :f do |f|
83
+ r.is ":f" do |f|
35
84
  "f#{f}"
36
85
  end
37
86
 
@@ -39,7 +88,7 @@ describe "symbol_matchers plugin" do
39
88
  "rest#{rest}"
40
89
  end
41
90
 
42
- r.is :w do |w|
91
+ r.is ":w" do |w|
43
92
  "w#{w}"
44
93
  end
45
94
 
@@ -5,6 +5,7 @@ describe "symbol_status plugin" do
5
5
  app(:symbol_status) do |r|
6
6
  r.on do
7
7
  response.status = :unauthorized
8
+ nil
8
9
  end
9
10
  end
10
11
 
@@ -15,6 +16,7 @@ describe "symbol_status plugin" do
15
16
  app(:symbol_status) do |r|
16
17
  r.on do
17
18
  response.status = 204
19
+ nil
18
20
  end
19
21
  end
20
22
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.21.0
4
+ version: 2.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-16 00:00:00.000000000 Z
11
+ date: 2017-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -201,6 +201,7 @@ extra_rdoc_files:
201
201
  - doc/release_notes/2.19.0.txt
202
202
  - doc/release_notes/2.20.0.txt
203
203
  - doc/release_notes/2.21.0.txt
204
+ - doc/release_notes/2.22.0.txt
204
205
  files:
205
206
  - CHANGELOG
206
207
  - MIT-LICENSE
@@ -226,6 +227,7 @@ files:
226
227
  - doc/release_notes/2.2.0.txt
227
228
  - doc/release_notes/2.20.0.txt
228
229
  - doc/release_notes/2.21.0.txt
230
+ - doc/release_notes/2.22.0.txt
229
231
  - doc/release_notes/2.3.0.txt
230
232
  - doc/release_notes/2.4.0.txt
231
233
  - doc/release_notes/2.5.0.txt
@@ -440,7 +442,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
440
442
  version: '0'
441
443
  requirements: []
442
444
  rubyforge_project:
443
- rubygems_version: 2.5.2
445
+ rubygems_version: 2.6.8
444
446
  signing_key:
445
447
  specification_version: 4
446
448
  summary: Routing tree web toolkit