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
data/spec/rails_spec.rb CHANGED
@@ -11,6 +11,11 @@ describe Mustermann::Rails do
11
11
 
12
12
  it { should expand.to('') }
13
13
  it { should_not expand(a: 1) }
14
+
15
+ it { should generate_template('') }
16
+
17
+ it { should respond_to(:expand) }
18
+ it { should respond_to(:to_templates) }
14
19
  end
15
20
 
16
21
  pattern '/' do
@@ -62,6 +67,8 @@ describe Mustermann::Rails do
62
67
  it { should_not expand(foo: 'foo', bar: 'bar') }
63
68
  it { should_not expand(bar: 'bar') }
64
69
  it { should_not expand }
70
+
71
+ it { should generate_template('/{foo}') }
65
72
  end
66
73
 
67
74
  pattern '/föö' do
@@ -86,23 +93,29 @@ describe Mustermann::Rails do
86
93
  it { should expand(foo: 'foo', bar: 'bar').to('/foo/bar') }
87
94
  it { should_not expand(foo: 'foo') }
88
95
  it { should_not expand(bar: 'bar') }
96
+
97
+ it { should generate_template('/{foo}/{bar}') }
89
98
  end
90
99
 
91
100
  pattern '/hello/:person' do
92
101
  it { should match('/hello/Frank').capturing person: 'Frank' }
93
102
  it { should expand(person: 'Frank') .to '/hello/Frank' }
94
103
  it { should expand(person: 'Frank?') .to '/hello/Frank%3F' }
104
+
105
+ it { should generate_template('/hello/{person}') }
95
106
  end
96
107
 
97
108
  pattern '/?:foo?/?:bar?' do
98
109
  it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
99
110
  it { should_not match('/hello/world/') }
100
111
  it { should expand(foo: 'hello', bar: 'world').to('/%3Fhello%3F/%3Fworld%3F') }
112
+ it { should generate_template('/?{foo}?/?{bar}?') }
101
113
  end
102
114
 
103
115
  pattern '/:foo_bar' do
104
116
  it { should match('/hello').capturing foo_bar: 'hello' }
105
117
  it { should expand(foo_bar: 'hello').to('/hello') }
118
+ it { should generate_template('/{foo_bar}') }
106
119
  end
107
120
 
108
121
  pattern '/*foo' do
@@ -116,6 +129,15 @@ describe Mustermann::Rails do
116
129
  it { should expand(foo: 'foo') .to('/foo') }
117
130
  it { should expand(foo: 'foo/bar') .to('/foo/bar') }
118
131
  it { should expand(foo: 'foo.bar') .to('/foo.bar') }
132
+
133
+ it { should generate_template('/{+foo}') }
134
+ end
135
+
136
+ pattern '/*splat' do
137
+ it { should match('/') .capturing splat: '' }
138
+ it { should match('/foo') .capturing splat: 'foo' }
139
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
140
+ it { should generate_template('/{+splat}') }
119
141
  end
120
142
 
121
143
  pattern '/:foo/*bar' do
@@ -129,6 +151,8 @@ describe Mustermann::Rails do
129
151
  it { should expand(foo: 'foo', bar: 'bar') .to('/foo/bar') }
130
152
  it { should expand(foo: 'foo', bar: 'foo/bar') .to('/foo/foo/bar') }
131
153
  it { should expand(foo: 'foo/bar', bar: 'bar') .to('/foo%2Fbar/bar') }
154
+
155
+ it { should generate_template('/{foo}/{+bar}') }
132
156
  end
133
157
 
134
158
  pattern '/test$/' do
@@ -148,6 +172,8 @@ describe Mustermann::Rails do
148
172
  it { should match('/path%2Bwith%2Bspaces') }
149
173
  it { should match('/path+with+spaces') }
150
174
  it { should expand.to('/path%20with%20spaces') }
175
+
176
+ it { should generate_template('/path%20with%20spaces') }
151
177
  end
152
178
 
153
179
  pattern '/foo&bar' do
@@ -158,6 +184,7 @@ describe Mustermann::Rails do
158
184
  it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
159
185
  example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
