mustermann19 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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