mustermann19 0.3.1

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.
Files changed (69) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +22 -0
  7. data/README.md +1081 -0
  8. data/Rakefile +6 -0
  9. data/bench/capturing.rb +57 -0
  10. data/bench/regexp.rb +21 -0
  11. data/bench/simple_vs_sinatra.rb +23 -0
  12. data/bench/template_vs_addressable.rb +26 -0
  13. data/internals.md +64 -0
  14. data/lib/mustermann.rb +61 -0
  15. data/lib/mustermann/ast/compiler.rb +168 -0
  16. data/lib/mustermann/ast/expander.rb +134 -0
  17. data/lib/mustermann/ast/node.rb +160 -0
  18. data/lib/mustermann/ast/parser.rb +137 -0
  19. data/lib/mustermann/ast/pattern.rb +84 -0
  20. data/lib/mustermann/ast/transformer.rb +129 -0
  21. data/lib/mustermann/ast/translator.rb +108 -0
  22. data/lib/mustermann/ast/tree_renderer.rb +29 -0
  23. data/lib/mustermann/ast/validation.rb +43 -0
  24. data/lib/mustermann/caster.rb +117 -0
  25. data/lib/mustermann/equality_map.rb +48 -0
  26. data/lib/mustermann/error.rb +6 -0
  27. data/lib/mustermann/expander.rb +206 -0
  28. data/lib/mustermann/extension.rb +52 -0
  29. data/lib/mustermann/identity.rb +19 -0
  30. data/lib/mustermann/mapper.rb +98 -0
  31. data/lib/mustermann/pattern.rb +182 -0
  32. data/lib/mustermann/rails.rb +17 -0
  33. data/lib/mustermann/regexp_based.rb +30 -0
  34. data/lib/mustermann/regular.rb +26 -0
  35. data/lib/mustermann/router.rb +9 -0
  36. data/lib/mustermann/router/rack.rb +50 -0
  37. data/lib/mustermann/router/simple.rb +144 -0
  38. data/lib/mustermann/shell.rb +29 -0
  39. data/lib/mustermann/simple.rb +38 -0
  40. data/lib/mustermann/simple_match.rb +30 -0
  41. data/lib/mustermann/sinatra.rb +22 -0
  42. data/lib/mustermann/template.rb +48 -0
  43. data/lib/mustermann/to_pattern.rb +45 -0
  44. data/lib/mustermann/version.rb +3 -0
  45. data/mustermann.gemspec +31 -0
  46. data/spec/expander_spec.rb +105 -0
  47. data/spec/extension_spec.rb +296 -0
  48. data/spec/identity_spec.rb +83 -0
  49. data/spec/mapper_spec.rb +83 -0
  50. data/spec/mustermann_spec.rb +65 -0
  51. data/spec/pattern_spec.rb +49 -0
  52. data/spec/rails_spec.rb +522 -0
  53. data/spec/regexp_based_spec.rb +8 -0
  54. data/spec/regular_spec.rb +36 -0
  55. data/spec/router/rack_spec.rb +39 -0
  56. data/spec/router/simple_spec.rb +32 -0
  57. data/spec/shell_spec.rb +109 -0
  58. data/spec/simple_match_spec.rb +10 -0
  59. data/spec/simple_spec.rb +237 -0
  60. data/spec/sinatra_spec.rb +574 -0
  61. data/spec/support.rb +5 -0
  62. data/spec/support/coverage.rb +16 -0
  63. data/spec/support/env.rb +15 -0
  64. data/spec/support/expand_matcher.rb +27 -0
  65. data/spec/support/match_matcher.rb +39 -0
  66. data/spec/support/pattern.rb +39 -0
  67. data/spec/template_spec.rb +815 -0
  68. data/spec/to_pattern_spec.rb +20 -0
  69. metadata +301 -0