160
186
  it { should expand(a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom').to('/bar/foo/bling/baz/boom') }
187
+ it { should generate_template('/{+a}/{foo}/{+b}/{+c}') }
161
188
  end
162
189
 
163
190
  pattern '/test.bar' do
@@ -189,6 +216,9 @@ describe Mustermann::Rails do
189
216
  it { should match('/axaxx') .capturing a: 'axax' }
190
217
  it { should expand(a: 'x').to('/xx') }
191
218
  it { should expand(a: 'a').to('/ax') }
219
+
220
+ it { should generate_template('/{a}x') }
221
+ it { should generate_template('/{a}') }
192
222
  end
193
223
 
194
224
  pattern '/:user(@:host)' do
@@ -198,6 +228,9 @@ describe Mustermann::Rails do
198
228
 
199
229
  it { should expand(user: 'foo') .to('/foo') }
200
230
  it { should expand(user: 'foo', host: 'bar') .to('/foo@bar') }
231
+
232
+ it { should generate_template('/{user}') }
233
+ it { should generate_template('/{user}@{host}') }
201
234
  end
202
235
 
203
236
  pattern '/:file(.:ext)' do
@@ -211,6 +244,9 @@ describe Mustermann::Rails do
211
244
 
212
245
  it { should expand(file: 'pony') .to('/pony') }
213
246
  it { should expand(file: 'pony', ext: 'jpg') .to('/pony.jpg') }
247
+
248
+ it { should generate_template('/{file}') }
249
+ it { should generate_template('/{file}.{ext}') }
214
250
  end
215
251
 
216
252
  pattern '/:id/test.bar' do
@@ -245,6 +281,10 @@ describe Mustermann::Rails do
245
281
 
246
282
  it { should expand(a: ?a, b: ?b) .to('/a/b.') }
247
283
  it { should expand(a: ?a, b: ?b, c: ?c) .to('/a/b.c') }
284
+
285
+ it { should generate_template('/{a}/{b}') }
286
+ it { should generate_template('/{a}/{b}.') }
287
+ it { should generate_template('/{a}/{b}.{c}') }
248
288
  end
249
289
 
250
290
  pattern '/:a(foo:b)' do
@@ -255,6 +295,10 @@ describe Mustermann::Rails do
255
295
 
256
296
  it { should expand(a: ?a) .to('/a') }
257
297
  it { should expand(a: ?a, b: ?b) .to('/afoob') }
298
+
299
+ it { should generate_template('/{a}foo{b}') }
300
+ it { should generate_template('/{a}') }
301
+ it { should_not generate_template('/{a}foo') }
258
302
  end
259
303
 
260
304
  pattern '/fo(o)' do
@@ -512,11 +556,41 @@ describe Mustermann::Rails do
512
556
  end
513
557
 
514
558
  describe :to_regexp do
515
- example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A\Z/ }
559
+ example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A(?-mix:)\Z/ }
516
560
 
517
561
  context 'Regexp.try_convert' do
518
- example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A\Z/ }
562
+ example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A(?-mix:)\Z/ }
519
563
  end
520
564
  end
521
565
  end
566
+
567
+ context 'Proc compatibility' do
568
+ describe :to_proc do
569
+ example { Mustermann::Rails.new("/").to_proc.should be_a(Proc) }
570
+ example('non-matching') { Mustermann::Rails.new("/") .to_proc.call('/foo').should be == false }
571
+ example('matching') { Mustermann::Rails.new("/:foo") .to_proc.call('/foo').should be == true }
572
+ end
573
+ end
574
+
575
+ context "peeking" do
576
+ subject(:pattern) { Mustermann::Rails.new(":name") }
577
+
578
+ describe :peek_size do
579
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
580
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
581
+ example { pattern.peek_size("/foo bar") .should be_nil }
582
+ end
583
+
584
+ describe :peek_match do
585
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
586
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
587
+ example { pattern.peek_match("/foo bar") .should be_nil }
588
+ end
589
+
590
+ describe :peek_params do
591
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
592
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
593
+ example { pattern.peek_params("/foo bar") .should be_nil }
594
+ end
595
+ end
522
596
  end
data/spec/regular_spec.rb CHANGED
@@ -7,6 +7,9 @@ describe Mustermann::Regular do
7
7
  pattern '' do
8
8
  it { should match('') }
