mustermann-uri-template 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +84 -0
- data/lib/mustermann/template.rb +61 -0
- data/lib/mustermann/uri_template.rb +1 -0
- data/mustermann-uri-template.gemspec +18 -0
- data/spec/template_spec.rb +841 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8d99fdcae417aa135df269a93872d1abec9ba1f3
|
4
|
+
data.tar.gz: 33eaef99d2e9442396c2456d16a5df1c40960079
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83b7c599cd1a1a8c599f28e1a99ff9958739ae989688c8b666b3f621282d1f1035adf527ec4aba76e3912b520b13a30c597505d0db88235b09a537667aadccbe
|
7
|
+
data.tar.gz: 306ebd2de32b51d42eb0fad5893e5bb87def3f40213b3198da50fc73c09726b421b2d4c0030b237a3a418fb35ed0ff0290d07aa2a8c65ac072033420fe7401f7
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# URI Template Syntax for Mustermann
|
2
|
+
|
3
|
+
This gem implements the `uri-template` (or `template`) pattern type for Mustermann. It is compatible with [RFC 6570](https://tools.ietf.org/html/rfc6570) (level 4), [JSON API](http://jsonapi.org/), [JSON Home Documents](http://tools.ietf.org/html/draft-nottingham-json-home-02) and [many more](https://code.google.com/p/uri-templates/wiki/Implementations)
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
**Supported options:**
|
8
|
+
`capture`, `except`, `greedy`, `space_matches_plus`, `uri_decode`, and `ignore_unknown_options`.
|
9
|
+
|
10
|
+
Please keep the following in mind:
|
11
|
+
|
12
|
+
> "Some URI Templates can be used in reverse for the purpose of variable matching: comparing the template to a fully formed URI in order to extract the variable parts from that URI and assign them to the named variables. Variable matching only works well if the template expressions are delimited by the beginning or end of the URI or by characters that cannot be part of the expansion, such as reserved characters surrounding a simple string expression. In general, regular expression languages are better suited for variable matching."
|
13
|
+
> — *RFC 6570, Sec 1.5: "Limitations"*
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
require 'mustermann'
|
17
|
+
|
18
|
+
pattern = Mustermann.new('/{example}', type: :template)
|
19
|
+
pattern === "/foo.bar" # => true
|
20
|
+
pattern === "/foo/bar" # => false
|
21
|
+
pattern.params("/foo.bar") # => { "example" => "foo.bar" }
|
22
|
+
pattern.params("/foo/bar") # => nil
|
23
|
+
|
24
|
+
pattern = Mustermann.new("{/segments*}/{page}{.ext,cmpr:2}", type: :template)
|
25
|
+
pattern.params("/a/b/c.tar.gz") # => {"segments"=>["a","b"], "page"=>"c", "ext"=>"tar", "cmpr"=>"gz"}
|
26
|
+
```
|
27
|
+
|
28
|
+
## Generating URI Templates
|
29
|
+
|
30
|
+
You do not need to use URI templates (and this gem) if all you want is reusing them for hypermedia links. Most other pattern types support generating these (via `#to_pattern`):
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
require 'mustermann'
|
34
|
+
|
35
|
+
Mustermann.new('/:name').to_templates # => ['/{name}']
|
36
|
+
```
|
37
|
+
|
38
|
+
Moreover, Mustermann's default pattern type implements a subset of URI templates (`{capture}` and `{+capture}`) and can therefore also be used for simple templates/
|
39
|
+
|
40
|
+
``` ruby
|
41
|
+
require 'mustermann'
|
42
|
+
|
43
|
+
Mustermann.new('/{name}').expand(name: "example") # => "/example"
|
44
|
+
```
|
45
|
+
|
46
|
+
## Syntax
|
47
|
+
|
48
|
+
<table>
|
49
|
+
<thead>
|
50
|
+
<tr>
|
51
|
+
<th>Syntax Element</th>
|
52
|
+
<th>Description</th>
|
53
|
+
</tr>
|
54
|
+
</thead>
|
55
|
+
<tbody>
|
56
|
+
<tr>
|
57
|
+
<td><b>{</b><i>o</i> <i>var</i> <i>m</i><b>,</b> <i>var</i> <i>m</i><b>,</b> ...<b>}</b></td>
|
58
|
+
<td>
|
59
|
+
Captures expansion.
|
60
|
+
Operator <i>o</i>: <code>+ # . / ; ? &</tt> or none.
|
61
|
+
Modifier <i>m</i>: <code>:num *</tt> or none.
|
62
|
+
</td>
|
63
|
+
</tr>
|
64
|
+
<tr>
|
65
|
+
<td><b>/</b></td>
|
66
|
+
<td>
|
67
|
+
Matches forward slash. Does not match URI encoded version of forward slash.
|
68
|
+
</td>
|
69
|
+
</tr>
|
70
|
+
<tr>
|
71
|
+
<td><i>any other character</i></td>
|
72
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
73
|
+
</tr>
|
74
|
+
</tbody>
|
75
|
+
</table>
|
76
|
+
|
77
|
+
The operators `+` and `#` will always match non-greedy, whereas all other operators match semi-greedy by default.
|
78
|
+
All modifiers and operators are supported. However, it does not parse lists as single values without the *explode* modifier (aka *star*).
|
79
|
+
Parametric operators (`;`, `?` and `&`) currently only match parameters in given order.
|
80
|
+
|
81
|
+
Note that it differs from URI templates in that it takes the unescaped version of special character instead of the escaped version.
|
82
|
+
|
83
|
+
If you reuse the exact same templates and expose them via an external API meant for expansion,
|
84
|
+
you should set `uri_decode` to `false` in order to conform with the specification.
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
require 'mustermann/ast/pattern'
|
3
|
+
|
4
|
+
module Mustermann
|
5
|
+
# URI template pattern implementation.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Mustermann.new('/{foo}') === '/bar' # => true
|
9
|
+
#
|
10
|
+
# @see Mustermann::Pattern
|
11
|
+
# @see file:README.md#template Syntax description in the README
|
12
|
+
# @see http://tools.ietf.org/html/rfc6570 RFC 6570
|
13
|
+
class Template < AST::Pattern
|
14
|
+
register :template, :uri_template
|
15
|
+
|
16
|
+
on ?{ do |char|
|
17
|
+
variable = proc do
|
18
|
+
start = pos
|
19
|
+
match = expect(/(?<name>\w+)(?:\:(?<prefix>\d{1,4})|(?<explode>\*))?/)
|
20
|
+
node(:variable, match[:name], prefix: match[:prefix], explode: match[:explode], start: start)
|
21
|
+
end
|
22
|
+
|
23
|
+
operator = buffer.scan(/[\+\#\.\/;\?\&\=\,\!\@\|]/)
|
24
|
+
expression = node(:expression, [variable[]], operator: operator) { variable[] if scan(?,) }
|
25
|
+
expression if expect(?})
|
26
|
+
end
|
27
|
+
|
28
|
+
on(?}) { |c| unexpected(c) }
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def compile(*args, **options)
|
32
|
+
@split_params = {}
|
33
|
+
super(*args, split_params: @split_params, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!visibility private
|
37
|
+
def map_param(key, value)
|
38
|
+
return super unless variable = @split_params[key]
|
39
|
+
value = value.split variable[:separator]
|
40
|
+
value.map! { |e| e.sub(/\A#{key}=/, '') } if variable[:parametric]
|
41
|
+
value.map! { |e| super(key, e) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# @!visibility private
|
45
|
+
def always_array?(key)
|
46
|
+
@split_params.include? key
|
47
|
+
end
|
48
|
+
|
49
|
+
# Identity patterns support generating templates (the logic is quite complex, though).
|
50
|
+
#
|
51
|
+
# @example (see Mustermann::Pattern#to_templates)
|
52
|
+
# @param (see Mustermann::Pattern#to_templates)
|
53
|
+
# @return (see Mustermann::Pattern#to_templates)
|
54
|
+
# @see Mustermann::Pattern#to_templates
|
55
|
+
def to_templates
|
56
|
+
[to_s]
|
57
|
+
end
|
58
|
+
|
59
|
+
private :compile, :map_param, :always_array?
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'mustermann/template'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.unshift File.expand_path("../../mustermann/lib", __FILE__)
|
2
|
+
require "mustermann/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "mustermann-uri-template"
|
6
|
+
s.version = Mustermann::VERSION
|
7
|
+
s.author = "Konstantin Haase"
|
8
|
+
s.email = "konstantin.mailinglists@googlemail.com"
|
9
|
+
s.homepage = "https://github.com/rkh/mustermann"
|
10
|
+
s.summary = %q{URI template syntax for Mustermann}
|
11
|
+
s.description = %q{Adds URI template style patterns to Mustermman}
|
12
|
+
s.license = 'MIT'
|
13
|
+
s.required_ruby_version = '>= 2.1.0'
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.add_dependency 'mustermann', Mustermann::VERSION
|
18
|
+
end
|
@@ -0,0 +1,841 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann/template'
|
3
|
+
|
4
|
+
describe Mustermann::Template do
|
5
|
+
extend Support::Pattern
|
6
|
+
|
7
|
+
pattern '' do
|
8
|
+
it { should match('') }
|
9
|
+
it { should_not match('/') }
|
10
|
+
|
11
|
+
it { should respond_to(:expand) }
|
12
|
+
it { should respond_to(:to_templates) }
|
13
|
+
end
|
14
|
+
|
15
|
+
pattern '/' do
|
16
|
+
it { should match('/') }
|
17
|
+
it { should_not match('/foo') }
|
18
|
+
end
|
19
|
+
|
20
|
+
pattern '/foo' do
|
21
|
+
it { should match('/foo') }
|
22
|
+
it { should_not match('/bar') }
|
23
|
+
it { should_not match('/foo.bar') }
|
24
|
+
end
|
25
|
+
|
26
|
+
pattern '/foo/bar' do
|
27
|
+
it { should match('/foo/bar') }
|
28
|
+
it { should_not match('/foo%2Fbar') }
|
29
|
+
it { should_not match('/foo%2fbar') }
|
30
|
+
end
|
31
|
+
|
32
|
+
pattern '/:foo' do
|
33
|
+
it { should match('/:foo') }
|
34
|
+
it { should match('/%3Afoo') }
|
35
|
+
it { should_not match('/foo') }
|
36
|
+
it { should_not match('/foo?') }
|
37
|
+
it { should_not match('/foo/bar') }
|
38
|
+
it { should_not match('/') }
|
39
|
+
it { should_not match('/foo/') }
|
40
|
+
end
|
41
|
+
|
42
|
+
pattern '/föö' do
|
43
|
+
it { should match("/f%C3%B6%C3%B6") }
|
44
|
+
end
|
45
|
+
|
46
|
+
pattern '/test$/' do
|
47
|
+
it { should match('/test$/') }
|
48
|
+
end
|
49
|
+
|
50
|
+
pattern '/te+st/' do
|
51
|
+
it { should match('/te+st/') }
|
52
|
+
it { should_not match('/test/') }
|
53
|
+
it { should_not match('/teest/') }
|
54
|
+
end
|
55
|
+
|
56
|
+
pattern "/path with spaces" do
|
57
|
+
it { should match('/path%20with%20spaces') }
|
58
|
+
it { should match('/path%2Bwith%2Bspaces') }
|
59
|
+
it { should match('/path+with+spaces') }
|
60
|
+
end
|
61
|
+
|
62
|
+
pattern '/foo&bar' do
|
63
|
+
it { should match('/foo&bar') }
|
64
|
+
end
|
65
|
+
|
66
|
+
pattern '/test.bar' do
|
67
|
+
it { should match('/test.bar') }
|
68
|
+
it { should_not match('/test0bar') }
|
69
|
+
end
|
70
|
+
|
71
|
+
pattern "/path with spaces", space_matches_plus: false do
|
72
|
+
it { should match('/path%20with%20spaces') }
|
73
|
+
it { should_not match('/path%2Bwith%2Bspaces') }
|
74
|
+
it { should_not match('/path+with+spaces') }
|
75
|
+
end
|
76
|
+
|
77
|
+
pattern "/path with spaces", uri_decode: false do
|
78
|
+
it { should_not match('/path%20with%20spaces') }
|
79
|
+
it { should_not match('/path%2Bwith%2Bspaces') }
|
80
|
+
it { should_not match('/path+with+spaces') }
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'level 1' do
|
84
|
+
context 'without operator' do
|
85
|
+
pattern '/hello/{person}' do
|
86
|
+
it { should match('/hello/Frank').capturing person: 'Frank' }
|
87
|
+
it { should match('/hello/a_b~c').capturing person: 'a_b~c' }
|
88
|
+
it { should match('/hello/a.%20').capturing person: 'a.%20' }
|
89
|
+
|
90
|
+
it { should_not match('/hello/:') }
|
91
|
+
it { should_not match('/hello//') }
|
92
|
+
it { should_not match('/hello/?') }
|
93
|
+
it { should_not match('/hello/#') }
|
94
|
+
it { should_not match('/hello/[') }
|
95
|
+
it { should_not match('/hello/]') }
|
96
|
+
it { should_not match('/hello/@') }
|
97
|
+
it { should_not match('/hello/!') }
|
98
|
+
it { should_not match('/hello/*') }
|
99
|
+
it { should_not match('/hello/+') }
|
100
|
+
it { should_not match('/hello/,') }
|
101
|
+
it { should_not match('/hello/;') }
|
102
|
+
it { should_not match('/hello/=') }
|
103
|
+
|
104
|
+
example { pattern.params('/hello/Frank').should be == {'person' => 'Frank'} }
|
105
|
+
end
|
106
|
+
|
107
|
+
pattern "/{foo}/{bar}" do
|
108
|
+
it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
|
109
|
+
it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
|
110
|
+
it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
|
111
|
+
it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
|
112
|
+
|
113
|
+
it { should_not match('/foo%2Fbar') }
|
114
|
+
it { should_not match('/foo%2fbar') }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'level 2' do
|
120
|
+
context 'operator +' do
|
121
|
+
pattern '/hello/{+person}' do
|
122
|
+
it { should match('/hello/Frank') .capturing person: 'Frank' }
|
123
|
+
it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
|
124
|
+
it { should match('/hello/a.%20') .capturing person: 'a.%20' }
|
125
|
+
it { should match('/hello/a/%20') .capturing person: 'a/%20' }
|
126
|
+
it { should match('/hello/:') .capturing person: ?: }
|
127
|
+
it { should match('/hello//') .capturing person: ?/ }
|
128
|
+
it { should match('/hello/?') .capturing person: ?? }
|
129
|
+
it { should match('/hello/#') .capturing person: ?# }
|
130
|
+
it { should match('/hello/[') .capturing person: ?[ }
|
131
|
+
it { should match('/hello/]') .capturing person: ?] }
|
132
|
+
it { should match('/hello/@') .capturing person: ?@ }
|
133
|
+
it { should match('/hello/!') .capturing person: ?! }
|
134
|
+
it { should match('/hello/*') .capturing person: ?* }
|
135
|
+
it { should match('/hello/+') .capturing person: ?+ }
|
136
|
+
it { should match('/hello/,') .capturing person: ?, }
|
137
|
+
it { should match('/hello/;') .capturing person: ?; }
|
138
|
+
it { should match('/hello/=') .capturing person: ?= }
|
139
|
+
end
|
140
|
+
|
141
|
+
pattern "/{+foo}/{bar}" do
|
142
|
+
it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
|
143
|
+
it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
|
144
|
+
it { should match('/foo/bar/bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
|
145
|
+
it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
|
146
|
+
it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
|
147
|
+
|
148
|
+
it { should_not match('/foo%2Fbar') }
|
149
|
+
it { should_not match('/foo%2fbar') }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'operator #' do
|
154
|
+
pattern '/hello/{#person}' do
|
155
|
+
it { should match('/hello/#Frank') .capturing person: 'Frank' }
|
156
|
+
it { should match('/hello/#a_b~c') .capturing person: 'a_b~c' }
|
157
|
+
it { should match('/hello/#a.%20') .capturing person: 'a.%20' }
|
158
|
+
it { should match('/hello/#a/%20') .capturing person: 'a/%20' }
|
159
|
+
it { should match('/hello/#:') .capturing person: ?: }
|
160
|
+
it { should match('/hello/#/') .capturing person: ?/ }
|
161
|
+
it { should match('/hello/#?') .capturing person: ?? }
|
162
|
+
it { should match('/hello/##') .capturing person: ?# }
|
163
|
+
it { should match('/hello/#[') .capturing person: ?[ }
|
164
|
+
it { should match('/hello/#]') .capturing person: ?] }
|
165
|
+
it { should match('/hello/#@') .capturing person: ?@ }
|
166
|
+
it { should match('/hello/#!') .capturing person: ?! }
|
167
|
+
it { should match('/hello/#*') .capturing person: ?* }
|
168
|
+
it { should match('/hello/#+') .capturing person: ?+ }
|
169
|
+
it { should match('/hello/#,') .capturing person: ?, }
|
170
|
+
it { should match('/hello/#;') .capturing person: ?; }
|
171
|
+
it { should match('/hello/#=') .capturing person: ?= }
|
172
|
+
|
173
|
+
|
174
|
+
it { should_not match('/hello/Frank') }
|
175
|
+
it { should_not match('/hello/a_b~c') }
|
176
|
+
it { should_not match('/hello/a.%20') }
|
177
|
+
|
178
|
+
it { should_not match('/hello/:') }
|
179
|
+
it { should_not match('/hello//') }
|
180
|
+
it { should_not match('/hello/?') }
|
181
|
+
it { should_not match('/hello/#') }
|
182
|
+
it { should_not match('/hello/[') }
|
183
|
+
it { should_not match('/hello/]') }
|
184
|
+
it { should_not match('/hello/@') }
|
185
|
+
it { should_not match('/hello/!') }
|
186
|
+
it { should_not match('/hello/*') }
|
187
|
+
it { should_not match('/hello/+') }
|
188
|
+
it { should_not match('/hello/,') }
|
189
|
+
it { should_not match('/hello/;') }
|
190
|
+
it { should_not match('/hello/=') }
|
191
|
+
|
192
|
+
|
193
|
+
example { pattern.params('/hello/#Frank').should be == {'person' => 'Frank'} }
|
194
|
+
end
|
195
|
+
|
196
|
+
pattern "/{+foo}/{#bar}" do
|
197
|
+
it { should match('/foo/#bar') .capturing foo: 'foo', bar: 'bar' }
|
198
|
+
it { should match('/foo.bar/#bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
|
199
|
+
it { should match('/foo/bar/#bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
|
200
|
+
it { should match('/10.1/#te.st') .capturing foo: '10.1', bar: 'te.st' }
|
201
|
+
it { should match('/10.1.2/#te.st') .capturing foo: '10.1.2', bar: 'te.st' }
|
202
|
+
|
203
|
+
it { should_not match('/foo%2F#bar') }
|
204
|
+
it { should_not match('/foo%2f#bar') }
|
205
|
+
|
206
|
+
example { pattern.params('/hello/#Frank').should be == {'foo' => 'hello', 'bar' => 'Frank'} }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'level 3' do
|
212
|
+
context 'without operator' do
|
213
|
+
pattern "{a,b,c}" do
|
214
|
+
it { should match("~x,42,_").capturing a: '~x', b: '42', c: '_' }
|
215
|
+
it { should_not match("~x,42") }
|
216
|
+
it { should_not match("~x/42") }
|
217
|
+
it { should_not match("~x#42") }
|
218
|
+
it { should_not match("~x,42,_#42") }
|
219
|
+
|
220
|
+
example { pattern.params('d,f,g').should be == {'a' => 'd', 'b' => 'f', 'c' => 'g'} }
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'operator +' do
|
225
|
+
pattern "{+a,b,c}" do
|
226
|
+
it { should match("~x,42,_") .capturing a: '~x', b: '42', c: '_' }
|
227
|
+
it { should match("~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
|
228
|
+
it { should match("~/x,42,_/42") .capturing a: '~/x', b: '42', c: '_/42' }
|
229
|
+
|
230
|
+
it { should_not match("~x,42") }
|
231
|
+
it { should_not match("~x/42") }
|
232
|
+
it { should_not match("~x#42") }
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context 'operator #' do
|
237
|
+
pattern "{#a,b,c}" do
|
238
|
+
it { should match("#~x,42,_") .capturing a: '~x', b: '42', c: '_' }
|
239
|
+
it { should match("#~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
|
240
|
+
it { should match("#~/x,42,_#42") .capturing a: '~/x', b: '42', c: '_#42' }
|
241
|
+
|
242
|
+
it { should_not match("~x,42,_") }
|
243
|
+
it { should_not match("~x,42,_#42") }
|
244
|
+
it { should_not match("~/x,42,_#42") }
|
245
|
+
|
246
|
+
it { should_not match("~x,42") }
|
247
|
+
it { should_not match("~x/42") }
|
248
|
+
it { should_not match("~x#42") }
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'operator .' do
|
253
|
+
pattern '/hello/{.person}' do
|
254
|
+
it { should match('/hello/.Frank') .capturing person: 'Frank' }
|
255
|
+
it { should match('/hello/.a_b~c') .capturing person: 'a_b~c' }
|
256
|
+
|
257
|
+
it { should_not match('/hello/.:') }
|
258
|
+
it { should_not match('/hello/./') }
|
259
|
+
it { should_not match('/hello/.?') }
|
260
|
+
it { should_not match('/hello/.#') }
|
261
|
+
it { should_not match('/hello/.[') }
|
262
|
+
it { should_not match('/hello/.]') }
|
263
|
+
it { should_not match('/hello/.@') }
|
264
|
+
it { should_not match('/hello/.!') }
|
265
|
+
it { should_not match('/hello/.*') }
|
266
|
+
it { should_not match('/hello/.+') }
|
267
|
+
it { should_not match('/hello/.,') }
|
268
|
+
it { should_not match('/hello/.;') }
|
269
|
+
it { should_not match('/hello/.=') }
|
270
|
+
|
271
|
+
it { should_not match('/hello/Frank') }
|
272
|
+
it { should_not match('/hello/a_b~c') }
|
273
|
+
it { should_not match('/hello/a.%20') }
|
274
|
+
|
275
|
+
it { should_not match('/hello/:') }
|
276
|
+
it { should_not match('/hello//') }
|
277
|
+
it { should_not match('/hello/?') }
|
278
|
+
it { should_not match('/hello/#') }
|
279
|
+
it { should_not match('/hello/[') }
|
280
|
+
it { should_not match('/hello/]') }
|
281
|
+
it { should_not match('/hello/@') }
|
282
|
+
it { should_not match('/hello/!') }
|
283
|
+
it { should_not match('/hello/*') }
|
284
|
+
it { should_not match('/hello/+') }
|
285
|
+
it { should_not match('/hello/,') }
|
286
|
+
it { should_not match('/hello/;') }
|
287
|
+
it { should_not match('/hello/=') }
|
288
|
+
end
|
289
|
+
|
290
|
+
pattern "{.a,b,c}" do
|
291
|
+
it { should match(".~x.42._").capturing a: '~x', b: '42', c: '_' }
|
292
|
+
it { should_not match(".~x,42") }
|
293
|
+
it { should_not match(".~x/42") }
|
294
|
+
it { should_not match(".~x#42") }
|
295
|
+
it { should_not match(".~x,42,_") }
|
296
|
+
it { should_not match("~x.42._") }
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context 'operator /' do
|
301
|
+
pattern '/hello{/person}' do
|
302
|
+
it { should match('/hello/Frank') .capturing person: 'Frank' }
|
303
|
+
it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
|
304
|
+
|
305
|
+
it { should_not match('/hello//:') }
|
306
|
+
it { should_not match('/hello///') }
|
307
|
+
it { should_not match('/hello//?') }
|
308
|
+
it { should_not match('/hello//#') }
|
309
|
+
it { should_not match('/hello//[') }
|
310
|
+
it { should_not match('/hello//]') }
|
311
|
+
it { should_not match('/hello//@') }
|
312
|
+
it { should_not match('/hello//!') }
|
313
|
+
it { should_not match('/hello//*') }
|
314
|
+
it { should_not match('/hello//+') }
|
315
|
+
it { should_not match('/hello//,') }
|
316
|
+
it { should_not match('/hello//;') }
|
317
|
+
it { should_not match('/hello//=') }
|
318
|
+
|
319
|
+
it { should_not match('/hello/:') }
|
320
|
+
it { should_not match('/hello//') }
|
321
|
+
it { should_not match('/hello/?') }
|
322
|
+
it { should_not match('/hello/#') }
|
323
|
+
it { should_not match('/hello/[') }
|
324
|
+
it { should_not match('/hello/]') }
|
325
|
+
it { should_not match('/hello/@') }
|
326
|
+
it { should_not match('/hello/!') }
|
327
|
+
it { should_not match('/hello/*') }
|
328
|
+
it { should_not match('/hello/+') }
|
329
|
+
it { should_not match('/hello/,') }
|
330
|
+
it { should_not match('/hello/;') }
|
331
|
+
it { should_not match('/hello/=') }
|
332
|
+
end
|
333
|
+
|
334
|
+
pattern "{/a,b,c}" do
|
335
|
+
it { should match("/~x/42/_").capturing a: '~x', b: '42', c: '_' }
|
336
|
+
it { should_not match("/~x,42") }
|
337
|
+
it { should_not match("/~x.42") }
|
338
|
+
it { should_not match("/~x#42") }
|
339
|
+
it { should_not match("/~x,42,_") }
|
340
|
+
it { should_not match("~x/42/_") }
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context 'operator ;' do
|
345
|
+
pattern '/hello/{;person}' do
|
346
|
+
it { should match('/hello/;person=Frank') .capturing person: 'Frank' }
|
347
|
+
it { should match('/hello/;person=a_b~c') .capturing person: 'a_b~c' }
|
348
|
+
it { should match('/hello/;person') .capturing person: nil }
|
349
|
+
|
350
|
+
it { should_not match('/hello/;persona=Frank') }
|
351
|
+
it { should_not match('/hello/;persona=a_b~c') }
|
352
|
+
|
353
|
+
it { should_not match('/hello/;person=:') }
|
354
|
+
it { should_not match('/hello/;person=/') }
|
355
|
+
it { should_not match('/hello/;person=?') }
|
356
|
+
it { should_not match('/hello/;person=#') }
|
357
|
+
it { should_not match('/hello/;person=[') }
|
358
|
+
it { should_not match('/hello/;person=]') }
|
359
|
+
it { should_not match('/hello/;person=@') }
|
360
|
+
it { should_not match('/hello/;person=!') }
|
361
|
+
it { should_not match('/hello/;person=*') }
|
362
|
+
it { should_not match('/hello/;person=+') }
|
363
|
+
it { should_not match('/hello/;person=,') }
|
364
|
+
it { should_not match('/hello/;person=;') }
|
365
|
+
it { should_not match('/hello/;person==') }
|
366
|
+
|
367
|
+
it { should_not match('/hello/;Frank') }
|
368
|
+
it { should_not match('/hello/;a_b~c') }
|
369
|
+
it { should_not match('/hello/;a.%20') }
|
370
|
+
|
371
|
+
it { should_not match('/hello/:') }
|
372
|
+
it { should_not match('/hello//') }
|
373
|
+
it { should_not match('/hello/?') }
|
374
|
+
it { should_not match('/hello/#') }
|
375
|
+
it { should_not match('/hello/[') }
|
376
|
+
it { should_not match('/hello/]') }
|
377
|
+
it { should_not match('/hello/@') }
|
378
|
+
it { should_not match('/hello/!') }
|
379
|
+
it { should_not match('/hello/*') }
|
380
|
+
it { should_not match('/hello/+') }
|
381
|
+
it { should_not match('/hello/,') }
|
382
|
+
it { should_not match('/hello/;') }
|
383
|
+
it { should_not match('/hello/=') }
|
384
|
+
end
|
385
|
+
|
386
|
+
pattern "{;a,b,c}" do
|
387
|
+
it { should match(";a=~x;b=42;c=_") .capturing a: '~x', b: '42', c: '_' }
|
388
|
+
it { should match(";a=~x;b;c=_") .capturing a: '~x', b: nil, c: '_' }
|
389
|
+
|
390
|
+
it { should_not match(";a=~x;c=_;b=42").capturing a: '~x', b: '42', c: '_' }
|
391
|
+
|
392
|
+
it { should_not match(";a=~x;b=42") }
|
393
|
+
it { should_not match("a=~x;b=42") }
|
394
|
+
it { should_not match(";a=~x;b=#42;c") }
|
395
|
+
it { should_not match(";a=~x,b=42,c=_") }
|
396
|
+
it { should_not match("~x;b=42;c=_") }
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
context 'operator ?' do
|
401
|
+
pattern '/hello/{?person}' do
|
402
|
+
it { should match('/hello/?person=Frank') .capturing person: 'Frank' }
|
403
|
+
it { should match('/hello/?person=a_b~c') .capturing person: 'a_b~c' }
|
404
|
+
it { should match('/hello/?person') .capturing person: nil }
|
405
|
+
|
406
|
+
it { should_not match('/hello/?persona=Frank') }
|
407
|
+
it { should_not match('/hello/?persona=a_b~c') }
|
408
|
+
|
409
|
+
it { should_not match('/hello/?person=:') }
|
410
|
+
it { should_not match('/hello/?person=/') }
|
411
|
+
it { should_not match('/hello/?person=?') }
|
412
|
+
it { should_not match('/hello/?person=#') }
|
413
|
+
it { should_not match('/hello/?person=[') }
|
414
|
+
it { should_not match('/hello/?person=]') }
|
415
|
+
it { should_not match('/hello/?person=@') }
|
416
|
+
it { should_not match('/hello/?person=!') }
|
417
|
+
it { should_not match('/hello/?person=*') }
|
418
|
+
it { should_not match('/hello/?person=+') }
|
419
|
+
it { should_not match('/hello/?person=,') }
|
420
|
+
it { should_not match('/hello/?person=;') }
|
421
|
+
it { should_not match('/hello/?person==') }
|
422
|
+
|
423
|
+
it { should_not match('/hello/?Frank') }
|
424
|
+
it { should_not match('/hello/?a_b~c') }
|
425
|
+
it { should_not match('/hello/?a.%20') }
|
426
|
+
|
427
|
+
it { should_not match('/hello/:') }
|
428
|
+
it { should_not match('/hello//') }
|
429
|
+
it { should_not match('/hello/?') }
|
430
|
+
it { should_not match('/hello/#') }
|
431
|
+
it { should_not match('/hello/[') }
|
432
|
+
it { should_not match('/hello/]') }
|
433
|
+
it { should_not match('/hello/@') }
|
434
|
+
it { should_not match('/hello/!') }
|
435
|
+
it { should_not match('/hello/*') }
|
436
|
+
it { should_not match('/hello/+') }
|
437
|
+
it { should_not match('/hello/,') }
|
438
|
+
it { should_not match('/hello/;') }
|
439
|
+
it { should_not match('/hello/=') }
|
440
|
+
end
|
441
|
+
|
442
|
+
pattern "{?a,b,c}" do
|
443
|
+
it { should match("?a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
|
444
|
+
it { should match("?a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
|
445
|
+
|
446
|
+
it { should_not match("?a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
|
447
|
+
|
448
|
+
it { should_not match("?a=~x&b=42") }
|
449
|
+
it { should_not match("a=~x&b=42") }
|
450
|
+
it { should_not match("?a=~x&b=#42&c") }
|
451
|
+
it { should_not match("?a=~x,b=42,c=_") }
|
452
|
+
it { should_not match("~x&b=42&c=_") }
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
context 'operator &' do
|
457
|
+
pattern '/hello/{&person}' do
|
458
|
+
it { should match('/hello/&person=Frank') .capturing person: 'Frank' }
|
459
|
+
it { should match('/hello/&person=a_b~c') .capturing person: 'a_b~c' }
|
460
|
+
it { should match('/hello/&person') .capturing person: nil }
|
461
|
+
|
462
|
+
it { should_not match('/hello/&persona=Frank') }
|
463
|
+
it { should_not match('/hello/&persona=a_b~c') }
|
464
|
+
|
465
|
+
it { should_not match('/hello/&person=:') }
|
466
|
+
it { should_not match('/hello/&person=/') }
|
467
|
+
it { should_not match('/hello/&person=?') }
|
468
|
+
it { should_not match('/hello/&person=#') }
|
469
|
+
it { should_not match('/hello/&person=[') }
|
470
|
+
it { should_not match('/hello/&person=]') }
|
471
|
+
it { should_not match('/hello/&person=@') }
|
472
|
+
it { should_not match('/hello/&person=!') }
|
473
|
+
it { should_not match('/hello/&person=*') }
|
474
|
+
it { should_not match('/hello/&person=+') }
|
475
|
+
it { should_not match('/hello/&person=,') }
|
476
|
+
it { should_not match('/hello/&person=;') }
|
477
|
+
it { should_not match('/hello/&person==') }
|
478
|
+
|
479
|
+
it { should_not match('/hello/&Frank') }
|
480
|
+
it { should_not match('/hello/&a_b~c') }
|
481
|
+
it { should_not match('/hello/&a.%20') }
|
482
|
+
|
483
|
+
it { should_not match('/hello/:') }
|
484
|
+
it { should_not match('/hello//') }
|
485
|
+
it { should_not match('/hello/?') }
|
486
|
+
it { should_not match('/hello/#') }
|
487
|
+
it { should_not match('/hello/[') }
|
488
|
+
it { should_not match('/hello/]') }
|
489
|
+
it { should_not match('/hello/@') }
|
490
|
+
it { should_not match('/hello/!') }
|
491
|
+
it { should_not match('/hello/*') }
|
492
|
+
it { should_not match('/hello/+') }
|
493
|
+
it { should_not match('/hello/,') }
|
494
|
+
it { should_not match('/hello/;') }
|
495
|
+
it { should_not match('/hello/=') }
|
496
|
+
end
|
497
|
+
|
498
|
+
pattern "{&a,b,c}" do
|
499
|
+
it { should match("&a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
|
500
|
+
it { should match("&a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
|
501
|
+
|
502
|
+
it { should_not match("&a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
|
503
|
+
|
504
|
+
it { should_not match("&a=~x&b=42") }
|
505
|
+
it { should_not match("a=~x&b=42") }
|
506
|
+
it { should_not match("&a=~x&b=#42&c") }
|
507
|
+
it { should_not match("&a=~x,b=42,c=_") }
|
508
|
+
it { should_not match("~x&b=42&c=_") }
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
context 'level 4' do
|
514
|
+
context 'without operator' do
|
515
|
+
context 'prefix' do
|
516
|
+
pattern '{a:3}/bar' do
|
517
|
+
it { should match('foo/bar') .capturing a: 'foo' }
|
518
|
+
it { should match('fo/bar') .capturing a: 'fo' }
|
519
|
+
it { should match('f/bar') .capturing a: 'f' }
|
520
|
+
it { should_not match('fooo/bar') }
|
521
|
+
end
|
522
|
+
|
523
|
+
pattern '{a:3}{b}' do
|
524
|
+
it { should match('foobar') .capturing a: 'foo', b: 'bar' }
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
context 'expand' do
|
529
|
+
pattern '{a*}' do
|
530
|
+
it { should match('a') .capturing a: 'a' }
|
531
|
+
it { should match('a,b') .capturing a: 'a,b' }
|
532
|
+
it { should match('a,b,c') .capturing a: 'a,b,c' }
|
533
|
+
it { should_not match('a,b/c') }
|
534
|
+
it { should_not match('a,') }
|
535
|
+
|
536
|
+
example { pattern.params('a').should be == { 'a' => ['a'] }}
|
537
|
+
example { pattern.params('a,b').should be == { 'a' => ['a', 'b'] }}
|
538
|
+
end
|
539
|
+
|
540
|
+
pattern '{a*},{b}' do
|
541
|
+
it { should match('a,b') .capturing a: 'a', b: 'b' }
|
542
|
+
it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
|
543
|
+
it { should_not match('a,b/c') }
|
544
|
+
it { should_not match('a,') }
|
545
|
+
|
546
|
+
example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
|
547
|
+
example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
|
548
|
+
end
|
549
|
+
|
550
|
+
pattern '{a*,b}' do
|
551
|
+
it { should match('a,b') .capturing a: 'a', b: 'b' }
|
552
|
+
it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
|
553
|
+
it { should_not match('a,b/c') }
|
554
|
+
it { should_not match('a,') }
|
555
|
+
|
556
|
+
example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
|
557
|
+
example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
context 'operator +' do
|
563
|
+
pattern '/{a}/{+b}' do
|
564
|
+
it { should match('/foo/bar/baz').capturing(a: 'foo', b: 'bar/baz') }
|
565
|
+
it { should expand(a: 'foo/bar', b: 'foo/bar').to('/foo%2Fbar/foo/bar') }
|
566
|
+
end
|
567
|
+
|
568
|
+
context 'prefix' do
|
569
|
+
pattern '{+a:3}/bar' do
|
570
|
+
it { should match('foo/bar') .capturing a: 'foo' }
|
571
|
+
it { should match('fo/bar') .capturing a: 'fo' }
|
572
|
+
it { should match('f/bar') .capturing a: 'f' }
|
573
|
+
it { should_not match('fooo/bar') }
|
574
|
+
end
|
575
|
+
|
576
|
+
pattern '{+a:3}{b}' do
|
577
|
+
it { should match('foobar') .capturing a: 'foo', b: 'bar' }
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
context 'expand' do
|
582
|
+
pattern '{+a*}' do
|
583
|
+
it { should match('a') .capturing a: 'a' }
|
584
|
+
it { should match('a,b') .capturing a: 'a,b' }
|
585
|
+
it { should match('a,b,c') .capturing a: 'a,b,c' }
|
586
|
+
it { should match('a,b/c') .capturing a: 'a,b/c' }
|
587
|
+
end
|
588
|
+
|
589
|
+
pattern '{+a*},{b}' do
|
590
|
+
it { should match('a,b') .capturing a: 'a', b: 'b' }
|
591
|
+
it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
|
592
|
+
it { should_not match('a,b/c') }
|
593
|
+
it { should_not match('a,') }
|
594
|
+
|
595
|
+
example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
|
596
|
+
example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
context 'operator #' do
|
602
|
+
context 'prefix' do
|
603
|
+
pattern '{#a:3}/bar' do
|
604
|
+
it { should match('#foo/bar') .capturing a: 'foo' }
|
605
|
+
it { should match('#fo/bar') .capturing a: 'fo' }
|
606
|
+
it { should match('#f/bar') .capturing a: 'f' }
|
607
|
+
it { should_not match('#fooo/bar') }
|
608
|
+
end
|
609
|
+
|
610
|
+
pattern '{#a:3}{b}' do
|
611
|
+
it { should match('#foobar') .capturing a: 'foo', b: 'bar' }
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
context 'expand' do
|
616
|
+
pattern '{#a*}' do
|
617
|
+
it { should match('#a') .capturing a: 'a' }
|
618
|
+
it { should match('#a,b') .capturing a: 'a,b' }
|
619
|
+
it { should match('#a,b,c') .capturing a: 'a,b,c' }
|
620
|
+
it { should match('#a,b/c') .capturing a: 'a,b/c' }
|
621
|
+
|
622
|
+
example { pattern.params('#a,b').should be == { 'a' => ['a', 'b'] }}
|
623
|
+
example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b', 'c'] }}
|
624
|
+
end
|
625
|
+
|
626
|
+
pattern '{#a*,b}' do
|
627
|
+
it { should match('#a,b') .capturing a: 'a', b: 'b' }
|
628
|
+
it { should match('#a,b,c') .capturing a: 'a,b', b: 'c' }
|
629
|
+
it { should_not match('#a,') }
|
630
|
+
|
631
|
+
example { pattern.params('#a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
|
632
|
+
example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
context 'operator .' do
|
638
|
+
context 'prefix' do
|
639
|
+
pattern '{.a:3}/bar' do
|
640
|
+
it { should match('.foo/bar') .capturing a: 'foo' }
|
641
|
+
it { should match('.fo/bar') .capturing a: 'fo' }
|
642
|
+
it { should match('.f/bar') .capturing a: 'f' }
|
643
|
+
it { should_not match('.fooo/bar') }
|
644
|
+
end
|
645
|
+
|
646
|
+
pattern '{.a:3}{b}' do
|
647
|
+
it { should match('.foobar') .capturing a: 'foo', b: 'bar' }
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
context 'expand' do
|
652
|
+
pattern '{.a*}' do
|
653
|
+
it { should match('.a') .capturing a: 'a' }
|
654
|
+
it { should match('.a.b') .capturing a: 'a.b' }
|
655
|
+
it { should match('.a.b.c') .capturing a: 'a.b.c' }
|
656
|
+
it { should_not match('.a.b,c') }
|
657
|
+
it { should_not match('.a,') }
|
658
|
+
end
|
659
|
+
|
660
|
+
pattern '{.a*,b}' do
|
661
|
+
it { should match('.a.b') .capturing a: 'a', b: 'b' }
|
662
|
+
it { should match('.a.b.c') .capturing a: 'a.b', b: 'c' }
|
663
|
+
it { should_not match('.a.b/c') }
|
664
|
+
it { should_not match('.a.') }
|
665
|
+
|
666
|
+
example { pattern.params('.a.b').should be == { 'a' => ['a'], 'b' => 'b' }}
|
667
|
+
example { pattern.params('.a.b.c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
|
668
|
+
end
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
context 'operator /' do
|
673
|
+
context 'prefix' do
|
674
|
+
pattern '{/a:3}/bar' do
|
675
|
+
it { should match('/foo/bar') .capturing a: 'foo' }
|
676
|
+
it { should match('/fo/bar') .capturing a: 'fo' }
|
677
|
+
it { should match('/f/bar') .capturing a: 'f' }
|
678
|
+
it { should_not match('/fooo/bar') }
|
679
|
+
end
|
680
|
+
|
681
|
+
pattern '{/a:3}{b}' do
|
682
|
+
it { should match('/foobar') .capturing a: 'foo', b: 'bar' }
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
context 'expand' do
|
687
|
+
pattern '{/a*}' do
|
688
|
+
it { should match('/a') .capturing a: 'a' }
|
689
|
+
it { should match('/a/b') .capturing a: 'a/b' }
|
690
|
+
it { should match('/a/b/c') .capturing a: 'a/b/c' }
|
691
|
+
it { should_not match('/a/b,c') }
|
692
|
+
it { should_not match('/a,') }
|
693
|
+
end
|
694
|
+
|
695
|
+
pattern '{/a*,b}' do
|
696
|
+
it { should match('/a/b') .capturing a: 'a', b: 'b' }
|
697
|
+
it { should match('/a/b/c') .capturing a: 'a/b', b: 'c' }
|
698
|
+
it { should_not match('/a/b,c') }
|
699
|
+
it { should_not match('/a/') }
|
700
|
+
|
701
|
+
example { pattern.params('/a/b').should be == { 'a' => ['a'], 'b' => 'b' }}
|
702
|
+
example { pattern.params('/a/b/c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
context 'operator ;' do
|
708
|
+
context 'prefix' do
|
709
|
+
pattern '{;a:3}/bar' do
|
710
|
+
it { should match(';a=foo/bar') .capturing a: 'foo' }
|
711
|
+
it { should match(';a=fo/bar') .capturing a: 'fo' }
|
712
|
+
it { should match(';a=f/bar') .capturing a: 'f' }
|
713
|
+
it { should_not match(';a=fooo/bar') }
|
714
|
+
end
|
715
|
+
|
716
|
+
pattern '{;a:3}{b}' do
|
717
|
+
it { should match(';a=foobar') .capturing a: 'foo', b: 'bar' }
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
context 'expand' do
|
722
|
+
pattern '{;a*}' do
|
723
|
+
it { should match(';a=1') .capturing a: 'a=1' }
|
724
|
+
it { should match(';a=1;a=2') .capturing a: 'a=1;a=2' }
|
725
|
+
it { should match(';a=1;a=2;a=3') .capturing a: 'a=1;a=2;a=3' }
|
726
|
+
it { should_not match(';a=1;a=2;b=3') }
|
727
|
+
it { should_not match(';a=1;a=2;a=3,') }
|
728
|
+
end
|
729
|
+
|
730
|
+
pattern '{;a*,b}' do
|
731
|
+
it { should match(';a=1;b') .capturing a: 'a=1', b: nil }
|
732
|
+
it { should match(';a=2;a=2;b=1') .capturing a: 'a=2;a=2', b: '1' }
|
733
|
+
it { should_not match(';a;b;c') }
|
734
|
+
it { should_not match(';a;') }
|
735
|
+
|
736
|
+
example { pattern.params(';a=2;a=2;b').should be == { 'a' => ['2', '2'], 'b' => nil }}
|
737
|
+
end
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
context 'operator ?' do
|
742
|
+
context 'prefix' do
|
743
|
+
pattern '{?a:3}/bar' do
|
744
|
+
it { should match('?a=foo/bar') .capturing a: 'foo' }
|
745
|
+
it { should match('?a=fo/bar') .capturing a: 'fo' }
|
746
|
+
it { should match('?a=f/bar') .capturing a: 'f' }
|
747
|
+
it { should_not match('?a=fooo/bar') }
|
748
|
+
end
|
749
|
+
|
750
|
+
pattern '{?a:3}{b}' do
|
751
|
+
it { should match('?a=foobar') .capturing a: 'foo', b: 'bar' }
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
context 'expand' do
|
756
|
+
pattern '{?a*}' do
|
757
|
+
it { should match('?a=1') .capturing a: 'a=1' }
|
758
|
+
it { should match('?a=1&a=2') .capturing a: 'a=1&a=2' }
|
759
|
+
it { should match('?a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
|
760
|
+
it { should_not match('?a=1&a=2&b=3') }
|
761
|
+
it { should_not match('?a=1&a=2&a=3,') }
|
762
|
+
end
|
763
|
+
|
764
|
+
pattern '{?a*,b}' do
|
765
|
+
it { should match('?a=1&b') .capturing a: 'a=1', b: nil }
|
766
|
+
it { should match('?a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
|
767
|
+
it { should_not match('?a&b&c') }
|
768
|
+
it { should_not match('?a&') }
|
769
|
+
|
770
|
+
example { pattern.params('?a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
context 'operator &' do
|
776
|
+
context 'prefix' do
|
777
|
+
pattern '{&a:3}/bar' do
|
778
|
+
it { should match('&a=foo/bar') .capturing a: 'foo' }
|
779
|
+
it { should match('&a=fo/bar') .capturing a: 'fo' }
|
780
|
+
it { should match('&a=f/bar') .capturing a: 'f' }
|
781
|
+
it { should_not match('&a=fooo/bar') }
|
782
|
+
end
|
783
|
+
|
784
|
+
pattern '{&a:3}{b}' do
|
785
|
+
it { should match('&a=foobar') .capturing a: 'foo', b: 'bar' }
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
789
|
+
context 'expand' do
|
790
|
+
pattern '{&a*}' do
|
791
|
+
it { should match('&a=1') .capturing a: 'a=1' }
|
792
|
+
it { should match('&a=1&a=2') .capturing a: 'a=1&a=2' }
|
793
|
+
it { should match('&a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
|
794
|
+
it { should_not match('&a=1&a=2&b=3') }
|
795
|
+
it { should_not match('&a=1&a=2&a=3,') }
|
796
|
+
end
|
797
|
+
|
798
|
+
pattern '{&a*,b}' do
|
799
|
+
it { should match('&a=1&b') .capturing a: 'a=1', b: nil }
|
800
|
+
it { should match('&a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
|
801
|
+
it { should_not match('&a&b&c') }
|
802
|
+
it { should_not match('&a&') }
|
803
|
+
|
804
|
+
example { pattern.params('&a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
|
805
|
+
example { pattern.params('&a=2&a=%20&b').should be == { 'a' => ['2', ' '], 'b' => nil }}
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
context 'invalid syntax' do
|
812
|
+
example 'unexpected closing bracket' do
|
813
|
+
expect { Mustermann::Template.new('foo}bar') }.
|
814
|
+
to raise_error(Mustermann::ParseError, 'unexpected } while parsing "foo}bar"')
|
815
|
+
end
|
816
|
+
|
817
|
+
example 'missing closing bracket' do
|
818
|
+
expect { Mustermann::Template.new('foo{bar') }.
|
819
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo{bar"')
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
context "peeking" do
|
824
|
+
subject(:pattern) { Mustermann::Template.new("{name}bar") }
|
825
|
+
|
826
|
+
describe :peek_size do
|
827
|
+
example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
|
828
|
+
example { pattern.peek_size("/foo bar") .should be_nil }
|
829
|
+
end
|
830
|
+
|
831
|
+
describe :peek_match do
|
832
|
+
example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
|
833
|
+
example { pattern.peek_match("/foo bar") .should be_nil }
|
834
|
+
end
|
835
|
+
|
836
|
+
describe :peek_params do
|
837
|
+
example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo "}, "foo%20bar".size] }
|
838
|
+
example { pattern.peek_params("/foo bar") .should be_nil }
|
839
|
+
end
|
840
|
+
end
|
841
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mustermann-uri-template
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Haase
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mustermann
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.4.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.4.0
|
27
|
+
description: Adds URI template style patterns to Mustermman
|
28
|
+
email: konstantin.mailinglists@googlemail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- README.md
|
34
|
+
- lib/mustermann/template.rb
|
35
|
+
- lib/mustermann/uri_template.rb
|
36
|
+
- mustermann-uri-template.gemspec
|
37
|
+
- spec/template_spec.rb
|
38
|
+
homepage: https://github.com/rkh/mustermann
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 2.1.0
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 2.4.3
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: URI template syntax for Mustermann
|
62
|
+
test_files:
|
63
|
+
- spec/template_spec.rb
|
64
|
+
has_rdoc:
|