mustermann19 0.3.1 → 0.3.1.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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +4 -3
  3. data/README.md +680 -376
  4. data/lib/mustermann/ast/compiler.rb +13 -7
  5. data/lib/mustermann/ast/expander.rb +11 -5
  6. data/lib/mustermann/ast/node.rb +27 -1
  7. data/lib/mustermann/ast/param_scanner.rb +20 -0
  8. data/lib/mustermann/ast/parser.rb +131 -12
  9. data/lib/mustermann/ast/pattern.rb +45 -6
  10. data/lib/mustermann/ast/template_generator.rb +28 -0
  11. data/lib/mustermann/ast/validation.rb +5 -3
  12. data/lib/mustermann/composite.rb +103 -0
  13. data/lib/mustermann/expander.rb +1 -1
  14. data/lib/mustermann/express.rb +34 -0
  15. data/lib/mustermann/flask.rb +204 -0
  16. data/lib/mustermann/identity.rb +54 -0
  17. data/lib/mustermann/pattern.rb +186 -12
  18. data/lib/mustermann/pattern_cache.rb +49 -0
  19. data/lib/mustermann/pyramid.rb +25 -0
  20. data/lib/mustermann/regexp_based.rb +18 -1
  21. data/lib/mustermann/regular.rb +1 -1
  22. data/lib/mustermann/shell.rb +8 -0
  23. data/lib/mustermann/simple.rb +1 -1
  24. data/lib/mustermann/simple_match.rb +5 -0
  25. data/lib/mustermann/sinatra.rb +19 -5
  26. data/lib/mustermann/string_scanner.rb +314 -0
  27. data/lib/mustermann/template.rb +10 -0
  28. data/lib/mustermann/to_pattern.rb +11 -6
  29. data/lib/mustermann/version.rb +1 -1
  30. data/lib/mustermann.rb +52 -3
  31. data/mustermann.gemspec +1 -1
  32. data/spec/composite_spec.rb +147 -0
  33. data/spec/expander_spec.rb +15 -0
  34. data/spec/express_spec.rb +209 -0
  35. data/spec/flask_spec.rb +361 -0
  36. data/spec/flask_subclass_spec.rb +368 -0
  37. data/spec/identity_spec.rb +44 -0
  38. data/spec/mustermann_spec.rb +14 -0
  39. data/spec/pattern_spec.rb +7 -3
  40. data/spec/pyramid_spec.rb +101 -0
  41. data/spec/rails_spec.rb +76 -2
  42. data/spec/regular_spec.rb +25 -0
  43. data/spec/shell_spec.rb +33 -0
  44. data/spec/simple_spec.rb +25 -0
  45. data/spec/sinatra_spec.rb +184 -9
  46. data/spec/string_scanner_spec.rb +271 -0
  47. data/spec/support/expand_matcher.rb +7 -5
  48. data/spec/support/generate_template_matcher.rb +27 -0
  49. data/spec/support/pattern.rb +3 -0
  50. data/spec/support/scan_matcher.rb +63 -0
  51. data/spec/support.rb +2 -1
  52. data/spec/template_spec.rb +22 -0
  53. data/spec/to_pattern_spec.rb +49 -0
  54. metadata +47 -61
  55. data/internals.md +0 -64
