mustermann 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'simplecov'
4
+ require 'coveralls'
5
+
6
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
7
+ SimpleCov::Formatter::HTMLFormatter,
8
+ Coveralls::SimpleCov::Formatter
9
+ ]
10
+
11
+ SimpleCov.start do
12
+ project_name 'mustermann'
13
+ minimum_coverage 100
14
+
15
+ add_filter "/spec/"
16
+ add_group 'Library', 'lib'
17
+ end
18
+
19
+ RSpec::Matchers.define :match do |expected|
20
+ match do |actual|
21
+ @captures ||= false
22
+ match = actual.match(expected)
23
+ match &&= @captures.all? { |k, v| match[k] == v } if @captures
24
+ match
25
+ end
26
+
27
+ chain :capturing do |captures|
28
+ @captures = captures
29
+ end
30
+
31
+ failure_message_for_should do |actual|
32
+ require 'pp'
33
+ match = actual.match(expected)
34
+ if match
35
+ key, value = @captures.detect { |k, v| match[k] != v }
36
+ "expected %p to capture %p as %p when matching %p, but got %p\n\nRegular Expression:\n%p" % [
37
+ actual.to_s, key, value, expected, match[key], actual.regexp
38
+ ]
39
+ else
40
+ "expected %p to match %p" % [ actual, expected ]
41
+ end
42
+ end
43
+
44
+ failure_message_for_should_not do |actual|
45
+ "expected %p not to match %p" % [
46
+ actual.to_s, expected
47
+ ]
48
+ end
49
+ end
50
+
51
+ module Support
52
+ module Pattern
53
+ def pattern(pattern, options = nil, &block)
54
+ description = "pattern %p" % pattern
55
+
56
+ if options
57
+ description << " with options %p" % options
58
+ instance = described_class.new(pattern, options)
59
+ else
60
+ instance = described_class.new(pattern)
61
+ end
62
+
63
+ context description do
64
+ subject(:pattern) { instance }
65
+ its(:to_s) { should be == pattern }
66
+ its(:inspect) { should be == "#<#{described_class}:#{pattern.inspect}>" }
67
+ its(:names) { should be_an(Array) }
68
+
69
+ example 'string should be immune to external change' do
70
+ subject.to_s.replace "NOT THE PATTERN"
71
+ subject.to_s.should be == pattern
72
+ end
73
+
74
+ instance_eval(&block)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,814 @@
1
+ require 'support'
2
+ require 'mustermann/template'
3
+
4
+ describe Mustermann::Template do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+ end
11
+
12
+ pattern '/' do
13
+ it { should match('/') }
14
+ it { should_not match('/foo') }
15
+ end
16
+
17
+ pattern '/foo' do
18
+ it { should match('/foo') }
19
+ it { should_not match('/bar') }
20
+ it { should_not match('/foo.bar') }
21
+ end
22
+
23
+ pattern '/foo/bar' do
24
+ it { should match('/foo/bar') }
25
+ it { should_not match('/foo%2Fbar') }
26
+ it { should_not match('/foo%2fbar') }
27
+ end
28
+
29
+ pattern '/:foo' do
30
+ it { should match('/:foo') }
31
+ it { should match('/%3Afoo') }
32
+ it { should_not match('/foo') }
33
+ it { should_not match('/foo?') }
34
+ it { should_not match('/foo/bar') }
35
+ it { should_not match('/') }
36
+ it { should_not match('/foo/') }
37
+ end
38
+
39
+ pattern '/föö' do
40
+ it { should match("/f%C3%B6%C3%B6") }
41
+ end
42
+
43
+ pattern '/test$/' do
44
+ it { should match('/test$/') }
45
+ end
46
+
47
+ pattern '/te+st/' do
48
+ it { should match('/te+st/') }
49
+ it { should_not match('/test/') }
50
+ it { should_not match('/teest/') }
51
+ end
52
+
53
+ pattern "/path with spaces" do
54
+ it { should match('/path%20with%20spaces') }
55
+ it { should match('/path%2Bwith%2Bspaces') }
56
+ it { should match('/path+with+spaces') }
57
+ end
58
+
59
+ pattern '/foo&bar' do
60
+ it { should match('/foo&bar') }
61
+ end
62
+
63
+ pattern '/test.bar' do
64
+ it { should match('/test.bar') }
65
+ it { should_not match('/test0bar') }
66
+ end
67
+
68
+ pattern "/path with spaces", space_matches_plus: false do
69
+ it { should match('/path%20with%20spaces') }
70
+ it { should_not match('/path%2Bwith%2Bspaces') }
71
+ it { should_not match('/path+with+spaces') }
72
+ end
73
+
74
+ pattern "/path with spaces", uri_decode: false do
75
+ it { should_not match('/path%20with%20spaces') }
76
+ it { should_not match('/path%2Bwith%2Bspaces') }
77
+ it { should_not match('/path+with+spaces') }
78
+ end
79
+
80
+ context 'level 1' do
81
+ context 'without operator' do
82
+ pattern '/hello/{person}' do
83
+ it { should match('/hello/Frank').capturing person: 'Frank' }
84
+ it { should match('/hello/a_b~c').capturing person: 'a_b~c' }
85
+ it { should match('/hello/a.%20').capturing person: 'a.%20' }
86
+
87
+ it { should_not match('/hello/:') }
88
+ it { should_not match('/hello//') }
89
+ it { should_not match('/hello/?') }
90
+ it { should_not match('/hello/#') }
91
+ it { should_not match('/hello/[') }
92
+ it { should_not match('/hello/]') }
93
+ it { should_not match('/hello/@') }
94
+ it { should_not match('/hello/!') }
95
+ it { should_not match('/hello/*') }
96
+ it { should_not match('/hello/+') }
97
+ it { should_not match('/hello/,') }
98
+ it { should_not match('/hello/;') }
99
+ it { should_not match('/hello/=') }
100
+
101
+ example { pattern.params('/hello/Frank').should be == {'person' => 'Frank'} }
102
+ end
103
+
104
+ pattern "/{foo}/{bar}" do
105
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
106
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
107
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
108
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
109
+
110
+ it { should_not match('/foo%2Fbar') }
111
+ it { should_not match('/foo%2fbar') }
112
+ end
113
+ end
114
+ end
115
+
116
+ context 'level 2' do
117
+ context 'operator +' do
118
+ pattern '/hello/{+person}' do
119
+ it { should match('/hello/Frank') .capturing person: 'Frank' }
120
+ it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
121
+ it { should match('/hello/a.%20') .capturing person: 'a.%20' }
122
+ it { should match('/hello/a/%20') .capturing person: 'a/%20' }
123
+ it { should match('/hello/:') .capturing person: ?: }
124
+ it { should match('/hello//') .capturing person: ?/ }
125
+ it { should match('/hello/?') .capturing person: ?? }
126
+ it { should match('/hello/#') .capturing person: ?# }
127
+ it { should match('/hello/[') .capturing person: ?[ }
128
+ it { should match('/hello/]') .capturing person: ?] }
129
+ it { should match('/hello/@') .capturing person: ?@ }
130
+ it { should match('/hello/!') .capturing person: ?! }
131
+ it { should match('/hello/*') .capturing person: ?* }
132
+ it { should match('/hello/+') .capturing person: ?+ }
133
+ it { should match('/hello/,') .capturing person: ?, }
134
+ it { should match('/hello/;') .capturing person: ?; }
135
+ it { should match('/hello/=') .capturing person: ?= }
136
+ end
137
+
138
+ pattern "/{+foo}/{bar}" do
139
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
140
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
141
+ it { should match('/foo/bar/bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
142
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
143
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
144
+
145
+ it { should_not match('/foo%2Fbar') }
146
+ it { should_not match('/foo%2fbar') }
147
+ end
148
+ end
149
+
150
+ context 'operator #' do
151
+ pattern '/hello/{#person}' do
152
+ it { should match('/hello/#Frank') .capturing person: 'Frank' }
153
+ it { should match('/hello/#a_b~c') .capturing person: 'a_b~c' }
154
+ it { should match('/hello/#a.%20') .capturing person: 'a.%20' }
155
+ it { should match('/hello/#a/%20') .capturing person: 'a/%20' }
156
+ it { should match('/hello/#:') .capturing person: ?: }
157
+ it { should match('/hello/#/') .capturing person: ?/ }
158
+ it { should match('/hello/#?') .capturing person: ?? }
159
+ it { should match('/hello/##') .capturing person: ?# }
160
+ it { should match('/hello/#[') .capturing person: ?[ }
161
+ it { should match('/hello/#]') .capturing person: ?] }
162
+ it { should match('/hello/#@') .capturing person: ?@ }
163
+ it { should match('/hello/#!') .capturing person: ?! }
164
+ it { should match('/hello/#*') .capturing person: ?* }
165
+ it { should match('/hello/#+') .capturing person: ?+ }
166
+ it { should match('/hello/#,') .capturing person: ?, }
167
+ it { should match('/hello/#;') .capturing person: ?; }
168
+ it { should match('/hello/#=') .capturing person: ?= }
169
+
170
+
171
+ it { should_not match('/hello/Frank') }
172
+ it { should_not match('/hello/a_b~c') }
173
+ it { should_not match('/hello/a.%20') }
174
+
175
+ it { should_not match('/hello/:') }
176
+ it { should_not match('/hello//') }
177
+ it { should_not match('/hello/?') }
178
+ it { should_not match('/hello/#') }
179
+ it { should_not match('/hello/[') }
180
+ it { should_not match('/hello/]') }
181
+ it { should_not match('/hello/@') }
182
+ it { should_not match('/hello/!') }
183
+ it { should_not match('/hello/*') }
184
+ it { should_not match('/hello/+') }
185
+ it { should_not match('/hello/,') }
186
+ it { should_not match('/hello/;') }
187
+ it { should_not match('/hello/=') }
188
+
189
+
190
+ example { pattern.params('/hello/#Frank').should be == {'person' => 'Frank'} }
191
+ end
192
+
193
+ pattern "/{+foo}/{#bar}" do
194
+ it { should match('/foo/#bar') .capturing foo: 'foo', bar: 'bar' }
195
+ it { should match('/foo.bar/#bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
196
+ it { should match('/foo/bar/#bar.foo') .capturing foo: 'foo/bar', bar: 'bar.foo' }
197
+ it { should match('/10.1/#te.st') .capturing foo: '10.1', bar: 'te.st' }
198
+ it { should match('/10.1.2/#te.st') .capturing foo: '10.1.2', bar: 'te.st' }
199
+
200
+ it { should_not match('/foo%2F#bar') }
201
+ it { should_not match('/foo%2f#bar') }
202
+
203
+ example { pattern.params('/hello/#Frank').should be == {'foo' => 'hello', 'bar' => 'Frank'} }
204
+ end
205
+ end
206
+ end
207
+
208
+ context 'level 3' do
209
+ context 'without operator' do
210
+ pattern "{a,b,c}" do
211
+ it { should match("~x,42,_").capturing a: '~x', b: '42', c: '_' }
212
+ it { should_not match("~x,42") }
213
+ it { should_not match("~x/42") }
214
+ it { should_not match("~x#42") }
215
+ it { should_not match("~x,42,_#42") }
216
+
217
+ example { pattern.params('d,f,g').should be == {'a' => 'd', 'b' => 'f', 'c' => 'g'} }
218
+ end
219
+ end
220
+
221
+ context 'operator +' do
222
+ pattern "{+a,b,c}" do
223
+ it { should match("~x,42,_") .capturing a: '~x', b: '42', c: '_' }
224
+ it { should match("~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
225
+ it { should match("~/x,42,_/42") .capturing a: '~/x', b: '42', c: '_/42' }
226
+
227
+ it { should_not match("~x,42") }
228
+ it { should_not match("~x/42") }
229
+ it { should_not match("~x#42") }
230
+ end
231
+ end
232
+
233
+ context 'operator #' do
234
+ pattern "{#a,b,c}" do
235
+ it { should match("#~x,42,_") .capturing a: '~x', b: '42', c: '_' }
236
+ it { should match("#~x,42,_#42") .capturing a: '~x', b: '42', c: '_#42' }
237
+ it { should match("#~/x,42,_#42") .capturing a: '~/x', b: '42', c: '_#42' }
238
+
239
+ it { should_not match("~x,42,_") }
240
+ it { should_not match("~x,42,_#42") }
241
+ it { should_not match("~/x,42,_#42") }
242
+
243
+ it { should_not match("~x,42") }
244
+ it { should_not match("~x/42") }
245
+ it { should_not match("~x#42") }
246
+ end
247
+ end
248
+
249
+ context 'operator .' do
250
+ pattern '/hello/{.person}' do
251
+ it { should match('/hello/.Frank') .capturing person: 'Frank' }
252
+ it { should match('/hello/.a_b~c') .capturing person: 'a_b~c' }
253
+
254
+ it { should_not match('/hello/.:') }
255
+ it { should_not match('/hello/./') }
256
+ it { should_not match('/hello/.?') }
257
+ it { should_not match('/hello/.#') }
258
+ it { should_not match('/hello/.[') }
259
+ it { should_not match('/hello/.]') }
260
+ it { should_not match('/hello/.@') }
261
+ it { should_not match('/hello/.!') }
262
+ it { should_not match('/hello/.*') }
263
+ it { should_not match('/hello/.+') }
264
+ it { should_not match('/hello/.,') }
265
+ it { should_not match('/hello/.;') }
266
+ it { should_not match('/hello/.=') }
267
+
268
+ it { should_not match('/hello/Frank') }
269
+ it { should_not match('/hello/a_b~c') }
270
+ it { should_not match('/hello/a.%20') }
271
+
272
+ it { should_not match('/hello/:') }
273
+ it { should_not match('/hello//') }
274
+ it { should_not match('/hello/?') }
275
+ it { should_not match('/hello/#') }
276
+ it { should_not match('/hello/[') }
277
+ it { should_not match('/hello/]') }
278
+ it { should_not match('/hello/@') }
279
+ it { should_not match('/hello/!') }
280
+ it { should_not match('/hello/*') }
281
+ it { should_not match('/hello/+') }
282
+ it { should_not match('/hello/,') }
283
+ it { should_not match('/hello/;') }
284
+ it { should_not match('/hello/=') }
285
+ end
286
+
287
+ pattern "{.a,b,c}" do
288
+ it { should match(".~x.42._").capturing a: '~x', b: '42', c: '_' }
289
+ it { should_not match(".~x,42") }
290
+ it { should_not match(".~x/42") }
291
+ it { should_not match(".~x#42") }
292
+ it { should_not match(".~x,42,_") }
293
+ it { should_not match("~x.42._") }
294
+ end
295
+ end
296
+
297
+ context 'operator /' do
298
+ pattern '/hello{/person}' do
299
+ it { should match('/hello/Frank') .capturing person: 'Frank' }
300
+ it { should match('/hello/a_b~c') .capturing person: 'a_b~c' }
301
+
302
+ it { should_not match('/hello//:') }
303
+ it { should_not match('/hello///') }
304
+ it { should_not match('/hello//?') }
305
+ it { should_not match('/hello//#') }
306
+ it { should_not match('/hello//[') }
307
+ it { should_not match('/hello//]') }
308
+ it { should_not match('/hello//@') }
309
+ it { should_not match('/hello//!') }
310
+ it { should_not match('/hello//*') }
311
+ it { should_not match('/hello//+') }
312
+ it { should_not match('/hello//,') }
313
+ it { should_not match('/hello//;') }
314
+ it { should_not match('/hello//=') }
315
+
316
+ it { should_not match('/hello/:') }
317
+ it { should_not match('/hello//') }
318
+ it { should_not match('/hello/?') }
319
+ it { should_not match('/hello/#') }
320
+ it { should_not match('/hello/[') }
321
+ it { should_not match('/hello/]') }
322
+ it { should_not match('/hello/@') }
323
+ it { should_not match('/hello/!') }
324
+ it { should_not match('/hello/*') }
325
+ it { should_not match('/hello/+') }
326
+ it { should_not match('/hello/,') }
327
+ it { should_not match('/hello/;') }
328
+ it { should_not match('/hello/=') }
329
+ end
330
+
331
+ pattern "{/a,b,c}" do
332
+ it { should match("/~x/42/_").capturing a: '~x', b: '42', c: '_' }
333
+ it { should_not match("/~x,42") }
334
+ it { should_not match("/~x.42") }
335
+ it { should_not match("/~x#42") }
336
+ it { should_not match("/~x,42,_") }
337
+ it { should_not match("~x/42/_") }
338
+ end
339
+ end
340
+
341
+ context 'operator ;' do
342
+ pattern '/hello/{;person}' do
343
+ it { should match('/hello/;person=Frank') .capturing person: 'Frank' }
344
+ it { should match('/hello/;person=a_b~c') .capturing person: 'a_b~c' }
345
+ it { should match('/hello/;person') .capturing person: nil }
346
+
347
+ it { should_not match('/hello/;persona=Frank') }
348
+ it { should_not match('/hello/;persona=a_b~c') }
349
+
350
+ it { should_not match('/hello/;person=:') }
351
+ it { should_not match('/hello/;person=/') }
352
+ it { should_not match('/hello/;person=?') }
353
+ it { should_not match('/hello/;person=#') }
354
+ it { should_not match('/hello/;person=[') }
355
+ it { should_not match('/hello/;person=]') }
356
+ it { should_not match('/hello/;person=@') }
357
+ it { should_not match('/hello/;person=!') }
358
+ it { should_not match('/hello/;person=*') }
359
+ it { should_not match('/hello/;person=+') }
360
+ it { should_not match('/hello/;person=,') }
361
+ it { should_not match('/hello/;person=;') }
362
+ it { should_not match('/hello/;person==') }
363
+
364
+ it { should_not match('/hello/;Frank') }
365
+ it { should_not match('/hello/;a_b~c') }
366
+ it { should_not match('/hello/;a.%20') }
367
+
368
+ it { should_not match('/hello/:') }
369
+ it { should_not match('/hello//') }
370
+ it { should_not match('/hello/?') }
371
+ it { should_not match('/hello/#') }
372
+ it { should_not match('/hello/[') }
373
+ it { should_not match('/hello/]') }
374
+ it { should_not match('/hello/@') }
375
+ it { should_not match('/hello/!') }
376
+ it { should_not match('/hello/*') }
377
+ it { should_not match('/hello/+') }
378
+ it { should_not match('/hello/,') }
379
+ it { should_not match('/hello/;') }
380
+ it { should_not match('/hello/=') }
381
+ end
382
+
383
+ pattern "{;a,b,c}" do
384
+ it { should match(";a=~x;b=42;c=_") .capturing a: '~x', b: '42', c: '_' }
385
+ it { should match(";a=~x;b;c=_") .capturing a: '~x', b: nil, c: '_' }
386
+
387
+ it { should_not match(";a=~x;c=_;b=42").capturing a: '~x', b: '42', c: '_' }
388
+
389
+ it { should_not match(";a=~x;b=42") }
390
+ it { should_not match("a=~x;b=42") }
391
+ it { should_not match(";a=~x;b=#42;c") }
392
+ it { should_not match(";a=~x,b=42,c=_") }
393
+ it { should_not match("~x;b=42;c=_") }
394
+ end
395
+ end
396
+
397
+ context 'operator ?' do
398
+ pattern '/hello/{?person}' do
399
+ it { should match('/hello/?person=Frank') .capturing person: 'Frank' }
400
+ it { should match('/hello/?person=a_b~c') .capturing person: 'a_b~c' }
401
+ it { should match('/hello/?person') .capturing person: nil }
402
+
403
+ it { should_not match('/hello/?persona=Frank') }
404
+ it { should_not match('/hello/?persona=a_b~c') }
405
+
406
+ it { should_not match('/hello/?person=:') }
407
+ it { should_not match('/hello/?person=/') }
408
+ it { should_not match('/hello/?person=?') }
409
+ it { should_not match('/hello/?person=#') }
410
+ it { should_not match('/hello/?person=[') }
411
+ it { should_not match('/hello/?person=]') }
412
+ it { should_not match('/hello/?person=@') }
413
+ it { should_not match('/hello/?person=!') }
414
+ it { should_not match('/hello/?person=*') }
415
+ it { should_not match('/hello/?person=+') }
416
+ it { should_not match('/hello/?person=,') }
417
+ it { should_not match('/hello/?person=;') }
418
+ it { should_not match('/hello/?person==') }
419
+
420
+ it { should_not match('/hello/?Frank') }
421
+ it { should_not match('/hello/?a_b~c') }
422
+ it { should_not match('/hello/?a.%20') }
423
+
424
+ it { should_not match('/hello/:') }
425
+ it { should_not match('/hello//') }
426
+ it { should_not match('/hello/?') }
427
+ it { should_not match('/hello/#') }
428
+ it { should_not match('/hello/[') }
429
+ it { should_not match('/hello/]') }
430
+ it { should_not match('/hello/@') }
431
+ it { should_not match('/hello/!') }
432
+ it { should_not match('/hello/*') }
433
+ it { should_not match('/hello/+') }
434
+ it { should_not match('/hello/,') }
435
+ it { should_not match('/hello/;') }
436
+ it { should_not match('/hello/=') }
437
+ end
438
+
439
+ pattern "{?a,b,c}" do
440
+ it { should match("?a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
441
+ it { should match("?a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
442
+
443
+ it { should_not match("?a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
444
+
445
+ it { should_not match("?a=~x&b=42") }
446
+ it { should_not match("a=~x&b=42") }
447
+ it { should_not match("?a=~x&b=#42&c") }
448
+ it { should_not match("?a=~x,b=42,c=_") }
449
+ it { should_not match("~x&b=42&c=_") }
450
+ end
451
+ end
452
+
453
+ context 'operator &' do
454
+ pattern '/hello/{&person}' do
455
+ it { should match('/hello/&person=Frank') .capturing person: 'Frank' }
456
+ it { should match('/hello/&person=a_b~c') .capturing person: 'a_b~c' }
457
+ it { should match('/hello/&person') .capturing person: nil }
458
+
459
+ it { should_not match('/hello/&persona=Frank') }
460
+ it { should_not match('/hello/&persona=a_b~c') }
461
+
462
+ it { should_not match('/hello/&person=:') }
463
+ it { should_not match('/hello/&person=/') }
464
+ it { should_not match('/hello/&person=?') }
465
+ it { should_not match('/hello/&person=#') }
466
+ it { should_not match('/hello/&person=[') }
467
+ it { should_not match('/hello/&person=]') }
468
+ it { should_not match('/hello/&person=@') }
469
+ it { should_not match('/hello/&person=!') }
470
+ it { should_not match('/hello/&person=*') }
471
+ it { should_not match('/hello/&person=+') }
472
+ it { should_not match('/hello/&person=,') }
473
+ it { should_not match('/hello/&person=;') }
474
+ it { should_not match('/hello/&person==') }
475
+
476
+ it { should_not match('/hello/&Frank') }
477
+ it { should_not match('/hello/&a_b~c') }
478
+ it { should_not match('/hello/&a.%20') }
479
+
480
+ it { should_not match('/hello/:') }
481
+ it { should_not match('/hello//') }
482
+ it { should_not match('/hello/?') }
483
+ it { should_not match('/hello/#') }
484
+ it { should_not match('/hello/[') }
485
+ it { should_not match('/hello/]') }
486
+ it { should_not match('/hello/@') }
487
+ it { should_not match('/hello/!') }
488
+ it { should_not match('/hello/*') }
489
+ it { should_not match('/hello/+') }
490
+ it { should_not match('/hello/,') }
491
+ it { should_not match('/hello/;') }
492
+ it { should_not match('/hello/=') }
493
+ end
494
+
495
+ pattern "{&a,b,c}" do
496
+ it { should match("&a=~x&b=42&c=_") .capturing a: '~x', b: '42', c: '_' }
497
+ it { should match("&a=~x&b&c=_") .capturing a: '~x', b: nil, c: '_' }
498
+
499
+ it { should_not match("&a=~x&c=_&b=42").capturing a: '~x', b: '42', c: '_' }
500
+
501
+ it { should_not match("&a=~x&b=42") }
502
+ it { should_not match("a=~x&b=42") }
503
+ it { should_not match("&a=~x&b=#42&c") }
504
+ it { should_not match("&a=~x,b=42,c=_") }
505
+ it { should_not match("~x&b=42&c=_") }
506
+ end
507
+ end
508
+ end
509
+
510
+ context 'level 4' do
511
+ context 'without operator' do
512
+ context 'prefix' do
513
+ pattern '{a:3}/bar' do
514
+ it { should match('foo/bar') .capturing a: 'foo' }
515
+ it { should match('fo/bar') .capturing a: 'fo' }
516
+ it { should match('f/bar') .capturing a: 'f' }
517
+ it { should_not match('fooo/bar') }
518
+ end
519
+
520
+ pattern '{a:3}{b}' do
521
+ it { should match('foobar') .capturing a: 'foo', b: 'bar' }
522
+ end
523
+ end
524
+
525
+ context 'expand' do
526
+ pattern '{a*}' do
527
+ it { should match('a') .capturing a: 'a' }
528
+ it { should match('a,b') .capturing a: 'a,b' }
529
+ it { should match('a,b,c') .capturing a: 'a,b,c' }
530
+ it { should_not match('a,b/c') }
531
+ it { should_not match('a,') }
532
+
533
+ example { pattern.params('a').should be == { 'a' => ['a'] }}
534
+ example { pattern.params('a,b').should be == { 'a' => ['a', 'b'] }}
535
+ end
536
+
537
+ pattern '{a*},{b}' do
538
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
539
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
540
+ it { should_not match('a,b/c') }
541
+ it { should_not match('a,') }
542
+
543
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
544
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
545
+ end
546
+
547
+ pattern '{a*,b}' do
548
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
549
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
550
+ it { should_not match('a,b/c') }
551
+ it { should_not match('a,') }
552
+
553
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
554
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
555
+ end
556
+ end
557
+ end
558
+
559
+ context 'operator +' do
560
+ context 'prefix' do
561
+ pattern '{+a:3}/bar' do
562
+ it { should match('foo/bar') .capturing a: 'foo' }
563
+ it { should match('fo/bar') .capturing a: 'fo' }
564
+ it { should match('f/bar') .capturing a: 'f' }
565
+ it { should_not match('fooo/bar') }
566
+ end
567
+
568
+ pattern '{+a:3}{b}' do
569
+ it { should match('foobar') .capturing a: 'foo', b: 'bar' }
570
+ end
571
+ end
572
+
573
+ context 'expand' do
574
+ pattern '{+a*}' do
575
+ it { should match('a') .capturing a: 'a' }
576
+ it { should match('a,b') .capturing a: 'a,b' }
577
+ it { should match('a,b,c') .capturing a: 'a,b,c' }
578
+ it { should match('a,b/c') .capturing a: 'a,b/c' }
579
+ end
580
+
581
+ pattern '{+a*},{b}' do
582
+ it { should match('a,b') .capturing a: 'a', b: 'b' }
583
+ it { should match('a,b,c') .capturing a: 'a,b', b: 'c' }
584
+ it { should_not match('a,b/c') }
585
+ it { should_not match('a,') }
586
+
587
+ example { pattern.params('a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
588
+ example { pattern.params('a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
589
+ end
590
+ end
591
+ end
592
+
593
+ context 'operator #' do
594
+ context 'prefix' do
595
+ pattern '{#a:3}/bar' do
596
+ it { should match('#foo/bar') .capturing a: 'foo' }
597
+ it { should match('#fo/bar') .capturing a: 'fo' }
598
+ it { should match('#f/bar') .capturing a: 'f' }
599
+ it { should_not match('#fooo/bar') }
600
+ end
601
+
602
+ pattern '{#a:3}{b}' do
603
+ it { should match('#foobar') .capturing a: 'foo', b: 'bar' }
604
+ end
605
+ end
606
+
607
+ context 'expand' do
608
+ pattern '{#a*}' do
609
+ it { should match('#a') .capturing a: 'a' }
610
+ it { should match('#a,b') .capturing a: 'a,b' }
611
+ it { should match('#a,b,c') .capturing a: 'a,b,c' }
612
+ it { should match('#a,b/c') .capturing a: 'a,b/c' }
613
+
614
+ example { pattern.params('#a,b').should be == { 'a' => ['a', 'b'] }}
615
+ example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b', 'c'] }}
616
+ end
617
+
618
+ pattern '{#a*,b}' do
619
+ it { should match('#a,b') .capturing a: 'a', b: 'b' }
620
+ it { should match('#a,b,c') .capturing a: 'a,b', b: 'c' }
621
+ it { should_not match('#a,') }
622
+
623
+ example { pattern.params('#a,b').should be == { 'a' => ['a'], 'b' => 'b' }}
624
+ example { pattern.params('#a,b,c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
625
+ end
626
+ end
627
+ end
628
+
629
+ context 'operator .' do
630
+ context 'prefix' do
631
+ pattern '{.a:3}/bar' do
632
+ it { should match('.foo/bar') .capturing a: 'foo' }
633
+ it { should match('.fo/bar') .capturing a: 'fo' }
634
+ it { should match('.f/bar') .capturing a: 'f' }
635
+ it { should_not match('.fooo/bar') }
636
+ end
637
+
638
+ pattern '{.a:3}{b}' do
639
+ it { should match('.foobar') .capturing a: 'foo', b: 'bar' }
640
+ end
641
+ end
642
+
643
+ context 'expand' do
644
+ pattern '{.a*}' do
645
+ it { should match('.a') .capturing a: 'a' }
646
+ it { should match('.a.b') .capturing a: 'a.b' }
647
+ it { should match('.a.b.c') .capturing a: 'a.b.c' }
648
+ it { should_not match('.a.b,c') }
649
+ it { should_not match('.a,') }
650
+ end
651
+
652
+ pattern '{.a*,b}' do
653
+ it { should match('.a.b') .capturing a: 'a', b: 'b' }
654
+ it { should match('.a.b.c') .capturing a: 'a.b', b: 'c' }
655
+ it { should_not match('.a.b/c') }
656
+ it { should_not match('.a.') }
657
+
658
+ example { pattern.params('.a.b').should be == { 'a' => ['a'], 'b' => 'b' }}
659
+ example { pattern.params('.a.b.c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
660
+ end
661
+ end
662
+ end
663
+
664
+ context 'operator /' do
665
+ context 'prefix' do
666
+ pattern '{/a:3}/bar' do
667
+ it { should match('/foo/bar') .capturing a: 'foo' }
668
+ it { should match('/fo/bar') .capturing a: 'fo' }
669
+ it { should match('/f/bar') .capturing a: 'f' }
670
+ it { should_not match('/fooo/bar') }
671
+ end
672
+
673
+ pattern '{/a:3}{b}' do
674
+ it { should match('/foobar') .capturing a: 'foo', b: 'bar' }
675
+ end
676
+ end
677
+
678
+ context 'expand' do
679
+ pattern '{/a*}' do
680
+ it { should match('/a') .capturing a: 'a' }
681
+ it { should match('/a/b') .capturing a: 'a/b' }
682
+ it { should match('/a/b/c') .capturing a: 'a/b/c' }
683
+ it { should_not match('/a/b,c') }
684
+ it { should_not match('/a,') }
685
+ end
686
+
687
+ pattern '{/a*,b}' do
688
+ it { should match('/a/b') .capturing a: 'a', b: 'b' }
689
+ it { should match('/a/b/c') .capturing a: 'a/b', b: 'c' }
690
+ it { should_not match('/a/b,c') }
691
+ it { should_not match('/a/') }
692
+
693
+ example { pattern.params('/a/b').should be == { 'a' => ['a'], 'b' => 'b' }}
694
+ example { pattern.params('/a/b/c').should be == { 'a' => ['a', 'b'], 'b' => 'c' }}
695
+ end
696
+ end
697
+ end
698
+
699
+ context 'operator ;' do
700
+ context 'prefix' do
701
+ pattern '{;a:3}/bar' do
702
+ it { should match(';a=foo/bar') .capturing a: 'foo' }
703
+ it { should match(';a=fo/bar') .capturing a: 'fo' }
704
+ it { should match(';a=f/bar') .capturing a: 'f' }
705
+ it { should_not match(';a=fooo/bar') }
706
+ end
707
+
708
+ pattern '{;a:3}{b}' do
709
+ it { should match(';a=foobar') .capturing a: 'foo', b: 'bar' }
710
+ end
711
+ end
712
+
713
+ context 'expand' do
714
+ pattern '{;a*}' do
715
+ it { should match(';a=1') .capturing a: 'a=1' }
716
+ it { should match(';a=1;a=2') .capturing a: 'a=1;a=2' }
717
+ it { should match(';a=1;a=2;a=3') .capturing a: 'a=1;a=2;a=3' }
718
+ it { should_not match(';a=1;a=2;b=3') }
719
+ it { should_not match(';a=1;a=2;a=3,') }
720
+ end
721
+
722
+ pattern '{;a*,b}' do
723
+ it { should match(';a=1;b') .capturing a: 'a=1', b: nil }
724
+ it { should match(';a=2;a=2;b=1') .capturing a: 'a=2;a=2', b: '1' }
725
+ it { should_not match(';a;b;c') }
726
+ it { should_not match(';a;') }
727
+
728
+ example { pattern.params(';a=2;a=2;b').should be == { 'a' => ['2', '2'], 'b' => nil }}
729
+ end
730
+ end
731
+ end
732
+
733
+ context 'operator ?' do
734
+ context 'prefix' do
735
+ pattern '{?a:3}/bar' do
736
+ it { should match('?a=foo/bar') .capturing a: 'foo' }
737
+ it { should match('?a=fo/bar') .capturing a: 'fo' }
738
+ it { should match('?a=f/bar') .capturing a: 'f' }
739
+ it { should_not match('?a=fooo/bar') }
740
+ end
741
+
742
+ pattern '{?a:3}{b}' do
743
+ it { should match('?a=foobar') .capturing a: 'foo', b: 'bar' }
744
+ end
745
+ end
746
+
747
+ context 'expand' do
748
+ pattern '{?a*}' do
749
+ it { should match('?a=1') .capturing a: 'a=1' }
750
+ it { should match('?a=1&a=2') .capturing a: 'a=1&a=2' }
751
+ it { should match('?a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
752
+ it { should_not match('?a=1&a=2&b=3') }
753
+ it { should_not match('?a=1&a=2&a=3,') }
754
+ end
755
+
756
+ pattern '{?a*,b}' do
757
+ it { should match('?a=1&b') .capturing a: 'a=1', b: nil }
758
+ it { should match('?a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
759
+ it { should_not match('?a&b&c') }
760
+ it { should_not match('?a&') }
761
+
762
+ example { pattern.params('?a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
763
+ end
764
+ end
765
+ end
766
+
767
+ context 'operator &' do
768
+ context 'prefix' do
769
+ pattern '{&a:3}/bar' do
770
+ it { should match('&a=foo/bar') .capturing a: 'foo' }
771
+ it { should match('&a=fo/bar') .capturing a: 'fo' }
772
+ it { should match('&a=f/bar') .capturing a: 'f' }
773
+ it { should_not match('&a=fooo/bar') }
774
+ end
775
+
776
+ pattern '{&a:3}{b}' do
777
+ it { should match('&a=foobar') .capturing a: 'foo', b: 'bar' }
778
+ end
779
+ end
780
+
781
+ context 'expand' do
782
+ pattern '{&a*}' do
783
+ it { should match('&a=1') .capturing a: 'a=1' }
784
+ it { should match('&a=1&a=2') .capturing a: 'a=1&a=2' }
785
+ it { should match('&a=1&a=2&a=3') .capturing a: 'a=1&a=2&a=3' }
786
+ it { should_not match('&a=1&a=2&b=3') }
787
+ it { should_not match('&a=1&a=2&a=3,') }
788
+ end
789
+
790
+ pattern '{&a*,b}' do
791
+ it { should match('&a=1&b') .capturing a: 'a=1', b: nil }
792
+ it { should match('&a=2&a=2&b=1') .capturing a: 'a=2&a=2', b: '1' }
793
+ it { should_not match('&a&b&c') }
794
+ it { should_not match('&a&') }
795
+
796
+ example { pattern.params('&a=2&a=2&b').should be == { 'a' => ['2', '2'], 'b' => nil }}
797
+ example { pattern.params('&a=2&a=%20&b').should be == { 'a' => ['2', ' '], 'b' => nil }}
798
+ end
799
+ end
800
+ end
801
+ end
802
+
803
+ context 'invalid syntax' do
804
+ example 'unexpected closing bracket' do
805
+ expect { Mustermann::Template.new('foo}bar') }.
806
+ to raise_error(Mustermann::ParseError, 'unexpected } while parsing "foo}bar"')
807
+ end
808
+
809
+ example 'missing closing bracket' do
810
+ expect { Mustermann::Template.new('foo{bar') }.
811
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo{bar"')
812
+ end
813
+ end
814
+ end