mustermann19 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +22 -0
  7. data/README.md +1081 -0
  8. data/Rakefile +6 -0
  9. data/bench/capturing.rb +57 -0
  10. data/bench/regexp.rb +21 -0
  11. data/bench/simple_vs_sinatra.rb +23 -0
  12. data/bench/template_vs_addressable.rb +26 -0
  13. data/internals.md +64 -0
  14. data/lib/mustermann.rb +61 -0
  15. data/lib/mustermann/ast/compiler.rb +168 -0
  16. data/lib/mustermann/ast/expander.rb +134 -0
  17. data/lib/mustermann/ast/node.rb +160 -0
  18. data/lib/mustermann/ast/parser.rb +137 -0
  19. data/lib/mustermann/ast/pattern.rb +84 -0
  20. data/lib/mustermann/ast/transformer.rb +129 -0
  21. data/lib/mustermann/ast/translator.rb +108 -0
  22. data/lib/mustermann/ast/tree_renderer.rb +29 -0
  23. data/lib/mustermann/ast/validation.rb +43 -0
  24. data/lib/mustermann/caster.rb +117 -0
  25. data/lib/mustermann/equality_map.rb +48 -0
  26. data/lib/mustermann/error.rb +6 -0
  27. data/lib/mustermann/expander.rb +206 -0
  28. data/lib/mustermann/extension.rb +52 -0
  29. data/lib/mustermann/identity.rb +19 -0
  30. data/lib/mustermann/mapper.rb +98 -0
  31. data/lib/mustermann/pattern.rb +182 -0
  32. data/lib/mustermann/rails.rb +17 -0
  33. data/lib/mustermann/regexp_based.rb +30 -0
  34. data/lib/mustermann/regular.rb +26 -0
  35. data/lib/mustermann/router.rb +9 -0
  36. data/lib/mustermann/router/rack.rb +50 -0
  37. data/lib/mustermann/router/simple.rb +144 -0
  38. data/lib/mustermann/shell.rb +29 -0
  39. data/lib/mustermann/simple.rb +38 -0
  40. data/lib/mustermann/simple_match.rb +30 -0
  41. data/lib/mustermann/sinatra.rb +22 -0
  42. data/lib/mustermann/template.rb +48 -0
  43. data/lib/mustermann/to_pattern.rb +45 -0
  44. data/lib/mustermann/version.rb +3 -0
  45. data/mustermann.gemspec +31 -0
  46. data/spec/expander_spec.rb +105 -0
  47. data/spec/extension_spec.rb +296 -0
  48. data/spec/identity_spec.rb +83 -0
  49. data/spec/mapper_spec.rb +83 -0
  50. data/spec/mustermann_spec.rb +65 -0
  51. data/spec/pattern_spec.rb +49 -0
  52. data/spec/rails_spec.rb +522 -0
  53. data/spec/regexp_based_spec.rb +8 -0
  54. data/spec/regular_spec.rb +36 -0
  55. data/spec/router/rack_spec.rb +39 -0
  56. data/spec/router/simple_spec.rb +32 -0
  57. data/spec/shell_spec.rb +109 -0
  58. data/spec/simple_match_spec.rb +10 -0
  59. data/spec/simple_spec.rb +237 -0
  60. data/spec/sinatra_spec.rb +574 -0
  61. data/spec/support.rb +5 -0
  62. data/spec/support/coverage.rb +16 -0
  63. data/spec/support/env.rb +15 -0
  64. data/spec/support/expand_matcher.rb +27 -0
  65. data/spec/support/match_matcher.rb +39 -0
  66. data/spec/support/pattern.rb +39 -0
  67. data/spec/template_spec.rb +815 -0
  68. data/spec/to_pattern_spec.rb +20 -0
  69. metadata +301 -0
