mustermann-contrib 1.0.0.beta2

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1239 -0
  3. data/examples/highlighting.rb +35 -0
  4. data/highlighting.png +0 -0
  5. data/irb.png +0 -0
  6. data/lib/mustermann/cake.rb +18 -0
  7. data/lib/mustermann/express.rb +37 -0
  8. data/lib/mustermann/file_utils.rb +217 -0
  9. data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
  10. data/lib/mustermann/fileutils.rb +1 -0
  11. data/lib/mustermann/flask.rb +198 -0
  12. data/lib/mustermann/grape.rb +35 -0
  13. data/lib/mustermann/pyramid.rb +28 -0
  14. data/lib/mustermann/rails.rb +46 -0
  15. data/lib/mustermann/shell.rb +56 -0
  16. data/lib/mustermann/simple.rb +50 -0
  17. data/lib/mustermann/string_scanner.rb +313 -0
  18. data/lib/mustermann/strscan.rb +1 -0
  19. data/lib/mustermann/template.rb +62 -0
  20. data/lib/mustermann/uri_template.rb +1 -0
  21. data/lib/mustermann/versions.rb +46 -0
  22. data/lib/mustermann/visualizer.rb +38 -0
  23. data/lib/mustermann/visualizer/highlight.rb +137 -0
  24. data/lib/mustermann/visualizer/highlighter.rb +37 -0
  25. data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
  26. data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
  27. data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
  28. data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
  29. data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
  30. data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
  31. data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
  32. data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
  33. data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
  34. data/lib/mustermann/visualizer/renderer/html.rb +50 -0
  35. data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
  36. data/lib/mustermann/visualizer/tree.rb +63 -0
  37. data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
  38. data/mustermann-contrib.gemspec +19 -0
  39. data/spec/cake_spec.rb +90 -0
  40. data/spec/express_spec.rb +209 -0
  41. data/spec/file_utils_spec.rb +119 -0
  42. data/spec/flask_spec.rb +361 -0
  43. data/spec/flask_subclass_spec.rb +368 -0
  44. data/spec/grape_spec.rb +747 -0
  45. data/spec/pattern_extension_spec.rb +49 -0
  46. data/spec/pyramid_spec.rb +101 -0
  47. data/spec/rails_spec.rb +647 -0
  48. data/spec/shell_spec.rb +147 -0
  49. data/spec/simple_spec.rb +268 -0
  50. data/spec/string_scanner_spec.rb +271 -0
  51. data/spec/template_spec.rb +841 -0
  52. data/spec/visualizer_spec.rb +199 -0
  53. data/theme.png +0 -0
  54. data/tree.png +0 -0
  55. metadata +126 -0
