mustermann 0.0.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.
@@ -0,0 +1,8 @@
1
+ require 'support'
2
+ require 'mustermann/regexp_based'
3
+
4
+ describe Mustermann::RegexpBased do
5
+ it 'raises a NotImplementedError when used directly' do
6
+ expect { described_class.new("") === "" }.to raise_error(NotImplementedError)
7
+ end
8
+ end
@@ -0,0 +1,108 @@
1
+ require 'support'
2
+ require 'mustermann/shell'
3
+
4
+ describe Mustermann::Shell do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+ end
11
+
12
+ pattern '/' do
13
+ it { should match('/') }
14
+ it { should_not match('/foo') }
15
+
16
+ example { pattern.params('/').should be == {} }
17
+ example { pattern.params('').should be_nil }
18
+ end
19
+
20
+ pattern '/foo' do
21
+ it { should match('/foo') }
22
+ it { should_not match('/bar') }
23
+ it { should_not match('/foo.bar') }
24
+ end
25
+
26
+ pattern '/foo/bar' do
27
+ it { should match('/foo/bar') }
28
+ it { should match('/foo%2Fbar') }
29
+ it { should match('/foo%2fbar') }
30
+ end
31
+
32
+ pattern '/*/bar' do
33
+ it { should match('/foo/bar') }
34
+ it { should match('/bar/bar') }
35
+ it { should match('/foo%2Fbar') }
36
+ it { should match('/foo%2fbar') }
37
+ it { should_not match('/foo/foo/bar') }
38
+ it { should_not match('/bar/foo') }
39
+ end
40
+
41
+ pattern '/**/foo' do
42
+ it { should match('/a/b/c/foo') }
43
+ it { should match('/a/b/c/foo') }
44
+ it { should match('/a/.b/c/foo') }
45
+ it { should match('/a/.b/c/foo') }
46
+ end
47
+
48
+ pattern '/:foo' do
49
+ it { should match('/:foo') }
50
+ it { should match('/%3Afoo') }
51
+ it { should_not match('/foo') }
52
+ it { should_not match('/foo?') }
53
+ it { should_not match('/foo/bar') }
54
+ it { should_not match('/') }
55
+ it { should_not match('/foo/') }
56
+ end
57
+
58
+ pattern '/föö' do
59
+ it { should match("/f%C3%B6%C3%B6") }
60
+ end
61
+
62
+ pattern '/test$/' do
63
+ it { should match('/test$/') }
64
+ end
65
+
66
+ pattern '/te+st/' do
67
+ it { should match('/te+st/') }
68
+ it { should_not match('/test/') }
69
+ it { should_not match('/teest/') }
70
+ end
71
+
72
+ pattern "/path with spaces" do
73
+ it { should match('/path%20with%20spaces') }
74
+ it { should_not match('/path%2Bwith%2Bspaces') }
75
+ it { should_not match('/path+with+spaces') }
76
+ end
77
+
78
+ pattern '/foo&bar' do
79
+ it { should match('/foo&bar') }
80
+ end
81
+
82
+ pattern '/test.bar' do
83
+ it { should match('/test.bar') }
84
+ it { should_not match('/test0bar') }
85
+ end
86
+
87
+ pattern '/{foo,bar}' do
88
+ it { should match('/foo') }
89
+ it { should match('/bar') }
90
+ it { should_not match('/foobar') }
91
+ end
92
+
93
+ pattern '/foo/bar', uri_decode: false do
94
+ it { should match('/foo/bar') }
95
+ it { should_not match('/foo%2Fbar') }
96
+ it { should_not match('/foo%2fbar') }
97
+ end
98
+
99
+ pattern "/path with spaces", uri_decode: false do
100
+ it { should_not match('/path%20with%20spaces') }
101
+ it { should_not match('/path%2Bwith%2Bspaces') }
102
+ it { should_not match('/path+with+spaces') }
103
+ end
104
+
105
+ describe :=~ do
106
+ example { '/foo'.should be =~ described_class.new('/foo') }
107
+ end
108
+ end
@@ -0,0 +1,10 @@
1
+ require 'support'
2
+ require 'mustermann/simple_match'
3
+
4
+ describe Mustermann::SimpleMatch do
5
+ subject { described_class.new('example') }
6
+ its(:to_s) { should be == 'example' }
7
+ its(:names) { should be == [] }
8
+ its(:captures) { should be == [] }
9
+ example { subject[1].should be == nil }
10
+ end
@@ -0,0 +1,236 @@
1
+ require 'support'
2
+ require 'mustermann/simple'
3
+
4
+ describe Mustermann::Simple do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+ end
11
+
12
+ pattern '/' do
13
+ it { should match('/') }
14
+ it { should_not match('/foo') }
15
+ end
16
+
17
+ pattern '/foo' do
18
+ it { should match('/foo') }
19
+ it { should_not match('/bar') }
20
+ it { should_not match('/foo.bar') }
21
+ end
22
+
23
+ pattern '/foo/bar' do
24
+ it { should match('/foo/bar') }
25
+ it { should_not match('/foo%2Fbar') }
26
+ it { should_not match('/foo%2fbar') }
27
+ end
28
+
29
+ pattern '/:foo' do
30
+ it { should match('/foo') .capturing foo: 'foo' }
31
+ it { should match('/bar') .capturing foo: 'bar' }
32
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
33
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
34
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
35
+
36
+ it { should_not match('/foo?') }
37
+ it { should_not match('/foo/bar') }
38
+ it { should_not match('/') }
39
+ it { should_not match('/foo/') }
40
+ end
41
+
42
+ pattern '/föö' do
43
+ it { should match("/f%C3%B6%C3%B6") }
44
+ end
45
+
46
+ pattern "/:foo/:bar" do
47
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
48
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
49
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
50
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
51
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
52
+
53
+ it { should_not match('/foo%2Fbar') }
54
+ it { should_not match('/foo%2fbar') }
55
+
56
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
57
+ example { pattern.params('').should be_nil }
58
+ end
59
+
60
+ pattern '/hello/:person' do
61
+ it { should match('/hello/Frank').capturing person: 'Frank' }
62
+ end
63
+
64
+ pattern '/?:foo?/?:bar?' do
65
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
66
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
67
+ it { should match('/') .capturing foo: nil, bar: nil }
68
+ it { should match('') .capturing foo: nil, bar: nil }
69
+
70
+ it { should_not match('/hello/world/') }
71
+ end
72
+
73
+ pattern '/*' do
74
+ it { should match('/') .capturing splat: '' }
75
+ it { should match('/foo') .capturing splat: 'foo' }
76
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
77
+
78
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
79
+ end
80
+
81
+ pattern '/:foo/*' do
82
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
83
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
84
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
85
+ it { should_not match('/foo') }
86
+
87
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
88
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
89
+ end
90
+
91
+ pattern '/test$/' do
92
+ it { should match('/test$/') }
93
+ end
94
+
95
+ pattern '/te+st/' do
96
+ it { should match('/te+st/') }
97
+ it { should_not match('/test/') }
98
+ it { should_not match('/teest/') }
99
+ end
100
+
101
+ pattern "/path with spaces" do
102
+ it { should match('/path%20with%20spaces') }
103
+ it { should match('/path%2Bwith%2Bspaces') }
104
+ it { should match('/path+with+spaces') }
105
+ end
106
+
107
+ pattern '/foo&bar' do
108
+ it { should match('/foo&bar') }
109
+ end
110
+
111
+ pattern '/*/:foo/*/*' do
112
+ it { should match('/bar/foo/bling/baz/boom') }
113
+
114
+ it "should capture all splat parts" do
115
+ match = pattern.match('/bar/foo/bling/baz/boom')
116
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
117
+ match.names.should be == ['splat', 'foo']
118
+ end
119
+
120
+ it 'should map to proper params' do
121
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
122
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
123
+ }
124
+ end
125
+ end
126
+
127
+ pattern '/test.bar' do
128
+ it { should match('/test.bar') }
129
+ it { should_not match('/test0bar') }
130
+ end
131
+
132
+ pattern '/:file.:ext' do
133
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
134
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
135
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
136
+
137
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
138
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
139
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
140
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
141
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
142
+
143
+ it { should_not match('/.jpg') }
144
+ end
145
+
146
+ pattern '/:id/test.bar' do
147
+ it { should match('/3/test.bar') .capturing id: '3' }
148
+ it { should match('/2/test.bar') .capturing id: '2' }
149
+ it { should match('/2E/test.bar') .capturing id: '2E' }
150
+ it { should match('/2e/test.bar') .capturing id: '2e' }
151
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
152
+ end
153
+
154
+ pattern '/10/:id' do
155
+ it { should match('/10/test') .capturing id: 'test' }
156
+ it { should match('/10/te.st') .capturing id: 'te.st' }
157
+ end
158
+
159
+ pattern '/10.1/:id' do
160
+ it { should match('/10.1/test') .capturing id: 'test' }
161
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
162
+ end
163
+
164
+ pattern '/foo?' do
165
+ it { should match('/fo') }
166
+ it { should match('/foo') }
167
+ it { should_not match('') }
168
+ it { should_not match('/') }
169
+ it { should_not match('/f') }
170
+ it { should_not match('/fooo') }
171
+ end
172
+
173
+ pattern '/:fOO' do
174
+ it { should match('/a').capturing fOO: 'a' }
175
+ end
176
+
177
+ pattern '/:_X' do
178
+ it { should match('/a').capturing _X: 'a' }
179
+ end
180
+
181
+ pattern '/:f00' do
182
+ it { should match('/a').capturing f00: 'a' }
183
+ end
184
+
185
+ pattern '/:foo.?' do
186
+ it { should match('/a.').capturing foo: 'a.' }
187
+ it { should match('/xy').capturing foo: 'xy' }
188
+ end
189
+
190
+ pattern '/(a)' do
191
+ it { should match('/(a)') }
192
+ it { should_not match('/a') }
193
+ end
194
+
195
+ pattern '/:foo.?', greedy: false do
196
+ it { should match('/a.').capturing foo: 'a' }
197
+ it { should match('/xy').capturing foo: 'xy' }
198
+ end
199
+
200
+ pattern '/foo?', uri_decode: false do
201
+ it { should match('/foo') }
202
+ it { should match('/fo') }
203
+ it { should_not match('/foo?') }
204
+ end
205
+
206
+ pattern '/foo/bar', uri_decode: false do
207
+ it { should match('/foo/bar') }
208
+ it { should_not match('/foo%2Fbar') }
209
+ it { should_not match('/foo%2fbar') }
210
+ end
211
+
212
+ pattern "/path with spaces", uri_decode: false do
213
+ it { should match('/path with spaces') }
214
+ it { should_not match('/path%20with%20spaces') }
215
+ it { should_not match('/path%2Bwith%2Bspaces') }
216
+ it { should_not match('/path+with+spaces') }
217
+ end
218
+
219
+ pattern "/path with spaces", space_matches_plus: false do
220
+ it { should match('/path%20with%20spaces') }
221
+ it { should_not match('/path%2Bwith%2Bspaces') }
222
+ it { should_not match('/path+with+spaces') }
223
+ end
224
+
225
+ context 'error handling' do
226
+ example '? at beginning of route' do
227
+ expect { Mustermann::Simple.new('?foobar') }.
228
+ to raise_error(Mustermann::ParseError)
229
+ end
230
+
231
+ example 'invalid capture name' do
232
+ expect { Mustermann::Simple.new('/:1a/') }.
233
+ to raise_error(Mustermann::CompileError)
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,554 @@
1
+ require 'support'
2
+ require 'mustermann/sinatra'
3
+
4
+ describe Mustermann::Sinatra do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+ end
11
+
12
+ pattern '/' do
13
+ it { should match('/') }
14
+ it { should_not match('/foo') }
15
+ end
16
+
17
+ pattern '/foo' do
18
+ it { should match('/foo') }
19
+ it { should_not match('/bar') }
20
+ it { should_not match('/foo.bar') }
21
+ end
22
+
23
+ pattern '/foo/bar' do
24
+ it { should match('/foo/bar') }
25
+ it { should_not match('/foo%2Fbar') }
26
+ it { should_not match('/foo%2fbar') }
27
+ end
28
+
29
+ pattern '/foo\/bar' do
30
+ it { should match('/foo/bar') }
31
+ it { should match('/foo%2Fbar') }
32
+ it { should match('/foo%2fbar') }
33
+ end
34
+
35
+ pattern '/:foo' do
36
+ it { should match('/foo') .capturing foo: 'foo' }
37
+ it { should match('/bar') .capturing foo: 'bar' }
38
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
39
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
40
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
41
+
42
+ it { should_not match('/foo?') }
43
+ it { should_not match('/foo/bar') }
44
+ it { should_not match('/') }
45
+ it { should_not match('/foo/') }
46
+ end
47
+
48
+ pattern '/föö' do
49
+ it { should match("/f%C3%B6%C3%B6") }
50
+ end
51
+
52
+ pattern "/:foo/:bar" do
53
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
54
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
55
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
56
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
57
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
58
+
59
+ it { should_not match('/foo%2Fbar') }
60
+ it { should_not match('/foo%2fbar') }
61
+
62
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
63
+ example { pattern.params('').should be_nil }
64
+ end
65
+
66
+ pattern '/hello/:person' do
67
+ it { should match('/hello/Frank').capturing person: 'Frank' }
68
+ end
69
+
70
+ pattern '/?:foo?/?:bar?' do
71
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
72
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
73
+ it { should match('/') .capturing foo: nil, bar: nil }
74
+ it { should match('') .capturing foo: nil, bar: nil }
75
+
76
+ it { should_not match('/hello/world/') }
77
+ end
78
+
79
+ pattern '/:foo_bar' do
80
+ it { should match('/hello').capturing foo_bar: 'hello' }
81
+ end
82
+
83
+ pattern '/*' do
84
+ it { should match('/') .capturing splat: '' }
85
+ it { should match('/foo') .capturing splat: 'foo' }
86
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
87
+
88
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
89
+ end
90
+
91
+ pattern '/:foo/*' do
92
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
93
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
94
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
95
+ it { should_not match('/foo') }
96
+
97
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
98
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
99
+ end
100
+
101
+ pattern '/test$/' do
102
+ it { should match('/test$/') }
103
+ end
104
+
105
+ pattern '/te+st/' do
106
+ it { should match('/te+st/') }
107
+ it { should_not match('/test/') }
108
+ it { should_not match('/teest/') }
109
+ end
110
+
111
+ pattern "/path with spaces" do
112
+ it { should match('/path%20with%20spaces') }
113
+ it { should match('/path%2Bwith%2Bspaces') }
114
+ it { should match('/path+with+spaces') }
115
+ end
116
+
117
+ pattern '/foo&bar' do
118
+ it { should match('/foo&bar') }
119
+ end
120
+
121
+ pattern '/*/:foo/*/*' do
122
+ it { should match('/bar/foo/bling/baz/boom') }
123
+
124
+ it "should capture all splat parts" do
125
+ match = pattern.match('/bar/foo/bling/baz/boom')
126
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
127
+ match.names.should be == ['splat', 'foo']
128
+ end
129
+
130
+ it 'should map to proper params' do
131
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
132
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
133
+ }
134
+ end
135
+ end
136
+
137
+ pattern '/test.bar' do
138
+ it { should match('/test.bar') }
139
+ it { should_not match('/test0bar') }
140
+ end
141
+
142
+ pattern '/:file.:ext' do
143
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
144
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
145
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
146
+
147
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
148
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
149
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
150
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
151
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
152
+
153
+ it { should_not match('/.jpg') }
154
+ end
155
+
156
+ pattern '/(:a)x?' do
157
+ it { should match('/a') .capturing a: 'a' }
158
+ it { should match('/xa') .capturing a: 'xa' }
159
+ it { should match('/axa') .capturing a: 'axa' }
160
+ it { should match('/ax') .capturing a: 'a' }
161
+ it { should match('/axax') .capturing a: 'axa' }
162
+ it { should match('/axaxx') .capturing a: 'axax' }
163
+ end
164
+
165
+ pattern '/:user(@:host)?' do
166
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
167
+ it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
168
+ it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
169
+ end
170
+
171
+ pattern '/:file(.:ext)?' do
172
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
173
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
174
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
175
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
176
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
177
+ it { should match('/pony.') .capturing file: 'pony.' }
178
+ it { should_not match('/.jpg') }
179
+ end
180
+
181
+ pattern '/:id/test.bar' do
182
+ it { should match('/3/test.bar') .capturing id: '3' }
183
+ it { should match('/2/test.bar') .capturing id: '2' }
184
+ it { should match('/2E/test.bar') .capturing id: '2E' }
185
+ it { should match('/2e/test.bar') .capturing id: '2e' }
186
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
187
+ end
188
+
189
+ pattern '/10/:id' do
190
+ it { should match('/10/test') .capturing id: 'test' }
191
+ it { should match('/10/te.st') .capturing id: 'te.st' }
192
+ end
193
+
194
+ pattern '/10.1/:id' do
195
+ it { should match('/10.1/test') .capturing id: 'test' }
196
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
197
+ end
198
+
199
+ pattern '/:foo.:bar/:id' do
200
+ it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
201
+ it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
202
+ end
203
+
204
+ pattern '/:a/:b.?:c?' do
205
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
206
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
207
+ it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
208
+ it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
209
+ it { should_not match('/a.b/c.d/e') }
210
+ end
211
+
212
+ pattern '/:a(foo:b)?' do
213
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
214
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
215
+ it { should match('/bar') .capturing a: 'bar', b: nil }
216
+ it { should_not match('/') }
217
+ end
218
+
219
+ pattern '/foo?' do
220
+ it { should match('/fo') }
221
+ it { should match('/foo') }
222
+ it { should_not match('') }
223
+ it { should_not match('/') }
224
+ it { should_not match('/f') }
225
+ it { should_not match('/fooo') }
226
+ end
227
+
228
+ pattern '/foo\?' do
229
+ it { should match('/foo?') }
230
+ it { should_not match('/foo\?') }
231
+ it { should_not match('/fo') }
232
+ it { should_not match('/foo') }
233
+ it { should_not match('') }
234
+ it { should_not match('/') }
235
+ it { should_not match('/f') }
236
+ it { should_not match('/fooo') }
237
+ end
238
+
239
+ pattern '/foo\\\?' do
240
+ it { should match('/foo%5c') }
241
+ it { should match('/foo') }
242
+ it { should_not match('/foo\?') }
243
+ it { should_not match('/fo') }
244
+ it { should_not match('') }
245
+ it { should_not match('/') }
246
+ it { should_not match('/f') }
247
+ it { should_not match('/fooo') }
248
+ end
249
+
250
+ pattern '/\(' do
251
+ it { should match('/(') }
252
+ end
253
+
254
+ pattern '/\(?' do
255
+ it { should match('/(') }
256
+ it { should match('/') }
257
+ end
258
+
259
+ pattern '/(\()?' do
260
+ it { should match('/(') }
261
+ it { should match('/') }
262
+ end
263
+
264
+ pattern '/(\(\))?' do
265
+ it { should match('/') }
266
+ it { should match('/()') }
267
+ it { should_not match('/(') }
268
+ end
269
+
270
+ pattern '/\(\)?' do
271
+ it { should match('/(') }
272
+ it { should match('/()') }
273
+ it { should_not match('/') }
274
+ end
275
+
276
+ pattern '/\*' do
277
+ it { should match('/*') }
278
+ it { should_not match('/a') }
279
+ end
280
+
281
+ pattern '/\*/*' do
282
+ it { should match('/*/b/c') }
283
+ it { should_not match('/a/b/c') }
284
+ end
285
+
286
+ pattern '/\:foo' do
287
+ it { should match('/:foo') }
288
+ it { should_not match('/foo') }
289
+ end
290
+
291
+ pattern '/:fOO' do
292
+ it { should match('/a').capturing fOO: 'a' }
293
+ end
294
+
295
+ pattern '/:_X' do
296
+ it { should match('/a').capturing _X: 'a' }
297
+ end
298
+
299
+ pattern '/:f00' do
300
+ it { should match('/a').capturing f00: 'a' }
301
+ end
302
+
303
+ pattern '/:foo(/:bar)?/:baz?' do
304
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
305
+ end
306
+
307
+ pattern '/:foo', capture: /\d+/ do
308
+ it { should match('/1') .capturing foo: '1' }
309
+ it { should match('/123') .capturing foo: '123' }
310
+
311
+ it { should_not match('/') }
312
+ it { should_not match('/foo') }
313
+ end
314
+
315
+ pattern '/:foo', capture: /\d+/ do
316
+ it { should match('/1') .capturing foo: '1' }
317
+ it { should match('/123') .capturing foo: '123' }
318
+
319
+ it { should_not match('/') }
320
+ it { should_not match('/foo') }
321
+ end
322
+
323
+ pattern '/:foo', capture: '1' do
324
+ it { should match('/1').capturing foo: '1' }
325
+
326
+ it { should_not match('/') }
327
+ it { should_not match('/foo') }
328
+ it { should_not match('/123') }
329
+ end
330
+
331
+ pattern '/:foo', capture: 'a.b' do
332
+ it { should match('/a.b') .capturing foo: 'a.b' }
333
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
334
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
335
+
336
+ it { should_not match('/ab') }
337
+ it { should_not match('/afb') }
338
+ it { should_not match('/a1b') }
339
+ it { should_not match('/a.bc') }
340
+ end
341
+
342
+ pattern '/:foo(/:bar)?', capture: :alpha do
343
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
344
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
345
+ it { should match('/a') .capturing foo: 'a', bar: nil }
346
+
347
+ it { should_not match('/1/2') }
348
+ it { should_not match('/a/2') }
349
+ it { should_not match('/1/b') }
350
+ it { should_not match('/1') }
351
+ it { should_not match('/1/') }
352
+ it { should_not match('/a/') }
353
+ it { should_not match('//a') }
354
+ end
355
+
356
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
357
+ it { should match('/1') .capturing foo: '1' }
358
+ it { should match('/123') .capturing foo: '123' }
359
+ it { should match('/foo') .capturing foo: 'foo' }
360
+ it { should match('/bar') .capturing foo: 'bar' }
361
+
362
+ it { should_not match('/') }
363
+ it { should_not match('/baz') }
364
+ it { should_not match('/foo1') }
365
+ end
366
+
367
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
368
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
369
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
370
+ it { should_not match('/123abcxy-1') }
371
+ it { should_not match('/abcxy-1') }
372
+ it { should_not match('/abc1') }
373
+ end
374
+
375
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
376
+ it { should match('/1') .capturing foo: '1' }
377
+ it { should match('/123') .capturing foo: '123' }
378
+ it { should match('/foo') .capturing foo: 'foo' }
379
+ it { should match('/bar') .capturing foo: 'bar' }
380
+
381
+ it { should_not match('/') }
382
+ it { should_not match('/baz') }
383
+ it { should_not match('/foo1') }
384
+ end
385
+
386
+ pattern '/:file(.:ext)?', capture: { ext: ['jpg', 'png'] } do
387
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
388
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
389
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
390
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
391
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
392
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
393
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
394
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
395
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
396
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
397
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
398
+ it { should_not match('.jpg') }
399
+ end
400
+
401
+ pattern '/:file:ext?', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
402
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
403
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
404
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
405
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
406
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
407
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
408
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
409
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
410
+ it { should_not match('/.jpg') }
411
+ end
412
+
413
+ pattern '/:a(@:b)?', capture: { b: /\d+/ } do
414
+ it { should match('/a') .capturing a: 'a', b: nil }
415
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
416
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
417
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
418
+ end
419
+
420
+ pattern '/(:a)b?', greedy: false do
421
+ it { should match('/ab').capturing a: 'a' }
422
+ end
423
+
424
+ pattern '/:file(.:ext)?', greedy: false do
425
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
426
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
427
+ it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
428
+ end
429
+
430
+ pattern '/auth/*', except: '/auth/login' do
431
+ it { should match('/auth/admin') }
432
+ it { should match('/auth/foobar') }
433
+ it { should_not match('/auth/login') }
434
+ end
435
+
436
+ pattern '/:foo/:bar', except: '/:bar/20' do
437
+ it { should match('/foo/bar').capturing foo: 'foo', bar: 'bar' }
438
+ it { should_not match('/20/20') }
439
+ end
440
+
441
+ pattern '/foo?', uri_decode: false do
442
+ it { should match('/foo') }
443
+ it { should match('/fo') }
444
+ it { should_not match('/foo?') }
445
+ end
446
+
447
+ pattern '/foo/bar', uri_decode: false do
448
+ it { should match('/foo/bar') }
449
+ it { should_not match('/foo%2Fbar') }
450
+ it { should_not match('/foo%2fbar') }
451
+ end
452
+
453
+ pattern "/path with spaces", uri_decode: false do
454
+ it { should match('/path with spaces') }
455
+ it { should_not match('/path%20with%20spaces') }
456
+ it { should_not match('/path%2Bwith%2Bspaces') }
457
+ it { should_not match('/path+with+spaces') }
458
+ end
459
+
460
+ pattern "/path with spaces", space_matches_plus: false do
461
+ it { should match('/path%20with%20spaces') }
462
+ it { should_not match('/path%2Bwith%2Bspaces') }
463
+ it { should_not match('/path+with+spaces') }
464
+ end
465
+
466
+ context 'invalid syntax' do
467
+ example 'unexpected closing parenthesis' do
468
+ expect { Mustermann::Sinatra.new('foo)bar') }.
469
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
470
+ end
471
+
472
+ example 'missing closing parenthesis' do
473
+ expect { Mustermann::Sinatra.new('foo(bar') }.
474
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
475
+ end
476
+
477
+ example 'missing unescaped closing parenthesis' do
478
+ expect { Mustermann::Sinatra.new('foo(bar\)') }.
479
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar\\\\)"')
480
+ end
481
+
482
+ example '? at beginning of route' do
483
+ expect { Mustermann::Sinatra.new('?foobar') }.
484
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "?foobar"')
485
+ end
486
+
487
+ example 'double ?' do
488
+ expect { Mustermann::Sinatra.new('foo??bar') }.
489
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
490
+ end
491
+
492
+ example 'dangling escape' do
493
+ expect { Mustermann::Sinatra.new('foo\\') }.
494
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')
495
+ end
496
+ end
497
+
498
+ context 'invalid capture names' do
499
+ example 'empty name' do
500
+ expect { Mustermann::Sinatra.new('/:/') }.
501
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
502
+ end
503
+
504
+ example 'named splat' do
505
+ expect { Mustermann::Sinatra.new('/:splat/') }.
506
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
507
+ end
508
+
509
+ example 'named captures' do
510
+ expect { Mustermann::Sinatra.new('/:captures/') }.
511
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
512
+ end
513
+
514
+ example 'with capital letter' do
515
+ expect { Mustermann::Sinatra.new('/:Foo/') }.
516
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
517
+ end
518
+
519
+ example 'with integer' do
520
+ expect { Mustermann::Sinatra.new('/:1a/') }.
521
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
522
+ end
523
+
524
+ example 'same name twice' do
525
+ expect { Mustermann::Sinatra.new('/:foo(/:bar)?/:bar?') }.
526
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)?/:bar?\"")
527
+ end
528
+ end
529
+
530
+ context 'Regexp compatibility' do
531
+ describe :=== do
532
+ example('non-matching') { Mustermann::Sinatra.new("/") .should_not be === '/foo' }
533
+ example('matching') { Mustermann::Sinatra.new("/:foo") .should be === '/foo' }
534
+ end
535
+
536
+ describe :=~ do
537
+ example('non-matching') { Mustermann::Sinatra.new("/") .should_not be =~ '/foo' }
538
+ example('matching') { Mustermann::Sinatra.new("/:foo") .should be =~ '/foo' }
539
+
540
+ context 'String#=~' do
541
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Sinatra.new("/") }
542
+ example('matching') { "/foo".should be =~ Mustermann::Sinatra.new("/:foo") }
543
+ end
544
+ end
545
+
546
+ describe :to_regexp do
547
+ example('empty pattern') { Mustermann::Sinatra.new('').to_regexp.should be == /\A\Z/ }
548
+
549
+ context 'Regexp.try_convert' do
550
+ example('empty pattern') { Regexp.try_convert(Mustermann::Sinatra.new('')).should be == /\A\Z/ }
551
+ end
552
+ end
553
+ end
554
+ end