9
9
  it { should_not match('/') }
10
+
11
+ it { should_not respond_to(:expand) }
12
+ it { should_not respond_to(:to_templates) }
10
13
  end
11
14
 
12
15
  pattern '/' do
@@ -33,4 +36,26 @@ describe Mustermann::Regular do
33
36
  it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
34
37
  it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
35
38
  end
39
+
40
+ context "peeking" do
41
+ subject(:pattern) { Mustermann::Regular.new("(?<name>[^/]+)") }
42
+
43
+ describe :peek_size do
44
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
45
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
46
+ example { pattern.peek_size("/foo bar") .should be_nil }
47
+ end
48
+
49
+ describe :peek_match do
50
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
51
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
52
+ example { pattern.peek_match("/foo bar") .should be_nil }
53
+ end
54
+
55
+ describe :peek_params do
56
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
57
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
58
+ example { pattern.peek_params("/foo bar") .should be_nil }
59
+ end
60
+ end
36
61
  end
data/spec/shell_spec.rb CHANGED
@@ -8,6 +8,9 @@ describe Mustermann::Shell do
8
8
  pattern '' do
9
9
  it { should match('') }
10
10
  it { should_not match('/') }
11
+
12
+ it { should_not respond_to(:expand) }
13
+ it { should_not respond_to(:to_templates) }
11
14
  end
12
15
 
13
16
  pattern '/' do
@@ -106,4 +109,34 @@ describe Mustermann::Shell do
106
109
  describe :=~ do
107
110
  example { '/foo'.should be =~ Mustermann::Shell.new('/foo') }
108
111
  end
112
+
113
+ context "peeking", :skip => true do
114
+ subject(:pattern) { Mustermann::Shell.new("foo*/") }
115
+
116
+ describe :peek_size do
117
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar/".size }
118
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar/".size }
119
+ example { pattern.peek_size("/foo bar") .should be_nil }
120
+
121
+ context 'with just * as pattern' do
122
+ subject(:pattern) { Mustermann::Shell.new('*') }
123
+ example { pattern.peek_size('foo') .should be == 3 }
124
+ example { pattern.peek_size('foo/bar') .should be == 3 }
125
+ example { pattern.peek_size('foo/bar/baz') .should be == 3 }
126
+ example { pattern.peek_size('foo/bar/baz/blah') .should be == 3 }
127
+ end
128
+ end
129
+
130
+ describe :peek_match do
131
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar/" }
132
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar/" }
133
+ example { pattern.peek_match("/foo bar") .should be_nil }
134
+ end
135
+
136
+ describe :peek_params do
137
+ example { pattern.peek_params("foo bar/blah") .should be == [{}, "foo bar/".size] }
138
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{}, "foo%20bar/".size] }
139
+ example { pattern.peek_params("/foo bar") .should be_nil }
140
+ end
141
+ end
109
142
  end
data/spec/simple_spec.rb CHANGED
@@ -8,6 +8,9 @@ describe Mustermann::Simple do
8
8
  pattern '' do
9
9
  it { should match('') }
10
10
  it { should_not match('/') }
11
+
12
+ it { should_not respond_to(:expand) }
13
+ it { should_not respond_to(:to_templates) }
11
14
  end
12
15
 
13
16
  pattern '/' do
@@ -234,4 +237,26 @@ describe Mustermann::Simple do
234
237
  to raise_error(Mustermann::CompileError)
235
238
  end
236
239
  end
240
+
241
+ context "peeking" do
242
+ subject(:pattern) { Mustermann::Simple.new(":name") }
243
+
244
+ describe :peek_size do
245
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
246
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
247
+ example { pattern.peek_size("/foo bar") .should be_nil }
248
+ end
249
+
250
+ describe :peek_match do
251
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
252
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
253
+ example { pattern.peek_match("/foo bar") .should be_nil }
254
+ end
255
+
256
+ describe :peek_params do
257
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
258
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
259
+ example { pattern.peek_params("/foo bar") .should be_nil }
260
+ end
261
+ end
237
262
  end
data/spec/sinatra_spec.rb CHANGED
@@ -8,6 +8,11 @@ describe Mustermann::Sinatra do
8
8
  pattern '' do
