mustermann-contrib 1.0.0.beta2

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1239 -0
  3. data/examples/highlighting.rb +35 -0
  4. data/highlighting.png +0 -0
  5. data/irb.png +0 -0
  6. data/lib/mustermann/cake.rb +18 -0
  7. data/lib/mustermann/express.rb +37 -0
  8. data/lib/mustermann/file_utils.rb +217 -0
  9. data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
  10. data/lib/mustermann/fileutils.rb +1 -0
  11. data/lib/mustermann/flask.rb +198 -0
  12. data/lib/mustermann/grape.rb +35 -0
  13. data/lib/mustermann/pyramid.rb +28 -0
  14. data/lib/mustermann/rails.rb +46 -0
  15. data/lib/mustermann/shell.rb +56 -0
  16. data/lib/mustermann/simple.rb +50 -0
  17. data/lib/mustermann/string_scanner.rb +313 -0
  18. data/lib/mustermann/strscan.rb +1 -0
  19. data/lib/mustermann/template.rb +62 -0
  20. data/lib/mustermann/uri_template.rb +1 -0
  21. data/lib/mustermann/versions.rb +46 -0
  22. data/lib/mustermann/visualizer.rb +38 -0
  23. data/lib/mustermann/visualizer/highlight.rb +137 -0
  24. data/lib/mustermann/visualizer/highlighter.rb +37 -0
  25. data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
  26. data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
  27. data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
  28. data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
  29. data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
  30. data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
  31. data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
  32. data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
  33. data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
  34. data/lib/mustermann/visualizer/renderer/html.rb +50 -0
  35. data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
  36. data/lib/mustermann/visualizer/tree.rb +63 -0
  37. data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
  38. data/mustermann-contrib.gemspec +19 -0
  39. data/spec/cake_spec.rb +90 -0
  40. data/spec/express_spec.rb +209 -0
  41. data/spec/file_utils_spec.rb +119 -0
  42. data/spec/flask_spec.rb +361 -0
  43. data/spec/flask_subclass_spec.rb +368 -0
  44. data/spec/grape_spec.rb +747 -0
  45. data/spec/pattern_extension_spec.rb +49 -0
  46. data/spec/pyramid_spec.rb +101 -0
  47. data/spec/rails_spec.rb +647 -0
  48. data/spec/shell_spec.rb +147 -0
  49. data/spec/simple_spec.rb +268 -0
  50. data/spec/string_scanner_spec.rb +271 -0
  51. data/spec/template_spec.rb +841 -0
  52. data/spec/visualizer_spec.rb +199 -0
  53. data/theme.png +0 -0
  54. data/tree.png +0 -0
  55. metadata +126 -0
