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,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