@@ -0,0 +1,49 @@
1
+ require 'support'
2
+ require 'mustermann/visualizer'
3
+ require 'pp'
4
+ require 'stringio'
5
+
6
+ describe Mustermann::Visualizer::PatternExtension do
7
+ subject(:pattern) { Mustermann.new("/:name") }
8
+ before { Hansi.mode = 16 }
9
+ after { Hansi.mode = nil }
10
+
11
+ specify :to_ansi do
12
+ pattern.to_ansi(inspect: true, capture: :red, default: nil).should be == "\e[0m\"\e[0m/\e[0m\e[91m:\e[0m\e[91mname\e[0m\"\e[0m"
13
+ pattern.to_ansi(inspect: false, capture: :green, default: nil).should be == "\e[0m/\e[0m\e[32m:\e[0m\e[32mname\e[0m"
14
+ pattern.to_ansi(inspect: nil, capture: :green, default: nil).should be == "\e[0m/\e[0m\e[32m:\e[0m\e[32mname\e[0m"
15
+ end
16
+
17
+ specify :to_html do
18
+ pattern.to_html(css: false, class_prefix: "", tag: :tt).should be == '<tt class="pattern"><tt class="root"><tt class="separator">/</tt><tt class="capture">:<tt class="name">name</tt></tt></tt></tt>'
19
+ end
20
+
21
+ specify :to_tree do
22
+ pattern.to_tree.should be == Mustermann::Visualizer.tree(pattern).to_s
23
+ end
24
+
25
+ specify :color_inspect do
26
+ pattern.color_inspect.should include(pattern.to_ansi(inspect: true))
27
+ pattern.color_inspect.should include("#<Mustermann::Sinatra:")
28
+ end
29
+
30
+ specify :to_s do
31
+ object = Class.new { def puts(arg) arg.to_s end }.new
32
+ object.puts(pattern).should be == pattern.to_ansi
33
+ end
34
+
35
+ context :pretty_print do
36
+ before(:all) { ColorPrinter = Class.new(::PP) }
37
+ let(:output) { StringIO.new }
38
+
39
+ specify 'with color printer' do
40
+ ColorPrinter.new(output, 79).pp(pattern)
41
+ output.string.should be == pattern.color_inspect
42
+ end
43
+
44
+ specify 'without color printer' do
45
+ ::PP.new(output, 79).pp(pattern)
46
+ output.string.should be == pattern.inspect
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,101 @@
1
+ require 'support'
2
+ require 'mustermann/pyramid'
3
+
4
+ describe Mustermann::Pyramid do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+
11
+ it { should expand.to('') }
12
+ it { should_not expand(a: 1) }
13
+
14
+ it { should generate_template('') }
15
+
16
+ it { should respond_to(:expand) }
17
+ it { should respond_to(:to_templates) }
18
+ end
19
+
20
+ pattern '/' do
21
+ it { should match('/') }
22
+ it { should_not match('/foo') }
23
+
24
+ it { should expand.to('/') }
25
+ it { should_not expand(a: 1) }
26
+ end
27
+
28
+ pattern '/foo' do
29
+ it { should match('/foo') }
30
+ it { should_not match('/bar') }
31
+ it { should_not match('/foo.bar') }
32
+
33
+ it { should expand.to('/foo') }
34
+ it { should_not expand(a: 1) }
35
+ end
36
+
37
+ pattern '/foo/bar' do
38
+ it { should match('/foo/bar') }
39
+ it { should_not match('/foo%2Fbar') }
40
+ it { should_not match('/foo%2fbar') }
41
+
42
+ it { should expand.to('/foo/bar') }
43
+ it { should_not expand(a: 1) }
44
+ end
45
+
46
+ pattern '/{foo}' do
47
+ it { should match('/foo') .capturing foo: 'foo' }
48
+ it { should match('/bar') .capturing foo: 'bar' }
49
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
50
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
51
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
52
+
53
+ it { should_not match('/foo?') }
54
+ it { should_not match('/foo/bar') }
55
+ it { should_not match('/') }
56
+ it { should_not match('/foo/') }
57
+
58
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
59
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
60
+ example { pattern.params('').should be_nil }
61
+
62
+ it { should expand(foo: 'bar') .to('/bar') }
63
+ it { should expand(foo: 'b r') .to('/b%20r') }
64
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
65
+
66
+ it { should_not expand(foo: 'foo', bar: 'bar') }
67
+ it { should_not expand(bar: 'bar') }
68
+ it { should_not expand }
69
+
70
+ it { should generate_template('/{foo}') }
71
+ end
72
+
73
+ pattern '/*foo' do
74
+ it { should match('/foo') .capturing foo: 'foo' }
75
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
76
+
77
+ it { should expand .to('/') }
78
+ it { should expand(foo: nil) .to('/') }
79
+ it { should expand(foo: '') .to('/') }
80
+ it { should expand(foo: 'foo') .to('/foo') }
81
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
82
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
83
+
84
+ example { pattern.params("/foo/bar").should be == {"foo" => ["foo", "bar"]}}
85
+ it { should generate_template('/{+foo}') }
86
+ end
87
+
88
+ pattern '/{foo:.*}' do
89
+ it { should match('/') .capturing foo: '' }
90
+ it { should match('/foo') .capturing foo: 'foo' }
91
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
92
+
93
+ it { should expand(foo: '') .to('/') }
94
+ it { should expand(foo: 'foo') .to('/foo') }
95
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
96
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
97
+
98
+ example { pattern.params("/foo/bar").should be == {"foo" => "foo/bar"}}
99
+ it { should generate_template('/{foo}') }
100
+ end
101
+ end
@@ -0,0 +1,647 @@
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
+
11
+ it { should expand.to('') }
12
+ it { should_not expand(a: 1) }
13
+
14
+ it { should generate_template('') }
15
+
16
+ it { should respond_to(:expand) }
17
+ it { should respond_to(:to_templates) }
18
+ end
19
+
20
+ pattern '/' do
21
+ it { should match('/') }
22
+ it { should_not match('/foo') }
23
+
24
+ it { should expand.to('/') }
25
+ it { should_not expand(a: 1) }
26
+ end
27
+
28
+ pattern '/foo' do
29
+ it { should match('/foo') }
30
+ it { should_not match('/bar') }
31
+ it { should_not match('/foo.bar') }
32
+
33
+ it { should expand.to('/foo') }
34
+ it { should_not expand(a: 1) }
35
+ end
36
+
37
+ pattern '/foo/bar' do
38
+ it { should match('/foo/bar') }
39
+ it { should_not match('/foo%2Fbar') }
40
+ it { should_not match('/foo%2fbar') }
41
+
42
+ it { should expand.to('/foo/bar') }
43
+ it { should_not expand(a: 1) }
44
+ end
45
+
46
+ pattern '/:foo' do
47
+ it { should match('/foo') .capturing foo: 'foo' }
48
+ it { should match('/bar') .capturing foo: 'bar' }
49
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
50
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
51
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
52
+
53
+ it { should_not match('/foo?') }
54
+ it { should_not match('/foo/bar') }
55
+ it { should_not match('/') }
56
+ it { should_not match('/foo/') }
57
+
58
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
59
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
60
+ example { pattern.params('').should be_nil }
61
+
62
+ it { should expand(foo: 'bar') .to('/bar') }
63
+ it { should expand(foo: 'b r') .to('/b%20r') }
64
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
65
+
66
+ it { should_not expand(foo: 'foo', bar: 'bar') }
67
+ it { should_not expand(bar: 'bar') }
68
+ it { should_not expand }
69
+
70
+ it { should generate_template('/{foo}') }
71
+ end
72
+
73
+ pattern '/föö' do
74
+ it { should match("/f%C3%B6%C3%B6") }
75
+ it { should expand.to("/f%C3%B6%C3%B6") }
76
+ it { should_not expand(a: 1) }
77
+ end
78
+
79
+ pattern "/:foo/:bar" do
80
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
81
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
82
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
83
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
84
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
85
+
86
+ it { should_not match('/foo%2Fbar') }
87
+ it { should_not match('/foo%2fbar') }
88
+
89
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
90
+ example { pattern.params('').should be_nil }
91
+
92
+ it { should expand(foo: 'foo', bar: 'bar').to('/foo/bar') }
93
+ it { should_not expand(foo: 'foo') }
94
+ it { should_not expand(bar: 'bar') }
95
+
96
+ it { should generate_template('/{foo}/{bar}') }
97
+ end
98
+
99
+ pattern '/hello/:person' do
100
+ it { should match('/hello/Frank').capturing person: 'Frank' }
101
+ it { should expand(person: 'Frank') .to '/hello/Frank' }
102
+ it { should expand(person: 'Frank?') .to '/hello/Frank%3F' }
103
+
104
+ it { should generate_template('/hello/{person}') }
105
+ end
106
+
107
+ pattern '/?:foo?/?:bar?' do
108
+ it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
109
+ it { should_not match('/hello/world/') }
110
+ it { should expand(foo: 'hello', bar: 'world').to('/%3Fhello%3F/%3Fworld%3F') }
111
+ it { should generate_template('/?{foo}?/?{bar}?') }
112
+ end
113
+
114
+ pattern '/:foo_bar' do
115
+ it { should match('/hello').capturing foo_bar: 'hello' }
116
+ it { should expand(foo_bar: 'hello').to('/hello') }
117
+ it { should generate_template('/{foo_bar}') }
118
+ end
119
+
120
+ pattern '/*foo' do
121
+ it { should match('/') .capturing foo: '' }
122
+ it { should match('/foo') .capturing foo: 'foo' }
123
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
124
+
125
+ it { should expand .to('/') }
126
+ it { should expand(foo: nil) .to('/') }
127
+ it { should expand(foo: '') .to('/') }
128
+ it { should expand(foo: 'foo') .to('/foo') }
129
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
130
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
131
+
132
+ it { should generate_template('/{+foo}') }
133
+ end
134
+
135
+ pattern '/*splat' do
136
+ it { should match('/') .capturing splat: '' }
137
+ it { should match('/foo') .capturing splat: 'foo' }
138
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
139
+ it { should generate_template('/{+splat}') }
140
+ end
141
+
142
+ pattern '/:foo/*bar' do
143
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', bar: 'bar/baz' }
144
+ it { should match("/foo%2Fbar/baz") .capturing foo: 'foo%2Fbar', bar: 'baz' }
145
+ it { should match("/foo/") .capturing foo: 'foo', bar: '' }
146
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', bar: 'h%20a%20y' }
147
+ it { should_not match('/foo') }
148
+
149
+ it { should expand(foo: 'foo') .to('/foo/') }
150
+ it { should expand(foo: 'foo', bar: 'bar') .to('/foo/bar') }
151
+ it { should expand(foo: 'foo', bar: 'foo/bar') .to('/foo/foo/bar') }
152
+ it { should expand(foo: 'foo/bar', bar: 'bar') .to('/foo%2Fbar/bar') }
153
+
154
+ it { should generate_template('/{foo}/{+bar}') }
155
+ end
156
+
157
+ pattern '/test$/' do
158
+ it { should match('/test$/') }
159
+ it { should expand.to('/test$/') }
160
+ end
161
+
162
+ pattern '/te+st/' do
163
+ it { should match('/te+st/') }
164
+ it { should_not match('/test/') }
165
+ it { should_not match('/teest/') }
166
+ it { should expand.to('/te+st/') }
167
+ end
168
+
169
+ pattern "/path with spaces" do
170
+ it { should match('/path%20with%20spaces') }
171
+ it { should match('/path%2Bwith%2Bspaces') }
172
+ it { should match('/path+with+spaces') }
173
+ it { should expand.to('/path%20with%20spaces') }
174
+
175
+ it { should generate_template('/path%20with%20spaces') }
176
+ end
177
+
178
+ pattern '/foo&bar' do
179
+ it { should match('/foo&bar') }
180
+ end
181
+
182
+ pattern '/*a/:foo/*b/*c' do
183
+ it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
184
+ example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
185
+ it { should expand(a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom').to('/bar/foo/bling/baz/boom') }
186
+ it { should generate_template('/{+a}/{foo}/{+b}/{+c}') }
187
+ end
188
+
189
+ pattern '/test.bar' do
190
+ it { should match('/test.bar') }
191
+ it { should_not match('/test0bar') }
192
+ end
193
+
194
+ pattern '/:file.:ext' do
195
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
196
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
197
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
198
+
199
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
200
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
201
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
202
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
203
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
204
+
205
+ it { should_not match('/.jpg') }
206
+ it { should expand(file: 'pony', ext: 'jpg').to('/pony.jpg') }
207
+ end
208
+
209
+ pattern '/:a(x)' do
210
+ it { should match('/a') .capturing a: 'a' }
211
+ it { should match('/xa') .capturing a: 'xa' }
212
+ it { should match('/axa') .capturing a: 'axa' }
213
+ it { should match('/ax') .capturing a: 'a' }
214
+ it { should match('/axax') .capturing a: 'axa' }
215
+ it { should match('/axaxx') .capturing a: 'axax' }
216
+ it { should expand(a: 'x').to('/xx') }
217
+ it { should expand(a: 'a').to('/ax') }
218
+
219
+ it { should generate_template('/{a}x') }
220
+ it { should generate_template('/{a}') }
221
+ end
222
+
223
+ pattern '/:user(@:host)' do
224
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
225
+ it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
226
+ it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
227
+
228
+ it { should expand(user: 'foo') .to('/foo') }
229
+ it { should expand(user: 'foo', host: 'bar') .to('/foo@bar') }
230
+
231
+ it { should generate_template('/{user}') }
232
+ it { should generate_template('/{user}@{host}') }
233
+ end
234
+
235
+ pattern '/:file(.:ext)' do
236
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
237
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
238
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
239
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
240
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
241
+ it { should match('/pony.') .capturing file: 'pony.' }
242
+ it { should_not match('/.jpg') }
243
+
244
+ it { should expand(file: 'pony') .to('/pony') }
245
+ it { should expand(file: 'pony', ext: 'jpg') .to('/pony.jpg') }
246
+
247
+ it { should generate_template('/{file}') }
248
+ it { should generate_template('/{file}.{ext}') }
249
+ end
250
+
251
+ pattern '/:id/test.bar' do
252
+ it { should match('/3/test.bar') .capturing id: '3' }
253
+ it { should match('/2/test.bar') .capturing id: '2' }
254
+ it { should match('/2E/test.bar') .capturing id: '2E' }
255
+ it { should match('/2e/test.bar') .capturing id: '2e' }
256
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
257
+ end
258
+
259
+ pattern '/10/:id' do
260
+ it { should match('/10/test') .capturing id: 'test' }
261
+ it { should match('/10/te.st') .capturing id: 'te.st' }
262
+ end
263
+
264
+ pattern '/10.1/:id' do
265
+ it { should match('/10.1/test') .capturing id: 'test' }
266
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
267
+ end
268
+
269
+ pattern '/:foo.:bar/:id' do
270
+ it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
271
+ it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
272
+ end
273
+
274
+ pattern '/:a/:b(.)(:c)' do
275
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
276
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
277
+ it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
278
+ it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
279
+ it { should_not match('/a.b/c.d/e') }
280
+
281
+ it { should expand(a: ?a, b: ?b) .to('/a/b.') }
282
+ it { should expand(a: ?a, b: ?b, c: ?c) .to('/a/b.c') }
283
+
284
+ it { should generate_template('/{a}/{b}') }
285
+ it { should generate_template('/{a}/{b}.') }
286
+ it { should generate_template('/{a}/{b}.{c}') }
287
+ end
288
+
289
+ pattern '/:a(foo:b)' do
290
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
291
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
292
+ it { should match('/bar') .capturing a: 'bar', b: nil }
293
+ it { should_not match('/') }
294
+
295
+ it { should expand(a: ?a) .to('/a') }
296
+ it { should expand(a: ?a, b: ?b) .to('/afoob') }
297
+
298
+ it { should generate_template('/{a}foo{b}') }
299
+ it { should generate_template('/{a}') }
300
+ it { should_not generate_template('/{a}foo') }
301
+ end
302
+
303
+ pattern '/fo(o)' do
304
+ it { should match('/fo') }
305
+ it { should match('/foo') }
306
+ it { should_not match('') }
307
+ it { should_not match('/') }
308
+ it { should_not match('/f') }
309
+ it { should_not match('/fooo') }
310
+
311
+ it { should expand.to('/foo') }
312
+ end
313
+
314
+ pattern '/foo?' do
315
+ it { should match('/foo?') }
316
+ it { should_not match('/foo\?') }
317
+ it { should_not match('/fo') }
318
+ it { should_not match('/foo') }
319
+ it { should_not match('') }
320
+ it { should_not match('/') }
321
+ it { should_not match('/f') }
322
+ it { should_not match('/fooo') }
323
+
324
+ it { should expand.to('/foo%3F') }
325
+ end
326
+
327
+ pattern '/:fOO' do
328
+ it { should match('/a').capturing fOO: 'a' }
329
+ end
330
+
331
+ pattern '/:_X' do
332
+ it { should match('/a').capturing _X: 'a' }
333
+ end
334
+
335
+ pattern '/:f00' do
336
+ it { should match('/a').capturing f00: 'a' }
337
+ end
338
+
339
+ pattern '/:foo(/:bar)/:baz' do
340
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
341
+ it { should expand(foo: ?a, baz: ?b) .to('/a/b') }
342
+ it { should expand(foo: ?a, baz: ?b, bar: ?x) .to('/a/x/b') }
343
+ end
344
+
345
+ pattern '/:foo', capture: /\d+/ do
346
+ it { should match('/1') .capturing foo: '1' }
347
+ it { should match('/123') .capturing foo: '123' }
348
+
349
+ it { should_not match('/') }
350
+ it { should_not match('/foo') }
351
+ end
352
+
353
+ pattern '/:foo', capture: /\d+/ do
354
+ it { should match('/1') .capturing foo: '1' }
355
+ it { should match('/123') .capturing foo: '123' }
356
+
357
+ it { should_not match('/') }
358
+ it { should_not match('/foo') }
359
+ end
360
+
361
+ pattern '/:foo', capture: '1' do
362
+ it { should match('/1').capturing foo: '1' }
363
+
364
+ it { should_not match('/') }
365
+ it { should_not match('/foo') }
366
+ it { should_not match('/123') }
367
+ end
368
+
369
+ pattern '/:foo', capture: 'a.b' do
370
+ it { should match('/a.b') .capturing foo: 'a.b' }
371
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
372
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
373
+
374
+ it { should_not match('/ab') }
375
+ it { should_not match('/afb') }
376
+ it { should_not match('/a1b') }
377
+ it { should_not match('/a.bc') }
378
+ end
379
+
380
+ pattern '/:foo(/:bar)', capture: :alpha do
381
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
382
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
383
+ it { should match('/a') .capturing foo: 'a', bar: nil }
384
+
385
+ it { should_not match('/1/2') }
386
+ it { should_not match('/a/2') }
387
+ it { should_not match('/1/b') }
388
+ it { should_not match('/1') }
389
+ it { should_not match('/1/') }
390
+ it { should_not match('/a/') }
391
+ it { should_not match('//a') }
392
+ end
393
+
394
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
395
+ it { should match('/1') .capturing foo: '1' }
396
+ it { should match('/123') .capturing foo: '123' }
397
+ it { should match('/foo') .capturing foo: 'foo' }
398
+ it { should match('/bar') .capturing foo: 'bar' }
399
+
400
+ it { should_not match('/') }
401
+ it { should_not match('/baz') }
402
+ it { should_not match('/foo1') }
403
+ end
404
+
405
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
406
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
407
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
408
+ it { should_not match('/123abcxy-1') }
409
+ it { should_not match('/abcxy-1') }
410
+ it { should_not match('/abc1') }
411
+ end
412
+
413
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
414
+ it { should match('/1') .capturing foo: '1' }
415
+ it { should match('/123') .capturing foo: '123' }
416
+ it { should match('/foo') .capturing foo: 'foo' }
417
+ it { should match('/bar') .capturing foo: 'bar' }
418
+
419
+ it { should_not match('/') }
420
+ it { should_not match('/baz') }
421
+ it { should_not match('/foo1') }
422
+ end
423
+
424
+ pattern '/:file(.:ext)', capture: { ext: ['jpg', 'png'] } do
425
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
426
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
427
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
428
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
429
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
430
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
431
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
432
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
433
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
434
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
435
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
436
+ it { should_not match('.jpg') }
437
+ end
438
+
439
+ pattern '/:file(:ext)', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
440
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
441
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
442
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
443
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
444
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
445
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
446
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
447
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
448
+ it { should_not match('/.jpg') }
449
+ end
450
+
451
+ pattern '/:a(@:b)', capture: { b: /\d+/ } do
452
+ it { should match('/a') .capturing a: 'a', b: nil }
453
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
454
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
455
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
456
+ end
457
+
458
+ pattern '/:a(b)', greedy: false do
459
+ it { should match('/ab').capturing a: 'a' }
460
+ end
461
+
462
+ pattern '/:file(.:ext)', greedy: false do
463
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
464
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
465
+ it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
466
+ end
467
+
468
+ pattern '/:controller(/:action(/:id(.:format)))' do
469
+ it { should match('/content').capturing controller: 'content' }
470
+ end
471
+
472
+ pattern '/fo(o)', uri_decode: false do
473
+ it { should match('/foo') }
474
+ it { should match('/fo') }
475
+ it { should_not match('/fo(o)') }
476
+ end
477
+
478
+ pattern '/foo/bar', uri_decode: false do
479
+ it { should match('/foo/bar') }
480
+ it { should_not match('/foo%2Fbar') }
481
+ it { should_not match('/foo%2fbar') }
482
+ end
483
+
484
+ pattern "/path with spaces", uri_decode: false do
485
+ it { should match('/path with spaces') }
486
+ it { should_not match('/path%20with%20spaces') }
487
+ it { should_not match('/path%2Bwith%2Bspaces') }
488
+ it { should_not match('/path+with+spaces') }
489
+ end
490
+
491
+ pattern "/path with spaces", space_matches_plus: false do
492
+ it { should match('/path%20with%20spaces') }
493
+ it { should_not match('/path%2Bwith%2Bspaces') }
494
+ it { should_not match('/path+with+spaces') }
495
+ end
496
+
497
+ context 'invalid syntax' do
498
+ example 'unexpected closing parenthesis' do
499
+ expect { Mustermann::Rails.new('foo)bar') }.
500
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
501
+ end
502
+
503
+ example 'missing closing parenthesis' do
504
+ expect { Mustermann::Rails.new('foo(bar') }.
505
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
506
+ end
507
+ end
508
+
509
+ context 'invalid capture names' do
510
+ example 'empty name' do
511
+ expect { Mustermann::Rails.new('/:/') }.
512
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
513
+ end
514
+
515
+ example 'named splat' do
516
+ expect { Mustermann::Rails.new('/:splat/') }.
517
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
518
+ end
519
+
520
+ example 'named captures' do
521
+ expect { Mustermann::Rails.new('/:captures/') }.
522
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
523
+ end
524
+
525
+ example 'with capital letter' do
526
+ expect { Mustermann::Rails.new('/:Foo/') }.
527
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
528
+ end
529
+
530
+ example 'with integer' do
531
+ expect { Mustermann::Rails.new('/:1a/') }.
532
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
533
+ end
534
+
535
+ example 'same name twice' do
536
+ expect { Mustermann::Rails.new('/:foo(/:bar)/:bar') }.
537
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)/:bar\"")
538
+ end
539
+ end
540
+
541
+ context 'Regexp compatibility' do
542
+ describe :=== do
543
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be === '/foo' }
544
+ example('matching') { Mustermann::Rails.new("/:foo") .should be === '/foo' }
545
+ end
546
+
547
+ describe :=~ do
548
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be =~ '/foo' }
549
+ example('matching') { Mustermann::Rails.new("/:foo") .should be =~ '/foo' }
550
+
551
+ context 'String#=~' do
552
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Rails.new("/") }
553
+ example('matching') { "/foo".should be =~ Mustermann::Rails.new("/:foo") }
554
+ end
555
+ end
556
+
557
+ describe :to_regexp do
558
+ example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A(?-mix:)\Z/ }
559
+
560
+ context 'Regexp.try_convert' do
561
+ example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A(?-mix:)\Z/ }
562
+ end
563
+ end
564
+ end
565
+
566
+ context 'Proc compatibility' do
567
+ describe :to_proc do
568
+ example { Mustermann::Rails.new("/").to_proc.should be_a(Proc) }
569
+ example('non-matching') { Mustermann::Rails.new("/") .to_proc.call('/foo').should be == false }
570
+ example('matching') { Mustermann::Rails.new("/:foo") .to_proc.call('/foo').should be == true }
571
+ end
572
+ end
573
+
574
+ context "peeking" do
575
+ subject(:pattern) { Mustermann::Rails.new(":name") }
576
+
577
+ describe :peek_size do
578
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
579
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
580
+ example { pattern.peek_size("/foo bar") .should be_nil }
581
+ end
582
+
583
+ describe :peek_match do
584
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
585
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
586
+ example { pattern.peek_match("/foo bar") .should be_nil }
587
+ end
588
+
589
+ describe :peek_params do
590
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
591
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
592
+ example { pattern.peek_params("/foo bar") .should be_nil }
593
+ end
594
+ end
595
+
596
+ context 'version compatibility' do
597
+ context '2.3' do
598
+ pattern '(foo)', version: '2.3' do
599
+ it { should_not match("") }
600
+ it { should_not match("foo") }
601
+ it { should match("(foo)") }
602
+ end
603
+
604
+ pattern '\\:name', version: '2.3' do
605
+ it { should match('%5cfoo').capturing(name: 'foo') }
606
+ end
607
+ end
608
+
609
+ context '3.0' do
610
+ pattern '(foo)', version: '3.0' do
611
+ it { should match("") }
612
+ it { should match("foo") }
613
+ end
614
+
615
+ pattern '\\:name', version: '3.0' do
616
+ it { should match(':name') }
617
+ it { should_not match(':foo') }
618
+ end
619
+ end
620
+
621
+ context '3.2' do
622
+ pattern '\\:name', version: '3.2' do
623
+ it { should match('%5cfoo').capturing(name: 'foo') }
624
+ end
625
+ end
626
+
627
+ context '4.0' do
628
+ pattern '\\:name', version: '4.0' do
629
+ it { should match('foo').capturing(name: 'foo') }
630
+ end
631
+ end
632
+
633
+ context '4.2' do
634
+ pattern '\\:name', version: '4.2' do
635
+ it { should match(':name') }
636
+ it { should_not match(':foo') }
637
+ end
638
+ end
639
+
640
+ context '5.0' do
641
+ pattern 'foo|bar', version: '5.0' do
642
+ it { should match('foo') }
643
+ it { should match('bar') }
644
+ end
645
+ end
646
+ end
647
+ end