mustermann19 0.3.1 → 0.3.1.1

Sign up to get free protection for your applications and to get access to all the features.
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