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,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
@@ -8,6 +8,21 @@ describe Mustermann::Identity do
8
8
  pattern '' do
9
9
  it { should match('') }
10
10
  it { should_not match('/') }
11
+
12
+ it { should respond_to(:expand) }
13
+ it { should respond_to(:to_templates) }
14
+
15
+
16
+ it { should generate_template('') }
17
+ it { should expand.to('') }
18
+ it { should expand(:ignore, a: 10).to('') }
19
+ it { should expand(:append, a: 10).to('?a=10') }
20
+ it { should_not expand(:raise, a: 10) }
21
+ it { should_not expand(a: 10) }
22
+
23
+ example do
24
+ pattern.match('').inspect.should be == '#<Mustermann::SimpleMatch "">'
25
+ end
11
26
  end
12
27
 
13
28
  pattern '/' do
@@ -16,6 +31,9 @@ describe Mustermann::Identity do
16
31
 
17
32
  example { pattern.params('/').should be == {} }
18
33
  example { pattern.params('').should be_nil }
34
+
35
+ it { should generate_template('/') }
36
+ it { should expand.to('/') }
19
37
  end
20
38
 
21
39
  pattern '/foo' do
@@ -38,6 +56,9 @@ describe Mustermann::Identity do
38
56
  it { should_not match('/foo/bar') }
39
57
  it { should_not match('/') }
40
58
  it { should_not match('/foo/') }
59
+
60
+ it { should generate_template('/:foo') }
61
+ it { should expand.to('/:foo') }
41
62
  end
42
63
 
43
64
  pattern '/föö' do
@@ -58,6 +79,7 @@ describe Mustermann::Identity do
58
79
  it { should match('/path%20with%20spaces') }
59
80
  it { should_not match('/path%2Bwith%2Bspaces') }
60
81
  it { should_not match('/path+with+spaces') }
82
+ it { should generate_template('/path%20with%20spaces') }
61
83
  end
62
84
 
63
85
  pattern '/foo&bar' do
@@ -80,4 +102,26 @@ describe Mustermann::Identity do
80
102
  it { should_not match('/path%2Bwith%2Bspaces') }
81
103
  it { should_not match('/path+with+spaces') }
82
104
  end
105
+
106
+ context "peeking" do
107
+ subject(:pattern) { Mustermann::Identity.new("foo bar") }
108
+
109
+ describe :peek_size do
110
+ example { pattern.peek_size("foo bar blah") .should be == "foo bar".size }
111
+ example { pattern.peek_size("foo%20bar blah") .should be == "foo%20bar".size }
112
+ example { pattern.peek_size("foobar") .should be_nil }
113
+ end
114
+
115
+ describe :peek_match do
116
+ example { pattern.peek_match("foo bar blah").to_s .should be == "foo bar" }
117
+ example { pattern.peek_match("foo%20bar blah").to_s .should be == "foo%20bar" }
118
+ example { pattern.peek_match("foobar") .should be_nil }
119
+ end
120
+
121
+ describe :peek_params do
122
+ example { pattern.peek_params("foo bar blah") .should be == [{}, "foo bar".size] }
123
+ example { pattern.peek_params("foo%20bar blah") .should be == [{}, "foo%20bar".size] }
124
+ example { pattern.peek_params("foobar") .should be_nil }
125
+ end
126
+ end
83
127
  end
@@ -35,6 +35,20 @@ describe Mustermann do
35
35
  example { Mustermann.new(pattern, type: :rails) .should be_a(Mustermann::Rails) }
36
36
  example { Mustermann.new(pattern).to_s.should be == 'foo' }
37
37
  end
38
+
39
+ context "multiple arguments" do
40
+ example { Mustermann.new('', '') .should be_a(Mustermann::Composite) }
41
+ example { Mustermann.new('', '').patterns.first .should be_a(Mustermann::Sinatra) }
42
+ example { Mustermann.new('', '').operator .should be == :| }
43
+ example { Mustermann.new('', '', operator: :&).operator .should be == :& }
44
+ example { Mustermann.new('', '', greedy: true) .should be_a(Mustermann::Composite) }
45
+ end
46
+
47
+ context "invalid arguments" do
48
+ it "raise a TypeError for unsupported types" do
49
+ expect { Mustermann.new(10) }.to raise_error(TypeError, "Fixnum can't be coerced into Mustermann::Pattern")
50
+ end
51
+ end
38
52
  end
39
53
 
40
54
  describe :[] do
data/spec/pattern_spec.rb CHANGED
@@ -20,10 +20,14 @@ describe Mustermann::Pattern do
20
20
  end
21
21
  end
22
22
 
23
- describe :expand do
23
+ describe :respond_to? do
24
24
  subject(:pattern) { Mustermann::Pattern.new("") }
25
- it { should_not respond_to(:expand) }
26
- it { expect { pattern.expand }.to raise_error(NotImplementedError) }
25
+
26
+ it { should_not respond_to(:expand) }
27
+ it { should_not respond_to(:to_templates) }
28
+
29
+ it { expect { pattern.expand } .to raise_error(NotImplementedError) }
30
+ it { expect { pattern.to_templates } .to raise_error(NotImplementedError) }
27
31
  end
28
32
 
29
33
  describe :== do
@@ -0,0 +1,101 @@
1
+ require 'support'
2
+ require 'mustermann/pyramid'
3
+
4
+ describe Mustermann::Pyramid 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 match('/foo') .capturing foo: 'foo' }
75
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
76
+
77
+ it { should expand .to('/') }
78
+ it { should expand(foo: nil) .to('/') }
79
+ it { should expand(foo: '') .to('/') }
80
+ it { should expand(foo: 'foo') .to('/foo') }
81
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
82
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
83
+
84
+ example { pattern.params("/foo/bar").should be == {"foo" => ["foo", "bar"]}}
85
+ it { should generate_template('/{+foo}') }
86
+ end
87
+
88
+ pattern '/{foo:.*}' do
89
+ it { should match('/') .capturing foo: '' }
90
+ it { should match('/foo') .capturing foo: 'foo' }
91
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
92
+
93
+ it { should expand(foo: '') .to('/') }
94
+ it { should expand(foo: 'foo') .to('/foo') }
95
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
96
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
97
+
98
+ example { pattern.params("/foo/bar").should be == {"foo" => "foo/bar"}}
99
+ it { should generate_template('/{foo}') }
100
+ end
101
+ end