mustermann-flask 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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>&lt;</b><i>name</i><b>&gt;</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>&lt;</b><i>converter</i><b>:</b><i>name</i><b>&gt;</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>&lt;</b><i>converter</i><b>(</b><i>arguments</i><b>):</b><i>name</i><b>&gt;</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
@@ -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: