mustermann 0.3.1 → 0.4.0

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