mustermann-grape 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba38957c7eb32bbaefc9fe49b1f6c9f93503cfa4
4
+ data.tar.gz: 5ddb2e96f0e4386d009f495e18d3ab2af0b950d7
5
+ SHA512:
6
+ metadata.gz: 1f7e508537b28dc35a4cb567575bf183c915afec58dd8dc243ceb73ba02acc0152360d71c4e1b85dcc848878c10a50c06f0323053f2e29afac18238206104a54
7
+ data.tar.gz: 89ffc4d47ab2414633fa313d6cf15e1ced409b574c1b8a093de4042f6407a7b079b129ed11b18aa6af44364bd4cc3a775dea54b41be0284da8a7a2377dce24aa
@@ -0,0 +1,77 @@
1
+ # Grape Syntax for Mustermann
2
+
3
+ This gem implements the `grape` pattern type for Mustermann.
4
+
5
+ ## Overview
6
+
7
+ **Supported options:**
8
+ `capture`, `except`, `greedy`, `space_matches_plus`, `uri_decode`, `converters` and `ignore_unknown_options`
9
+
10
+ ``` ruby
11
+ require 'mustermann/grape'
12
+
13
+ Mustermann.new('/:id', type: :grape).params('/foo') # => { id: 'foo' }
14
+ ```
15
+
16
+ ## Syntax
17
+
18
+ <table>
19
+ <thead>
20
+ <tr>
21
+ <th>Syntax Element</th>
22
+ <th>Description</th>
23
+ </tr>
24
+ </thead>
25
+ <tbody>
26
+ <tr>
27
+ <td><b>:</b><i>name</i> <i><b>or</b></i> <b>&#123;</b><i>name</i><b>&#125;</b></td>
28
+ <td>
29
+ Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
30
+ Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
31
+ </td>
32
+ </tr>
33
+ <tr>
34
+ <td><b>*</b><i>name</i> <i><b>or</b></i> <b>&#123;+</b><i>name</i><b>&#125;</b></td>
35
+ <td>
36
+ Captures anything in a non-greedy fashion. Capture is named <i>name</i>.
37
+ </td>
38
+ </tr>
39
+ <tr>
40
+ <td><b>*</b> <i><b>or</b></i> <b>&#123;+splat&#125;</b></td>
41
+ <td>
42
+ Captures anything in a non-greedy fashion. Capture is named splat.
43
+ It is always an array of captures, as you can use it more than once in a pattern.
44
+ </td>
45
+ </tr>
46
+ <tr>
47
+ <td><b>(</b><i>expression</i><b>)</b></td>
48
+ <td>
49
+ Enclosed <i>expression</i> is optional.
50
+ </td>
51
+ </tr>
52
+ <tr>
53
+ <td><i>expression</i><b>|</b><i>expression</i><b>|</b><i>...</i></td>
54
+ <td>
55
+ Will match anything matching the nested expressions. May contain any other syntax element, including captures.
56
+ </td>
57
+ </tr>
58
+ <tr>
59
+ <td><i>x</i><b>?</b></td>
60
+ <td>Makes <i>x</i> optional. For instance, <tt>(foo)?</tt> matches <tt>foo</tt> or an empty string.</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><b>\</b><i>x</i></td>
70
+ <td>Matches <i>x</i> or URI encoded version of <i>x</i>. For instance <tt>\*</tt> matches <tt>*</tt>.</td>
71
+ </tr>
72
+ <tr>
73
+ <td><i>any other character</i></td>
74
+ <td>Matches exactly that character or a URI encoded version of it.</td>
75
+ </tr>
76
+ </tbody>
77
+ </table>
@@ -0,0 +1,35 @@
1
+ require 'mustermann'
2
+ require 'mustermann/ast/pattern'
3
+
4
+ module Mustermann
5
+ # Grape style pattern implementation.
6
+ #
7
+ # @example
8
+ # Mustermann.new('/:foo', type: :grape) === '/bar' # => true
9
+ #
10
+ # @see Mustermann::Pattern
11
+ # @see file:README.md#grape Syntax description in the README
12
+ class Grape < AST::Pattern
13
+ register :grape
14
+
15
+ on(nil, ??, ?)) { |c| unexpected(c) }
16
+
17
+ on(?*) { |c| scan(/\w+/) ? node(:named_splat, buffer.matched) : node(:splat) }
18
+ on(?:) { |c| node(:capture, constraint: "[^/\\?#\.]") { scan(/\w+/) } }
19
+ on(?\\) { |c| node(:char, expect(/./)) }
20
+ on(?() { |c| node(:optional, node(:group) { read unless scan(?)) }) }
21
+ on(?|) { |c| node(:or) }
22
+
23
+ on ?{ do |char|
24
+ type = scan(?+) ? :named_splat : :capture
25
+ name = expect(/[\w\.]+/)
26
+ type = :splat if type == :named_splat and name == 'splat'
27
+ expect(?})
28
+ node(type, name)
29
+ end
30
+
31
+ suffix ?? do |char, element|
32
+ node(:optional, element)
33
+ end
34
+ end
35
+ 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-grape"
6
+ s.version = Mustermann::VERSION
7
+ s.authors = ["namusyaka", "Konstantin Haase"]
8
+ s.email = "namusyaka@gmail.com"
9
+ s.homepage = "https://github.com/rkh/mustermann"
10
+ s.summary = %q{Grape syntax for Mustermann}
11
+ s.description = %q{Adds Grape 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,747 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'support'
3
+ require 'mustermann/grape'
4
+
5
+ describe Mustermann::Grape do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should generate_template('') }
13
+
14
+ it { should respond_to(:expand) }
15
+ it { should respond_to(:to_templates) }
16
+ end
17
+
18
+ pattern '/' do
19
+ it { should match('/') }
20
+ it { should_not match('/foo') }
21
+ end
22
+
23
+ pattern '/foo' do
24
+ it { should match('/foo') }
25
+ it { should_not match('/bar') }
26
+ it { should_not match('/foo.bar') }
27
+ end
28
+
29
+ pattern '/foo/bar' do
30
+ it { should match('/foo/bar') }
31
+ it { should_not match('/foo%2Fbar') }
32
+ it { should_not match('/foo%2fbar') }
33
+ end
34
+
35
+ pattern '/foo\/bar' do
36
+ it { should match('/foo/bar') }
37
+ it { should match('/foo%2Fbar') }
38
+ it { should match('/foo%2fbar') }
39
+ end
40
+
41
+ pattern '/:foo' do
42
+ it { should match('/foo') .capturing foo: 'foo' }
43
+ it { should match('/bar') .capturing foo: 'bar' }
44
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
45
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
46
+
47
+ it { should_not match('/foo.bar') }
48
+ it { should_not match('/foo?') }
49
+ it { should_not match('/foo/bar') }
50
+ it { should_not match('/') }
51
+ it { should_not match('/foo/') }
52
+
53
+ it { should generate_template('/{foo}') }
54
+ end
55
+
56
+ pattern '/föö' do
57
+ it { should match("/f%C3%B6%C3%B6") }
58
+ end
59
+
60
+ pattern "/:foo/:bar" do
61
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
62
+ it { should_not match('/foo.bar/bar.foo') }
63
+ it { should_not match('/user@example.com/name') }
64
+ it { should_not match('/10.1/te.st') }
65
+ it { should_not match('/10.1.2/te.st') }
66
+
67
+ it { should_not match('/foo%2Fbar') }
68
+ it { should_not match('/foo%2fbar') }
69
+
70
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
71
+ example { pattern.params('').should be_nil }
72
+
73
+ it { should generate_template('/{foo}/{bar}') }
74
+ end
75
+
76
+ pattern "/{foo}/{bar}" do
77
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
78
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
79
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
80
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
81
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
82
+
83
+ it { should_not match('/foo%2Fbar') }
84
+ it { should_not match('/foo%2fbar') }
85
+
86
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
87
+ example { pattern.params('').should be_nil }
88
+
89
+ it { should generate_template('/{foo}/{bar}') }
90
+ end
91
+
92
+ pattern '/hello/:person' do
93
+ it { should match('/hello/Frank').capturing person: 'Frank' }
94
+ it { should generate_template('/hello/{person}') }
95
+ end
96
+
97
+ pattern '/hello/{person}' do
98
+ it { should match('/hello/Frank').capturing person: 'Frank' }
99
+ it { should generate_template('/hello/{person}') }
100
+ end
101
+
102
+ pattern '/?:foo?/?:bar?' do
103
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
104
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
105
+ it { should match('/') .capturing foo: nil, bar: nil }
106
+ it { should match('') .capturing foo: nil, bar: nil }
107
+
108
+ it { should expand(foo: 'hello') .to('/hello/') }
109
+ it { should expand(foo: 'hello', bar: 'world') .to('/hello/world') }
110
+ it { should expand(bar: 'world') .to('//world') }
111
+ it { should expand .to('//') }
112
+ it { should_not expand(baz: '') }
113
+
114
+ it { should_not match('/hello/world/') }
115
+ it { should generate_templates("", "/", "//", "//{bar}", "/{bar}", "/{foo}", "/{foo}/", "/{foo}/{bar}", "/{foo}{bar}", "{bar}", "{foo}", "{foo}/", "{foo}/{bar}", "{foo}{bar}") }
116
+ end
117
+
118
+ pattern '/:foo_bar' do
119
+ it { should match('/hello').capturing foo_bar: 'hello' }
120
+ it { should generate_template('/{foo_bar}') }
121
+ end
122
+
123
+ pattern '/{foo.bar}' do
124
+ it { should match('/hello').capturing :"foo.bar" => 'hello' }
125
+ it { should generate_template('/{foo.bar}') }
126
+ end
127
+
128
+ pattern '/*' do
129
+ it { should match('/') .capturing splat: '' }
130
+ it { should match('/foo') .capturing splat: 'foo' }
131
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
132
+ it { should generate_template('/{+splat}') }
133
+
134
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
135
+ end
136
+
137
+ pattern '/{+splat}' do
138
+ it { should match('/') .capturing splat: '' }
139
+ it { should match('/foo') .capturing splat: 'foo' }
140
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
141
+ it { should generate_template('/{+splat}') }
142
+
143
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
144
+ end
145
+
146
+ pattern '/*foo' do
147
+ it { should match('/') .capturing foo: '' }
148
+ it { should match('/foo') .capturing foo: 'foo' }
149
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
150
+ it { should generate_template('/{+foo}') }
151
+
152
+ example { pattern.params('/foo') .should be == {"foo" => "foo" } }
153
+ example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
154
+ end
155
+
156
+ pattern '/{+foo}' do
157
+ it { should match('/') .capturing foo: '' }
158
+ it { should match('/foo') .capturing foo: 'foo' }
159
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
160
+ it { should generate_template('/{+foo}') }
161
+
162
+ example { pattern.params('/foo') .should be == {"foo" => "foo" } }
163
+ example { pattern.params('/foo/bar') .should be == {"foo" => "foo/bar" } }
164
+ end
165
+
166
+ pattern '/*foo/*bar' do
167
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
168
+ it { should generate_template('/{+foo}/{+bar}') }
169
+ end
170
+
171
+ pattern '/{+foo}/{+bar}' do
172
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
173
+ it { should generate_template('/{+foo}/{+bar}') }
174
+ end
175
+
176
+ pattern '/:foo/*' do
177
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
178
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
179
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
180
+ it { should_not match('/foo') }
181
+ it { should generate_template('/{foo}/{+splat}') }
182
+
183
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
184
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
185
+ end
186
+
187
+ pattern '/{foo}/*' do
188
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
189
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
190
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
191
+ it { should_not match('/foo') }
192
+ it { should generate_template('/{foo}/{+splat}') }
193
+
194
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
195
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
196
+ end
197
+
198
+ pattern '/test$/' do
199
+ it { should match('/test$/') }
200
+ end
201
+
202
+ pattern '/te+st/' do
203
+ it { should match('/te+st/') }
204
+ it { should_not match('/test/') }
205
+ it { should_not match('/teest/') }
206
+ end
207
+
208
+ pattern "/path with spaces" do
209
+ it { should match('/path%20with%20spaces') }
210
+ it { should match('/path%2Bwith%2Bspaces') }
211
+ it { should match('/path+with+spaces') }
212
+
213
+ it { should generate_template('/path%20with%20spaces') }
214
+ end
215
+
216
+ pattern '/foo&bar' do
217
+ it { should match('/foo&bar') }
218
+ end
219
+
220
+ pattern '/foo\{bar' do
221
+ it { should match('/foo%7Bbar') }
222
+ end
223
+
224
+ pattern '/*/:foo/*/*' do
225
+ it { should match('/bar/foo/bling/baz/boom') }
226
+
227
+ it "should capture all splat parts" do
228
+ match = pattern.match('/bar/foo/bling/baz/boom')
229
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
230
+ match.names.should be == ['splat', 'foo']
231
+ end
232
+
233
+ it 'should map to proper params' do
234
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
235
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
236
+ }
237
+ end
238
+ end
239
+
240
+ pattern '/{+splat}/{foo}/{+splat}/{+splat}' do
241
+ it { should match('/bar/foo/bling/baz/boom') }
242
+
243
+ it "should capture all splat parts" do
244
+ match = pattern.match('/bar/foo/bling/baz/boom')
245
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
246
+ match.names.should be == ['splat', 'foo']
247
+ end
248
+
249
+ it 'should map to proper params' do
250
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
251
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
252
+ }
253
+ end
254
+ end
255
+
256
+ pattern '/test.bar' do
257
+ it { should match('/test.bar') }
258
+ it { should_not match('/test0bar') }
259
+ end
260
+
261
+ pattern '/:file.:ext' do
262
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
263
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
264
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
265
+
266
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
267
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
268
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
269
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
270
+
271
+ it { should_not match('/pony正..jpg') }
272
+ it { should_not match('/.jpg') }
273
+ end
274
+
275
+ pattern '/(:a)x?' do
276
+ it { should match('/a') .capturing a: 'a' }
277
+ it { should match('/xa') .capturing a: 'xa' }
278
+ it { should match('/axa') .capturing a: 'axa' }
279
+
280
+ it { should generate_template('/{a}x') }
281
+ it { should generate_template('/{a}') }
282
+ end
283
+
284
+ pattern '/:user(@:host)?' do
285
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
286
+ it { should_not match('/foo.foo@bar') }
287
+ it { should_not match('/foo@bar.bar') }
288
+
289
+ it { should generate_template('/{user}') }
290
+ it { should generate_template('/{user}@{host}') }
291
+ end
292
+
293
+ pattern '/:file(.:ext)?' do
294
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
295
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
296
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
297
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
298
+ it { should_not match('/pony.png.jpg') }
299
+ it { should_not match('/pony.') }
300
+ it { should_not match('/.jpg') }
301
+
302
+ it { should generate_template('/{file}') }
303
+ it { should generate_template('/{file}.{ext}') }
304
+ it { should_not generate_template('/{file}.') }
305
+ end
306
+
307
+ pattern '/:id/test.bar' do
308
+ it { should match('/3/test.bar') .capturing id: '3' }
309
+ it { should match('/2/test.bar') .capturing id: '2' }
310
+ it { should match('/2E/test.bar') .capturing id: '2E' }
311
+ it { should match('/2e/test.bar') .capturing id: '2e' }
312
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
313
+ end
314
+
315
+ pattern '/10/:id' do
316
+ it { should match('/10/test') .capturing id: 'test' }
317
+ it { should_not match('/10/te.st') }
318
+ end
319
+
320
+ pattern '/10.1/:id' do
321
+ it { should match('/10.1/test') .capturing id: 'test' }
322
+ it { should_not match('/10.1/te.st') }
323
+ end
324
+
325
+ pattern '/:foo.:bar/:id' do
326
+ it { should_not match('/10.1/te.st') }
327
+ it { should_not match('/10.1.2/te.st') }
328
+ end
329
+
330
+ pattern '/:a/:b.?:c?' do
331
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
332
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
333
+ it { should_not match('/a.b/c') }
334
+ it { should_not match('/a.b/c.d') }
335
+ it { should_not match('/a.b/c.d/e') }
336
+ end
337
+
338
+ pattern '/:a(foo:b)?' do
339
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
340
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
341
+ it { should match('/bar') .capturing a: 'bar', b: nil }
342
+ it { should_not match('/') }
343
+ end
344
+
345
+ pattern '/foo?' do
346
+ it { should match('/fo') }
347
+ it { should match('/foo') }
348
+ it { should_not match('') }
349
+ it { should_not match('/') }
350
+ it { should_not match('/f') }
351
+ it { should_not match('/fooo') }
352
+ end
353
+
354
+ pattern '/foo\?' do
355
+ it { should match('/foo?') }
356
+ it { should_not match('/foo\?') }
357
+ it { should_not match('/fo') }
358
+ it { should_not match('/foo') }
359
+ it { should_not match('') }
360
+ it { should_not match('/') }
361
+ it { should_not match('/f') }
362
+ it { should_not match('/fooo') }
363
+ end
364
+
365
+ pattern '/foo\\\?' do
366
+ it { should match('/foo%5c') }
367
+ it { should match('/foo') }
368
+ it { should_not match('/foo\?') }
369
+ it { should_not match('/fo') }
370
+ it { should_not match('') }
371
+ it { should_not match('/') }
372
+ it { should_not match('/f') }
373
+ it { should_not match('/fooo') }
374
+ end
375
+
376
+ pattern '/\(' do
377
+ it { should match('/(') }
378
+ end
379
+
380
+ pattern '/\(?' do
381
+ it { should match('/(') }
382
+ it { should match('/') }
383
+ end
384
+
385
+ pattern '/(\()?' do
386
+ it { should match('/(') }
387
+ it { should match('/') }
388
+ end
389
+
390
+ pattern '/(\(\))?' do
391
+ it { should match('/') }
392
+ it { should match('/()') }
393
+ it { should_not match('/(') }
394
+ end
395
+
396
+ pattern '/\(\)?' do
397
+ it { should match('/(') }
398
+ it { should match('/()') }
399
+ it { should_not match('/') }
400
+ end
401
+
402
+ pattern '/\*' do
403
+ it { should match('/*') }
404
+ it { should_not match('/a') }
405
+ end
406
+
407
+ pattern '/\*/*' do
408
+ it { should match('/*/b/c') }
409
+ it { should_not match('/a/b/c') }
410
+ end
411
+
412
+ pattern '/\:foo' do
413
+ it { should match('/:foo') }
414
+ it { should_not match('/foo') }
415
+ end
416
+
417
+ pattern '/:fOO' do
418
+ it { should match('/a').capturing fOO: 'a' }
419
+ end
420
+
421
+ pattern '/:_X' do
422
+ it { should match('/a').capturing _X: 'a' }
423
+ end
424
+
425
+ pattern '/:f00' do
426
+ it { should match('/a').capturing f00: 'a' }
427
+ end
428
+
429
+ pattern '/:foo(/:bar)?/:baz?' do
430
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
431
+ end
432
+
433
+ pattern "/(foo|bar)" do
434
+ it { should match("/foo") }
435
+ it { should match("/bar") }
436
+ end
437
+
438
+ pattern "/(foo\\|bar)" do
439
+ it { should match "/foo%7Cbar" }
440
+ it { should generate_template "/foo%7Cbar" }
441
+
442
+ it { should_not match("/foo") }
443
+ it { should_not match("/bar") }
444
+
445
+ it { should_not generate_template('/foo') }
446
+ it { should_not generate_template('/bar') }
447
+ end
448
+
449
+ pattern "/(:a/:b|:c)" do
450
+ it { should match("/foo") .capturing c: 'foo' }
451
+ it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
452
+
453
+ it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
454
+ it { should expand(c: 'foo') .to('/foo') }
455
+ it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
456
+ end
457
+
458
+ pattern "/:a/:b|:c" do
459
+ it { should match("foo") .capturing c: 'foo' }
460
+ it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
461
+
462
+ it { should generate_template('/{a}/{b}') }
463
+ it { should generate_template('{c}') }
464
+
465
+ it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
466
+ it { should expand(c: 'foo') .to('foo') }
467
+ it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
468
+ end
469
+
470
+ pattern '/:foo', capture: /\d+/ do
471
+ it { should match('/1') .capturing foo: '1' }
472
+ it { should match('/123') .capturing foo: '123' }
473
+
474
+ it { should_not match('/') }
475
+ it { should_not match('/foo') }
476
+ end
477
+
478
+ pattern '/:foo', capture: /\d+/ do
479
+ it { should match('/1') .capturing foo: '1' }
480
+ it { should match('/123') .capturing foo: '123' }
481
+
482
+ it { should_not match('/') }
483
+ it { should_not match('/foo') }
484
+ end
485
+
486
+ pattern '/:foo', capture: '1' do
487
+ it { should match('/1').capturing foo: '1' }
488
+
489
+ it { should_not match('/') }
490
+ it { should_not match('/foo') }
491
+ it { should_not match('/123') }
492
+ end
493
+
494
+ pattern '/:foo', capture: 'a.b' do
495
+ it { should match('/a.b') .capturing foo: 'a.b' }
496
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
497
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
498
+
499
+ it { should_not match('/ab') }
500
+ it { should_not match('/afb') }
501
+ it { should_not match('/a1b') }
502
+ it { should_not match('/a.bc') }
503
+ end
504
+
505
+ pattern '/:foo(/:bar)?', capture: :alpha do
506
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
507
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
508
+ it { should match('/a') .capturing foo: 'a', bar: nil }
509
+
510
+ it { should_not match('/1/2') }
511
+ it { should_not match('/a/2') }
512
+ it { should_not match('/1/b') }
513
+ it { should_not match('/1') }
514
+ it { should_not match('/1/') }
515
+ it { should_not match('/a/') }
516
+ it { should_not match('//a') }
517
+ end
518
+
519
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
520
+ it { should match('/1') .capturing foo: '1' }
521
+ it { should match('/123') .capturing foo: '123' }
522
+ it { should match('/foo') .capturing foo: 'foo' }
523
+ it { should match('/bar') .capturing foo: 'bar' }
524
+
525
+ it { should_not match('/') }
526
+ it { should_not match('/baz') }
527
+ it { should_not match('/foo1') }
528
+ end
529
+
530
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
531
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
532
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
533
+ it { should_not match('/123abcxy-1') }
534
+ it { should_not match('/abcxy-1') }
535
+ it { should_not match('/abc1') }
536
+ end
537
+
538
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
539
+ it { should match('/1') .capturing foo: '1' }
540
+ it { should match('/123') .capturing foo: '123' }
541
+ it { should match('/foo') .capturing foo: 'foo' }
542
+ it { should match('/bar') .capturing foo: 'bar' }
543
+
544
+ it { should_not match('/') }
545
+ it { should_not match('/baz') }
546
+ it { should_not match('/foo1') }
547
+ end
548
+
549
+ pattern '/:file(.:ext)?', capture: { ext: ['jpg', 'png'] } do
550
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
551
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
552
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
553
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
554
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
555
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
556
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
557
+ it { should_not match('/pony.png.jpg') }
558
+ it { should_not match('/pony.jpg.png') }
559
+ it { should_not match('/pony.gif') }
560
+ it { should_not match('/pony.') }
561
+ it { should_not match('.jpg') }
562
+ end
563
+
564
+ pattern '/:file:ext?', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
565
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
566
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
567
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
568
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
569
+ it { should_not match('/pony.png.jpg') }
570
+ it { should_not match('/pony.jpg.png') }
571
+ it { should_not match('/pony.gif') }
572
+ it { should_not match('/pony.') }
573
+ it { should_not match('/.jpg') }
574
+ end
575
+
576
+ pattern '/:a(@:b)?', capture: { b: /\d+/ } do
577
+ it { should match('/a') .capturing a: 'a', b: nil }
578
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
579
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
580
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
581
+ end
582
+
583
+ pattern '/(:a)b?', greedy: false do
584
+ it { should match('/ab').capturing a: 'a' }
585
+ end
586
+
587
+ pattern '/:file(.:ext)?', greedy: false do
588
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
589
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
590
+ it { should_not match('/pony.png.jpg') }
591
+ end
592
+
593
+ pattern '/auth/*', except: '/auth/login' do
594
+ it { should match('/auth/admin') }
595
+ it { should match('/auth/foobar') }
596
+ it { should_not match('/auth/login') }
597
+ end
598
+
599
+ pattern '/:foo/:bar', except: '/:bar/20' do
600
+ it { should match('/foo/bar').capturing foo: 'foo', bar: 'bar' }
601
+ it { should_not match('/20/20') }
602
+ end
603
+
604
+ pattern '/foo?', uri_decode: false do
605
+ it { should match('/foo') }
606
+ it { should match('/fo') }
607
+ it { should_not match('/foo?') }
608
+ end
609
+
610
+ pattern '/foo/bar', uri_decode: false do
611
+ it { should match('/foo/bar') }
612
+ it { should_not match('/foo%2Fbar') }
613
+ it { should_not match('/foo%2fbar') }
614
+ end
615
+
616
+ pattern "/path with spaces", uri_decode: false do
617
+ it { should match('/path with spaces') }
618
+ it { should_not match('/path%20with%20spaces') }
619
+ it { should_not match('/path%2Bwith%2Bspaces') }
620
+ it { should_not match('/path+with+spaces') }
621
+ end
622
+
623
+ pattern "/path with spaces", space_matches_plus: false do
624
+ it { should match('/path%20with%20spaces') }
625
+ it { should_not match('/path%2Bwith%2Bspaces') }
626
+ it { should_not match('/path+with+spaces') }
627
+ end
628
+
629
+ context 'invalid syntax' do
630
+ example 'unexpected closing parenthesis' do
631
+ expect { Mustermann::Grape.new('foo)bar') }.
632
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
633
+ end
634
+
635
+ example 'missing closing parenthesis' do
636
+ expect { Mustermann::Grape.new('foo(bar') }.
637
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
638
+ end
639
+
640
+ example 'missing unescaped closing parenthesis' do
641
+ expect { Mustermann::Grape.new('foo(bar\)') }.
642
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar\\\\)"')
643
+ end
644
+
645
+ example '? at beginning of route' do
646
+ expect { Mustermann::Grape.new('?foobar') }.
647
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "?foobar"')
648
+ end
649
+
650
+ example 'double ?' do
651
+ expect { Mustermann::Grape.new('foo??bar') }.
652
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
653
+ end
654
+
655
+ example 'dangling escape' do
656
+ expect { Mustermann::Grape.new('foo\\') }.
657
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')
658
+ end
659
+ end
660
+
661
+ context 'invalid capture names' do
662
+ example 'empty name' do
663
+ expect { Mustermann::Grape.new('/:/') }.
664
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
665
+ end
666
+
667
+ example 'named splat' do
668
+ expect { Mustermann::Grape.new('/:splat/') }.
669
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
670
+ end
671
+
672
+ example 'named captures' do
673
+ expect { Mustermann::Grape.new('/:captures/') }.
674
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
675
+ end
676
+
677
+ example 'with capital letter' do
678
+ expect { Mustermann::Grape.new('/:Foo/') }.
679
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
680
+ end
681
+
682
+ example 'with integer' do
683
+ expect { Mustermann::Grape.new('/:1a/') }.
684
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
685
+ end
686
+
687
+ example 'same name twice' do
688
+ expect { Mustermann::Grape.new('/:foo(/:bar)?/:bar?') }.
689
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)?/:bar?\"")
690
+ end
691
+ end
692
+
693
+ context 'Regexp compatibility' do
694
+ describe :=== do
695
+ example('non-matching') { Mustermann::Grape.new("/") .should_not be === '/foo' }
696
+ example('matching') { Mustermann::Grape.new("/:foo") .should be === '/foo' }
697
+ end
698
+
699
+ describe :=~ do
700
+ example('non-matching') { Mustermann::Grape.new("/") .should_not be =~ '/foo' }
701
+ example('matching') { Mustermann::Grape.new("/:foo") .should be =~ '/foo' }
702
+
703
+ context 'String#=~' do
704
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Grape.new("/") }
705
+ example('matching') { "/foo".should be =~ Mustermann::Grape.new("/:foo") }
706
+ end
707
+ end
708
+
709
+ describe :to_regexp do
710
+ example('empty pattern') { Mustermann::Grape.new('').to_regexp.should be == /\A(?-mix:)\Z/ }
711
+
712
+ context 'Regexp.try_convert' do
713
+ example('empty pattern') { Regexp.try_convert(Mustermann::Grape.new('')).should be == /\A(?-mix:)\Z/ }
714
+ end
715
+ end
716
+ end
717
+
718
+ context 'Proc compatibility' do
719
+ describe :to_proc do
720
+ example { Mustermann::Grape.new("/").to_proc.should be_a(Proc) }
721
+ example('non-matching') { Mustermann::Grape.new("/") .to_proc.call('/foo').should be == false }
722
+ example('matching') { Mustermann::Grape.new("/:foo") .to_proc.call('/foo').should be == true }
723
+ end
724
+ end
725
+
726
+ context "peeking" do
727
+ subject(:pattern) { Mustermann::Grape.new(":name") }
728
+
729
+ describe :peek_size do
730
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
731
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
732
+ example { pattern.peek_size("/foo bar") .should be_nil }
733
+ end
734
+
735
+ describe :peek_match do
736
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
737
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
738
+ example { pattern.peek_match("/foo bar") .should be_nil }
739
+ end
740
+
741
+ describe :peek_params do
742
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
743
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
744
+ example { pattern.peek_params("/foo bar") .should be_nil }
745
+ end
746
+ end
747
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mustermann-grape
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - namusyaka
8
+ - Konstantin Haase
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-07-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mustermann
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '='
19
+ - !ruby/object:Gem::Version
20
+ version: 0.4.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '='
26
+ - !ruby/object:Gem::Version
27
+ version: 0.4.0
28
+ description: Adds Grape style patterns to Mustermman
29
+ email: namusyaka@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/mustermann/grape.rb
36
+ - mustermann-grape.gemspec
37
+ - spec/grape_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.5.1
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Grape syntax for Mustermann
62
+ test_files:
63
+ - spec/grape_spec.rb