9
9
  it { should match('') }
10
10
  it { should_not match('/') }
11
+
12
+ it { should generate_template('') }
13
+
14
+ it { should respond_to(:expand) }
15
+ it { should respond_to(:to_templates) }
11
16
  end
12
17
 
13
18
  pattern '/' do
@@ -44,6 +49,8 @@ describe Mustermann::Sinatra do
44
49
  it { should_not match('/foo/bar') }
45
50
  it { should_not match('/') }
46
51
  it { should_not match('/foo/') }
52
+
53
+ it { should generate_template('/{foo}') }
47
54
  end
48
55
 
49
56
  pattern '/föö' do
@@ -62,10 +69,34 @@ describe Mustermann::Sinatra do
62
69
 
63
70
  example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
64
71
  example { pattern.params('').should be_nil }
72
+
73
+ it { should generate_template('/{foo}/{bar}') }
74
+ end
75
+
76
+ pattern "/{foo}/{bar}" do
77
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
78
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
79
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
80
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
81
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
82
+
83
+ it { should_not match('/foo%2Fbar') }
84
+ it { should_not match('/foo%2fbar') }
85
+
86
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
87
+ example { pattern.params('').should be_nil }
88
+
89
+ it { should generate_template('/{foo}/{bar}') }
65
90
  end
66
91
 
67
92
  pattern '/hello/:person' do
68
93
  it { should match('/hello/Frank').capturing person: 'Frank' }
94
+ it { should generate_template('/hello/{person}') }
95
+ end
96
+
97
+ pattern '/hello/{person}' do
98
+ it { should match('/hello/Frank').capturing person: 'Frank' }
99
+ it { should generate_template('/hello/{person}') }
69
100
  end
70
101
 
71
102
  pattern '/?:foo?/?:bar?' do
@@ -74,23 +105,40 @@ describe Mustermann::Sinatra do
74
105
  it { should match('/') .capturing foo: nil, bar: nil }
75
106
  it { should match('') .capturing foo: nil, bar: nil }
76
107
 
77
- it { should_not match('/hello/world/') }
108
+ it { should expand(foo: 'hello') .to('/hello/') }
109
+ it { should expand(foo: 'hello', bar: 'world') .to('/hello/world') }
110
+ it { should expand(bar: 'world') .to('//world') }
111
+ it { should expand .to('//') }
112
+ it { should_not expand(baz: '') }
78
113
 
79
- # it { should expand(foo: 'hello') .to('/hello') }
80
- # it { should expand(foo: 'hello', bar: 'world') .to('/hello/world') }
81
- # it { should expand(bar: 'world') .to('//world') }
82
- # it { should expand .to('') }
83
- # it { should_not expand(baz: '') }
114
+ it { should_not match('/hello/world/') }
115
+ it { should generate_templates("", "/", "//", "//{bar}", "/{bar}", "/{foo}", "/{foo}/", "/{foo}/{bar}", "/{foo}{bar}", "{bar}", "{foo}", "{foo}/", "{foo}/{bar}", "{foo}{bar}") }
84
116
  end
85
117
 
86
118
  pattern '/:foo_bar' do
87
119
  it { should match('/hello').capturing foo_bar: 'hello' }
120
+ it { should generate_template('/{foo_bar}') }
121
+ end
122
+
123
+ pattern '/{foo.bar}' do
124
+ it { should match('/hello').capturing :"foo.bar" => 'hello' }
125
+ it { should generate_template('/{foo.bar}') }
88
126
  end
89
127
 
90
128
  pattern '/*' do
91
129
  it { should match('/') .capturing splat: '' }
92
130
  it { should match('/foo') .capturing splat: 'foo' }
93
131
  it { should match('/foo/bar') .capturing splat: 'foo/bar' }
132
+ it { should generate_template('/{+splat}') }
133
+
134
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
135
+ end
136
+
137
+ pattern '/{+splat}' do
138
+ it { should match('/') .capturing splat: '' }
139
+ it { should match('/foo') .capturing splat: 'foo' }
140
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
141
+ it { should generate_template('/{+splat}') }
94
142
 
95
143
  example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
96
144
  end
@@ -99,6 +147,17 @@ describe Mustermann::Sinatra do
99
147
  it { should match('/') .capturing foo: '' }
