mustermann-flask 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 +7 -0
- data/README.md +246 -0
- data/lib/mustermann/flask.rb +197 -0
- data/mustermann-flask.gemspec +18 -0
- data/spec/flask_spec.rb +361 -0
- data/spec/flask_subclass_spec.rb +368 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3711353dec9fd3af110d0e77f5bec7a991b17a9b
|
4
|
+
data.tar.gz: 7ac3a0d796f83314f7ec0306594e411639db6493
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c04ab10704006935895cfeccb302867731393f794c53e130f1b9ab85ee57df4cf3d37bf0e29c0715a24d508636017f108436cce7909d2d7e77abe74a8b845ef6
|
7
|
+
data.tar.gz: acaa0a0b96dabb337e96284a3503fab35e1c0d8fb44ed6e2834fae3698a8f704dd9c6309322a5ba5ee36de3b23e545f508be34c5a1c5b4ea28dd559283e36555
|
data/README.md
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# Flask Syntax for Mustermann
|
2
|
+
|
3
|
+
This gem implements the `flask` pattern type for Mustermann. It is compatible with [Flask](http://flask.pocoo.org/) and [Werkzeug](http://werkzeug.pocoo.org/).
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
**Supported options:**
|
8
|
+
`capture`, `except`, `greedy`, `space_matches_plus`, `uri_decode`, `converters` and `ignore_unknown_options`
|
9
|
+
|
10
|
+
**External documentation:**
|
11
|
+
[Werkzeug: URL Routing](http://werkzeug.pocoo.org/docs/0.9/routing/)
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
require 'mustermann/flask'
|
15
|
+
|
16
|
+
Mustermann.new('/<prefix>/<path:page>', type: :flask).params('/a/b/c') # => { prefix: 'a', page: 'b/c' }
|
17
|
+
|
18
|
+
pattern = Mustermann.new('/<name>', type: :flask)
|
19
|
+
|
20
|
+
pattern.respond_to? :expand # => true
|
21
|
+
pattern.expand(name: 'foo') # => '/foo'
|
22
|
+
|
23
|
+
pattern.respond_to? :to_templates # => true
|
24
|
+
pattern.to_templates # => ['/{name}']
|
25
|
+
```
|
26
|
+
|
27
|
+
## Syntax
|
28
|
+
|
29
|
+
<table>
|
30
|
+
<thead>
|
31
|
+
<tr>
|
32
|
+
<th>Syntax Element</th>
|
33
|
+
<th>Description</th>
|
34
|
+
</tr>
|
35
|
+
</thead>
|
36
|
+
<tbody>
|
37
|
+
<tr>
|
38
|
+
<td><b><</b><i>name</i><b>></b></td>
|
39
|
+
<td>
|
40
|
+
Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
|
41
|
+
Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
|
42
|
+
</td>
|
43
|
+
</tr>
|
44
|
+
<tr>
|
45
|
+
<td><b><</b><i>converter</i><b>:</b><i>name</i><b>></b></td>
|
46
|
+
<td>
|
47
|
+
Captures depending on the converter constraint. Capture is named <i>name</i>.
|
48
|
+
Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
|
49
|
+
See below.
|
50
|
+
</td>
|
51
|
+
</tr>
|
52
|
+
<tr>
|
53
|
+
<td><b><</b><i>converter</i><b>(</b><i>arguments</i><b>):</b><i>name</i><b>></b></td>
|
54
|
+
<td>
|
55
|
+
Captures depending on the converter constraint. Capture is named <i>name</i>.
|
56
|
+
Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
|
57
|
+
Arguments are separated by comma. An argument can be a simple string, a string enclosed
|
58
|
+
in single or double quotes, or a key value pair (keys and values being separated by an
|
59
|
+
equal sign). See below.
|
60
|
+
</td>
|
61
|
+
</tr>
|
62
|
+
<tr>
|
63
|
+
<td><b>/</b></td>
|
64
|
+
<td>
|
65
|
+
Matches forward slash. Does not match URI encoded version of forward slash.
|
66
|
+
</td>
|
67
|
+
</tr>
|
68
|
+
<tr>
|
69
|
+
<td><i>any other character</i></td>
|
70
|
+
<td>Matches exactly that character or a URI encoded version of it.</td>
|
71
|
+
</tr>
|
72
|
+
</tbody>
|
73
|
+
</table>
|
74
|
+
|
75
|
+
## Converters
|
76
|
+
|
77
|
+
### Builtin Converters
|
78
|
+
|
79
|
+
#### `string`
|
80
|
+
|
81
|
+
Possible arguments: `minlength`, `maxlength`, `length`
|
82
|
+
|
83
|
+
Captures anything but a forward slash in a semi-greedy fashion.
|
84
|
+
Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
|
85
|
+
|
86
|
+
This is also the default converter.
|
87
|
+
|
88
|
+
Examples:
|
89
|
+
|
90
|
+
```
|
91
|
+
<name>
|
92
|
+
<string:name>
|
93
|
+
<string(minlength=3,maxlength=10):slug>
|
94
|
+
<string(length=10):slug>
|
95
|
+
```
|
96
|
+
|
97
|
+
#### `int`
|
98
|
+
|
99
|
+
Possible arguments: `min`, `max`, `fixed_digits`
|
100
|
+
|
101
|
+
Captures digits.
|
102
|
+
Captured value will be converted to an Integer.
|
103
|
+
|
104
|
+
Examples:
|
105
|
+
|
106
|
+
```
|
107
|
+
<int:id>
|
108
|
+
<int(min=1,max=5):page>
|
109
|
+
<int(fixed_digits=16):uuid>
|
110
|
+
```
|
111
|
+
|
112
|
+
#### `float`
|
113
|
+
|
114
|
+
Possible arguments: `min`, `max`
|
115
|
+
|
116
|
+
Captures digits with a dot.
|
117
|
+
Captured value will be converted to an Float.
|
118
|
+
|
119
|
+
Examples:
|
120
|
+
|
121
|
+
```
|
122
|
+
<float:precision>
|
123
|
+
<float(min=0,max=1):precision>
|
124
|
+
```
|
125
|
+
|
126
|
+
#### `path`
|
127
|
+
|
128
|
+
Captures anything in a non-greedy fashion.
|
129
|
+
|
130
|
+
Example:
|
131
|
+
|
132
|
+
```
|
133
|
+
<path:rest>
|
134
|
+
```
|
135
|
+
|
136
|
+
#### `any`
|
137
|
+
|
138
|
+
Possible arguments: List of accepted strings.
|
139
|
+
|
140
|
+
Captures anything that matches one of the arguments exactly.
|
141
|
+
|
142
|
+
Example:
|
143
|
+
|
144
|
+
```
|
145
|
+
<any(home,search,contact):page>
|
146
|
+
```
|
147
|
+
|
148
|
+
### Custom Converters
|
149
|
+
|
150
|
+
[Flask patterns](#-pattern-details-flask) support registering custom converters.
|
151
|
+
|
152
|
+
A converter object may implement any of the following methods:
|
153
|
+
|
154
|
+
* `convert`: Should return a block converting a string value to whatever value should end up in the `params` hash.
|
155
|
+
* `constraint`: Should return a regular expression limiting which input string will match the capture.
|
156
|
+
* `new`: Returns an object that may respond to `convert` and/or `constraint` as described above. Any arguments used for the converter inside the pattern will be passed to `new`.
|
157
|
+
|
158
|
+
``` ruby
|
159
|
+
require 'mustermann/flask'
|
160
|
+
|
161
|
+
SimpleConverter = Struct.new(:constraint, :convert)
|
162
|
+
id_converter = SimpleConverter.new(/\d/, -> s { s.to_i })
|
163
|
+
|
164
|
+
class NumConverter
|
165
|
+
def initialize(base: 10)
|
166
|
+
@base = Integer(base)
|
167
|
+
end
|
168
|
+
|
169
|
+
def convert
|
170
|
+
-> s { s.to_i(@base) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def constraint
|
174
|
+
@base > 10 ? /[\da-#{(@base-1).to_s(@base)}]/ : /[0-#{@base-1}]/
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>',
|
179
|
+
type: :flask, converters: { id: id_converter, num: NumConverter})
|
180
|
+
|
181
|
+
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
|
182
|
+
```
|
183
|
+
|
184
|
+
### Global Converters
|
185
|
+
|
186
|
+
It is also possible to register a converter for all flask patterns, using `register_converter`:
|
187
|
+
|
188
|
+
``` ruby
|
189
|
+
Mustermann::Flask.register_converter(:id, id_converter)
|
190
|
+
Mustermann::Flask.register_converter(:num, NumConverter)
|
191
|
+
|
192
|
+
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>', type: :flask)
|
193
|
+
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
|
194
|
+
```
|
195
|
+
|
196
|
+
There is a handy syntax for quickly creating new converter classes: When you pass a block instead of a converter object, it will yield a generic converter with setters and getters for `convert` and `constraint`, and any arguments passed to the converter.
|
197
|
+
|
198
|
+
``` ruby
|
199
|
+
require 'mustermann/flask'
|
200
|
+
|
201
|
+
Mustermann::Flask.register_converter(:id) do |converter|
|
202
|
+
converter.constraint = /\d/
|
203
|
+
converter.convert = -> s { s.to_i }
|
204
|
+
end
|
205
|
+
|
206
|
+
Mustermann::Flask.register_converter(:num) do |converter, base: 10|
|
207
|
+
converter.constraint = base > 10 ? /[\da-#{(@base-1).to_s(base)}]/ : /[0-#{base-1}]/
|
208
|
+
converter.convert = -> s { s.to_i(base) }
|
209
|
+
end
|
210
|
+
|
211
|
+
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>', type: :flask)
|
212
|
+
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
|
213
|
+
```
|
214
|
+
|
215
|
+
### Subclassing
|
216
|
+
|
217
|
+
Registering global converters will make these available for all Flask patterns. It might even override already registered converters. This global state might break unrelated code.
|
218
|
+
|
219
|
+
It is therefore recommended that, if you don't want to pass in the converters option for every pattern, you create your own subclass of `Mustermann::Flask`.
|
220
|
+
|
221
|
+
``` ruby
|
222
|
+
require 'mustermann/flask'
|
223
|
+
|
224
|
+
MyFlask = Class.new(Mustermann::Flask)
|
225
|
+
|
226
|
+
MyFlask.register_converter(:id) do |converter|
|
227
|
+
converter.constraint = /\d/
|
228
|
+
converter.convert = -> s { s.to_i }
|
229
|
+
end
|
230
|
+
|
231
|
+
MyFlask.register_converter(:num) do |converter, base: 10|
|
232
|
+
converter.constraint = base > 10 ? /[\da-#{(@base-1).to_s(base)}]/ : /[0-#{base-1}]/
|
233
|
+
converter.convert = -> s { s.to_i(base) }
|
234
|
+
end
|
235
|
+
|
236
|
+
pattern = MyFlask.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>')
|
237
|
+
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
|
238
|
+
```
|
239
|
+
|
240
|
+
You can even register this type for usage with `Mustermann.new`:
|
241
|
+
|
242
|
+
``` ruby
|
243
|
+
Mustermann.register(:my_flask, MyFlask)
|
244
|
+
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>', type: :my_flask)
|
245
|
+
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
|
246
|
+
```
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
require 'mustermann/ast/pattern'
|
3
|
+
|
4
|
+
module Mustermann
|
5
|
+
# Flask style pattern implementation.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Mustermann.new('/<foo>', type: :flask) === '/bar' # => true
|
9
|
+
#
|
10
|
+
# @see Mustermann::Pattern
|
11
|
+
# @see file:README.md#flask Syntax description in the README
|
12
|
+
class Flask < AST::Pattern
|
13
|
+
register :flask
|
14
|
+
|
15
|
+
on(nil, ?>, ?:) { |c| unexpected(c) }
|
16
|
+
|
17
|
+
on(?<) do |char|
|
18
|
+
converter_name = expect(/\w+/, char: char)
|
19
|
+
args, opts = scan(?() ? read_args(?=, ?)) : [[], {}]
|
20
|
+
|
21
|
+
if scan(?:)
|
22
|
+
name = read_escaped(?>)
|
23
|
+
else
|
24
|
+
converter_name, name = 'default', converter_name
|
25
|
+
expect(?>)
|
26
|
+
end
|
27
|
+
|
28
|
+
converter = pattern.converters.fetch(converter_name) { unexpected("converter %p" % converter_name) }
|
29
|
+
converter = converter.new(*args, **opts) if converter.respond_to? :new
|
30
|
+
constraint = converter.constraint if converter.respond_to? :constraint
|
31
|
+
convert = converter.convert if converter.respond_to? :convert
|
32
|
+
qualifier = converter.qualifier if converter.respond_to? :qualifier
|
33
|
+
node_type = converter.node_type if converter.respond_to? :node_type
|
34
|
+
node_type ||= :capture
|
35
|
+
|
36
|
+
node(node_type, name, convert: convert, constraint: constraint, qualifier: qualifier)
|
37
|
+
end
|
38
|
+
|
39
|
+
# A class for easy creating of converters.
|
40
|
+
# @see Mustermann::Flask#register_converter
|
41
|
+
class Converter
|
42
|
+
# Constraint on the format used for the capture.
|
43
|
+
# Should be a regexp (or a string corresponding to a regexp)
|
44
|
+
# @see Mustermann::Flask#register_converter
|
45
|
+
attr_accessor :constraint
|
46
|
+
|
47
|
+
# Callback
|
48
|
+
# Should be a Proc.
|
49
|
+
# @see Mustermann::Flask#register_converter
|
50
|
+
attr_accessor :convert
|
51
|
+
|
52
|
+
# Constraint on the format used for the capture.
|
53
|
+
# Should be a regexp (or a string corresponding to a regexp)
|
54
|
+
# @see Mustermann::Flask#register_converter
|
55
|
+
# @!visibility private
|
56
|
+
attr_accessor :node_type
|
57
|
+
|
58
|
+
# Constraint on the format used for the capture.
|
59
|
+
# Should be a regexp (or a string corresponding to a regexp)
|
60
|
+
# @see Mustermann::Flask#register_converter
|
61
|
+
# @!visibility private
|
62
|
+
attr_accessor :qualifier
|
63
|
+
|
64
|
+
# @!visibility private
|
65
|
+
def self.create(&block)
|
66
|
+
Class.new(self) do
|
67
|
+
define_method(:initialize) { |*a, **o| block[self, *a, **o] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Makes sure a given value falls inbetween a min and a max.
|
72
|
+
# Uses the passed block to convert the value from a string to whatever
|
73
|
+
# format you'd expect.
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# require 'mustermann/flask'
|
77
|
+
#
|
78
|
+
# class MyPattern < Mustermann::Flask
|
79
|
+
# register_converter(:x) { between(5, 15, &:to_i) }
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# pattern = MyPattern.new('<x:id>')
|
83
|
+
# pattern.params('/12') # => { 'id' => 12 }
|
84
|
+
# pattern.params('/16') # => { 'id' => 15 }
|
85
|
+
#
|
86
|
+
# @see Mustermann::Flask#register_converter
|
87
|
+
def between(min, max)
|
88
|
+
self.convert = proc do |input|
|
89
|
+
value = yield(input)
|
90
|
+
value = yield(min) if min and value < yield(min)
|
91
|
+
value = yield(max) if max and value > yield(max)
|
92
|
+
value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generally available converters.
|
98
|
+
# @!visibility private
|
99
|
+
def self.converters(inherited = true)
|
100
|
+
return @converters ||= {} unless inherited
|
101
|
+
defaults = superclass.respond_to?(:converters) ? superclass.converters : {}
|
102
|
+
defaults.merge(converters(false))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Allows you to register your own converters.
|
106
|
+
#
|
107
|
+
# It is reommended to use this on a subclass, so to not influence other subsystems
|
108
|
+
# using flask templates.
|
109
|
+
#
|
110
|
+
# The object passed in as converter can implement #convert and/or #constraint.
|
111
|
+
#
|
112
|
+
# It can also instead implement #new, which will then return an object responding
|
113
|
+
# to some of these methods. Arguments from the flask pattern will be passed to #new.
|
114
|
+
#
|
115
|
+
# If passed a block, it will be yielded to with a {Mustermann::Flask::Converter}
|
116
|
+
# instance and any arguments in the flask pattern.
|
117
|
+
#
|
118
|
+
# @example with simple object
|
119
|
+
# require 'mustermann/flask'
|
120
|
+
#
|
121
|
+
# MyPattern = Class.new(Mustermann::Flask)
|
122
|
+
# up_converter = Struct.new(:convert).new(:upcase.to_proc)
|
123
|
+
# MyPattern.register_converter(:upper, up_converter)
|
124
|
+
#
|
125
|
+
# MyPattern.new("/<up:name>").params('/foo') # => { "name" => "FOO" }
|
126
|
+
#
|
127
|
+
# @example with block
|
128
|
+
# require 'mustermann/flask'
|
129
|
+
#
|
130
|
+
# MyPattern = Class.new(Mustermann::Flask)
|
131
|
+
# MyPattern.register_converter(:upper) { |c| c.convert = :upcase.to_proc }
|
132
|
+
#
|
133
|
+
# MyPattern.new("/<up:name>").params('/foo') # => { "name" => "FOO" }
|
134
|
+
#
|
135
|
+
# @example with converter class
|
136
|
+
# require 'mustermann/flasl'
|
137
|
+
#
|
138
|
+
# class MyPattern < Mustermann::Flask
|
139
|
+
# class Converter
|
140
|
+
# attr_reader :convert
|
141
|
+
# def initialize(send: :to_s)
|
142
|
+
# @convert = send.to_sym.to_proc
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# register_converter(:t, Converter)
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# MyPattern.new("/<t(send=upcase):name>").params('/Foo') # => { "name" => "FOO" }
|
150
|
+
# MyPattern.new("/<t(send=downcase):name>").params('/Foo') # => { "name" => "foo" }
|
151
|
+
#
|
152
|
+
# @param [#to_s] name converter name
|
153
|
+
# @param [#new, #convert, #constraint, nil] converter
|
154
|
+
def self.register_converter(name, converter = nil, &block)
|
155
|
+
converter ||= Converter.create(&block)
|
156
|
+
converters(false)[name.to_s] = converter
|
157
|
+
end
|
158
|
+
|
159
|
+
register_converter(:string) do |converter, minlength: nil, maxlength: nil, length: nil|
|
160
|
+
converter.qualifier = "{%s,%s}" % [minlength || 1, maxlength] if minlength or maxlength
|
161
|
+
converter.qualifier = "{%s}" % length if length
|
162
|
+
end
|
163
|
+
|
164
|
+
register_converter(:int) do |converter, min: nil, max: nil, fixed_digits: false|
|
165
|
+
converter.constraint = /\d/
|
166
|
+
converter.qualifier = "{#{fixed_digits}}" if fixed_digits
|
167
|
+
converter.between(min, max) { |string| Integer(string) }
|
168
|
+
end
|
169
|
+
|
170
|
+
register_converter(:float) do |converter, min: nil, max: nil|
|
171
|
+
converter.constraint = /\d*\.?\d+/
|
172
|
+
converter.qualifier = ""
|
173
|
+
converter.between(min, max) { |string| Float(string) }
|
174
|
+
end
|
175
|
+
|
176
|
+
register_converter(:path) do |converter|
|
177
|
+
converter.node_type = :named_splat
|
178
|
+
end
|
179
|
+
|
180
|
+
register_converter(:any) do |converter, *strings|
|
181
|
+
strings = strings.map { |s| Regexp.escape(s) unless s == {} }.compact
|
182
|
+
converter.qualifier = ""
|
183
|
+
converter.constraint = Regexp.union(*strings)
|
184
|
+
end
|
185
|
+
|
186
|
+
register_converter(:default, converters['string'])
|
187
|
+
|
188
|
+
supported_options :converters
|
189
|
+
attr_reader :converters
|
190
|
+
|
191
|
+
def initialize(input, converters: {}, **options)
|
192
|
+
@converters = self.class.converters.dup
|
193
|
+
converters.each { |k,v| @converters[k.to_s] = v } if converters
|
194
|
+
super(input, **options)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -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-flask"
|
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{Flask syntax for Mustermann}
|
11
|
+
s.description = %q{Adds Flask 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
|
data/spec/flask_spec.rb
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann/flask'
|
3
|
+
|
4
|
+
describe Mustermann::Flask do
|
5
|
+
extend Support::Pattern
|
6
|
+
|
7
|
+
pattern '' do
|
8
|
+
it { should match('') }
|
9
|
+
it { should_not match('/') }
|
10
|
+
|
11
|
+
it { should expand.to('') }
|
12
|
+
it { should_not expand(a: 1) }
|
13
|
+
|
14
|
+
it { should generate_template('') }
|
15
|
+
|
16
|
+
it { should respond_to(:expand) }
|
17
|
+
it { should respond_to(:to_templates) }
|
18
|
+
end
|
19
|
+
|
20
|
+
pattern '/' do
|
21
|
+
it { should match('/') }
|
22
|
+
it { should_not match('/foo') }
|
23
|
+
|
24
|
+
it { should expand.to('/') }
|
25
|
+
it { should_not expand(a: 1) }
|
26
|
+
end
|
27
|
+
|
28
|
+
pattern '/foo' do
|
29
|
+
it { should match('/foo') }
|
30
|
+
it { should_not match('/bar') }
|
31
|
+
it { should_not match('/foo.bar') }
|
32
|
+
|
33
|
+
it { should expand.to('/foo') }
|
34
|
+
it { should_not expand(a: 1) }
|
35
|
+
end
|
36
|
+
|
37
|
+
pattern '/foo/bar' do
|
38
|
+
it { should match('/foo/bar') }
|
39
|
+
it { should_not match('/foo%2Fbar') }
|
40
|
+
it { should_not match('/foo%2fbar') }
|
41
|
+
|
42
|
+
it { should expand.to('/foo/bar') }
|
43
|
+
it { should_not expand(a: 1) }
|
44
|
+
end
|
45
|
+
|
46
|
+
pattern '/<foo>' do
|
47
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
48
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
49
|
+
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
|
50
|
+
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
|
51
|
+
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
|
52
|
+
|
53
|
+
it { should_not match('/foo?') }
|
54
|
+
it { should_not match('/foo/bar') }
|
55
|
+
it { should_not match('/') }
|
56
|
+
it { should_not match('/foo/') }
|
57
|
+
|
58
|
+
example { pattern.params('/foo') .should be == {"foo" => "foo"} }
|
59
|
+
example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
|
60
|
+
example { pattern.params('').should be_nil }
|
61
|
+
|
62
|
+
it { should expand(foo: 'bar') .to('/bar') }
|
63
|
+
it { should expand(foo: 'b r') .to('/b%20r') }
|
64
|
+
it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
|
65
|
+
|
66
|
+
it { should_not expand(foo: 'foo', bar: 'bar') }
|
67
|
+
it { should_not expand(bar: 'bar') }
|
68
|
+
it { should_not expand }
|
69
|
+
|
70
|
+
it { should generate_template('/{foo}') }
|
71
|
+
end
|
72
|
+
|
73
|
+
pattern '/<string:foo>' do
|
74
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
75
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
76
|
+
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
|
77
|
+
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
|
78
|
+
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
|
79
|
+
|
80
|
+
it { should_not match('/foo?') }
|
81
|
+
it { should_not match('/foo/bar') }
|
82
|
+
it { should_not match('/') }
|
83
|
+
it { should_not match('/foo/') }
|
84
|
+
|
85
|
+
example { pattern.params('/foo') .should be == {"foo" => "foo"} }
|
86
|
+
example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
|
87
|
+
example { pattern.params('').should be_nil }
|
88
|
+
|
89
|
+
it { should expand(foo: 'bar') .to('/bar') }
|
90
|
+
it { should expand(foo: 'b r') .to('/b%20r') }
|
91
|
+
it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
|
92
|
+
|
93
|
+
it { should_not expand(foo: 'foo', bar: 'bar') }
|
94
|
+
it { should_not expand(bar: 'bar') }
|
95
|
+
it { should_not expand }
|
96
|
+
|
97
|
+
it { should generate_template('/{foo}') }
|
98
|
+
end
|
99
|
+
|
100
|
+
pattern '/<string(minlength=2):foo>' do
|
101
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
102
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
103
|
+
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
|
104
|
+
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
|
105
|
+
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
|
106
|
+
|
107
|
+
it { should_not match('/f') }
|
108
|
+
it { should_not match('/foo?') }
|
109
|
+
it { should_not match('/foo/bar') }
|
110
|
+
it { should_not match('/') }
|
111
|
+
it { should_not match('/foo/') }
|
112
|
+
|
113
|
+
it { should generate_template('/{foo}') }
|
114
|
+
end
|
115
|
+
|
116
|
+
pattern '/<string(maxlength=3):foo>' do
|
117
|
+
it { should match('/f') .capturing foo: 'f' }
|
118
|
+
it { should match('/fo') .capturing foo: 'fo' }
|
119
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
120
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
121
|
+
|
122
|
+
it { should_not match('/fooo') }
|
123
|
+
it { should_not match('/foo.bar') }
|
124
|
+
it { should_not match('/foo?') }
|
125
|
+
it { should_not match('/foo/bar') }
|
126
|
+
it { should_not match('/') }
|
127
|
+
it { should_not match('/foo/') }
|
128
|
+
|
129
|
+
it { should generate_template('/{foo}') }
|
130
|
+
end
|
131
|
+
|
132
|
+
pattern '/<string(length=3):foo>' do
|
133
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
134
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
135
|
+
|
136
|
+
it { should_not match('/f') }
|
137
|
+
it { should_not match('/fo') }
|
138
|
+
it { should_not match('/fooo') }
|
139
|
+
it { should_not match('/foo.bar') }
|
140
|
+
it { should_not match('/foo?') }
|
141
|
+
it { should_not match('/foo/bar') }
|
142
|
+
it { should_not match('/') }
|
143
|
+
it { should_not match('/foo/') }
|
144
|
+
|
145
|
+
it { should generate_template('/{foo}') }
|
146
|
+
end
|
147
|
+
|
148
|
+
pattern '/<int:foo>' do
|
149
|
+
it { should match('/42').capturing foo: '42' }
|
150
|
+
|
151
|
+
it { should_not match('/1.0') }
|
152
|
+
it { should_not match('/.5') }
|
153
|
+
it { should_not match('/foo') }
|
154
|
+
it { should_not match('/bar') }
|
155
|
+
it { should_not match('/foo.bar') }
|
156
|
+
it { should_not match('/%0Afoo') }
|
157
|
+
it { should_not match('/foo%2Fbar') }
|
158
|
+
|
159
|
+
it { should_not match('/foo?') }
|
160
|
+
it { should_not match('/foo/bar') }
|
161
|
+
it { should_not match('/') }
|
162
|
+
it { should_not match('/foo/') }
|
163
|
+
|
164
|
+
example { pattern.params('/42').should be == {"foo" => 42} }
|
165
|
+
it { should expand(foo: 12).to('/12') }
|
166
|
+
it { should generate_template('/{foo}') }
|
167
|
+
end
|
168
|
+
|
169
|
+
pattern '/<int:foo>' do
|
170
|
+
it { should match('/42').capturing foo: '42' }
|
171
|
+
|
172
|
+
it { should_not match('/1.0') }
|
173
|
+
it { should_not match('/.5') }
|
174
|
+
it { should_not match('/foo') }
|
175
|
+
it { should_not match('/bar') }
|
176
|
+
it { should_not match('/foo.bar') }
|
177
|
+
it { should_not match('/%0Afoo') }
|
178
|
+
it { should_not match('/foo%2Fbar') }
|
179
|
+
|
180
|
+
it { should_not match('/foo?') }
|
181
|
+
it { should_not match('/foo/bar') }
|
182
|
+
it { should_not match('/') }
|
183
|
+
it { should_not match('/foo/') }
|
184
|
+
|
185
|
+
example { pattern.params('/42').should be == {"foo" => 42} }
|
186
|
+
it { should expand(foo: 12).to('/12') }
|
187
|
+
it { should generate_template('/{foo}') }
|
188
|
+
end
|
189
|
+
|
190
|
+
pattern '/<any(foo,bar):foo>' do
|
191
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
192
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
193
|
+
|
194
|
+
it { should_not match('/f') }
|
195
|
+
it { should_not match('/fo') }
|
196
|
+
it { should_not match('/fooo') }
|
197
|
+
it { should_not match('/foo.bar') }
|
198
|
+
it { should_not match('/foo?') }
|
199
|
+
it { should_not match('/foo/bar') }
|
200
|
+
it { should_not match('/') }
|
201
|
+
it { should_not match('/foo/') }
|
202
|
+
it { should_not match('/baz') }
|
203
|
+
|
204
|
+
it { should generate_template('/{foo}') }
|
205
|
+
end
|
206
|
+
|
207
|
+
pattern '/<any( foo, bar ):foo>' do
|
208
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
209
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
210
|
+
|
211
|
+
it { should_not match('/f') }
|
212
|
+
it { should_not match('/fo') }
|
213
|
+
it { should_not match('/fooo') }
|
214
|
+
it { should_not match('/foo.bar') }
|
215
|
+
it { should_not match('/foo?') }
|
216
|
+
it { should_not match('/foo/bar') }
|
217
|
+
it { should_not match('/') }
|
218
|
+
it { should_not match('/foo/') }
|
219
|
+
it { should_not match('/baz') }
|
220
|
+
|
221
|
+
it { should generate_template('/{foo}') }
|
222
|
+
end
|
223
|
+
|
224
|
+
pattern '/<any(foo, bar, "foo,bar"):foo>' do
|
225
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
226
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
227
|
+
it { should match('/foo,bar') .capturing foo: 'foo,bar' }
|
228
|
+
|
229
|
+
it { should_not match('/f') }
|
230
|
+
it { should_not match('/fo') }
|
231
|
+
it { should_not match('/fooo') }
|
232
|
+
it { should_not match('/foo.bar') }
|
233
|
+
it { should_not match('/foo?') }
|
234
|
+
it { should_not match('/foo/bar') }
|
235
|
+
it { should_not match('/') }
|
236
|
+
it { should_not match('/foo/') }
|
237
|
+
it { should_not match('/baz') }
|
238
|
+
|
239
|
+
it { should generate_template('/{foo}') }
|
240
|
+
end
|
241
|
+
|
242
|
+
pattern '/<any(foo, bar, foo\,bar):foo>' do
|
243
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
244
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
245
|
+
it { should match('/foo,bar') .capturing foo: 'foo,bar' }
|
246
|
+
|
247
|
+
it { should_not match('/f') }
|
248
|
+
it { should_not match('/fo') }
|
249
|
+
it { should_not match('/fooo') }
|
250
|
+
it { should_not match('/foo.bar') }
|
251
|
+
it { should_not match('/foo?') }
|
252
|
+
it { should_not match('/foo/bar') }
|
253
|
+
it { should_not match('/') }
|
254
|
+
it { should_not match('/foo/') }
|
255
|
+
it { should_not match('/baz') }
|
256
|
+
|
257
|
+
it { should generate_template('/{foo}') }
|
258
|
+
end
|
259
|
+
|
260
|
+
pattern '/<any(foo, bar, "foo\,bar"):foo>' do
|
261
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
262
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
263
|
+
it { should match('/foo,bar') .capturing foo: 'foo,bar' }
|
264
|
+
|
265
|
+
it { should_not match('/f') }
|
266
|
+
it { should_not match('/fo') }
|
267
|
+
it { should_not match('/fooo') }
|
268
|
+
it { should_not match('/foo.bar') }
|
269
|
+
it { should_not match('/foo?') }
|
270
|
+
it { should_not match('/foo/bar') }
|
271
|
+
it { should_not match('/') }
|
272
|
+
it { should_not match('/foo/') }
|
273
|
+
it { should_not match('/baz') }
|
274
|
+
|
275
|
+
it { should generate_template('/{foo}') }
|
276
|
+
end
|
277
|
+
|
278
|
+
pattern '/<int(min=5,max=50):foo>' do
|
279
|
+
example { pattern.params('/42').should be == {"foo" => 42} }
|
280
|
+
example { pattern.params('/52').should be == {"foo" => 50} }
|
281
|
+
example { pattern.params('/2').should be == {"foo" => 5} }
|
282
|
+
end
|
283
|
+
|
284
|
+
pattern '/<float(min=5,max=50.5):foo>' do
|
285
|
+
example { pattern.params('/42.5').should be == {"foo" => 42.5} }
|
286
|
+
example { pattern.params('/52.5').should be == {"foo" => 50.5} }
|
287
|
+
example { pattern.params('/2.5').should be == {"foo" => 5.0} }
|
288
|
+
end
|
289
|
+
|
290
|
+
pattern '/<prefix>/<float:foo>/<int:bar>' do
|
291
|
+
it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
|
292
|
+
it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
|
293
|
+
it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
|
294
|
+
|
295
|
+
it { should_not match('/foo/1/1.0') }
|
296
|
+
it { should_not match('/foo/1.0/1.0') }
|
297
|
+
|
298
|
+
it { should generate_template('/{prefix}/{foo}/{bar}') }
|
299
|
+
|
300
|
+
example do
|
301
|
+
pattern.params('/foo/1.0/1').should be == {
|
302
|
+
"prefix" => "foo",
|
303
|
+
"foo" => 1.0,
|
304
|
+
"bar" => 1
|
305
|
+
}
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
pattern '/<path:foo>' do
|
310
|
+
it { should match('/') .capturing foo: '' }
|
311
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
312
|
+
it { should match('/foo/bar') .capturing foo: 'foo/bar' }
|
313
|
+
|
314
|
+
it { should expand .to('/') }
|
315
|
+
it { should expand(foo: nil) .to('/') }
|
316
|
+
it { should expand(foo: '') .to('/') }
|
317
|
+
it { should expand(foo: 'foo') .to('/foo') }
|
318
|
+
it { should expand(foo: 'foo/bar') .to('/foo/bar') }
|
319
|
+
it { should expand(foo: 'foo.bar') .to('/foo.bar') }
|
320
|
+
|
321
|
+
it { should generate_template('/{+foo}') }
|
322
|
+
end
|
323
|
+
|
324
|
+
converter = Struct.new(:convert).new(:upcase.to_proc)
|
325
|
+
pattern '/<foo:bar>', converters: { foo: converter } do
|
326
|
+
it { should match('/foo').capturing bar: 'foo' }
|
327
|
+
example { pattern.params('/foo').should be == {"bar" => "FOO"} }
|
328
|
+
end
|
329
|
+
|
330
|
+
context 'invalid syntax' do
|
331
|
+
example 'unexpected end of capture' do
|
332
|
+
expect { Mustermann::Flask.new('foo>bar') }.
|
333
|
+
to raise_error(Mustermann::ParseError, 'unexpected > while parsing "foo>bar"')
|
334
|
+
end
|
335
|
+
|
336
|
+
example 'missing end of capture' do
|
337
|
+
expect { Mustermann::Flask.new('foo<bar') }.
|
338
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo<bar"')
|
339
|
+
end
|
340
|
+
|
341
|
+
example 'unknown converter' do
|
342
|
+
expect { Mustermann::Flask.new('foo<bar:name>') }.
|
343
|
+
to raise_error(Mustermann::ParseError, 'unexpected converter "bar" while parsing "foo<bar:name>"')
|
344
|
+
end
|
345
|
+
|
346
|
+
example 'broken argument synax' do
|
347
|
+
expect { Mustermann::Flask.new('<string(length=3=2):foo>') }.
|
348
|
+
to raise_error(Mustermann::ParseError, 'unexpected = while parsing "<string(length=3=2):foo>"')
|
349
|
+
end
|
350
|
+
|
351
|
+
example 'missing )' do
|
352
|
+
expect { Mustermann::Flask.new('<string(foo') }.
|
353
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(foo"')
|
354
|
+
end
|
355
|
+
|
356
|
+
example 'missing ""' do
|
357
|
+
expect { Mustermann::Flask.new('<string("foo') }.
|
358
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(\\"foo"')
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
@@ -0,0 +1,368 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann/flask'
|
3
|
+
|
4
|
+
FlaskSubclass ||= Class.new(Mustermann::Flask)
|
5
|
+
FlaskSubclass.register_converter(:foo) {}
|
6
|
+
|
7
|
+
describe FlaskSubclass do
|
8
|
+
extend Support::Pattern
|
9
|
+
|
10
|
+
pattern '' do
|
11
|
+
it { should match('') }
|
12
|
+
it { should_not match('/') }
|
13
|
+
|
14
|
+
it { should expand.to('') }
|
15
|
+
it { should_not expand(a: 1) }
|
16
|
+
|
17
|
+
it { should generate_template('') }
|
18
|
+
|
19
|
+
it { should respond_to(:expand) }
|
20
|
+
it { should respond_to(:to_templates) }
|
21
|
+
end
|
22
|
+
|
23
|
+
pattern '/' do
|
24
|
+
it { should match('/') }
|
25
|
+
it { should_not match('/foo') }
|
26
|
+
|
27
|
+
it { should expand.to('/') }
|
28
|
+
it { should_not expand(a: 1) }
|
29
|
+
end
|
30
|
+
|
31
|
+
pattern '/foo' do
|
32
|
+
it { should match('/foo') }
|
33
|
+
it { should_not match('/bar') }
|
34
|
+
it { should_not match('/foo.bar') }
|
35
|
+
|
36
|
+
it { should expand.to('/foo') }
|
37
|
+
it { should_not expand(a: 1) }
|
38
|
+
end
|
39
|
+
|
40
|
+
pattern '/foo/bar' do
|
41
|
+
it { should match('/foo/bar') }
|
42
|
+
it { should_not match('/foo%2Fbar') }
|
43
|
+
it { should_not match('/foo%2fbar') }
|
44
|
+
|
45
|
+
it { should expand.to('/foo/bar') }
|
46
|
+
it { should_not expand(a: 1) }
|
47
|
+
end
|
48
|
+
|
49
|
+
pattern '/<foo>' do
|
50
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
51
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
52
|
+
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
|
53
|
+
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
|
54
|
+
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
|
55
|
+
|
56
|
+
it { should_not match('/foo?') }
|
57
|
+
it { should_not match('/foo/bar') }
|
58
|
+
it { should_not match('/') }
|
59
|
+
it { should_not match('/foo/') }
|
60
|
+
|
61
|
+
example { pattern.params('/foo') .should be == {"foo" => "foo"} }
|
62
|
+
example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
|
63
|
+
example { pattern.params('').should be_nil }
|
64
|
+
|
65
|
+
it { should expand(foo: 'bar') .to('/bar') }
|
66
|
+
it { should expand(foo: 'b r') .to('/b%20r') }
|
67
|
+
it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
|
68
|
+
|
69
|
+
it { should_not expand(foo: 'foo', bar: 'bar') }
|
70
|
+
it { should_not expand(bar: 'bar') }
|
71
|
+
it { should_not expand }
|
72
|
+
|
73
|
+
it { should generate_template('/{foo}') }
|
74
|
+
end
|
75
|
+
|
76
|
+
pattern '/<string:foo>' do
|
77
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
78
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
79
|
+
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
|
80
|
+
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
|
81
|
+
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
|
82
|
+
|
83
|
+
it { should_not match('/foo?') }
|
84
|
+
it { should_not match('/foo/bar') }
|
85
|
+
it { should_not match('/') }
|
86
|
+
it { should_not match('/foo/') }
|
87
|
+
|
88
|
+
example { pattern.params('/foo') .should be == {"foo" => "foo"} }
|
89
|
+
example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
|
90
|
+
example { pattern.params('').should be_nil }
|
91
|
+
|
92
|
+
it { should expand(foo: 'bar') .to('/bar') }
|
93
|
+
it { should expand(foo: 'b r') .to('/b%20r') }
|
94
|
+
it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
|
95
|
+
|
96
|
+
it { should_not expand(foo: 'foo', bar: 'bar') }
|
97
|
+
it { should_not expand(bar: 'bar') }
|
98
|
+
it { should_not expand }
|
99
|
+
|
100
|
+
it { should generate_template('/{foo}') }
|
101
|
+
end
|
102
|
+
|
103
|
+
pattern '/<string(minlength=2):foo>' do
|
104
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
105
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
106
|
+
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
|
107
|
+
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
|
108
|
+
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
|
109
|
+
|
110
|
+
it { should_not match('/f') }
|
111
|
+
it { should_not match('/foo?') }
|
112
|
+
it { should_not match('/foo/bar') }
|
113
|
+
it { should_not match('/') }
|
114
|
+
it { should_not match('/foo/') }
|
115
|
+
|
116
|
+
it { should generate_template('/{foo}') }
|
117
|
+
end
|
118
|
+
|
119
|
+
pattern '/<string(maxlength=3):foo>' do
|
120
|
+
it { should match('/f') .capturing foo: 'f' }
|
121
|
+
it { should match('/fo') .capturing foo: 'fo' }
|
122
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
123
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
124
|
+
|
125
|
+
it { should_not match('/fooo') }
|
126
|
+
it { should_not match('/foo.bar') }
|
127
|
+
it { should_not match('/foo?') }
|
128
|
+
it { should_not match('/foo/bar') }
|
129
|
+
it { should_not match('/') }
|
130
|
+
it { should_not match('/foo/') }
|
131
|
+
|
132
|
+
it { should generate_template('/{foo}') }
|
133
|
+
end
|
134
|
+
|
135
|
+
pattern '/<string(length=3):foo>' do
|
136
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
137
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
138
|
+
|
139
|
+
it { should_not match('/f') }
|
140
|
+
it { should_not match('/fo') }
|
141
|
+
it { should_not match('/fooo') }
|
142
|
+
it { should_not match('/foo.bar') }
|
143
|
+
it { should_not match('/foo?') }
|
144
|
+
it { should_not match('/foo/bar') }
|
145
|
+
it { should_not match('/') }
|
146
|
+
it { should_not match('/foo/') }
|
147
|
+
|
148
|
+
it { should generate_template('/{foo}') }
|
149
|
+
end
|
150
|
+
|
151
|
+
pattern '/<int:foo>' do
|
152
|
+
it { should match('/42').capturing foo: '42' }
|
153
|
+
|
154
|
+
it { should_not match('/1.0') }
|
155
|
+
it { should_not match('/.5') }
|
156
|
+
it { should_not match('/foo') }
|
157
|
+
it { should_not match('/bar') }
|
158
|
+
it { should_not match('/foo.bar') }
|
159
|
+
it { should_not match('/%0Afoo') }
|
160
|
+
it { should_not match('/foo%2Fbar') }
|
161
|
+
|
162
|
+
it { should_not match('/foo?') }
|
163
|
+
it { should_not match('/foo/bar') }
|
164
|
+
it { should_not match('/') }
|
165
|
+
it { should_not match('/foo/') }
|
166
|
+
|
167
|
+
example { pattern.params('/42').should be == {"foo" => 42} }
|
168
|
+
it { should expand(foo: 12).to('/12') }
|
169
|
+
it { should generate_template('/{foo}') }
|
170
|
+
end
|
171
|
+
|
172
|
+
pattern '/<int:foo>' do
|
173
|
+
it { should match('/42').capturing foo: '42' }
|
174
|
+
|
175
|
+
it { should_not match('/1.0') }
|
176
|
+
it { should_not match('/.5') }
|
177
|
+
it { should_not match('/foo') }
|
178
|
+
it { should_not match('/bar') }
|
179
|
+
it { should_not match('/foo.bar') }
|
180
|
+
it { should_not match('/%0Afoo') }
|
181
|
+
it { should_not match('/foo%2Fbar') }
|
182
|
+
|
183
|
+
it { should_not match('/foo?') }
|
184
|
+
it { should_not match('/foo/bar') }
|
185
|
+
it { should_not match('/') }
|
186
|
+
it { should_not match('/foo/') }
|
187
|
+
|
188
|
+
example { pattern.params('/42').should be == {"foo" => 42} }
|
189
|
+
it { should expand(foo: 12).to('/12') }
|
190
|
+
it { should generate_template('/{foo}') }
|
191
|
+
end
|
192
|
+
|
193
|
+
pattern '/<any(foo,bar):foo>' do
|
194
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
195
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
196
|
+
|
197
|
+
it { should_not match('/f') }
|
198
|
+
it { should_not match('/fo') }
|
199
|
+
it { should_not match('/fooo') }
|
200
|
+
it { should_not match('/foo.bar') }
|
201
|
+
it { should_not match('/foo?') }
|
202
|
+
it { should_not match('/foo/bar') }
|
203
|
+
it { should_not match('/') }
|
204
|
+
it { should_not match('/foo/') }
|
205
|
+
it { should_not match('/baz') }
|
206
|
+
|
207
|
+
it { should generate_template('/{foo}') }
|
208
|
+
end
|
209
|
+
|
210
|
+
pattern '/<any( foo, bar ):foo>' do
|
211
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
212
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
213
|
+
|
214
|
+
it { should_not match('/f') }
|
215
|
+
it { should_not match('/fo') }
|
216
|
+
it { should_not match('/fooo') }
|
217
|
+
it { should_not match('/foo.bar') }
|
218
|
+
it { should_not match('/foo?') }
|
219
|
+
it { should_not match('/foo/bar') }
|
220
|
+
it { should_not match('/') }
|
221
|
+
it { should_not match('/foo/') }
|
222
|
+
it { should_not match('/baz') }
|
223
|
+
|
224
|
+
it { should generate_template('/{foo}') }
|
225
|
+
end
|
226
|
+
|
227
|
+
pattern '/<any(foo, bar, "foo,bar"):foo>' do
|
228
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
229
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
230
|
+
it { should match('/foo,bar') .capturing foo: 'foo,bar' }
|
231
|
+
|
232
|
+
it { should_not match('/f') }
|
233
|
+
it { should_not match('/fo') }
|
234
|
+
it { should_not match('/fooo') }
|
235
|
+
it { should_not match('/foo.bar') }
|
236
|
+
it { should_not match('/foo?') }
|
237
|
+
it { should_not match('/foo/bar') }
|
238
|
+
it { should_not match('/') }
|
239
|
+
it { should_not match('/foo/') }
|
240
|
+
it { should_not match('/baz') }
|
241
|
+
|
242
|
+
it { should generate_template('/{foo}') }
|
243
|
+
end
|
244
|
+
|
245
|
+
pattern '/<any(foo, bar, foo\,bar):foo>' do
|
246
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
247
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
248
|
+
it { should match('/foo,bar') .capturing foo: 'foo,bar' }
|
249
|
+
|
250
|
+
it { should_not match('/f') }
|
251
|
+
it { should_not match('/fo') }
|
252
|
+
it { should_not match('/fooo') }
|
253
|
+
it { should_not match('/foo.bar') }
|
254
|
+
it { should_not match('/foo?') }
|
255
|
+
it { should_not match('/foo/bar') }
|
256
|
+
it { should_not match('/') }
|
257
|
+
it { should_not match('/foo/') }
|
258
|
+
it { should_not match('/baz') }
|
259
|
+
|
260
|
+
it { should generate_template('/{foo}') }
|
261
|
+
end
|
262
|
+
|
263
|
+
pattern '/<any(foo, bar, "foo\,bar"):foo>' do
|
264
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
265
|
+
it { should match('/bar') .capturing foo: 'bar' }
|
266
|
+
it { should match('/foo,bar') .capturing foo: 'foo,bar' }
|
267
|
+
|
268
|
+
it { should_not match('/f') }
|
269
|
+
it { should_not match('/fo') }
|
270
|
+
it { should_not match('/fooo') }
|
271
|
+
it { should_not match('/foo.bar') }
|
272
|
+
it { should_not match('/foo?') }
|
273
|
+
it { should_not match('/foo/bar') }
|
274
|
+
it { should_not match('/') }
|
275
|
+
it { should_not match('/foo/') }
|
276
|
+
it { should_not match('/baz') }
|
277
|
+
|
278
|
+
it { should generate_template('/{foo}') }
|
279
|
+
end
|
280
|
+
|
281
|
+
pattern '/<int(min=5,max=50):foo>' do
|
282
|
+
example { pattern.params('/42').should be == {"foo" => 42} }
|
283
|
+
example { pattern.params('/52').should be == {"foo" => 50} }
|
284
|
+
example { pattern.params('/2').should be == {"foo" => 5} }
|
285
|
+
end
|
286
|
+
|
287
|
+
pattern '/<float(min=5,max=50.5):foo>' do
|
288
|
+
example { pattern.params('/42.5').should be == {"foo" => 42.5} }
|
289
|
+
example { pattern.params('/52.5').should be == {"foo" => 50.5} }
|
290
|
+
example { pattern.params('/2.5').should be == {"foo" => 5.0} }
|
291
|
+
end
|
292
|
+
|
293
|
+
pattern '/<prefix>/<float:foo>/<int:bar>' do
|
294
|
+
it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
|
295
|
+
it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
|
296
|
+
it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
|
297
|
+
|
298
|
+
it { should_not match('/foo/1/1.0') }
|
299
|
+
it { should_not match('/foo/1.0/1.0') }
|
300
|
+
|
301
|
+
it { should generate_template('/{prefix}/{foo}/{bar}') }
|
302
|
+
|
303
|
+
example do
|
304
|
+
pattern.params('/foo/1.0/1').should be == {
|
305
|
+
"prefix" => "foo",
|
306
|
+
"foo" => 1.0,
|
307
|
+
"bar" => 1
|
308
|
+
}
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
pattern '/<path:foo>' do
|
313
|
+
it { should match('/') .capturing foo: '' }
|
314
|
+
it { should match('/foo') .capturing foo: 'foo' }
|
315
|
+
it { should match('/foo/bar') .capturing foo: 'foo/bar' }
|
316
|
+
|
317
|
+
it { should expand .to('/') }
|
318
|
+
it { should expand(foo: nil) .to('/') }
|
319
|
+
it { should expand(foo: '') .to('/') }
|
320
|
+
it { should expand(foo: 'foo') .to('/foo') }
|
321
|
+
it { should expand(foo: 'foo/bar') .to('/foo/bar') }
|
322
|
+
it { should expand(foo: 'foo.bar') .to('/foo.bar') }
|
323
|
+
|
324
|
+
it { should generate_template('/{+foo}') }
|
325
|
+
end
|
326
|
+
|
327
|
+
pattern '/<foo:bar>' do
|
328
|
+
it { should match('/foo').capturing bar: 'foo' }
|
329
|
+
it { should generate_template('/{bar}') }
|
330
|
+
|
331
|
+
example do
|
332
|
+
expect { Mustermann::Flask.new('/<foo:bar>') }.to \
|
333
|
+
raise_error(Mustermann::ParseError, 'unexpected converter "foo" while parsing "/<foo:bar>"')
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
context 'invalid syntax' do
|
338
|
+
example 'unexpected end of capture' do
|
339
|
+
expect { FlaskSubclass.new('foo>bar') }.
|
340
|
+
to raise_error(Mustermann::ParseError, 'unexpected > while parsing "foo>bar"')
|
341
|
+
end
|
342
|
+
|
343
|
+
example 'missing end of capture' do
|
344
|
+
expect { FlaskSubclass.new('foo<bar') }.
|
345
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo<bar"')
|
346
|
+
end
|
347
|
+
|
348
|
+
example 'unknown converter' do
|
349
|
+
expect { FlaskSubclass.new('foo<bar:name>') }.
|
350
|
+
to raise_error(Mustermann::ParseError, 'unexpected converter "bar" while parsing "foo<bar:name>"')
|
351
|
+
end
|
352
|
+
|
353
|
+
example 'broken argument synax' do
|
354
|
+
expect { FlaskSubclass.new('<string(length=3=2):foo>') }.
|
355
|
+
to raise_error(Mustermann::ParseError, 'unexpected = while parsing "<string(length=3=2):foo>"')
|
356
|
+
end
|
357
|
+
|
358
|
+
example 'missing )' do
|
359
|
+
expect { FlaskSubclass.new('<string(foo') }.
|
360
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(foo"')
|
361
|
+
end
|
362
|
+
|
363
|
+
example 'missing ""' do
|
364
|
+
expect { FlaskSubclass.new('<string("foo') }.
|
365
|
+
to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "<string(\\"foo"')
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mustermann-flask
|
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 Flask 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/flask.rb
|
35
|
+
- mustermann-flask.gemspec
|
36
|
+
- spec/flask_spec.rb
|
37
|
+
- spec/flask_subclass_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: Flask syntax for Mustermann
|
62
|
+
test_files:
|
63
|
+
- spec/flask_spec.rb
|
64
|
+
- spec/flask_subclass_spec.rb
|
65
|
+
has_rdoc:
|