mustermann-grape 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: 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