mustermann 0.0.1

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,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