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.
- checksums.yaml +4 -4
- data/README.md +429 -672
- data/lib/mustermann.rb +95 -20
- data/lib/mustermann/ast/boundaries.rb +44 -0
- data/lib/mustermann/ast/compiler.rb +13 -7
- data/lib/mustermann/ast/expander.rb +22 -12
- data/lib/mustermann/ast/node.rb +69 -5
- data/lib/mustermann/ast/param_scanner.rb +20 -0
- data/lib/mustermann/ast/parser.rb +138 -19
- data/lib/mustermann/ast/pattern.rb +59 -7
- data/lib/mustermann/ast/template_generator.rb +28 -0
- data/lib/mustermann/ast/transformer.rb +2 -2
- data/lib/mustermann/ast/translator.rb +20 -0
- data/lib/mustermann/ast/validation.rb +4 -3
- data/lib/mustermann/composite.rb +101 -0
- data/lib/mustermann/expander.rb +2 -2
- data/lib/mustermann/identity.rb +56 -0
- data/lib/mustermann/pattern.rb +185 -10
- data/lib/mustermann/pattern_cache.rb +49 -0
- data/lib/mustermann/regexp.rb +1 -0
- data/lib/mustermann/regexp_based.rb +18 -1
- data/lib/mustermann/regular.rb +4 -1
- data/lib/mustermann/simple_match.rb +5 -0
- data/lib/mustermann/sinatra.rb +22 -5
- data/lib/mustermann/to_pattern.rb +11 -6
- data/lib/mustermann/version.rb +1 -1
- data/mustermann.gemspec +1 -14
- data/spec/ast_spec.rb +14 -0
- data/spec/composite_spec.rb +147 -0
- data/spec/expander_spec.rb +15 -0
- data/spec/identity_spec.rb +44 -0
- data/spec/mustermann_spec.rb +17 -2
- data/spec/pattern_spec.rb +7 -3
- data/spec/regular_spec.rb +25 -0
- data/spec/sinatra_spec.rb +184 -9
- data/spec/to_pattern_spec.rb +49 -0
- metadata +15 -180
- data/.gitignore +0 -18
- data/.rspec +0 -2
- data/.travis.yml +0 -4
- data/.yardopts +0 -1
- data/Gemfile +0 -2
- data/LICENSE +0 -22
- data/Rakefile +0 -6
- data/internals.md +0 -64
- data/lib/mustermann/ast/tree_renderer.rb +0 -29
- data/lib/mustermann/rails.rb +0 -17
- data/lib/mustermann/shell.rb +0 -29
- data/lib/mustermann/simple.rb +0 -35
- data/lib/mustermann/template.rb +0 -47
- data/spec/rails_spec.rb +0 -521
- data/spec/shell_spec.rb +0 -108
- data/spec/simple_spec.rb +0 -236
- data/spec/support.rb +0 -5
- data/spec/support/coverage.rb +0 -16
- data/spec/support/env.rb +0 -16
- data/spec/support/expand_matcher.rb +0 -27
- data/spec/support/match_matcher.rb +0 -39
- data/spec/support/pattern.rb +0 -39
- data/spec/template_spec.rb +0 -814
data/lib/mustermann/rails.rb
DELETED
@@ -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
|
data/lib/mustermann/shell.rb
DELETED
@@ -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
|
data/lib/mustermann/simple.rb
DELETED
@@ -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
|
data/lib/mustermann/template.rb
DELETED
@@ -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
|
data/spec/rails_spec.rb
DELETED
@@ -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
|