@@ -0,0 +1,747 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/grape'
4
+
5
+ describe Mustermann::Grape do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should generate_template('') }
13
+
14
+ it { should respond_to(:expand) }
15
+ it { should respond_to(:to_templates) }
16
+ end
17
+
18
+ pattern '/' do
19
+ it { should match('/') }
20
+ it { should_not match('/foo') }
21
+ end
22
+
23
+ pattern '/foo' do
24
+ it { should match('/foo') }
25
+ it { should_not match('/bar') }
26
+ it { should_not match('/foo.bar') }
27
+ end
28
+
29
+ pattern '/foo/bar' do
30
+ it { should match('/foo/bar') }
31
+ it { should_not match('/foo%2Fbar') }
32
+ it { should_not match('/foo%2fbar') }
33
+ end
34
+
35
+ pattern '/foo\/bar' do
36
+ it { should match('/foo/bar') }
37
+ it { should match('/foo%2Fbar') }
38
+ it { should match('/foo%2fbar') }
39
+ end
40
+
41
+ pattern '/:foo' do
42
+ it { should match('/foo') .capturing foo: 'foo' }
43
+ it { should match('/bar') .capturing foo: 'bar' }
44
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
45
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
46
+
47
+ it { should_not match('/foo.bar') }
48
+ it { should_not match('/foo?') }
49
+ it { should_not match('/foo/bar') }
50
+ it { should_not match('/') }
51
+ it { should_not match('/foo/') }
52
+
53
+ it { should generate_template('/{foo}') }
54
+ end
55
+
56
+ pattern '/föö' do
57
+ it { should match("/f%C3%B6%C3%B6") }
58
+ end
59
+
60
+ pattern "/:foo/:bar" do
61
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
62
+ it { should_not match('/foo.bar/bar.foo') }
63
+ it { should_not match('/user@example.com/name') }
64
+ it { should_not match('/10.1/te.st') }
65
+ it { should_not match('/10.1.2/te.st') }
66
+
67
+ it { should_not match('/foo%2Fbar') }
68
+ it { should_not match('/foo%2fbar') }
69
+
70
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
71
+ example { pattern.params('').should be_nil }
72
+
73
+ it { should generate_template('/{foo}/{bar}') }
74
+ end
75
+
76
+ pattern "/{foo}/{bar}" do
77
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
78
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
79
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
80
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
81
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
82
+
83
+ it { should_not match('/foo%2Fbar') }
84
+ it { should_not match('/foo%2fbar') }
85
+
86
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
87
+ example { pattern.params('').should be_nil }
88
+
89
+ it { should generate_template('/{foo}/{bar}') }
90
+ end
91
+
92
+ pattern '/hello/:person' do
93
+ it { should match('/hello/Frank').capturing person: 'Frank' }
94
+ it { should generate_template('/hello/{person}') }
95
+ end
96
+
97
+ pattern '/hello/{person}' do
98
+ it { should match('/hello/Frank').capturing person: 'Frank' }
99
+ it { should generate_template('/hello/{person}') }
100
+ end
101
+
102
+ pattern '/?:foo?/?:bar?' do
103
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
104
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
105
+ it { should match('/') .capturing foo: nil, bar: nil }
106
+ it { should match('') .capturing foo: nil, bar: nil }
107
+
108
+ it { should expand(foo: 'hello') .to('/hello/') }
109
+ it { should expand(foo: 'hello', bar: 'world') .to('/hello/world') }
110
+ it { should expand(bar: 'world') .to('//world') }
111
+ it { should expand .to('//') }
112
+ it { should_not expand(baz: '') }
113
+
114
+ it { should_not match('/hello/world/') }
115
+ it { should generate_templates("", "/", "//", "//{bar}", "/{bar}", "/{foo}", "/{foo}/", "/{foo}/{bar}", "/{foo}{bar}", "{bar}", "{foo}", "{foo}/", "{foo}/{bar}", "{foo}{bar}") }
116
+ end
117
+
118
+ pattern '/:foo_bar' do
119
+ it { should match('/hello').capturing foo_bar: 'hello' }
120
+ it { should generate_template('/{foo_bar}') }
121
+ end
122
+
123
+ pattern '/{foo.bar}' do
124
+ it { should match('/hello').capturing :"foo.bar" => 'hello' }
125
+ it { should generate_template('/{foo.bar}') }
126
+ end
127
+
128
+ pattern '/*' do
129
+ it { should match('/') .capturing splat: '' }
130
+ it { should match('/foo') .capturing splat: 'foo' }
131
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
132
+ it { should generate_template('/{+splat}') }
133
+
134
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
135
+ end
136
+
137
+ pattern '/{+splat}' do
138
+ it { should match('/') .capturing splat: '' }
139
+ it { should match('/foo') .capturing splat: 'foo' }
140
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
141
+ it { should generate_template('/{+splat}') }
142
+
143
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
144
+ end
145
+
146
+ pattern '/*foo' do
147
+ it { should match('/') .capturing foo: '' }
148
+ it { should match('/foo') .capturing foo: 'foo' }
149
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
150
+ it { should generate_template('/{+foo}') }
151
+
152
+ example { pattern.params('/foo') .should be == {"foo" => "foo" } }
153
+ example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
154
+ end
155
+
156
+ pattern '/{+foo}' do
157
+ it { should match('/') .capturing foo: '' }
158
+ it { should match('/foo') .capturing foo: 'foo' }
159
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
160
+ it { should generate_template('/{+foo}') }
161
+
162
+ example { pattern.params('/foo') .should be == {"foo" => "foo" } }
163
+ example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
164
+ end
165
+
166
+ pattern '/*foo/*bar' do
167
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
168
+ it { should generate_template('/{+foo}/{+bar}') }
169
+ end
170
+
171
+ pattern '/{+foo}/{+bar}' do
172
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
173
+ it { should generate_template('/{+foo}/{+bar}') }
174
+ end
175
+
176
+ pattern '/:foo/*' do
177
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
178
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
179
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
180
+ it { should_not match('/foo') }
181
+ it { should generate_template('/{foo}/{+splat}') }
182
+
183
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
184
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
185
+ end
186
+
187
+ pattern '/{foo}/*' do
188
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
189
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
190
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
191
+ it { should_not match('/foo') }
192
+ it { should generate_template('/{foo}/{+splat}') }
193
+
194
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
195
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
196
+ end
197
+
198
+ pattern '/test$/' do
199
+ it { should match('/test$/') }
200
+ end
201
+
202
+ pattern '/te+st/' do
203
+ it { should match('/te+st/') }
204
+ it { should_not match('/test/') }
205
+ it { should_not match('/teest/') }
206
+ end
207
+
208
+ pattern "/path with spaces" do
209
+ it { should match('/path%20with%20spaces') }
210
+ it { should match('/path%2Bwith%2Bspaces') }
211
+ it { should match('/path+with+spaces') }
212
+
213
+ it { should generate_template('/path%20with%20spaces') }
214
+ end
215
+
216
+ pattern '/foo&bar' do
217
+ it { should match('/foo&bar') }
218
+ end
219
+
220
+ pattern '/foo\{bar' do
221
+ it { should match('/foo%7Bbar') }
222
+ end
223
+
224
+ pattern '/*/:foo/*/*' do
225
+ it { should match('/bar/foo/bling/baz/boom') }
226
+
227
+ it "should capture all splat parts" do
228
+ match = pattern.match('/bar/foo/bling/baz/boom')
229
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
230
+ match.names.should be == ['splat', 'foo']
231
+ end
232
+
233
+ it 'should map to proper params' do
234
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
235
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
236
+ }
237
+ end
238
+ end
239
+
240
+ pattern '/{+splat}/{foo}/{+splat}/{+splat}' do
241
+ it { should match('/bar/foo/bling/baz/boom') }
242
+
243
+ it "should capture all splat parts" do
244
+ match = pattern.match('/bar/foo/bling/baz/boom')
245
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
246
+ match.names.should be == ['splat', 'foo']
247
+ end
248
+
249
+ it 'should map to proper params' do
250
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
251
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
252
+ }
253
+ end
254
+ end
255
+
256
+ pattern '/test.bar' do
257
+ it { should match('/test.bar') }
258
+ it { should_not match('/test0bar') }
259
+ end
260
+
261
+ pattern '/:file.:ext' do
262
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
263
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
264
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
265
+
266
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
267
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
268
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
269
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
270
+
271
+ it { should_not match('/pony正..jpg') }
272
+ it { should_not match('/.jpg') }
273
+ end
274
+
275
+ pattern '/(:a)x?' do
276
+ it { should match('/a') .capturing a: 'a' }
277
+ it { should match('/xa') .capturing a: 'xa' }
278
+ it { should match('/axa') .capturing a: 'axa' }
279
+
280
+ it { should generate_template('/{a}x') }
281
+ it { should generate_template('/{a}') }
282
+ end
283
+
284
+ pattern '/:user(@:host)?' do
285
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
286
+ it { should_not match('/foo.foo@bar') }
287
+ it { should_not match('/foo@bar.bar') }
288
+
289
+ it { should generate_template('/{user}') }
290
+ it { should generate_template('/{user}@{host}') }
291
+ end
292
+
293
+ pattern '/:file(.:ext)?' do
294
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
295
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
296
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
297
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
298
+ it { should_not match('/pony.png.jpg') }
299
+ it { should_not match('/pony.') }
300
+ it { should_not match('/.jpg') }
301
+
302
+ it { should generate_template('/{file}') }
303
+ it { should generate_template('/{file}.{ext}') }
304
+ it { should_not generate_template('/{file}.') }
305
+ end
306
+
307
+ pattern '/:id/test.bar' do
308
+ it { should match('/3/test.bar') .capturing id: '3' }
309
+ it { should match('/2/test.bar') .capturing id: '2' }
310
+ it { should match('/2E/test.bar') .capturing id: '2E' }
311
+ it { should match('/2e/test.bar') .capturing id: '2e' }
312
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
313
+ end
314
+
315
+ pattern '/10/:id' do
316
+ it { should match('/10/test') .capturing id: 'test' }
317
+ it { should_not match('/10/te.st') }
318
+ end
319
+
320
+ pattern '/10.1/:id' do
321
+ it { should match('/10.1/test') .capturing id: 'test' }
322
+ it { should_not match('/10.1/te.st') }
323
+ end
324
+
325
+ pattern '/:foo.:bar/:id' do
326
+ it { should_not match('/10.1/te.st') }
327
+ it { should_not match('/10.1.2/te.st') }
328
+ end
329
+
330
+ pattern '/:a/:b.?:c?' do
331
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
332
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
333
+ it { should_not match('/a.b/c') }
334
+ it { should_not match('/a.b/c.d') }
335
+ it { should_not match('/a.b/c.d/e') }
336
+ end
337
+
338
+ pattern '/:a(foo:b)?' do
339
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
340
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
341
+ it { should match('/bar') .capturing a: 'bar', b: nil }
342
+ it { should_not match('/') }
343
+ end
344
+
345
+ pattern '/foo?' do
346
+ it { should match('/fo') }
347
+ it { should match('/foo') }
348
+ it { should_not match('') }
349
+ it { should_not match('/') }
350
+ it { should_not match('/f') }
351
+ it { should_not match('/fooo') }
352
+ end
353
+
354
+ pattern '/foo\?' do
355
+ it { should match('/foo?') }
356
+ it { should_not match('/foo\?') }
357
+ it { should_not match('/fo') }
358
+ it { should_not match('/foo') }
359
+ it { should_not match('') }
360
+ it { should_not match('/') }
361
+ it { should_not match('/f') }
362
+ it { should_not match('/fooo') }
363
+ end
364
+
365
+ pattern '/foo\\\?' do
366
+ it { should match('/foo%5c') }
367
+ it { should match('/foo') }
368
+ it { should_not match('/foo\?') }
369
+ it { should_not match('/fo') }
370
+ it { should_not match('') }
371
+ it { should_not match('/') }
372
+ it { should_not match('/f') }
373
+ it { should_not match('/fooo') }
374
+ end
375
+
376
+ pattern '/\(' do
377
+ it { should match('/(') }
378
+ end
379
+
380
+ pattern '/\(?' do
381
+ it { should match('/(') }
382
+ it { should match('/') }
383
+ end
384
+
385
+ pattern '/(\()?' do
386
+ it { should match('/(') }
387
+ it { should match('/') }
388
+ end
389
+
390
+ pattern '/(\(\))?' do
391
+ it { should match('/') }
392
+ it { should match('/()') }
393
+ it { should_not match('/(') }
394
+ end
395
+
396
+ pattern '/\(\)?' do
397
+ it { should match('/(') }
398
+ it { should match('/()') }
399
+ it { should_not match('/') }
400
+ end
401
+
402
+ pattern '/\*' do
403
+ it { should match('/*') }
404
+ it { should_not match('/a') }
405
+ end
406
+
407
+ pattern '/\*/*' do
408
+ it { should match('/*/b/c') }
409
+ it { should_not match('/a/b/c') }
410
+ end
411
+
412
+ pattern '/\:foo' do
413
+ it { should match('/:foo') }
414
+ it { should_not match('/foo') }
415
+ end
416
+
417
+ pattern '/:fOO' do
418
+ it { should match('/a').capturing fOO: 'a' }
419
+ end
420
+
421
+ pattern '/:_X' do
422
+ it { should match('/a').capturing _X: 'a' }
423
+ end
424
+
425
+ pattern '/:f00' do
426
+ it { should match('/a').capturing f00: 'a' }
427
+ end
428
+
429
+ pattern '/:foo(/:bar)?/:baz?' do
430
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
431
+ end
432
+
433
+ pattern "/(foo|bar)" do
434
+ it { should match("/foo") }
435
+ it { should match("/bar") }
436
+ end
437
+
438
+ pattern "/(foo\\|bar)" do
439
+ it { should match "/foo%7Cbar" }
440
+ it { should generate_template "/foo%7Cbar" }
441
+
442
+ it { should_not match("/foo") }
443
+ it { should_not match("/bar") }
444
+
445
+ it { should_not generate_template('/foo') }
446
+ it { should_not generate_template('/bar') }
447
+ end
448
+
449
+ pattern "/(:a/:b|:c)" do
450
+ it { should match("/foo") .capturing c: 'foo' }
451
+ it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
452
+
453
+ it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
454
+ it { should expand(c: 'foo') .to('/foo') }
455
+ it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
456
+ end
457
+
458
+ pattern "/:a/:b|:c" do
459
+ it { should match("foo") .capturing c: 'foo' }
460
+ it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
461
+
462
+ it { should generate_template('/{a}/{b}') }
463
+ it { should generate_template('{c}') }
464
+
465
+ it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
466
+ it { should expand(c: 'foo') .to('foo') }
467
+ it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
468
+ end
469
+
470
+ pattern '/:foo', capture: /\d+/ do
471
+ it { should match('/1') .capturing foo: '1' }
472
+ it { should match('/123') .capturing foo: '123' }
473
+
474
+ it { should_not match('/') }
475
+ it { should_not match('/foo') }
476
+ end
477
+
478
+ pattern '/:foo', capture: /\d+/ do
479
+ it { should match('/1') .capturing foo: '1' }
480
+ it { should match('/123') .capturing foo: '123' }
481
+
482
+ it { should_not match('/') }
483
+ it { should_not match('/foo') }
484
+ end
485
+
486
+ pattern '/:foo', capture: '1' do
487
+ it { should match('/1').capturing foo: '1' }
488
+
489
+ it { should_not match('/') }
490
+ it { should_not match('/foo') }
491
+ it { should_not match('/123') }
492
+ end
493
+
494
+ pattern '/:foo', capture: 'a.b' do
495
+ it { should match('/a.b') .capturing foo: 'a.b' }
496
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
497
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
498
+
499
+ it { should_not match('/ab') }
500
+ it { should_not match('/afb') }
501
+ it { should_not match('/a1b') }
502
+ it { should_not match('/a.bc') }
503
+ end
504
+
505
+ pattern '/:foo(/:bar)?', capture: :alpha do
506
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
507
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
508
+ it { should match('/a') .capturing foo: 'a', bar: nil }
509
+
510
+ it { should_not match('/1/2') }
511
+ it { should_not match('/a/2') }
512
+ it { should_not match('/1/b') }
513
+ it { should_not match('/1') }
514
+ it { should_not match('/1/') }
515
+ it { should_not match('/a/') }
516
+ it { should_not match('//a') }
517
+ end
518
+
519
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
520
+ it { should match('/1') .capturing foo: '1' }
521
+ it { should match('/123') .capturing foo: '123' }
522
+ it { should match('/foo') .capturing foo: 'foo' }
523
+ it { should match('/bar') .capturing foo: 'bar' }
524
+
525
+ it { should_not match('/') }
526
+ it { should_not match('/baz') }
527
+ it { should_not match('/foo1') }
528
+ end
529
+
530
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
531
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
532
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
533
+ it { should_not match('/123abcxy-1') }
534
+ it { should_not match('/abcxy-1') }
535
+ it { should_not match('/abc1') }
536
+ end
537
+
538
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
539
+ it { should match('/1') .capturing foo: '1' }
540
+ it { should match('/123') .capturing foo: '123' }
541
+ it { should match('/foo') .capturing foo: 'foo' }
542
+ it { should match('/bar') .capturing foo: 'bar' }
543
+
544
+ it { should_not match('/') }
545
+ it { should_not match('/baz') }
546
+ it { should_not match('/foo1') }
547
+ end
548
+
549
+ pattern '/:file(.:ext)?', capture: { ext: ['jpg', 'png'] } do
550
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
551
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
552
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
553
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
554
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
555
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
556
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
557
+ it { should_not match('/pony.png.jpg') }
558
+ it { should_not match('/pony.jpg.png') }
559
+ it { should_not match('/pony.gif') }
560
+ it { should_not match('/pony.') }
561
+ it { should_not match('.jpg') }
562
+ end
563
+
564
+ pattern '/:file:ext?', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
565
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
566
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
567
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
568
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
569
+ it { should_not match('/pony.png.jpg') }
570
+ it { should_not match('/pony.jpg.png') }
571
+ it { should_not match('/pony.gif') }
572
+ it { should_not match('/pony.') }
573
+ it { should_not match('/.jpg') }
574
+ end
575
+
576
+ pattern '/:a(@:b)?', capture: { b: /\d+/ } do
577
+ it { should match('/a') .capturing a: 'a', b: nil }
578
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
579
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
580
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
581
+ end
582
+
583
+ pattern '/(:a)b?', greedy: false do
584
+ it { should match('/ab').capturing a: 'a' }
585
+ end
586
+
587
+ pattern '/:file(.:ext)?', greedy: false do
588
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
589
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
590
+ it { should_not match('/pony.png.jpg') }
591
+ end
592
+
593
+ pattern '/auth/*', except: '/auth/login' do
594
+ it { should match('/auth/admin') }
595
+ it { should match('/auth/foobar') }
596
+ it { should_not match('/auth/login') }
597
+ end
598
+
599
+ pattern '/:foo/:bar', except: '/:bar/20' do
600
+ it { should match('/foo/bar').capturing foo: 'foo', bar: 'bar' }
601
+ it { should_not match('/20/20') }
602
+ end
603
+
604
+ pattern '/foo?', uri_decode: false do
605
+ it { should match('/foo') }
606
+ it { should match('/fo') }
607
+ it { should_not match('/foo?') }
608
+ end
609
+
610
+ pattern '/foo/bar', uri_decode: false do
611
+ it { should match('/foo/bar') }
612
+ it { should_not match('/foo%2Fbar') }
613
+ it { should_not match('/foo%2fbar') }
614
+ end
615
+
616
+ pattern "/path with spaces", uri_decode: false do
617
+ it { should match('/path with spaces') }
618
+ it { should_not match('/path%20with%20spaces') }
619
+ it { should_not match('/path%2Bwith%2Bspaces') }
620
+ it { should_not match('/path+with+spaces') }
621
+ end
622
+
623
+ pattern "/path with spaces", space_matches_plus: false do
624
+ it { should match('/path%20with%20spaces') }
625
+ it { should_not match('/path%2Bwith%2Bspaces') }
626
+ it { should_not match('/path+with+spaces') }
627
+ end
628
+
629
+ context 'invalid syntax' do
630
+ example 'unexpected closing parenthesis' do
631
+ expect { Mustermann::Grape.new('foo)bar') }.
632
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
633
+ end
634
+
635
+ example 'missing closing parenthesis' do
636
+ expect { Mustermann::Grape.new('foo(bar') }.
637
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
638
+ end
639
+
640
+ example 'missing unescaped closing parenthesis' do
641
+ expect { Mustermann::Grape.new('foo(bar\)') }.
642
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar\\\\)"')
643
+ end
644
+
645
+ example '? at beginning of route' do
646
+ expect { Mustermann::Grape.new('?foobar') }.
647
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "?foobar"')
648
+ end
649
+
650
+ example 'double ?' do
651
+ expect { Mustermann::Grape.new('foo??bar') }.
652
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
653
+ end
654
+
655
+ example 'dangling escape' do
656
+ expect { Mustermann::Grape.new('foo\\') }.
657
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')
658
+ end
659
+ end
660
+
661
+ context 'invalid capture names' do
662
+ example 'empty name' do
663
+ expect { Mustermann::Grape.new('/:/') }.
664
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
665
+ end
666
+
667
+ example 'named splat' do
668
+ expect { Mustermann::Grape.new('/:splat/') }.
669
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
670
+ end
671
+
672
+ example 'named captures' do
673
+ expect { Mustermann::Grape.new('/:captures/') }.
674
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
675
+ end
676
+
677
+ example 'with capital letter' do
678
+ expect { Mustermann::Grape.new('/:Foo/') }.
679
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
680
+ end
681
+
682
+ example 'with integer' do
683
+ expect { Mustermann::Grape.new('/:1a/') }.
684
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
685
+ end
686
+
687
+ example 'same name twice' do
688
+ expect { Mustermann::Grape.new('/:foo(/:bar)?/:bar?') }.
689
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)?/:bar?\"")
690
+ end
691
+ end
692
+
693
+ context 'Regexp compatibility' do
694
+ describe :=== do
695
+ example('non-matching') { Mustermann::Grape.new("/") .should_not be === '/foo' }
696
+ example('matching') { Mustermann::Grape.new("/:foo") .should be === '/foo' }
697
+ end
698
+
699
+ describe :=~ do
700
+ example('non-matching') { Mustermann::Grape.new("/") .should_not be =~ '/foo' }
701
+ example('matching') { Mustermann::Grape.new("/:foo") .should be =~ '/foo' }
702
+
703
+ context 'String#=~' do
704
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Grape.new("/") }
705
+ example('matching') { "/foo".should be =~ Mustermann::Grape.new("/:foo") }
706
+ end
707
+ end
708
+
709
+ describe :to_regexp do
710
+ example('empty pattern') { Mustermann::Grape.new('').to_regexp.should be == /\A(?-mix:)\Z/ }
711
+
712
+ context 'Regexp.try_convert' do
713
+ example('empty pattern') { Regexp.try_convert(Mustermann::Grape.new('')).should be == /\A(?-mix:)\Z/ }
714
+ end
715
+ end
716
+ end
717
+
718
+ context 'Proc compatibility' do
719
+ describe :to_proc do
720
+ example { Mustermann::Grape.new("/").to_proc.should be_a(Proc) }
721
+ example('non-matching') { Mustermann::Grape.new("/") .to_proc.call('/foo').should be == false }
722
+ example('matching') { Mustermann::Grape.new("/:foo") .to_proc.call('/foo').should be == true }
723
+ end
724
+ end
725
+
726
+ context "peeking" do
727
+ subject(:pattern) { Mustermann::Grape.new(":name") }
728
+
729
+ describe :peek_size do
730
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
731
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
732
+ example { pattern.peek_size("/foo bar") .should be_nil }
733
+ end
734
+
735
+ describe :peek_match do
736
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
737
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
738
+ example { pattern.peek_match("/foo bar") .should be_nil }
739
+ end
740
+
741
+ describe :peek_params do
742
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
743
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
744
+ example { pattern.peek_params("/foo bar") .should be_nil }
745
+ end
746
+ end
747
+ end