@@ -0,0 +1,209 @@
1
+ require 'support'
2
+ require 'mustermann/express'
3
+
4
+ describe Mustermann::Express 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 '/:foo+' do
74
+ it { should_not match('/') }
75
+ it { should match('/foo') .capturing foo: 'foo' }
76
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
77
+
78
+ it { should expand .to('/') }
79
+ it { should expand(foo: nil) .to('/') }
80
+ it { should expand(foo: '') .to('/') }
81
+ it { should expand(foo: 'foo') .to('/foo') }
82
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
83
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
84
+
85
+ it { should generate_template('/{+foo}') }
86
+ end
87
+
88
+ pattern '/:foo?' do
89
+ it { should match('/foo') .capturing foo: 'foo' }
90
+ it { should match('/bar') .capturing foo: 'bar' }
91
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
92
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
93
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
94
+ it { should match('/') }
95
+
96
+ it { should_not match('/foo?') }
97
+ it { should_not match('/foo/bar') }
98
+ it { should_not match('/foo/') }
99
+
100
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
101
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
102
+ example { pattern.params('/') .should be == {"foo" => nil } }
103
+
104
+ it { should expand(foo: 'bar') .to('/bar') }
105
+ it { should expand(foo: 'b r') .to('/b%20r') }
106
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
107
+ it { should expand .to('/') }
108
+
109
+ it { should_not expand(foo: 'foo', bar: 'bar') }
110
+ it { should_not expand(bar: 'bar') }
111
+
112
+ it { should generate_template('/{foo}') }
113
+ it { should generate_template('/') }
114
+ end
115
+
116
+ pattern '/:foo*' do
117
+ it { should match('/') .capturing foo: '' }
118
+ it { should match('/foo') .capturing foo: 'foo' }
119
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
120
+
121
+ it { should expand .to('/') }
122
+ it { should expand(foo: nil) .to('/') }
123
+ it { should expand(foo: '') .to('/') }
124
+ it { should expand(foo: 'foo') .to('/foo') }
125
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
126
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
127
+
128
+ it { should generate_template('/{+foo}') }
129
+ end
130
+
131
+ pattern '/:foo(.*)' do
132
+ it { should match('/') .capturing foo: '' }
133
+ it { should match('/foo') .capturing foo: 'foo' }
134
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
135
+
136
+ it { should expand(foo: '') .to('/') }
137
+ it { should expand(foo: 'foo') .to('/foo') }
138
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
139
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
140
+
141
+ it { should generate_template('/{foo}') }
142
+ end
143
+
144
+ pattern '/:foo(\d+)' do
145
+ it { should_not match('/') }
146
+ it { should_not match('/foo') }
147
+ it { should match('/15') .capturing foo: '15' }
148
+ it { should generate_template('/{foo}') }
149
+ end
150
+
151
+ pattern '/:foo(\d+|bar)' do
152
+ it { should_not match('/') }
153
+ it { should_not match('/foo') }
154
+ it { should match('/15') .capturing foo: '15' }
155
+ it { should match('/bar') .capturing foo: 'bar' }
156
+ it { should generate_template('/{foo}') }
157
+ end
158
+
159
+ pattern '/:foo(\))' do
160
+ it { should_not match('/') }
161
+ it { should_not match('/foo') }
162
+ it { should match('/)').capturing foo: ')' }
163
+ it { should generate_template('/{foo}') }
164
+ end
165
+
166
+ pattern '/:foo(prefix(\d+|bar))' do
167
+ it { should_not match('/prefix') }
168
+ it { should_not match('/prefixfoo') }
169
+ it { should match('/prefix15') .capturing foo: 'prefix15' }
170
+ it { should match('/prefixbar') .capturing foo: 'prefixbar' }
171
+ it { should generate_template('/{foo}') }
172
+ end
173
+
174
+ pattern '/(.+)' do
175
+ it { should_not match('/') }
176
+ it { should match('/foo') .capturing splat: 'foo' }
177
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
178
+ it { should generate_template('/{+splat}') }
179
+ end
180
+
181
+ pattern '/(foo(a|b))' do
182
+ it { should_not match('/') }
183
+ it { should match('/fooa') .capturing splat: 'fooa' }
184
+ it { should match('/foob') .capturing splat: 'foob' }
185
+ it { should generate_template('/{+splat}') }
186
+ end
187
+
188
+ context 'invalid syntax' do
189
+ example 'unexpected closing parenthesis' do
190
+ expect { Mustermann::Express.new('foo)bar') }.
191
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
192
+ end
193
+
194
+ example 'missing closing parenthesis' do
195
+ expect { Mustermann::Express.new('foo(bar') }.
196
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
197
+ end
198
+
199
+ example 'unexpected ?' do
200
+ expect { Mustermann::Express.new('foo?bar') }.
201
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo?bar"')
202
+ end
203
+
204
+ example 'unexpected *' do
205
+ expect { Mustermann::Express.new('foo*bar') }.
206
+ to raise_error(Mustermann::ParseError, 'unexpected * while parsing "foo*bar"')
207
+ end
208
+ end
209
+ end
@@ -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