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,361 @@
1
+ require 'support'
2
+ require 'mustermann/flask'
3
+
4
+ describe Mustermann::Flask do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+
11
+ it { should expand.to('') }
12
+ it { should_not expand(a: 1) }
13
+
14
+ it { should generate_template('') }
15
+
16
+ it { should respond_to(:expand) }
17
+ it { should respond_to(:to_templates) }
18
+ end
19
+
20
+ pattern '/' do
21
+ it { should match('/') }
22
+ it { should_not match('/foo') }
23
+
24
+ it { should expand.to('/') }
25
+ it { should_not expand(a: 1) }
26
+ end
27
+
28
+ pattern '/foo' do
29
+ it { should match('/foo') }
30
+ it { should_not match('/bar') }
31
+ it { should_not match('/foo.bar') }
32
+
33
+ it { should expand.to('/foo') }
34
+ it { should_not expand(a: 1) }
35
+ end
36
+
37
+ pattern '/foo/bar' do
38
+ it { should match('/foo/bar') }
39
+ it { should_not match('/foo%2Fbar') }
40
+ it { should_not match('/foo%2fbar') }
41
+
42
+ it { should expand.to('/foo/bar') }
43
+ it { should_not expand(a: 1) }
44
+ end
45
+
46
+ pattern '/<foo>' do
47
+ it { should match('/foo') .capturing foo: 'foo' }
48
+ it { should match('/bar') .capturing foo: 'bar' }
49
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
50
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
51
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
52
+
53
+ it { should_not match('/foo?') }
54
+ it { should_not match('/foo/bar') }
55
+ it { should_not match('/') }
56
+ it { should_not match('/foo/') }
57
+
58
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
59
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
60
+ example { pattern.params('').should be_nil }
61
+
62
+ it { should expand(foo: 'bar') .to('/bar') }
63
+ it { should expand(foo: 'b r') .to('/b%20r') }
64
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
65
+
66
+ it { should_not expand(foo: 'foo', bar: 'bar') }
67
+ it { should_not expand(bar: 'bar') }
68
+ it { should_not expand }
69
+
70
+ it { should generate_template('/{foo}') }
71
+ end
72
+
73
+ pattern '/<string:foo>' do
74
+ it { should match('/foo') .capturing foo: 'foo' }
75
+ it { should match('/bar') .capturing foo: 'bar' }
76
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
77
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
78
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
79
+
80
+ it { should_not match('/foo?') }
81
+ it { should_not match('/foo/bar') }
82
+ it { should_not match('/') }
83
+ it { should_not match('/foo/') }
84
+
85
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
86
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
87
+ example { pattern.params('').should be_nil }
88
+
89
+ it { should expand(foo: 'bar') .to('/bar') }
90
+ it { should expand(foo: 'b r') .to('/b%20r') }
91
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
92
+
93
+ it { should_not expand(foo: 'foo', bar: 'bar') }
94
+ it { should_not expand(bar: 'bar') }
95
+ it { should_not expand }
96
+
97
+ it { should generate_template('/{foo}') }
98
+ end
99
+
100
+ pattern '/<string(minlength=2):foo>' do
101
+ it { should match('/foo') .capturing foo: 'foo' }
102
+ it { should match('/bar') .capturing foo: 'bar' }
103
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
104
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
105
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
106
+
107
+ it { should_not match('/f') }
108
+ it { should_not match('/foo?') }
109
+ it { should_not match('/foo/bar') }
110
+ it { should_not match('/') }
111
+ it { should_not match('/foo/') }
112
+
113
+ it { should generate_template('/{foo}') }
114
+ end
115
+
116
+ pattern '/<string(maxlength=3):foo>' do
117
+ it { should match('/f') .capturing foo: 'f' }
118
+ it { should match('/fo') .capturing foo: 'fo' }
119
+ it { should match('/foo') .capturing foo: 'foo' }
120
+ it { should match('/bar') .capturing foo: 'bar' }
121
+
122
+ it { should_not match('/fooo') }
123
+ it { should_not match('/foo.bar') }
124
+ it { should_not match('/foo?') }
125
+ it { should_not match('/foo/bar') }
126
+ it { should_not match('/') }
127
+ it { should_not match('/foo/') }
128
+
129
+ it { should generate_template('/{foo}') }
130
+ end
131
+
132
+ pattern '/<string(length=3):foo>' do
133
+ it { should match('/foo') .capturing foo: 'foo' }
134
+ it { should match('/bar') .capturing foo: 'bar' }
135
+
136
+ it { should_not match('/f') }
137
+ it { should_not match('/fo') }
138
+ it { should_not match('/fooo') }
139
+ it { should_not match('/foo.bar') }
140
+ it { should_not match('/foo?') }
141
+ it { should_not match('/foo/bar') }
142
+ it { should_not match('/') }
143
+ it { should_not match('/foo/') }
144
+
145
+ it { should generate_template('/{foo}') }
146
+ end
147
+
148
+ pattern '/<int:foo>' do
149
+ it { should match('/42').capturing foo: '42' }
150
+
151
+ it { should_not match('/1.0') }
152
+ it { should_not match('/.5') }
153
+ it { should_not match('/foo') }
154
+ it { should_not match('/bar') }
155
+ it { should_not match('/foo.bar') }
156
+ it { should_not match('/%0Afoo') }
157
+ it { should_not match('/foo%2Fbar') }
158
+
159
+ it { should_not match('/foo?') }
160
+ it { should_not match('/foo/bar') }
161
+ it { should_not match('/') }
162
+ it { should_not match('/foo/') }
163
+
164
+ example { pattern.params('/42').should be == {"foo" => 42} }
165
+ it { should expand(foo: 12).to('/12') }
166
+ it { should generate_template('/{foo}') }
167
+ end
168
+
169
+ pattern '/<int:foo>' do
170
+ it { should match('/42').capturing foo: '42' }
171
+
172
+ it { should_not match('/1.0') }
173
+ it { should_not match('/.5') }
174
+ it { should_not match('/foo') }
175
+ it { should_not match('/bar') }
176
+ it { should_not match('/foo.bar') }
177
+ it { should_not match('/%0Afoo') }
178
+ it { should_not match('/foo%2Fbar') }
179
+
180
+ it { should_not match('/foo?') }
181
+ it { should_not match('/foo/bar') }
182
+ it { should_not match('/') }
183
+ it { should_not match('/foo/') }
184
+
185
+ example { pattern.params('/42').should be == {"foo" => 42} }
186
+ it { should expand(foo: 12).to('/12') }
187
+ it { should generate_template('/{foo}') }
188
+ end
189
+
190
+ pattern '/<any(foo,bar):foo>' do
191
+ it { should match('/foo') .capturing foo: 'foo' }
192
+ it { should match('/bar') .capturing foo: 'bar' }
193
+
194
+ it { should_not match('/f') }
195
+ it { should_not match('/fo') }
196
+ it { should_not match('/fooo') }
197
+ it { should_not match('/foo.bar') }
198
+ it { should_not match('/foo?') }
199
+ it { should_not match('/foo/bar') }
200
+ it { should_not match('/') }
201
+ it { should_not match('/foo/') }
202
+ it { should_not match('/baz') }
203
+
204
+ it { should generate_template('/{foo}') }
205
+ end
206
+
207
+ pattern '/<any( foo, bar ):foo>' do
208
+ it { should match('/foo') .capturing foo: 'foo' }
209
+ it { should match('/bar') .capturing foo: 'bar' }
210
+
211
+ it { should_not match('/f') }
212
+ it { should_not match('/fo') }
213
+ it { should_not match('/fooo') }
214
+ it { should_not match('/foo.bar') }
215
+ it { should_not match('/foo?') }
216
+ it { should_not match('/foo/bar') }
217
+ it { should_not match('/') }
218
+ it { should_not match('/foo/') }
219
+ it { should_not match('/baz') }
220
+
221
+ it { should generate_template('/{foo}') }
222
+ end
223
+
224
+ pattern '/<any(foo, bar, "foo,bar"):foo>' do
225
+ it { should match('/foo') .capturing foo: 'foo' }
226
+ it { should match('/bar') .capturing foo: 'bar' }
227
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
228
+
229
+ it { should_not match('/f') }
230
+ it { should_not match('/fo') }
231
+ it { should_not match('/fooo') }
232
+ it { should_not match('/foo.bar') }
233
+ it { should_not match('/foo?') }
234
+ it { should_not match('/foo/bar') }
235
+ it { should_not match('/') }
236
+ it { should_not match('/foo/') }
237
+ it { should_not match('/baz') }
238
+
239
+ it { should generate_template('/{foo}') }
240
+ end
241
+
242
+ pattern '/<any(foo, bar, foo\,bar):foo>' do
243
+ it { should match('/foo') .capturing foo: 'foo' }
244
+ it { should match('/bar') .capturing foo: 'bar' }
245
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
246
+
247
+ it { should_not match('/f') }
248
+ it { should_not match('/fo') }
249
+ it { should_not match('/fooo') }
250
+ it { should_not match('/foo.bar') }
251
+ it { should_not match('/foo?') }
252
+ it { should_not match('/foo/bar') }
253
+ it { should_not match('/') }
254
+ it { should_not match('/foo/') }
255
+ it { should_not match('/baz') }
256
+
257
+ it { should generate_template('/{foo}') }
258
+ end
259
+
260
+ pattern '/<any(foo, bar, "foo\,bar"):foo>' do
261
+ it { should match('/foo') .capturing foo: 'foo' }
262
+ it { should match('/bar') .capturing foo: 'bar' }
263
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
264
+
265
+ it { should_not match('/f') }
266
+ it { should_not match('/fo') }
267
+ it { should_not match('/fooo') }
268
+ it { should_not match('/foo.bar') }
269
+ it { should_not match('/foo?') }
270
+ it { should_not match('/foo/bar') }
271
+ it { should_not match('/') }
272
+ it { should_not match('/foo/') }
273
+ it { should_not match('/baz') }
274
+
275
+ it { should generate_template('/{foo}') }
276
+ end
277
+
278
+ pattern '/<int(min=5,max=50):foo>' do
279
+ example { pattern.params('/42').should be == {"foo" => 42} }
280
+ example { pattern.params('/52').should be == {"foo" => 50} }
281
+ example { pattern.params('/2').should be == {"foo" => 5} }
282
+ end
283
+
284
+ pattern '/<float(min=5,max=50.5):foo>' do
285
+ example { pattern.params('/42.5').should be == {"foo" => 42.5} }
286
+ example { pattern.params('/52.5').should be == {"foo" => 50.5} }
287
+ example { pattern.params('/2.5').should be == {"foo" => 5.0} }
288
+ end
289
+
290
+ pattern '/<prefix>/<float:foo>/<int:bar>' do
291
+ it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
292
+ it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
293
+ it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
294
+
295
+ it { should_not match('/foo/1/1.0') }
296
+ it { should_not match('/foo/1.0/1.0') }
297
+
298
+ it { should generate_template('/{prefix}/{foo}/{bar}') }
299
+
300
+ example do
301
+ pattern.params('/foo/1.0/1').should be == {
302
+ "prefix" => "foo",
303
+ "foo" => 1.0,
304
+ "bar" => 1
305
+ }
306
+ end
307
+ end
308
+
309
+ pattern '/<path:foo>' do
310
+ it { should match('/') .capturing foo: '' }
311
+ it { should match('/foo') .capturing foo: 'foo' }
312
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
313
+
314
+ it { should expand .to('/') }
315
+ it { should expand(foo: nil) .to('/') }
316
+ it { should expand(foo: '') .to('/') }
317
+ it { should expand(foo: 'foo') .to('/foo') }
318
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
319
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
320
+
321
+ it { should generate_template('/{+foo}') }
322
+ end
323
+
324
+ converter = Struct.new(:convert).new(:upcase.to_proc)
325
+ pattern '/<foo:bar>', converters: { foo: converter } do
326
+ it { should match('/foo').capturing bar: 'foo' }
327
+ example { pattern.params('/foo').should be == {"bar" => "FOO"} }
328
+ end
329
+
330
+ context 'invalid syntax' do
331
+ example 'unexpected end of capture' do
332
+ expect { Mustermann::Flask.new('foo>bar') }.
333
+ to raise_error(Mustermann::ParseError, 'unexpected > while parsing "foo>bar"')
334
+ end
335
+
336
+ example 'missing end of capture' do
337
+ expect { Mustermann::Flask.new('foo<bar') }.
338
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo<bar"')
339
+ end
340
+
341
+ example 'unknown converter' do
342
+ expect { Mustermann::Flask.new('foo<bar:name>') }.
343
+ to raise_error(Mustermann::ParseError, 'unexpected converter "bar" while parsing "foo<bar:name>"')
344
+ end
345
+
346
+ example 'broken argument synax' do
347
+ expect { Mustermann::Flask.new('<string(length=3=2):foo>') }.
348
+ to raise_error(Mustermann::ParseError, 'unexpected = while parsing "<string(length=3=2):foo>"')
349
+ end
350
+
351
+ example 'missing )' do
352
+ expect { Mustermann::Flask.new('<string(foo') }.
353
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(foo"')
354
+ end
355
+
356
+ example 'missing ""' do
357
+ expect { Mustermann::Flask.new('<string("foo') }.
358
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(\\"foo"')
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,368 @@
1
+ require 'support'
2
+ require 'mustermann/flask'
3
+
4
+ FlaskSubclass ||= Class.new(Mustermann::Flask)
5
+ FlaskSubclass.register_converter(:foo) {}
6
+
7
+ describe FlaskSubclass do
8
+ extend Support::Pattern
9
+
10
+ pattern '' do
11
+ it { should match('') }
12
+ it { should_not match('/') }
13
+
14
+ it { should expand.to('') }
15
+ it { should_not expand(a: 1) }
16
+
17
+ it { should generate_template('') }
18
+
19
+ it { should respond_to(:expand) }
20
+ it { should respond_to(:to_templates) }
21
+ end
22
+
23
+ pattern '/' do
24
+ it { should match('/') }
25
+ it { should_not match('/foo') }
26
+
27
+ it { should expand.to('/') }
28
+ it { should_not expand(a: 1) }
29
+ end
30
+
31
+ pattern '/foo' do
32
+ it { should match('/foo') }
33
+ it { should_not match('/bar') }
34
+ it { should_not match('/foo.bar') }
35
+
36
+ it { should expand.to('/foo') }
37
+ it { should_not expand(a: 1) }
38
+ end
39
+
40
+ pattern '/foo/bar' do
41
+ it { should match('/foo/bar') }
42
+ it { should_not match('/foo%2Fbar') }
43
+ it { should_not match('/foo%2fbar') }
44
+
45
+ it { should expand.to('/foo/bar') }
46
+ it { should_not expand(a: 1) }
47
+ end
48
+
49
+ pattern '/<foo>' do
50
+ it { should match('/foo') .capturing foo: 'foo' }
51
+ it { should match('/bar') .capturing foo: 'bar' }
52
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
53
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
54
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
55
+
56
+ it { should_not match('/foo?') }
57
+ it { should_not match('/foo/bar') }
58
+ it { should_not match('/') }
59
+ it { should_not match('/foo/') }
60
+
61
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
62
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
63
+ example { pattern.params('').should be_nil }
64
+
65
+ it { should expand(foo: 'bar') .to('/bar') }
66
+ it { should expand(foo: 'b r') .to('/b%20r') }
67
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
68
+
69
+ it { should_not expand(foo: 'foo', bar: 'bar') }
70
+ it { should_not expand(bar: 'bar') }
71
+ it { should_not expand }
72
+
73
+ it { should generate_template('/{foo}') }
74
+ end
75
+
76
+ pattern '/<string:foo>' do
77
+ it { should match('/foo') .capturing foo: 'foo' }
78
+ it { should match('/bar') .capturing foo: 'bar' }
79
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
80
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
81
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
82
+
83
+ it { should_not match('/foo?') }
84
+ it { should_not match('/foo/bar') }
85
+ it { should_not match('/') }
86
+ it { should_not match('/foo/') }
87
+
88
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
89
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
90
+ example { pattern.params('').should be_nil }
91
+
92
+ it { should expand(foo: 'bar') .to('/bar') }
93
+ it { should expand(foo: 'b r') .to('/b%20r') }
94
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
95
+
96
+ it { should_not expand(foo: 'foo', bar: 'bar') }
97
+ it { should_not expand(bar: 'bar') }
98
+ it { should_not expand }
99
+
100
+ it { should generate_template('/{foo}') }
101
+ end
102
+
103
+ pattern '/<string(minlength=2):foo>' do
104
+ it { should match('/foo') .capturing foo: 'foo' }
105
+ it { should match('/bar') .capturing foo: 'bar' }
106
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
107
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
108
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
109
+
110
+ it { should_not match('/f') }
111
+ it { should_not match('/foo?') }
112
+ it { should_not match('/foo/bar') }
113
+ it { should_not match('/') }
114
+ it { should_not match('/foo/') }
115
+
116
+ it { should generate_template('/{foo}') }
117
+ end
118
+
119
+ pattern '/<string(maxlength=3):foo>' do
120
+ it { should match('/f') .capturing foo: 'f' }
121
+ it { should match('/fo') .capturing foo: 'fo' }
122
+ it { should match('/foo') .capturing foo: 'foo' }
123
+ it { should match('/bar') .capturing foo: 'bar' }
124
+
125
+ it { should_not match('/fooo') }
126
+ it { should_not match('/foo.bar') }
127
+ it { should_not match('/foo?') }
128
+ it { should_not match('/foo/bar') }
129
+ it { should_not match('/') }
130
+ it { should_not match('/foo/') }
131
+
132
+ it { should generate_template('/{foo}') }
133
+ end
134
+
135
+ pattern '/<string(length=3):foo>' do
136
+ it { should match('/foo') .capturing foo: 'foo' }
137
+ it { should match('/bar') .capturing foo: 'bar' }
138
+
139
+ it { should_not match('/f') }
140
+ it { should_not match('/fo') }
141
+ it { should_not match('/fooo') }
142
+ it { should_not match('/foo.bar') }
143
+ it { should_not match('/foo?') }
144
+ it { should_not match('/foo/bar') }
145
+ it { should_not match('/') }
146
+ it { should_not match('/foo/') }
147
+
148
+ it { should generate_template('/{foo}') }
149
+ end
150
+
151
+ pattern '/<int:foo>' do
152
+ it { should match('/42').capturing foo: '42' }
153
+
154
+ it { should_not match('/1.0') }
155
+ it { should_not match('/.5') }
156
+ it { should_not match('/foo') }
157
+ it { should_not match('/bar') }
158
+ it { should_not match('/foo.bar') }
159
+ it { should_not match('/%0Afoo') }
160
+ it { should_not match('/foo%2Fbar') }
161
+
162
+ it { should_not match('/foo?') }
163
+ it { should_not match('/foo/bar') }
164
+ it { should_not match('/') }
165
+ it { should_not match('/foo/') }
166
+
167
+ example { pattern.params('/42').should be == {"foo" => 42} }
168
+ it { should expand(foo: 12).to('/12') }
169
+ it { should generate_template('/{foo}') }
170
+ end
171
+
172
+ pattern '/<int:foo>' do
173
+ it { should match('/42').capturing foo: '42' }
174
+
175
+ it { should_not match('/1.0') }
176
+ it { should_not match('/.5') }
177
+ it { should_not match('/foo') }
178
+ it { should_not match('/bar') }
179
+ it { should_not match('/foo.bar') }
180
+ it { should_not match('/%0Afoo') }
181
+ it { should_not match('/foo%2Fbar') }
182
+
183
+ it { should_not match('/foo?') }
184
+ it { should_not match('/foo/bar') }
185
+ it { should_not match('/') }
186
+ it { should_not match('/foo/') }
187
+
188
+ example { pattern.params('/42').should be == {"foo" => 42} }
189
+ it { should expand(foo: 12).to('/12') }
190
+ it { should generate_template('/{foo}') }
191
+ end
192
+
193
+ pattern '/<any(foo,bar):foo>' do
194
+ it { should match('/foo') .capturing foo: 'foo' }
195
+ it { should match('/bar') .capturing foo: 'bar' }
196
+
197
+ it { should_not match('/f') }
198
+ it { should_not match('/fo') }
199
+ it { should_not match('/fooo') }
200
+ it { should_not match('/foo.bar') }
201
+ it { should_not match('/foo?') }
202
+ it { should_not match('/foo/bar') }
203
+ it { should_not match('/') }
204
+ it { should_not match('/foo/') }
205
+ it { should_not match('/baz') }
206
+
207
+ it { should generate_template('/{foo}') }
208
+ end
209
+
210
+ pattern '/<any( foo, bar ):foo>' do
211
+ it { should match('/foo') .capturing foo: 'foo' }
212
+ it { should match('/bar') .capturing foo: 'bar' }
213
+
214
+ it { should_not match('/f') }
215
+ it { should_not match('/fo') }
216
+ it { should_not match('/fooo') }
217
+ it { should_not match('/foo.bar') }
218
+ it { should_not match('/foo?') }
219
+ it { should_not match('/foo/bar') }
220
+ it { should_not match('/') }
221
+ it { should_not match('/foo/') }
222
+ it { should_not match('/baz') }
223
+
224
+ it { should generate_template('/{foo}') }
225
+ end
226
+
227
+ pattern '/<any(foo, bar, "foo,bar"):foo>' do
228
+ it { should match('/foo') .capturing foo: 'foo' }
229
+ it { should match('/bar') .capturing foo: 'bar' }
230
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
231
+
232
+ it { should_not match('/f') }
233
+ it { should_not match('/fo') }
234
+ it { should_not match('/fooo') }
235
+ it { should_not match('/foo.bar') }
236
+ it { should_not match('/foo?') }
237
+ it { should_not match('/foo/bar') }
238
+ it { should_not match('/') }
239
+ it { should_not match('/foo/') }
240
+ it { should_not match('/baz') }
241
+
242
+ it { should generate_template('/{foo}') }
243
+ end
244
+
245
+ pattern '/<any(foo, bar, foo\,bar):foo>' do
246
+ it { should match('/foo') .capturing foo: 'foo' }
247
+ it { should match('/bar') .capturing foo: 'bar' }
248
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
249
+
250
+ it { should_not match('/f') }
251
+ it { should_not match('/fo') }
252
+ it { should_not match('/fooo') }
253
+ it { should_not match('/foo.bar') }
254
+ it { should_not match('/foo?') }
255
+ it { should_not match('/foo/bar') }
256
+ it { should_not match('/') }
257
+ it { should_not match('/foo/') }
258
+ it { should_not match('/baz') }
259
+
260
+ it { should generate_template('/{foo}') }
261
+ end
262
+
263
+ pattern '/<any(foo, bar, "foo\,bar"):foo>' do
264
+ it { should match('/foo') .capturing foo: 'foo' }
265
+ it { should match('/bar') .capturing foo: 'bar' }
266
+ it { should match('/foo,bar') .capturing foo: 'foo,bar' }
267
+
268
+ it { should_not match('/f') }
269
+ it { should_not match('/fo') }
270
+ it { should_not match('/fooo') }
271
+ it { should_not match('/foo.bar') }
272
+ it { should_not match('/foo?') }
273
+ it { should_not match('/foo/bar') }
274
+ it { should_not match('/') }
275
+ it { should_not match('/foo/') }
276
+ it { should_not match('/baz') }
277
+
278
+ it { should generate_template('/{foo}') }
279
+ end
280
+
281
+ pattern '/<int(min=5,max=50):foo>' do
282
+ example { pattern.params('/42').should be == {"foo" => 42} }
283
+ example { pattern.params('/52').should be == {"foo" => 50} }
284
+ example { pattern.params('/2').should be == {"foo" => 5} }
285
+ end
286
+
287
+ pattern '/<float(min=5,max=50.5):foo>' do
288
+ example { pattern.params('/42.5').should be == {"foo" => 42.5} }
289
+ example { pattern.params('/52.5').should be == {"foo" => 50.5} }
290
+ example { pattern.params('/2.5').should be == {"foo" => 5.0} }
291
+ end
292
+
293
+ pattern '/<prefix>/<float:foo>/<int:bar>' do
294
+ it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
295
+ it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
296
+ it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
297
+
298
+ it { should_not match('/foo/1/1.0') }
299
+ it { should_not match('/foo/1.0/1.0') }
300
+
301
+ it { should generate_template('/{prefix}/{foo}/{bar}') }
302
+
303
+ example do
304
+ pattern.params('/foo/1.0/1').should be == {
305
+ "prefix" => "foo",
306
+ "foo" => 1.0,
307
+ "bar" => 1
308
+ }
309
+ end
310
+ end
311
+
312
+ pattern '/<path:foo>' do
313
+ it { should match('/') .capturing foo: '' }
314
+ it { should match('/foo') .capturing foo: 'foo' }
315
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
316
+
317
+ it { should expand .to('/') }
318
+ it { should expand(foo: nil) .to('/') }
319
+ it { should expand(foo: '') .to('/') }
320
+ it { should expand(foo: 'foo') .to('/foo') }
321
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
322
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
323
+
324
+ it { should generate_template('/{+foo}') }
325
+ end
326
+
327
+ pattern '/<foo:bar>' do
328
+ it { should match('/foo').capturing bar: 'foo' }
329
+ it { should generate_template('/{bar}') }
330
+
331
+ example do
332
+ expect { Mustermann::Flask.new('/<foo:bar>') }.to \
333
+ raise_error(Mustermann::ParseError, 'unexpected converter "foo" while parsing "/<foo:bar>"')
334
+ end
335
+ end
336
+
337
+ context 'invalid syntax' do
338
+ example 'unexpected end of capture' do
339
+ expect { FlaskSubclass.new('foo>bar') }.
340
+ to raise_error(Mustermann::ParseError, 'unexpected > while parsing "foo>bar"')
341
+ end
342
+
343
+ example 'missing end of capture' do
344
+ expect { FlaskSubclass.new('foo<bar') }.
345
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo<bar"')
346
+ end
347
+
348
+ example 'unknown converter' do
349
+ expect { FlaskSubclass.new('foo<bar:name>') }.
350
+ to raise_error(Mustermann::ParseError, 'unexpected converter "bar" while parsing "foo<bar:name>"')
351
+ end
352
+
353
+ example 'broken argument synax' do
354
+ expect { FlaskSubclass.new('<string(length=3=2):foo>') }.
355
+ to raise_error(Mustermann::ParseError, 'unexpected = while parsing "<string(length=3=2):foo>"')
356
+ end
357
+
358
+ example 'missing )' do
359
+ expect { FlaskSubclass.new('<string(foo') }.
360
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(foo"')
361
+ end
362
+
363
+ example 'missing ""' do
364
+ expect { FlaskSubclass.new('<string("foo') }.
365
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(\\"foo"')
366
+ end
367
+ end
368
+ end