@@ -0,0 +1,65 @@
1
+ require 'support'
2
+ require 'mustermann'
3
+ require 'mustermann/extension'
4
+ require 'sinatra/base'
5
+
6
+ describe Mustermann do
7
+ describe :new do
8
+ context "string argument" do
9
+ example { Mustermann.new('') .should be_a(Mustermann::Sinatra) }
10
+ example { Mustermann.new('', type: :identity) .should be_a(Mustermann::Identity) }
11
+ example { Mustermann.new('', type: :rails) .should be_a(Mustermann::Rails) }
12
+ example { Mustermann.new('', type: :shell) .should be_a(Mustermann::Shell) }
13
+ example { Mustermann.new('', type: :sinatra) .should be_a(Mustermann::Sinatra) }
14
+ example { Mustermann.new('', type: :simple) .should be_a(Mustermann::Simple) }
15
+ example { Mustermann.new('', type: :template) .should be_a(Mustermann::Template) }
16
+
17
+ example { expect { Mustermann.new('', foo: :bar) }.to raise_error(ArgumentError, "unsupported option :foo for Mustermann::Sinatra") }
18
+ example { expect { Mustermann.new('', type: :ast) }.to raise_error(ArgumentError, "unsupported type :ast") }
19
+ end
20
+
21
+ context "pattern argument" do
22
+ subject(:pattern) { Mustermann.new('') }
23
+ example { Mustermann.new(pattern).should be == pattern }
24
+ example { Mustermann.new(pattern, type: :rails).should be_a(Mustermann::Sinatra) }
25
+ end
26
+
27
+ context "regexp argument" do
28
+ example { Mustermann.new(//) .should be_a(Mustermann::Regular) }
29
+ example { Mustermann.new(//, type: :rails) .should be_a(Mustermann::Regular) }
30
+ end
31
+
32
+ context "argument implementing #to_pattern" do
33
+ subject(:pattern) { Class.new { def to_pattern(o={}) Mustermann.new('foo', o) end }.new }
34
+ example { Mustermann.new(pattern) .should be_a(Mustermann::Sinatra) }
35
+ example { Mustermann.new(pattern, type: :rails) .should be_a(Mustermann::Rails) }
36
+ example { Mustermann.new(pattern).to_s.should be == 'foo' }
37
+ end
38
+ end
39
+
40
+ describe :[] do
41
+ example { Mustermann[:identity] .should be == Mustermann::Identity }
42
+ example { Mustermann[:rails] .should be == Mustermann::Rails }
43
+ example { Mustermann[:shell] .should be == Mustermann::Shell }
44
+ example { Mustermann[:sinatra] .should be == Mustermann::Sinatra }
45
+ example { Mustermann[:simple] .should be == Mustermann::Simple }
46
+ example { Mustermann[:template] .should be == Mustermann::Template }
47
+
48
+ example { expect { Mustermann[:ast] }.to raise_error(ArgumentError, "unsupported type :ast") }
49
+ end
50
+
51
+ describe :extend_object do
52
+ context 'special behavior for Sinatra only' do
53
+ example { Object .new.extend(Mustermann).should be_a(Mustermann) }
54
+ example { Object .new.extend(Mustermann).should_not be_a(Mustermann::Extension) }
55
+ example { Class .new.extend(Mustermann).should be_a(Mustermann) }
56
+ example { Class .new.extend(Mustermann).should_not be_a(Mustermann::Extension) }
57
+ example { Sinatra .new.extend(Mustermann).should_not be_a(Mustermann) }
58
+ example { Sinatra .new.extend(Mustermann).should be_a(Mustermann::Extension) }
59
+ end
60
+ end
61
+
62
+ describe :=== do
63
+ example { Mustermann.should be === Mustermann.new("") }
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ require 'support'
2
+ require 'mustermann/pattern'
3
+ require 'mustermann/sinatra'
4
+ require 'mustermann/rails'
5
+
6
+ describe Mustermann::Pattern do
7
+ describe :=== do
8
+ it 'raises a NotImplementedError when used directly' do
9
+ expect { Mustermann::Pattern.new("") === "" }.to raise_error(NotImplementedError)
10
+ end
11
+ end
12
+
13
+ describe :initialize do
14
+ it 'raises an ArgumentError for unknown options' do
15
+ expect { Mustermann::Pattern.new("", foo: :bar) }.to raise_error(ArgumentError)
16
+ end
17
+
18
+ it 'does not complain about unknown options if ignore_unknown_options is enabled' do
19
+ expect { Mustermann::Pattern.new("", foo: :bar, ignore_unknown_options: true) }.not_to raise_error
20
+ end
21
+ end
22
+
23
+ describe :expand do
24
+ subject(:pattern) { Mustermann::Pattern.new("") }
25
+ it { should_not respond_to(:expand) }
26
+ it { expect { pattern.expand }.to raise_error(NotImplementedError) }
27
+ end
28
+
29
+ describe :== do
30
+ example { Mustermann::Pattern.new('/foo') .should be == Mustermann::Pattern.new('/foo') }
31
+ example { Mustermann::Pattern.new('/foo') .should_not be == Mustermann::Pattern.new('/bar') }
32
+ example { Mustermann::Sinatra.new('/foo') .should be == Mustermann::Sinatra.new('/foo') }
33
+ example { Mustermann::Rails.new('/foo') .should_not be == Mustermann::Sinatra.new('/foo') }
34
+ end
35
+
36
+ describe :eql? do
37
+ example { Mustermann::Pattern.new('/foo') .should be_eql Mustermann::Pattern.new('/foo') }
38
+ example { Mustermann::Pattern.new('/foo') .should_not be_eql Mustermann::Pattern.new('/bar') }
39
+ example { Mustermann::Sinatra.new('/foo') .should be_eql Mustermann::Sinatra.new('/foo') }
40
+ example { Mustermann::Rails.new('/foo') .should_not be_eql Mustermann::Sinatra.new('/foo') }
41
+ end
42
+
43
+ describe :equal? do
44
+ example { Mustermann::Pattern.new('/foo') .should be_equal Mustermann::Pattern.new('/foo') }
45
+ example { Mustermann::Pattern.new('/foo') .should_not be_equal Mustermann::Pattern.new('/bar') }
46
+ example { Mustermann::Sinatra.new('/foo') .should be_equal Mustermann::Sinatra.new('/foo') }
47
+ example { Mustermann::Rails.new('/foo') .should_not be_equal Mustermann::Sinatra.new('/foo') }
48
+ end
49
+ end
@@ -0,0 +1,522 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/rails'
4
+
5
+ describe Mustermann::Rails do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should expand.to('') }
13
+ it { should_not expand(a: 1) }
14
+ end
15
+
16
+ pattern '/' do
17
+ it { should match('/') }
18
+ it { should_not match('/foo') }
19
+
20
+ it { should expand.to('/') }
21
+ it { should_not expand(a: 1) }
22
+ end
23
+
24
+ pattern '/foo' do
25
+ it { should match('/foo') }
26
+ it { should_not match('/bar') }
27
+ it { should_not match('/foo.bar') }
28
+
29
+ it { should expand.to('/foo') }
30
+ it { should_not expand(a: 1) }
31
+ end
32
+
33
+ pattern '/foo/bar' do
34
+ it { should match('/foo/bar') }
35
+ it { should_not match('/foo%2Fbar') }
36
+ it { should_not match('/foo%2fbar') }
37
+
38
+ it { should expand.to('/foo/bar') }
39
+ it { should_not expand(a: 1) }
40
+ end
41
+
42
+ pattern '/:foo' do
43
+ it { should match('/foo') .capturing foo: 'foo' }
44
+ it { should match('/bar') .capturing foo: 'bar' }
45
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
46
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
47
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
48
+
49
+ it { should_not match('/foo?') }
50
+ it { should_not match('/foo/bar') }
51
+ it { should_not match('/') }
52
+ it { should_not match('/foo/') }
53
+
54
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
55
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
56
+ example { pattern.params('').should be_nil }
57
+
58
+ it { should expand(foo: 'bar') .to('/bar') }
59
+ it { should expand(foo: 'b r') .to('/b%20r') }
60
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
61
+
62
+ it { should_not expand(foo: 'foo', bar: 'bar') }
63
+ it { should_not expand(bar: 'bar') }
64
+ it { should_not expand }
65
+ end
66
+
67
+ pattern '/föö' do
68
+ it { should match("/f%C3%B6%C3%B6") }
69
+ it { should expand.to("/f%C3%B6%C3%B6") }
70
+ it { should_not expand(a: 1) }
71
+ end
72
+
73
+ pattern "/:foo/:bar" do
74
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
75
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
76
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
77
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
78
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
79
+
80
+ it { should_not match('/foo%2Fbar') }
81
+ it { should_not match('/foo%2fbar') }
82
+
83
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
84
+ example { pattern.params('').should be_nil }
85
+
86
+ it { should expand(foo: 'foo', bar: 'bar').to('/foo/bar') }
87
+ it { should_not expand(foo: 'foo') }
88
+ it { should_not expand(bar: 'bar') }
89
+ end
90
+
91
+ pattern '/hello/:person' do
92
+ it { should match('/hello/Frank').capturing person: 'Frank' }
93
+ it { should expand(person: 'Frank') .to '/hello/Frank' }
94
+ it { should expand(person: 'Frank?') .to '/hello/Frank%3F' }
95
+ end
96
+
97
+ pattern '/?:foo?/?:bar?' do
98
+ it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
99
+ it { should_not match('/hello/world/') }
100
+ it { should expand(foo: 'hello', bar: 'world').to('/%3Fhello%3F/%3Fworld%3F') }
101
+ end
102
+
103
+ pattern '/:foo_bar' do
104
+ it { should match('/hello').capturing foo_bar: 'hello' }
105
+ it { should expand(foo_bar: 'hello').to('/hello') }
106
+ end
107
+
108
+ pattern '/*foo' do
109
+ it { should match('/') .capturing foo: '' }
110
+ it { should match('/foo') .capturing foo: 'foo' }
111
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
112
+
113
+ it { should expand .to('/') }
114
+ it { should expand(foo: nil) .to('/') }
115
+ it { should expand(foo: '') .to('/') }
116
+ it { should expand(foo: 'foo') .to('/foo') }
117
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
118
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
119
+ end
120
+
121
+ pattern '/:foo/*bar' do
122
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', bar: 'bar/baz' }
123
+ it { should match("/foo%2Fbar/baz") .capturing foo: 'foo%2Fbar', bar: 'baz' }
124
+ it { should match("/foo/") .capturing foo: 'foo', bar: '' }
125
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', bar: 'h%20a%20y' }
126
+ it { should_not match('/foo') }
127
+
128
+ it { should expand(foo: 'foo') .to('/foo/') }
129
+ it { should expand(foo: 'foo', bar: 'bar') .to('/foo/bar') }
130
+ it { should expand(foo: 'foo', bar: 'foo/bar') .to('/foo/foo/bar') }
131
+ it { should expand(foo: 'foo/bar', bar: 'bar') .to('/foo%2Fbar/bar') }
132
+ end
133
+
134
+ pattern '/test$/' do
135
+ it { should match('/test$/') }
136
+ it { should expand.to('/test$/') }
137
+ end
138
+
139
+ pattern '/te+st/' do
140
+ it { should match('/te+st/') }
141
+ it { should_not match('/test/') }
142
+ it { should_not match('/teest/') }
143
+ it { should expand.to('/te+st/') }
144
+ end
145
+
146
+ pattern "/path with spaces" do
147
+ it { should match('/path%20with%20spaces') }
148
+ it { should match('/path%2Bwith%2Bspaces') }
149
+ it { should match('/path+with+spaces') }
150
+ it { should expand.to('/path%20with%20spaces') }
151
+ end
152
+
153
+ pattern '/foo&bar' do
154
+ it { should match('/foo&bar') }
155
+ end
156
+
157
+ pattern '/*a/:foo/*b/*c' do
158
+ it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
159
+ example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
160
+ it { should expand(a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom').to('/bar/foo/bling/baz/boom') }
161
+ end
162
+
163
+ pattern '/test.bar' do
164
+ it { should match('/test.bar') }
165
+ it { should_not match('/test0bar') }
166
+ end
167
+
168
+ pattern '/:file.:ext' do
169
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
170
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
171
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
172
+
173
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
174
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
175
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
176
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
177
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
178
+
179
+ it { should_not match('/.jpg') }
180
+ it { should expand(file: 'pony', ext: 'jpg').to('/pony.jpg') }
181
+ end
182
+
183
+ pattern '/:a(x)' do
184
+ it { should match('/a') .capturing a: 'a' }
185
+ it { should match('/xa') .capturing a: 'xa' }
186
+ it { should match('/axa') .capturing a: 'axa' }
187
+ it { should match('/ax') .capturing a: 'a' }
188
+ it { should match('/axax') .capturing a: 'axa' }
189
+ it { should match('/axaxx') .capturing a: 'axax' }
190
+ it { should expand(a: 'x').to('/xx') }
191
+ it { should expand(a: 'a').to('/ax') }
192
+ end
193
+
194
+ pattern '/:user(@:host)' do
195
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
196
+ it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
197
+ it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
198
+
199
+ it { should expand(user: 'foo') .to('/foo') }
200
+ it { should expand(user: 'foo', host: 'bar') .to('/foo@bar') }
201
+ end
202
+
203
+ pattern '/:file(.:ext)' do
204
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
205
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
206
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
207
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
208
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
209
+ it { should match('/pony.') .capturing file: 'pony.' }
210
+ it { should_not match('/.jpg') }
211
+
212
+ it { should expand(file: 'pony') .to('/pony') }
213
+ it { should expand(file: 'pony', ext: 'jpg') .to('/pony.jpg') }
214
+ end
215
+
216
+ pattern '/:id/test.bar' do
217
+ it { should match('/3/test.bar') .capturing id: '3' }
218
+ it { should match('/2/test.bar') .capturing id: '2' }
219
+ it { should match('/2E/test.bar') .capturing id: '2E' }
220
+ it { should match('/2e/test.bar') .capturing id: '2e' }
221
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
222
+ end
223
+
224
+ pattern '/10/:id' do
225
+ it { should match('/10/test') .capturing id: 'test' }
226
+ it { should match('/10/te.st') .capturing id: 'te.st' }
227
+ end
228
+
229
+ pattern '/10.1/:id' do
230
+ it { should match('/10.1/test') .capturing id: 'test' }
231
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
232
+ end
233
+
234
+ pattern '/:foo.:bar/:id' do
235
+ it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
236
+ it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
237
+ end
238
+
239
+ pattern '/:a/:b(.)(:c)' do
240
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
241
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
242
+ it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
243
+ it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
244
+ it { should_not match('/a.b/c.d/e') }
245
+
246
+ it { should expand(a: ?a, b: ?b) .to('/a/b.') }
247
+ it { should expand(a: ?a, b: ?b, c: ?c) .to('/a/b.c') }
248
+ end
249
+
250
+ pattern '/:a(foo:b)' do
251
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
252
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
253
+ it { should match('/bar') .capturing a: 'bar', b: nil }
254
+ it { should_not match('/') }
255
+
256
+ it { should expand(a: ?a) .to('/a') }
257
+ it { should expand(a: ?a, b: ?b) .to('/afoob') }
258
+ end
259
+
260
+ pattern '/fo(o)' do
261
+ it { should match('/fo') }
262
+ it { should match('/foo') }
263
+ it { should_not match('') }
264
+ it { should_not match('/') }
265
+ it { should_not match('/f') }
266
+ it { should_not match('/fooo') }
267
+
268
+ it { should expand.to('/foo') }
269
+ end
270
+
271
+ pattern '/foo?' do
272
+ it { should match('/foo?') }
273
+ it { should_not match('/foo\?') }
274
+ it { should_not match('/fo') }
275
+ it { should_not match('/foo') }
276
+ it { should_not match('') }
277
+ it { should_not match('/') }
278
+ it { should_not match('/f') }
279
+ it { should_not match('/fooo') }
280
+
281
+ it { should expand.to('/foo%3F') }
282
+ end
283
+
284
+ pattern '/:fOO' do
285
+ it { should match('/a').capturing fOO: 'a' }
286
+ end
287
+
288
+ pattern '/:_X' do
289
+ it { should match('/a').capturing _X: 'a' }
290
+ end
291
+
292
+ pattern '/:f00' do
293
+ it { should match('/a').capturing f00: 'a' }
294
+ end
295
+
296
+ pattern '/:foo(/:bar)/:baz' do
297
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
298
+ it { should expand(foo: ?a, baz: ?b) .to('/a/b') }
299
+ it { should expand(foo: ?a, baz: ?b, bar: ?x) .to('/a/x/b') }
300
+ end
301
+
302
+ pattern '/:foo', capture: /\d+/ do
303
+ it { should match('/1') .capturing foo: '1' }
304
+ it { should match('/123') .capturing foo: '123' }
305
+
306
+ it { should_not match('/') }
307
+ it { should_not match('/foo') }
308
+ end
309
+
310
+ pattern '/:foo', capture: /\d+/ do
311
+ it { should match('/1') .capturing foo: '1' }
312
+ it { should match('/123') .capturing foo: '123' }
313
+
314
+ it { should_not match('/') }
315
+ it { should_not match('/foo') }
316
+ end
317
+
318
+ pattern '/:foo', capture: '1' do
319
+ it { should match('/1').capturing foo: '1' }
320
+
321
+ it { should_not match('/') }
322
+ it { should_not match('/foo') }
323
+ it { should_not match('/123') }
324
+ end
325
+
326
+ pattern '/:foo', capture: 'a.b' do
327
+ it { should match('/a.b') .capturing foo: 'a.b' }
328
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
329
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
330
+
331
+ it { should_not match('/ab') }
332
+ it { should_not match('/afb') }
333
+ it { should_not match('/a1b') }
334
+ it { should_not match('/a.bc') }
335
+ end
336
+
337
+ pattern '/:foo(/:bar)', capture: :alpha do
338
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
339
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
340
+ it { should match('/a') .capturing foo: 'a', bar: nil }
341
+
342
+ it { should_not match('/1/2') }
343
+ it { should_not match('/a/2') }
344
+ it { should_not match('/1/b') }
345
+ it { should_not match('/1') }
346
+ it { should_not match('/1/') }
347
+ it { should_not match('/a/') }
348
+ it { should_not match('//a') }
349
+ end
350
+
351
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
352
+ it { should match('/1') .capturing foo: '1' }
353
+ it { should match('/123') .capturing foo: '123' }
354
+ it { should match('/foo') .capturing foo: 'foo' }
355
+ it { should match('/bar') .capturing foo: 'bar' }
356
+
357
+ it { should_not match('/') }
358
+ it { should_not match('/baz') }
359
+ it { should_not match('/foo1') }
360
+ end
361
+
362
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
363
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
364
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
365
+ it { should_not match('/123abcxy-1') }
366
+ it { should_not match('/abcxy-1') }
367
+ it { should_not match('/abc1') }
368
+ end
369
+
370
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
371
+ it { should match('/1') .capturing foo: '1' }
372
+ it { should match('/123') .capturing foo: '123' }
373
+ it { should match('/foo') .capturing foo: 'foo' }
374
+ it { should match('/bar') .capturing foo: 'bar' }
375
+
376
+ it { should_not match('/') }
377
+ it { should_not match('/baz') }
378
+ it { should_not match('/foo1') }
379
+ end
380
+
381
+ pattern '/:file(.:ext)', capture: { ext: ['jpg', 'png'] } do
382
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
383
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
384
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
385
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
386
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
387
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
388
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
389
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
390
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
391
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
392
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
393
+ it { should_not match('.jpg') }
394
+ end
395
+
396
+ pattern '/:file(:ext)', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
397
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
398
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
399
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
400
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
401
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
402
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
403
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
404
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
405
+ it { should_not match('/.jpg') }
406
+ end
407
+
408
+ pattern '/:a(@:b)', capture: { b: /\d+/ } do
409
+ it { should match('/a') .capturing a: 'a', b: nil }
410
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
411
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
412
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
413
+ end
414
+
415
+ pattern '/:a(b)', greedy: false do
416
+ it { should match('/ab').capturing a: 'a' }
417
+ end
418
+
419
+ pattern '/:file(.:ext)', greedy: false do
420
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
421
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
422
+ it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
423
+ end
424
+
425
+ pattern '/:controller(/:action(/:id(.:format)))' do
426
+ it { should match('/content').capturing controller: 'content' }
427
+ end
428
+
429
+ pattern '/fo(o)', uri_decode: false do
430
+ it { should match('/foo') }
431
+ it { should match('/fo') }
432
+ it { should_not match('/fo(o)') }
433
+ end
434
+
435
+ pattern '/foo/bar', uri_decode: false do
436
+ it { should match('/foo/bar') }
437
+ it { should_not match('/foo%2Fbar') }
438
+ it { should_not match('/foo%2fbar') }
439
+ end
440
+
441
+ pattern "/path with spaces", uri_decode: false do
442
+ it { should match('/path with spaces') }
443
+ it { should_not match('/path%20with%20spaces') }
444
+ it { should_not match('/path%2Bwith%2Bspaces') }
445
+ it { should_not match('/path+with+spaces') }
446
+ end
447
+
448
+ pattern "/path with spaces", space_matches_plus: false do
449
+ it { should match('/path%20with%20spaces') }
450
+ it { should_not match('/path%2Bwith%2Bspaces') }
451
+ it { should_not match('/path+with+spaces') }
452
+ end
453
+
454
+ context 'invalid syntax' do
455
+ example 'unexpected closing parenthesis' do
456
+ expect { Mustermann::Rails.new('foo)bar') }.
457
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
458
+ end
459
+
460
+ example 'missing closing parenthesis' do
461
+ expect { Mustermann::Rails.new('foo(bar') }.
462
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
463
+ end
464
+ end
465
+
466
+ context 'invalid capture names' do
467
+ example 'empty name' do
468
+ expect { Mustermann::Rails.new('/:/') }.
469
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
470
+ end
471
+
472
+ example 'named splat' do
473
+ expect { Mustermann::Rails.new('/:splat/') }.
474
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
475
+ end
476
+
477
+ example 'named captures' do
478
+ expect { Mustermann::Rails.new('/:captures/') }.
479
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
480
+ end
481
+
482
+ example 'with capital letter' do
483
+ expect { Mustermann::Rails.new('/:Foo/') }.
484
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
485
+ end
486
+
487
+ example 'with integer' do
488
+ expect { Mustermann::Rails.new('/:1a/') }.
489
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
490
+ end
491
+
492
+ example 'same name twice' do
493
+ expect { Mustermann::Rails.new('/:foo(/:bar)/:bar') }.
494
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)/:bar\"")
495
+ end
496
+ end
497
+
498
+ context 'Regexp compatibility' do
499
+ describe :=== do
500
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be === '/foo' }
501
+ example('matching') { Mustermann::Rails.new("/:foo") .should be === '/foo' }
502
+ end
503
+
504
+ describe :=~ do
505
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be =~ '/foo' }
506
+ example('matching') { Mustermann::Rails.new("/:foo") .should be =~ '/foo' }
507
+
508
+ context 'String#=~' do
509
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Rails.new("/") }
510
+ example('matching') { "/foo".should be =~ Mustermann::Rails.new("/:foo") }
511
+ end
512
+ end
513
+
514
+ describe :to_regexp do
515
+ example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A\Z/ }
516
+
517
+ context 'Regexp.try_convert' do
518
+ example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A\Z/ }
519
+ end
520
+ end
521
+ end
522
+ end