mustermann 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Mustermann
2
+ VERSION ||= '0.0.1'
3
+ end
@@ -0,0 +1,28 @@
1
+ $:.unshift File.expand_path("../lib", __FILE__)
2
+ require "mustermann/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "mustermann"
6
+ s.version = Mustermann::VERSION
7
+ s.author = "Konstantin Haase"
8
+ s.email = "konstantin.mailinglists@googlemail.com"
9
+ s.homepage = "https://github.com/rkh/mustermann"
10
+ s.summary = %q{use patterns like regular expressions}
11
+ s.description = %q{library implementing patterns that behave like regular expressions}
12
+ s.license = 'MIT'
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.extra_rdoc_files = %w[README.md]
17
+ s.require_path = 'lib'
18
+ s.required_ruby_version = '>= 2.0.0'
19
+
20
+ s.add_development_dependency 'rspec'
21
+ s.add_development_dependency 'sinatra', '~> 1.4'
22
+ s.add_development_dependency 'rack-test'
23
+ s.add_development_dependency 'rake'
24
+ s.add_development_dependency 'yard'
25
+ s.add_development_dependency 'redcarpet'
26
+ s.add_development_dependency 'simplecov'
27
+ s.add_development_dependency 'coveralls'
28
+ end
@@ -0,0 +1,8 @@
1
+ require 'support'
2
+ require 'mustermann/ast'
3
+
4
+ describe Mustermann::AST do
5
+ it 'raises a NotImplementedError when used directly' do
6
+ expect { described_class.new("x") === "x" }.to raise_error(NotImplementedError)
7
+ end
8
+ end
@@ -0,0 +1,153 @@
1
+ require 'support'
2
+ require 'mustermann/extension'
3
+ require 'sinatra/base'
4
+ require 'rack/test'
5
+
6
+ describe Mustermann::Extension do
7
+ include Rack::Test::Methods
8
+
9
+ subject :app do
10
+ Sinatra.new do
11
+ set :environment, :test
12
+ register Mustermann
13
+ end
14
+ end
15
+
16
+ it 'sets up the extension' do
17
+ app.should be_a(Mustermann::Extension)
18
+ end
19
+
20
+ context 'uses Sinatra-style patterns by default' do
21
+ before { app.get('/:slug(.:extension)?') { params[:slug] } }
22
+ example { get('/foo') .body.should be == 'foo' }
23
+ example { get('/foo.') .body.should be == 'foo.' }
24
+ example { get('/foo.bar') .body.should be == 'foo' }
25
+ example { get('/a%20b') .body.should be == 'a b' }
26
+ end
27
+
28
+ describe :except do
29
+ before { app.get('/auth/*', except: '/auth/login') { 'ok' } }
30
+ example { get('/auth/dunno').should be_ok }
31
+ example { get('/auth/login').should_not be_ok }
32
+ end
33
+
34
+ describe :capture do
35
+ context 'global' do
36
+ before do
37
+ app.set(:pattern, capture: { ext: %w[png jpg gif] })
38
+ app.get('/:slug(.:ext)?') { params[:slug] }
39
+ end
40
+
41
+ example { get('/foo.bar').body.should be == 'foo.bar' }
42
+ example { get('/foo.png').body.should be == 'foo' }
43
+ end
44
+
45
+ context 'route local' do
46
+ before do
47
+ app.get('/:id', capture: /\d+/) { 'ok' }
48
+ end
49
+
50
+ example { get('/42').should be_ok }
51
+ example { get('/foo').should_not be_ok }
52
+ end
53
+
54
+ context 'global and route local' do
55
+ context 'global is a hash' do
56
+ before do
57
+ app.set(:pattern, capture: { id: /\d+/ })
58
+ app.get('/:id(.:ext)?', capture: { ext: 'png' }) { ?a }
59
+ app.get('/:id', capture: { id: 'foo' }) { ?b }
60
+ app.get('/:id', capture: :alpha) { ?c }
61
+ end
62
+
63
+ example { get('/20') .body.should be == ?a }
64
+ example { get('/20.png') .body.should be == ?a }
65
+ example { get('/foo') .body.should be == ?b }
66
+ example { get('/bar') .body.should be == ?c }
67
+ end
68
+
69
+ context 'global is not a hash' do
70
+ before do
71
+ app.set(:pattern, capture: /\d+/)
72
+ app.get('/:slug(.:ext)?', capture: { ext: 'png' }) { params[:slug] }
73
+ app.get('/:slug', capture: :alpha) { 'ok' }
74
+ end
75
+
76
+ example { get('/20.png').should be_ok }
77
+ example { get('/foo.png').should_not be_ok }
78
+ example { get('/foo').should be_ok }
79
+
80
+ example { get('/20.png') .body.should be == '20' }
81
+ example { get('/42') .body.should be == '42' }
82
+ example { get('/foo') .body.should be == 'ok' }
83
+ end
84
+ end
85
+ end
86
+
87
+ describe :type do
88
+ describe :identity do
89
+ before do
90
+ app.set(:pattern, type: :identity)
91
+ app.get('/:foo') { 'ok' }
92
+ end
93
+
94
+ example { get('/:foo').should be_ok }
95
+ example { get('/foo').should_not be_ok }
96
+ end
97
+
98
+ describe :rails do
99
+ before do
100
+ app.set(:pattern, type: :rails)
101
+ app.get('/:slug(.:extension)') { params[:slug] }
102
+ end
103
+
104
+ example { get('/foo') .body.should be == 'foo' }
105
+ example { get('/foo.') .body.should be == 'foo.' }
106
+ example { get('/foo.bar') .body.should be == 'foo' }
107
+ example { get('/a%20b') .body.should be == 'a b' }
108
+ end
109
+
110
+ describe :shell do
111
+ before do
112
+ app.set(:pattern, type: :shell)
113
+ app.get('/{foo,bar}') { 'ok' }
114
+ end
115
+
116
+ example { get('/foo').should be_ok }
117
+ example { get('/bar').should be_ok }
118
+ end
119
+
120
+ describe :simple do
121
+ before do
122
+ app.set(:pattern, type: :simple)
123
+ app.get('/(a)') { 'ok' }
124
+ end
125
+
126
+ example { get('/(a)').should be_ok }
127
+ example { get('/a').should_not be_ok }
128
+ end
129
+
130
+ describe :simple do
131
+ before do
132
+ app.set(:pattern, type: :template)
133
+ app.get('/foo{/segments*}{.ext}') { "%p %p" % [params[:segments], params[:ext]] }
134
+ end
135
+
136
+ example { get('/foo/a.png').should be_ok }
137
+ example { get('/foo/a').should_not be_ok }
138
+
139
+ example { get('/foo/a.png').body.should be == '["a"] "png"' }
140
+ example { get('/foo/a/b.png').body.should be == '["a", "b"] "png"' }
141
+ end
142
+ end
143
+
144
+ context 'works with filters' do
145
+ before do
146
+ app.before('/auth/*', except: '/auth/login') { halt 'auth required' }
147
+ app.get('/auth/login') { 'please log in' }
148
+ end
149
+
150
+ example { get('/auth/dunno').body.should be == 'auth required' }
151
+ example { get('/auth/login').body.should be == 'please log in' }
152
+ end
153
+ end
@@ -0,0 +1,82 @@
1
+ require 'support'
2
+ require 'mustermann/identity'
3
+
4
+ describe Mustermann::Identity 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 '/:foo' do
33
+ it { should match('/:foo') }
34
+ it { should match('/%3Afoo') }
35
+ it { should_not match('/foo') }
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 '/test$/' do
47
+ it { should match('/test$/') }
48
+ end
49
+
50
+ pattern '/te+st/' do
51
+ it { should match('/te+st/') }
52
+ it { should_not match('/test/') }
53
+ it { should_not match('/teest/') }
54
+ end
55
+
56
+ pattern "/path with spaces" do
57
+ it { should match('/path%20with%20spaces') }
58
+ it { should_not match('/path%2Bwith%2Bspaces') }
59
+ it { should_not match('/path+with+spaces') }
60
+ end
61
+
62
+ pattern '/foo&bar' do
63
+ it { should match('/foo&bar') }
64
+ end
65
+
66
+ pattern '/test.bar' do
67
+ it { should match('/test.bar') }
68
+ it { should_not match('/test0bar') }
69
+ end
70
+
71
+ pattern '/foo/bar', uri_decode: false do
72
+ it { should match('/foo/bar') }
73
+ it { should_not match('/foo%2Fbar') }
74
+ it { should_not match('/foo%2fbar') }
75
+ end
76
+
77
+ pattern "/path with spaces", uri_decode: false do
78
+ it { should_not match('/path%20with%20spaces') }
79
+ it { should_not match('/path%2Bwith%2Bspaces') }
80
+ it { should_not match('/path+with+spaces') }
81
+ end
82
+ end
File without changes
@@ -0,0 +1,16 @@
1
+ require 'support'
2
+ require 'mustermann/pattern'
3
+
4
+ describe Mustermann::Pattern do
5
+ it 'raises a NotImplementedError when used directly' do
6
+ expect { described_class.new("") === "" }.to raise_error(NotImplementedError)
7
+ end
8
+
9
+ it 'raises an ArgumentError for unknown options' do
10
+ expect { described_class.new("", foo: :bar) }.to raise_error(ArgumentError)
11
+ end
12
+
13
+ it 'does not complain about unknown options if ignore_unknown_options is enabled' do
14
+ expect { described_class.new("", foo: :bar, ignore_unknown_options: true) }.not_to raise_error
15
+ end
16
+ end
@@ -0,0 +1,453 @@
1
+ require 'support'
2
+ require 'mustermann/rails'
3
+
4
+ describe Mustermann::Rails 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
+
41
+ example { pattern.params('/foo').should be == {"foo" => "foo"} }
42
+ example { pattern.params('/f%20o').should be == {"foo" => "f o"} }
43
+ example { pattern.params('').should be_nil }
44
+ end
45
+
46
+ pattern '/föö' do
47
+ it { should match("/f%C3%B6%C3%B6") }
48
+ end
49
+
50
+ pattern "/:foo/:bar" do
51
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
52
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
53
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
54
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
55
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
56
+
57
+ it { should_not match('/foo%2Fbar') }
58
+ it { should_not match('/foo%2fbar') }
59
+
60
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
61
+ example { pattern.params('').should be_nil }
62
+ end
63
+
64
+ pattern '/hello/:person' do
65
+ it { should match('/hello/Frank').capturing person: 'Frank' }
66
+ end
67
+
68
+ pattern '/?:foo?/?:bar?' do
69
+ it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
70
+ it { should_not match('/hello/world/') }
71
+ end
72
+
73
+ pattern '/:foo_bar' do
74
+ it { should match('/hello').capturing foo_bar: 'hello' }
75
+ end
76
+
77
+ pattern '/*foo' do
78
+ it { should match('/') .capturing foo: '' }
79
+ it { should match('/foo') .capturing foo: 'foo' }
80
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
81
+ end
82
+
83
+ pattern '/:foo/*bar' do
84
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', bar: 'bar/baz' }
85
+ it { should match("/foo/") .capturing foo: 'foo', bar: '' }
86
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', bar: 'h%20a%20y' }
87
+ it { should_not match('/foo') }
88
+ end
89
+
90
+ pattern '/test$/' do
91
+ it { should match('/test$/') }
92
+ end
93
+
94
+ pattern '/te+st/' do
95
+ it { should match('/te+st/') }
96
+ it { should_not match('/test/') }
97
+ it { should_not match('/teest/') }
98
+ end
99
+
100
+ pattern "/path with spaces" do
101
+ it { should match('/path%20with%20spaces') }
102
+ it { should match('/path%2Bwith%2Bspaces') }
103
+ it { should match('/path+with+spaces') }
104
+ end
105
+
106
+ pattern '/foo&bar' do
107
+ it { should match('/foo&bar') }
108
+ end
109
+
110
+ pattern '/*a/:foo/*b/*c' do
111
+ it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
112
+ example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
113
+ end
114
+
115
+ pattern '/test.bar' do
116
+ it { should match('/test.bar') }
117
+ it { should_not match('/test0bar') }
118
+ end
119
+
120
+ pattern '/:file.:ext' do
121
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
122
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
123
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
124
+
125
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
126
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
127
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
128
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
129
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
130
+
131
+ it { should_not match('/.jpg') }
132
+ end
133
+
134
+ pattern '/:a(x)' do
135
+ it { should match('/a') .capturing a: 'a' }
136
+ it { should match('/xa') .capturing a: 'xa' }
137
+ it { should match('/axa') .capturing a: 'axa' }
138
+ it { should match('/ax') .capturing a: 'a' }
139
+ it { should match('/axax') .capturing a: 'axa' }
140
+ it { should match('/axaxx') .capturing a: 'axax' }
141
+ end
142
+
143
+ pattern '/:user(@:host)' do
144
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
145
+ it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
146
+ it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
147
+ end
148
+
149
+ pattern '/:file(.:ext)' do
150
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
151
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
152
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
153
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
154
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
155
+ it { should match('/pony.') .capturing file: 'pony.' }
156
+ it { should_not match('/.jpg') }
157
+ end
158
+
159
+ pattern '/:id/test.bar' do
160
+ it { should match('/3/test.bar') .capturing id: '3' }
161
+ it { should match('/2/test.bar') .capturing id: '2' }
162
+ it { should match('/2E/test.bar') .capturing id: '2E' }
163
+ it { should match('/2e/test.bar') .capturing id: '2e' }
164
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
165
+ end
166
+
167
+ pattern '/10/:id' do
168
+ it { should match('/10/test') .capturing id: 'test' }
169
+ it { should match('/10/te.st') .capturing id: 'te.st' }
170
+ end
171
+
172
+ pattern '/10.1/:id' do
173
+ it { should match('/10.1/test') .capturing id: 'test' }
174
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
175
+ end
176
+
177
+ pattern '/:foo.:bar/:id' do
178
+ it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
179
+ it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
180
+ end
181
+
182
+ pattern '/:a/:b(.)(:c)' do
183
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
184
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
185
+ it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
186
+ it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
187
+ it { should_not match('/a.b/c.d/e') }
188
+ end
189
+
190
+ pattern '/:a(foo:b)' do
191
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
192
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
193
+ it { should match('/bar') .capturing a: 'bar', b: nil }
194
+ it { should_not match('/') }
195
+ end
196
+
197
+ pattern '/fo(o)' do
198
+ it { should match('/fo') }
199
+ it { should match('/foo') }
200
+ it { should_not match('') }
201
+ it { should_not match('/') }
202
+ it { should_not match('/f') }
203
+ it { should_not match('/fooo') }
204
+ end
205
+
206
+ pattern '/foo?' do
207
+ it { should match('/foo?') }
208
+ it { should_not match('/foo\?') }
209
+ it { should_not match('/fo') }
210
+ it { should_not match('/foo') }
211
+ it { should_not match('') }
212
+ it { should_not match('/') }
213
+ it { should_not match('/f') }
214
+ it { should_not match('/fooo') }
215
+ end
216
+
217
+ pattern '/:fOO' do
218
+ it { should match('/a').capturing fOO: 'a' }
219
+ end
220
+
221
+ pattern '/:_X' do
222
+ it { should match('/a').capturing _X: 'a' }
223
+ end
224
+
225
+ pattern '/:f00' do
226
+ it { should match('/a').capturing f00: 'a' }
227
+ end
228
+
229
+ pattern '/:foo(/:bar)/:baz' do
230
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
231
+ end
232
+
233
+ pattern '/:foo', capture: /\d+/ do
234
+ it { should match('/1') .capturing foo: '1' }
235
+ it { should match('/123') .capturing foo: '123' }
236
+
237
+ it { should_not match('/') }
238
+ it { should_not match('/foo') }
239
+ end
240
+
241
+ pattern '/:foo', capture: /\d+/ do
242
+ it { should match('/1') .capturing foo: '1' }
243
+ it { should match('/123') .capturing foo: '123' }
244
+
245
+ it { should_not match('/') }
246
+ it { should_not match('/foo') }
247
+ end
248
+
249
+ pattern '/:foo', capture: '1' do
250
+ it { should match('/1').capturing foo: '1' }
251
+
252
+ it { should_not match('/') }
253
+ it { should_not match('/foo') }
254
+ it { should_not match('/123') }
255
+ end
256
+
257
+ pattern '/:foo', capture: 'a.b' do
258
+ it { should match('/a.b') .capturing foo: 'a.b' }
259
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
260
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
261
+
262
+ it { should_not match('/ab') }
263
+ it { should_not match('/afb') }
264
+ it { should_not match('/a1b') }
265
+ it { should_not match('/a.bc') }
266
+ end
267
+
268
+ pattern '/:foo(/:bar)', capture: :alpha do
269
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
270
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
271
+ it { should match('/a') .capturing foo: 'a', bar: nil }
272
+
273
+ it { should_not match('/1/2') }
274
+ it { should_not match('/a/2') }
275
+ it { should_not match('/1/b') }
276
+ it { should_not match('/1') }
277
+ it { should_not match('/1/') }
278
+ it { should_not match('/a/') }
279
+ it { should_not match('//a') }
280
+ end
281
+
282
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
283
+ it { should match('/1') .capturing foo: '1' }
284
+ it { should match('/123') .capturing foo: '123' }
285
+ it { should match('/foo') .capturing foo: 'foo' }
286
+ it { should match('/bar') .capturing foo: 'bar' }
287
+
288
+ it { should_not match('/') }
289
+ it { should_not match('/baz') }
290
+ it { should_not match('/foo1') }
291
+ end
292
+
293
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
294
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
295
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
296
+ it { should_not match('/123abcxy-1') }
297
+ it { should_not match('/abcxy-1') }
298
+ it { should_not match('/abc1') }
299
+ end
300
+
301
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
302
+ it { should match('/1') .capturing foo: '1' }
303
+ it { should match('/123') .capturing foo: '123' }
304
+ it { should match('/foo') .capturing foo: 'foo' }
305
+ it { should match('/bar') .capturing foo: 'bar' }
306
+
307
+ it { should_not match('/') }
308
+ it { should_not match('/baz') }
309
+ it { should_not match('/foo1') }
310
+ end
311
+
312
+ pattern '/:file(.:ext)', capture: { ext: ['jpg', 'png'] } do
313
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
314
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
315
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
316
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
317
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
318
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
319
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
320
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
321
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
322
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
323
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
324
+ it { should_not match('.jpg') }
325
+ end
326
+
327
+ pattern '/:file(:ext)', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
328
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
329
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
330
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
331
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
332
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
333
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
334
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
335
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
336
+ it { should_not match('/.jpg') }
337
+ end
338
+
339
+ pattern '/:a(@:b)', capture: { b: /\d+/ } do
340
+ it { should match('/a') .capturing a: 'a', b: nil }
341
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
342
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
343
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
344
+ end
345
+
346
+ pattern '/:a(b)', greedy: false do
347
+ it { should match('/ab').capturing a: 'a' }
348
+ end
349
+
350
+ pattern '/:file(.:ext)', greedy: false do
351
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
352
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
353
+ it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
354
+ end
355
+
356
+ pattern '/:controller(/:action(/:id(.:format)))' do
357
+ it { should match('/content').capturing controller: 'content' }
358
+ end
359
+
360
+ pattern '/fo(o)', uri_decode: false do
361
+ it { should match('/foo') }
362
+ it { should match('/fo') }
363
+ it { should_not match('/fo(o)') }
364
+ end
365
+
366
+ pattern '/foo/bar', uri_decode: false do
367
+ it { should match('/foo/bar') }
368
+ it { should_not match('/foo%2Fbar') }
369
+ it { should_not match('/foo%2fbar') }
370
+ end
371
+
372
+ pattern "/path with spaces", uri_decode: false do
373
+ it { should match('/path with spaces') }
374
+ it { should_not match('/path%20with%20spaces') }
375
+ it { should_not match('/path%2Bwith%2Bspaces') }
376
+ it { should_not match('/path+with+spaces') }
377
+ end
378
+
379
+ pattern "/path with spaces", space_matches_plus: false do
380
+ it { should match('/path%20with%20spaces') }
381
+ it { should_not match('/path%2Bwith%2Bspaces') }
382
+ it { should_not match('/path+with+spaces') }
383
+ end
384
+
385
+ context 'invalid syntax' do
386
+ example 'unexpected closing parenthesis' do
387
+ expect { Mustermann::Rails.new('foo)bar') }.
388
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
389
+ end
390
+
391
+ example 'missing closing parenthesis' do
392
+ expect { Mustermann::Rails.new('foo(bar') }.
393
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
394
+ end
395
+ end
396
+
397
+ context 'invalid capture names' do
398
+ example 'empty name' do
399
+ expect { Mustermann::Rails.new('/:/') }.
400
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
401
+ end
402
+
403
+ example 'named splat' do
404
+ expect { Mustermann::Rails.new('/:splat/') }.
405
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
406
+ end
407
+
408
+ example 'named captures' do
409
+ expect { Mustermann::Rails.new('/:captures/') }.
410
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
411
+ end
412
+
413
+ example 'with capital letter' do
414
+ expect { Mustermann::Rails.new('/:Foo/') }.
415
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
416
+ end
417
+
418
+ example 'with integer' do
419
+ expect { Mustermann::Rails.new('/:1a/') }.
420
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
421
+ end
422
+
423
+ example 'same name twice' do
424
+ expect { Mustermann::Rails.new('/:foo(/:bar)/:bar') }.
425
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)/:bar\"")
426
+ end
427
+ end
428
+
429
+ context 'Regexp compatibility' do
430
+ describe :=== do
431
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be === '/foo' }
432
+ example('matching') { Mustermann::Rails.new("/:foo") .should be === '/foo' }
433
+ end
434
+
435
+ describe :=~ do
436
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be =~ '/foo' }
437
+ example('matching') { Mustermann::Rails.new("/:foo") .should be =~ '/foo' }
438
+
439
+ context 'String#=~' do
440
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Rails.new("/") }
441
+ example('matching') { "/foo".should be =~ Mustermann::Rails.new("/:foo") }
442
+ end
443
+ end
444
+
445
+ describe :to_regexp do
446
+ example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A\Z/ }
447
+
448
+ context 'Regexp.try_convert' do
449
+ example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A\Z/ }
450
+ end
451
+ end
452
+ end
453
+ end