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