100
148
  it { should match('/foo') .capturing foo: 'foo' }
101
149
  it { should match('/foo/bar') .capturing foo: 'foo/bar' }
150
+ it { should generate_template('/{+foo}') }
151
+
152
+ example { pattern.params('/foo') .should be == {"foo" => "foo" } }
153
+ example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
154
+ end
155
+
156
+ pattern '/{+foo}' do
157
+ it { should match('/') .capturing foo: '' }
158
+ it { should match('/foo') .capturing foo: 'foo' }
159
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
160
+ it { should generate_template('/{+foo}') }
102
161
 
103
162
  example { pattern.params('/foo') .should be == {"foo" => "foo" } }
104
163
  example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
@@ -106,6 +165,12 @@ describe Mustermann::Sinatra do
106
165
 
107
166
  pattern '/*foo/*bar' do
108
167
  it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
168
+ it { should generate_template('/{+foo}/{+bar}') }
169
+ end
170
+
171
+ pattern '/{+foo}/{+bar}' do
172
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
173
+ it { should generate_template('/{+foo}/{+bar}') }
109
174
  end
110
175
 
111
176
  pattern '/:foo/*' do
@@ -113,6 +178,18 @@ describe Mustermann::Sinatra do
113
178
  it { should match("/foo/") .capturing foo: 'foo', splat: '' }
114
179
  it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
115
180
  it { should_not match('/foo') }
181
+ it { should generate_template('/{foo}/{+splat}') }
182
+
183
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
184
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
185
+ end
186
+
187
+ pattern '/{foo}/*' do
188
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
189
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
190
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
191
+ it { should_not match('/foo') }
192
+ it { should generate_template('/{foo}/{+splat}') }
116
193
 
117
194
  example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
118
195
  example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
@@ -132,12 +209,18 @@ describe Mustermann::Sinatra do
132
209
  it { should match('/path%20with%20spaces') }
133
210
  it { should match('/path%2Bwith%2Bspaces') }
134
211
  it { should match('/path+with+spaces') }
212
+
213
+ it { should generate_template('/path%20with%20spaces') }
135
214
  end
136
215
 
137
216
  pattern '/foo&bar' do
138
217
  it { should match('/foo&bar') }
139
218
  end
140
219
 
220
+ pattern '/foo\{bar' do
221
+ it { should match('/foo%7Bbar') }
222
+ end
223
+
141
224
  pattern '/*/:foo/*/*' do
142
225
  it { should match('/bar/foo/bling/baz/boom') }
143
226
 
@@ -154,13 +237,29 @@ describe Mustermann::Sinatra do
154
237
  end
155
238
  end
156
239
 
240
+ pattern '/{+splat}/{foo}/{+splat}/{+splat}' do
241
+ it { should match('/bar/foo/bling/baz/boom') }
242
+
243
+ it "should capture all splat parts" do
244
+ match = pattern.match('/bar/foo/bling/baz/boom')
245
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
246
+ match.names.should be == ['splat', 'foo']
247
+ end
248
+
249
+ it 'should map to proper params' do
250
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
251
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
252
+ }
253
+ end
254
+ end
255
+
157
256
  pattern '/test.bar' do
158
257
  it { should match('/test.bar') }
159
258
  it { should_not match('/test0bar') }
160
259
  end
161
260
 
162
261
  pattern '/:file.:ext' do
163
- it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
262
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
164
263
  it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
165
264
  it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
166
265
 
@@ -180,12 +279,18 @@ describe Mustermann::Sinatra do
180
279
  it { should match('/ax') .capturing a: 'a' }
181
280
  it { should match('/axax') .capturing a: 'axa' }
182
281
  it { should match('/axaxx') .capturing a: 'axax' }
282
+
283
+ it { should generate_template('/{a}x') }
284
+ it { should generate_template('/{a}') }
183
285
  end
184
286
 
185
287
  pattern '/:user(@:host)?' do
186
288
  it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
187
289
  it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
188
290
  it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
291
+
292
+ it { should generate_template('/{user}') }
293
+ it { should generate_template('/{user}@{host}') }
189
294
  end
190
295
 
191
296
  pattern '/:file(.:ext)?' do