@@ -0,0 +1,574 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/sinatra'
4
+
5
+ describe Mustermann::Sinatra do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+ end
12
+
13
+ pattern '/' do
14
+ it { should match('/') }
15
+ it { should_not match('/foo') }
16
+ end
17
+
18
+ pattern '/foo' do
19
+ it { should match('/foo') }
20
+ it { should_not match('/bar') }
21
+ it { should_not match('/foo.bar') }
22
+ end
23
+
24
+ pattern '/foo/bar' do
25
+ it { should match('/foo/bar') }
26
+ it { should_not match('/foo%2Fbar') }
27
+ it { should_not match('/foo%2fbar') }
28
+ end
29
+
30
+ pattern '/foo\/bar' do
31
+ it { should match('/foo/bar') }
32
+ it { should match('/foo%2Fbar') }
33
+ it { should match('/foo%2fbar') }
34
+ end
35
+
36
+ pattern '/:foo' do
37
+ it { should match('/foo') .capturing foo: 'foo' }
38
+ it { should match('/bar') .capturing foo: 'bar' }
39
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
40
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
41
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
42
+
43
+ it { should_not match('/foo?') }
44
+ it { should_not match('/foo/bar') }
45
+ it { should_not match('/') }
46
+ it { should_not match('/foo/') }
47
+ end
48
+
49
+ pattern '/föö' do
50
+ it { should match("/f%C3%B6%C3%B6") }
51
+ end
52
+
53
+ pattern "/:foo/:bar" do
54
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
55
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
56
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
57
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
58
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
59
+
60
+ it { should_not match('/foo%2Fbar') }
61
+ it { should_not match('/foo%2fbar') }
62
+
63
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
64
+ example { pattern.params('').should be_nil }
65
+ end
66
+
67
+ pattern '/hello/:person' do
68
+ it { should match('/hello/Frank').capturing person: 'Frank' }
69
+ end
70
+
71
+ pattern '/?:foo?/?:bar?' do
72
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
73
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
74
+ it { should match('/') .capturing foo: nil, bar: nil }
75
+ it { should match('') .capturing foo: nil, bar: nil }
76
+
77
+ it { should_not match('/hello/world/') }
78
+
79
+ # it { should expand(foo: 'hello') .to('/hello') }
80
+ # it { should expand(foo: 'hello', bar: 'world') .to('/hello/world') }
81
+ # it { should expand(bar: 'world') .to('//world') }
82
+ # it { should expand .to('') }
83
+ # it { should_not expand(baz: '') }
84
+ end
85
+
86
+ pattern '/:foo_bar' do
87
+ it { should match('/hello').capturing foo_bar: 'hello' }
88
+ end
89
+
90
+ pattern '/*' do
91
+ it { should match('/') .capturing splat: '' }
92
+ it { should match('/foo') .capturing splat: 'foo' }
93
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
94
+
95
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
96
+ end
97
+
98
+ pattern '/*foo' do
99
+ it { should match('/') .capturing foo: '' }
100
+ it { should match('/foo') .capturing foo: 'foo' }
101
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
102
+
103
+ example { pattern.params('/foo') .should be == {"foo" => "foo" } }
104
+ example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
105
+ end
106
+
107
+ pattern '/*foo/*bar' do
108
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
109
+ end
110
+
111
+ pattern '/:foo/*' do
112
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
113
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
114
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
115
+ it { should_not match('/foo') }
116
+
117
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
118
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
119
+ end
120
+
121
+ pattern '/test$/' do
122
+ it { should match('/test$/') }
123
+ end
124
+
125
+ pattern '/te+st/' do
126
+ it { should match('/te+st/') }
127
+ it { should_not match('/test/') }
128
+ it { should_not match('/teest/') }
129
+ end
130
+
131
+ pattern "/path with spaces" do
132
+ it { should match('/path%20with%20spaces') }
133
+ it { should match('/path%2Bwith%2Bspaces') }
134
+ it { should match('/path+with+spaces') }
135
+ end
136
+
137
+ pattern '/foo&bar' do
138
+ it { should match('/foo&bar') }
139
+ end
140
+
141
+ pattern '/*/:foo/*/*' do
142
+ it { should match('/bar/foo/bling/baz/boom') }
143
+
144
+ it "should capture all splat parts" do
145
+ match = pattern.match('/bar/foo/bling/baz/boom')
146
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
147
+ match.names.should be == ['splat', 'foo']
148
+ end
149
+
150
+ it 'should map to proper params' do
151
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
152
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
153
+ }
154
+ end
155
+ end
156
+
157
+ pattern '/test.bar' do
158
+ it { should match('/test.bar') }
159
+ it { should_not match('/test0bar') }
160
+ end
161
+
162
+ pattern '/:file.:ext' do
163
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
164
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
165
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
166
+
167
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
168
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
169
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
170
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
171
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
172
+
173
+ it { should_not match('/.jpg') }
174
+ end
175
+
176
+ pattern '/(:a)x?' do
177
+ it { should match('/a') .capturing a: 'a' }
178
+ it { should match('/xa') .capturing a: 'xa' }
179
+ it { should match('/axa') .capturing a: 'axa' }
180
+ it { should match('/ax') .capturing a: 'a' }
181
+ it { should match('/axax') .capturing a: 'axa' }
182
+ it { should match('/axaxx') .capturing a: 'axax' }
183
+ end
184
+
185
+ pattern '/:user(@:host)?' do
186
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
187
+ it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
188
+ it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
189
+ end
190
+
191
+ pattern '/:file(.:ext)?' do
192
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
193
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
194
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
195
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
196
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
197
+ it { should match('/pony.') .capturing file: 'pony.' }
198
+ it { should_not match('/.jpg') }
199
+ end
200
+
201
+ pattern '/:id/test.bar' do
202
+ it { should match('/3/test.bar') .capturing id: '3' }
203
+ it { should match('/2/test.bar') .capturing id: '2' }
204
+ it { should match('/2E/test.bar') .capturing id: '2E' }
205
+ it { should match('/2e/test.bar') .capturing id: '2e' }
206
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
207
+ end
208
+
209
+ pattern '/10/:id' do
210
+ it { should match('/10/test') .capturing id: 'test' }
211
+ it { should match('/10/te.st') .capturing id: 'te.st' }
212
+ end
213
+
214
+ pattern '/10.1/:id' do
215
+ it { should match('/10.1/test') .capturing id: 'test' }
216
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
217
+ end
218
+
219
+ pattern '/:foo.:bar/:id' do
220
+ it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
221
+ it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
222
+ end
223
+
224
+ pattern '/:a/:b.?:c?' do
225
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
226
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
227
+ it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
228
+ it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
229
+ it { should_not match('/a.b/c.d/e') }
230
+ end
231
+
232
+ pattern '/:a(foo:b)?' do
233
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
234
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
235
+ it { should match('/bar') .capturing a: 'bar', b: nil }
236
+ it { should_not match('/') }
237
+ end
238
+
239
+ pattern '/foo?' do
240
+ it { should match('/fo') }
241
+ it { should match('/foo') }
242
+ it { should_not match('') }
243
+ it { should_not match('/') }
244
+ it { should_not match('/f') }
245
+ it { should_not match('/fooo') }
246
+ end
247
+
248
+ pattern '/foo\?' do
249
+ it { should match('/foo?') }
250
+ it { should_not match('/foo\?') }
251
+ it { should_not match('/fo') }
252
+ it { should_not match('/foo') }
253
+ it { should_not match('') }
254
+ it { should_not match('/') }
255
+ it { should_not match('/f') }
256
+ it { should_not match('/fooo') }
257
+ end
258
+
259
+ pattern '/foo\\\?' do
260
+ it { should match('/foo%5c') }
261
+ it { should match('/foo') }
262
+ it { should_not match('/foo\?') }
263
+ it { should_not match('/fo') }
264
+ it { should_not match('') }
265
+ it { should_not match('/') }
266
+ it { should_not match('/f') }
267
+ it { should_not match('/fooo') }
268
+ end
269
+
270
+ pattern '/\(' do
271
+ it { should match('/(') }
272
+ end
273
+
274
+ pattern '/\(?' do
275
+ it { should match('/(') }
276
+ it { should match('/') }
277
+ end
278
+
279
+ pattern '/(\()?' do
280
+ it { should match('/(') }
281
+ it { should match('/') }
282
+ end
283
+
284
+ pattern '/(\(\))?' do
285
+ it { should match('/') }
286
+ it { should match('/()') }
287
+ it { should_not match('/(') }
288
+ end
289
+
290
+ pattern '/\(\)?' do
291
+ it { should match('/(') }
292
+ it { should match('/()') }
293
+ it { should_not match('/') }
294
+ end
295
+
296
+ pattern '/\*' do
297
+ it { should match('/*') }
298
+ it { should_not match('/a') }
299
+ end
300
+
301
+ pattern '/\*/*' do
302
+ it { should match('/*/b/c') }
303
+ it { should_not match('/a/b/c') }
304
+ end
305
+
306
+ pattern '/\:foo' do
307
+ it { should match('/:foo') }
308
+ it { should_not match('/foo') }
309
+ end
310
+
311
+ pattern '/:fOO' do
312
+ it { should match('/a').capturing fOO: 'a' }
313
+ end
314
+
315
+ pattern '/:_X' do
316
+ it { should match('/a').capturing _X: 'a' }
317
+ end
318
+
319
+ pattern '/:f00' do
320
+ it { should match('/a').capturing f00: 'a' }
321
+ end
322
+
323
+ pattern '/:foo(/:bar)?/:baz?' do
324
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
325
+ end
326
+
327
+ pattern '/:foo', capture: /\d+/ do
328
+ it { should match('/1') .capturing foo: '1' }
329
+ it { should match('/123') .capturing foo: '123' }
330
+
331
+ it { should_not match('/') }
332
+ it { should_not match('/foo') }
333
+ end
334
+
335
+ pattern '/:foo', capture: /\d+/ do
336
+ it { should match('/1') .capturing foo: '1' }
337
+ it { should match('/123') .capturing foo: '123' }
338
+
339
+ it { should_not match('/') }
340
+ it { should_not match('/foo') }
341
+ end
342
+
343
+ pattern '/:foo', capture: '1' do
344
+ it { should match('/1').capturing foo: '1' }
345
+
346
+ it { should_not match('/') }
347
+ it { should_not match('/foo') }
348
+ it { should_not match('/123') }
349
+ end
350
+
351
+ pattern '/:foo', capture: 'a.b' do
352
+ it { should match('/a.b') .capturing foo: 'a.b' }
353
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
354
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
355
+
356
+ it { should_not match('/ab') }
357
+ it { should_not match('/afb') }
358
+ it { should_not match('/a1b') }
359
+ it { should_not match('/a.bc') }
360
+ end
361
+
362
+ pattern '/:foo(/:bar)?', capture: :alpha do
363
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
364
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
365
+ it { should match('/a') .capturing foo: 'a', bar: nil }
366
+
367
+ it { should_not match('/1/2') }
368
+ it { should_not match('/a/2') }
369
+ it { should_not match('/1/b') }
370
+ it { should_not match('/1') }
371
+ it { should_not match('/1/') }
372
+ it { should_not match('/a/') }
373
+ it { should_not match('//a') }
374
+ end
375
+
376
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
377
+ it { should match('/1') .capturing foo: '1' }
378
+ it { should match('/123') .capturing foo: '123' }
379
+ it { should match('/foo') .capturing foo: 'foo' }
380
+ it { should match('/bar') .capturing foo: 'bar' }
381
+
382
+ it { should_not match('/') }
383
+ it { should_not match('/baz') }
384
+ it { should_not match('/foo1') }
385
+ end
386
+
387
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
388
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
389
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
390
+ it { should_not match('/123abcxy-1') }
391
+ it { should_not match('/abcxy-1') }
392
+ it { should_not match('/abc1') }
393
+ end
394
+
395
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
396
+ it { should match('/1') .capturing foo: '1' }
397
+ it { should match('/123') .capturing foo: '123' }
398
+ it { should match('/foo') .capturing foo: 'foo' }
399
+ it { should match('/bar') .capturing foo: 'bar' }
400
+
401
+ it { should_not match('/') }
402
+ it { should_not match('/baz') }
403
+ it { should_not match('/foo1') }
404
+ end
405
+
406
+ pattern '/:file(.:ext)?', capture: { ext: ['jpg', 'png'] } do
407
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
408
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
409
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
410
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
411
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
412
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
413
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
414
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
415
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
416
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
417
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
418
+ it { should_not match('.jpg') }
419
+ end
420
+
421
+ pattern '/:file:ext?', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
422
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
423
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
424
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
425
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
426
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
427
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
428
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
429
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
430
+ it { should_not match('/.jpg') }
431
+ end
432
+
433
+ pattern '/:a(@:b)?', capture: { b: /\d+/ } do
434
+ it { should match('/a') .capturing a: 'a', b: nil }
435
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
436
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
437
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
438
+ end
439
+
440
+ pattern '/(:a)b?', greedy: false do
441
+ it { should match('/ab').capturing a: 'a' }
442
+ end
443
+
444
+ pattern '/:file(.:ext)?', greedy: false do
445
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
446
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
447
+ it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
448
+ end
449
+
450
+ pattern '/auth/*', except: '/auth/login' do
451
+ it { should match('/auth/admin') }
452
+ it { should match('/auth/foobar') }
453
+ it { should_not match('/auth/login') }
454
+ end
455
+
456
+ pattern '/:foo/:bar', except: '/:bar/20' do
457
+ it { should match('/foo/bar').capturing foo: 'foo', bar: 'bar' }
458
+ it { should_not match('/20/20') }
459
+ end
460
+
461
+ pattern '/foo?', uri_decode: false do
462
+ it { should match('/foo') }
463
+ it { should match('/fo') }
464
+ it { should_not match('/foo?') }
465
+ end
466
+
467
+ pattern '/foo/bar', uri_decode: false do
468
+ it { should match('/foo/bar') }
469
+ it { should_not match('/foo%2Fbar') }
470
+ it { should_not match('/foo%2fbar') }
471
+ end
472
+
473
+ pattern "/path with spaces", uri_decode: false do
474
+ it { should match('/path with spaces') }
475
+ it { should_not match('/path%20with%20spaces') }
476
+ it { should_not match('/path%2Bwith%2Bspaces') }
477
+ it { should_not match('/path+with+spaces') }
478
+ end
479
+
480
+ pattern "/path with spaces", space_matches_plus: false do
481
+ it { should match('/path%20with%20spaces') }
482
+ it { should_not match('/path%2Bwith%2Bspaces') }
483
+ it { should_not match('/path+with+spaces') }
484
+ end
485
+
486
+ context 'invalid syntax' do
487
+ example 'unexpected closing parenthesis' do
488
+ expect { Mustermann::Sinatra.new('foo)bar') }.
489
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
490
+ end
491
+
492
+ example 'missing closing parenthesis' do
493
+ expect { Mustermann::Sinatra.new('foo(bar') }.
494
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
495
+ end
496
+
497
+ example 'missing unescaped closing parenthesis' do
498
+ expect { Mustermann::Sinatra.new('foo(bar\)') }.
499
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar\\\\)"')
500
+ end
501
+
502
+ example '? at beginning of route' do
503
+ expect { Mustermann::Sinatra.new('?foobar') }.
504
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "?foobar"')
505
+ end
506
+
507
+ example 'double ?' do
508
+ expect { Mustermann::Sinatra.new('foo??bar') }.
509
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
510
+ end
511
+
512
+ example 'dangling escape' do
513
+ expect { Mustermann::Sinatra.new('foo\\') }.
514
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')
515
+ end
516
+ end
517
+
518
+ context 'invalid capture names' do
519
+ example 'empty name' do
520
+ expect { Mustermann::Sinatra.new('/:/') }.
521
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
522
+ end
523
+
524
+ example 'named splat' do
525
+ expect { Mustermann::Sinatra.new('/:splat/') }.
526
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
527
+ end
528
+
529
+ example 'named captures' do
530
+ expect { Mustermann::Sinatra.new('/:captures/') }.
531
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
532
+ end
533
+
534
+ example 'with capital letter' do
535
+ expect { Mustermann::Sinatra.new('/:Foo/') }.
536
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
537
+ end
538
+
539
+ example 'with integer' do
540
+ expect { Mustermann::Sinatra.new('/:1a/') }.
541
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
542
+ end
543
+
544
+ example 'same name twice' do
545
+ expect { Mustermann::Sinatra.new('/:foo(/:bar)?/:bar?') }.
546
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)?/:bar?\"")
547
+ end
548
+ end
549
+
550
+ context 'Regexp compatibility' do
551
+ describe :=== do
552
+ example('non-matching') { Mustermann::Sinatra.new("/") .should_not be === '/foo' }
553
+ example('matching') { Mustermann::Sinatra.new("/:foo") .should be === '/foo' }
554
+ end
555
+
556
+ describe :=~ do
557
+ example('non-matching') { Mustermann::Sinatra.new("/") .should_not be =~ '/foo' }
558
+ example('matching') { Mustermann::Sinatra.new("/:foo") .should be =~ '/foo' }
559
+
560
+ context 'String#=~' do
561
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Sinatra.new("/") }
562
+ example('matching') { "/foo".should be =~ Mustermann::Sinatra.new("/:foo") }
563
+ end
564
+ end
565
+
566
+ describe :to_regexp do
567
+ example('empty pattern') { Mustermann::Sinatra.new('').to_regexp.should be == /\A\Z/ }
568
+
569
+ context 'Regexp.try_convert' do
570
+ example('empty pattern') { Regexp.try_convert(Mustermann::Sinatra.new('')).should be == /\A\Z/ }
571
+ end
572
+ end
573
+ end
574
+ end