mustermann 0.3.1 → 0.4.0

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