@@ -196,6 +301,10 @@ describe Mustermann::Sinatra do
196
301
  it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
197
302
  it { should match('/pony.') .capturing file: 'pony.' }
198
303
  it { should_not match('/.jpg') }
304
+
305
+ it { should generate_template('/{file}') }
306
+ it { should generate_template('/{file}.{ext}') }
307
+ it { should_not generate_template('/{file}.') }
199
308
  end
200
309
 
201
310
  pattern '/:id/test.bar' do
@@ -324,6 +433,37 @@ describe Mustermann::Sinatra do
324
433
  it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
325
434
  end
326
435
 
436
+ pattern "/(foo|bar)" do
437
+ it { should match("/foo") }
438
+ it { should match("/bar") }
439
+
440
+ it { should generate_template('/foo') }
441
+ it { should generate_template('/bar') }
442
+ end
443
+
444
+ pattern "/(foo\\|bar)" do
445
+ it { should match "/foo%7Cbar" }
446
+ it { should generate_template "/foo%7Cbar" }
447
+
448
+ it { should_not match("/foo") }
449
+ it { should_not match("/bar") }
450
+
451
+ it { should_not generate_template('/foo') }
452
+ it { should_not generate_template('/bar') }
453
+ end
454
+
455
+ pattern "/(:a/:b|:c)" do
456
+ it { should match("/foo") .capturing c: 'foo' }
457
+ it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
458
+
459
+ it { should generate_template('/{a}/{b}') }
460
+ it { should generate_template('/{c}') }
461
+
462
+ it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
463
+ it { should expand(c: 'foo') .to('/foo') }
464
+ it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
465
+ end
466
+
327
467
  pattern '/:foo', capture: /\d+/ do
328
468
  it { should match('/1') .capturing foo: '1' }
329
469
  it { should match('/123') .capturing foo: '123' }
@@ -509,6 +649,11 @@ describe Mustermann::Sinatra do
509
649
  to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
510
650
  end
511
651
 
652
+ example '| outside of group' do
653
+ expect { Mustermann::Sinatra.new('foo|bar') }.
654
+ to raise_error(Mustermann::ParseError, 'unexpected | while parsing "foo|bar"')
655
+ end
656
+
512
657
  example 'dangling escape' do
513
658
  expect { Mustermann::Sinatra.new('foo\\') }.
514
659
  to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')
@@ -564,11 +709,41 @@ describe Mustermann::Sinatra do
564
709
  end
565
710
 
566
711
  describe :to_regexp do
567
- example('empty pattern') { Mustermann::Sinatra.new('').to_regexp.should be == /\A\Z/ }
712
+ example('empty pattern') { Mustermann::Sinatra.new('').to_regexp.should be == /\A(?-mix:)\Z/ }
568
713
 
569
714
  context 'Regexp.try_convert' do
570
- example('empty pattern') { Regexp.try_convert(Mustermann::Sinatra.new('')).should be == /\A\Z/ }
715
+ example('empty pattern') { Regexp.try_convert(Mustermann::Sinatra.new('')).should be == /\A(?-mix:)\Z/ }
571
716
  end
572
717
  end
573
718
  end
719
+
720
+ context 'Proc compatibility' do
721
+ describe :to_proc do
722
+ example { Mustermann::Sinatra.new("/").to_proc.should be_a(Proc) }
723
+ example('non-matching') { Mustermann::Sinatra.new("/") .to_proc.call('/foo').should be == false }
724
+ example('matching') { Mustermann::Sinatra.new("/:foo") .to_proc.call('/foo').should be == true }
725
+ end
726
+ end
727
+
728
+ context "peeking" do
729
+ subject(:pattern) { Mustermann::Sinatra.new(":name") }
730
+
731
+ describe :peek_size do
732
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
733
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
734
+ example { pattern.peek_size("/foo bar") .should be_nil }
735
+ end
736
+
737
+ describe :peek_match do
738
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
739
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
740
+ example { pattern.peek_match("/foo bar") .should be_nil }
741
+ end
742
+
743
+ describe :peek_params do
744
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
745
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
746
+ example { pattern.peek_params("/foo bar") .should be_nil }
747
+ end
748
+ end
574
749
  end