addressabler 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1152 @@
1
+ # coding: utf-8
2
+ # Copyright (C) 2006-2013 Bob Aman
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ require "spec_helper"
18
+
19
+ require "addressable/template"
20
+
21
+ shared_examples_for 'expands' do |tests|
22
+ tests.each do |template, expansion|
23
+ exp = expansion.is_a?(Array) ? expansion.first : expansion
24
+ it "#{template} to #{exp}" do
25
+ tmpl = Addressable::Template.new(template).expand(subject)
26
+ if expansion.is_a?(Array)
27
+ expansion.any?{|i| i == tmpl.to_str}.should be_true
28
+ else
29
+ tmpl.to_str.should == expansion
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "Type conversion" do
36
+ subject{
37
+ {:var => true, :hello => 1234, :nothing => nil, :sym => :symbolic}
38
+ }
39
+ it_behaves_like 'expands', {
40
+ '{var}' => 'true',
41
+ '{hello}' => '1234',
42
+ '{nothing}' => '',
43
+ '{sym}' => 'symbolic'
44
+ }
45
+ end
46
+
47
+ describe "Level 1:" do
48
+ subject{
49
+ {:var => "value", :hello => "Hello World!"}
50
+ }
51
+ it_behaves_like 'expands', {
52
+ '{var}' => 'value',
53
+ '{hello}' => 'Hello%20World%21'
54
+ }
55
+ end
56
+
57
+ describe "Level 2" do
58
+ subject{
59
+ {
60
+ :var => "value",
61
+ :hello => "Hello World!",
62
+ :path => "/foo/bar"
63
+ }
64
+ }
65
+ context "Operator +:" do
66
+ it_behaves_like 'expands', {
67
+ '{+var}' => 'value',
68
+ '{+hello}' => 'Hello%20World!',
69
+ '{+path}/here' => '/foo/bar/here',
70
+ 'here?ref={+path}' => 'here?ref=/foo/bar'
71
+ }
72
+ end
73
+ context "Operator #:" do
74
+ it_behaves_like 'expands', {
75
+ 'X{#var}' => 'X#value',
76
+ 'X{#hello}' => 'X#Hello%20World!'
77
+ }
78
+ end
79
+ end
80
+
81
+ describe "Level 3" do
82
+ subject{
83
+ {
84
+ :var => "value",
85
+ :hello => "Hello World!",
86
+ :empty => "",
87
+ :path => "/foo/bar",
88
+ :x => "1024",
89
+ :y => "768"
90
+ }
91
+ }
92
+ context "Operator nil (multiple vars):" do
93
+ it_behaves_like 'expands', {
94
+ 'map?{x,y}' => 'map?1024,768',
95
+ '{x,hello,y}' => '1024,Hello%20World%21,768'
96
+ }
97
+ end
98
+ context "Operator + (multiple vars):" do
99
+ it_behaves_like 'expands', {
100
+ '{+x,hello,y}' => '1024,Hello%20World!,768',
101
+ '{+path,x}/here' => '/foo/bar,1024/here'
102
+ }
103
+ end
104
+ context "Operator # (multiple vars):" do
105
+ it_behaves_like 'expands', {
106
+ '{#x,hello,y}' => '#1024,Hello%20World!,768',
107
+ '{#path,x}/here' => '#/foo/bar,1024/here'
108
+ }
109
+ end
110
+ context "Operator ." do
111
+ it_behaves_like 'expands', {
112
+ 'X{.var}' => 'X.value',
113
+ 'X{.x,y}' => 'X.1024.768'
114
+ }
115
+ end
116
+ context "Operator /" do
117
+ it_behaves_like 'expands', {
118
+ '{/var}' => '/value',
119
+ '{/var,x}/here' => '/value/1024/here'
120
+ }
121
+ end
122
+ context "Operator ;" do
123
+ it_behaves_like 'expands', {
124
+ '{;x,y}' => ';x=1024;y=768',
125
+ '{;x,y,empty}' => ';x=1024;y=768;empty'
126
+ }
127
+ end
128
+ context "Operator ?" do
129
+ it_behaves_like 'expands', {
130
+ '{?x,y}' => '?x=1024&y=768',
131
+ '{?x,y,empty}' => '?x=1024&y=768&empty='
132
+ }
133
+ end
134
+ context "Operator &" do
135
+ it_behaves_like 'expands', {
136
+ '?fixed=yes{&x}' => '?fixed=yes&x=1024',
137
+ '{&x,y,empty}' => '&x=1024&y=768&empty='
138
+ }
139
+ end
140
+ end
141
+
142
+ describe "Level 4" do
143
+ subject{
144
+ {
145
+ :var => "value",
146
+ :hello => "Hello World!",
147
+ :path => "/foo/bar",
148
+ :semi => ";",
149
+ :list => %w(red green blue),
150
+ :keys => {"semi" => ';', "dot" => '.', "comma" => ','}
151
+ }
152
+ }
153
+ context "Expansion with value modifiers" do
154
+ it_behaves_like 'expands', {
155
+ '{var:3}' => 'val',
156
+ '{var:30}' => 'value',
157
+ '{list}' => 'red,green,blue',
158
+ '{list*}' => 'red,green,blue',
159
+ '{keys}' => [
160
+ 'semi,%3B,dot,.,comma,%2C',
161
+ 'dot,.,semi,%3B,comma,%2C',
162
+ 'comma,%2C,semi,%3B,dot,.',
163
+ 'semi,%3B,comma,%2C,dot,.',
164
+ 'dot,.,comma,%2C,semi,%3B',
165
+ 'comma,%2C,dot,.,semi,%3B'
166
+ ],
167
+ '{keys*}' => [
168
+ 'semi=%3B,dot=.,comma=%2C',
169
+ 'dot=.,semi=%3B,comma=%2C',
170
+ 'comma=%2C,semi=%3B,dot=.',
171
+ 'semi=%3B,comma=%2C,dot=.',
172
+ 'dot=.,comma=%2C,semi=%3B',
173
+ 'comma=%2C,dot=.,semi=%3B'
174
+ ]
175
+ }
176
+ end
177
+ context "Operator + with value modifiers" do
178
+ it_behaves_like 'expands', {
179
+ '{+path:6}/here' => '/foo/b/here',
180
+ '{+list}' => 'red,green,blue',
181
+ '{+list*}' => 'red,green,blue',
182
+ '{+keys}' => [
183
+ 'semi,;,dot,.,comma,,',
184
+ 'dot,.,semi,;,comma,,',
185
+ 'comma,,,semi,;,dot,.',
186
+ 'semi,;,comma,,,dot,.',
187
+ 'dot,.,comma,,,semi,;',
188
+ 'comma,,,dot,.,semi,;'
189
+ ],
190
+ '{+keys*}' => [
191
+ 'semi=;,dot=.,comma=,',
192
+ 'dot=.,semi=;,comma=,',
193
+ 'comma=,,semi=;,dot=.',
194
+ 'semi=;,comma=,,dot=.',
195
+ 'dot=.,comma=,,semi=;',
196
+ 'comma=,,dot=.,semi=;'
197
+ ]
198
+ }
199
+ end
200
+ context "Operator # with value modifiers" do
201
+ it_behaves_like 'expands', {
202
+ '{#path:6}/here' => '#/foo/b/here',
203
+ '{#list}' => '#red,green,blue',
204
+ '{#list*}' => '#red,green,blue',
205
+ '{#keys}' => [
206
+ '#semi,;,dot,.,comma,,',
207
+ '#dot,.,semi,;,comma,,',
208
+ '#comma,,,semi,;,dot,.',
209
+ '#semi,;,comma,,,dot,.',
210
+ '#dot,.,comma,,,semi,;',
211
+ '#comma,,,dot,.,semi,;'
212
+ ],
213
+ '{#keys*}' => [
214
+ '#semi=;,dot=.,comma=,',
215
+ '#dot=.,semi=;,comma=,',
216
+ '#comma=,,semi=;,dot=.',
217
+ '#semi=;,comma=,,dot=.',
218
+ '#dot=.,comma=,,semi=;',
219
+ '#comma=,,dot=.,semi=;'
220
+ ]
221
+ }
222
+ end
223
+ context "Operator . with value modifiers" do
224
+ it_behaves_like 'expands', {
225
+ 'X{.var:3}' => 'X.val',
226
+ 'X{.list}' => 'X.red,green,blue',
227
+ 'X{.list*}' => 'X.red.green.blue',
228
+ 'X{.keys}' => [
229
+ 'X.semi,%3B,dot,.,comma,%2C',
230
+ 'X.dot,.,semi,%3B,comma,%2C',
231
+ 'X.comma,%2C,semi,%3B,dot,.',
232
+ 'X.semi,%3B,comma,%2C,dot,.',
233
+ 'X.dot,.,comma,%2C,semi,%3B',
234
+ 'X.comma,%2C,dot,.,semi,%3B'
235
+ ],
236
+ 'X{.keys*}' => [
237
+ 'X.semi=%3B.dot=..comma=%2C',
238
+ 'X.dot=..semi=%3B.comma=%2C',
239
+ 'X.comma=%2C.semi=%3B.dot=.',
240
+ 'X.semi=%3B.comma=%2C.dot=.',
241
+ 'X.dot=..comma=%2C.semi=%3B',
242
+ 'X.comma=%2C.dot=..semi=%3B'
243
+ ]
244
+ }
245
+ end
246
+ context "Operator / with value modifiers" do
247
+ it_behaves_like 'expands', {
248
+ '{/var:1,var}' => '/v/value',
249
+ '{/list}' => '/red,green,blue',
250
+ '{/list*}' => '/red/green/blue',
251
+ '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
252
+ '{/keys}' => [
253
+ '/semi,%3B,dot,.,comma,%2C',
254
+ '/dot,.,semi,%3B,comma,%2C',
255
+ '/comma,%2C,semi,%3B,dot,.',
256
+ '/semi,%3B,comma,%2C,dot,.',
257
+ '/dot,.,comma,%2C,semi,%3B',
258
+ '/comma,%2C,dot,.,semi,%3B'
259
+ ],
260
+ '{/keys*}' => [
261
+ '/semi=%3B/dot=./comma=%2C',
262
+ '/dot=./semi=%3B/comma=%2C',
263
+ '/comma=%2C/semi=%3B/dot=.',
264
+ '/semi=%3B/comma=%2C/dot=.',
265
+ '/dot=./comma=%2C/semi=%3B',
266
+ '/comma=%2C/dot=./semi=%3B'
267
+ ]
268
+ }
269
+ end
270
+ context "Operator ; with value modifiers" do
271
+ it_behaves_like 'expands', {
272
+ '{;hello:5}' => ';hello=Hello',
273
+ '{;list}' => ';list=red,green,blue',
274
+ '{;list*}' => ';list=red;list=green;list=blue',
275
+ '{;keys}' => [
276
+ ';keys=semi,%3B,dot,.,comma,%2C',
277
+ ';keys=dot,.,semi,%3B,comma,%2C',
278
+ ';keys=comma,%2C,semi,%3B,dot,.',
279
+ ';keys=semi,%3B,comma,%2C,dot,.',
280
+ ';keys=dot,.,comma,%2C,semi,%3B',
281
+ ';keys=comma,%2C,dot,.,semi,%3B'
282
+ ],
283
+ '{;keys*}' => [
284
+ ';semi=%3B;dot=.;comma=%2C',
285
+ ';dot=.;semi=%3B;comma=%2C',
286
+ ';comma=%2C;semi=%3B;dot=.',
287
+ ';semi=%3B;comma=%2C;dot=.',
288
+ ';dot=.;comma=%2C;semi=%3B',
289
+ ';comma=%2C;dot=.;semi=%3B'
290
+ ]
291
+ }
292
+ end
293
+ context "Operator ? with value modifiers" do
294
+ it_behaves_like 'expands', {
295
+ '{?var:3}' => '?var=val',
296
+ '{?list}' => '?list=red,green,blue',
297
+ '{?list*}' => '?list=red&list=green&list=blue',
298
+ '{?keys}' => [
299
+ '?keys=semi,%3B,dot,.,comma,%2C',
300
+ '?keys=dot,.,semi,%3B,comma,%2C',
301
+ '?keys=comma,%2C,semi,%3B,dot,.',
302
+ '?keys=semi,%3B,comma,%2C,dot,.',
303
+ '?keys=dot,.,comma,%2C,semi,%3B',
304
+ '?keys=comma,%2C,dot,.,semi,%3B'
305
+ ],
306
+ '{?keys*}' => [
307
+ '?semi=%3B&dot=.&comma=%2C',
308
+ '?dot=.&semi=%3B&comma=%2C',
309
+ '?comma=%2C&semi=%3B&dot=.',
310
+ '?semi=%3B&comma=%2C&dot=.',
311
+ '?dot=.&comma=%2C&semi=%3B',
312
+ '?comma=%2C&dot=.&semi=%3B'
313
+ ]
314
+ }
315
+ end
316
+ context "Operator & with value modifiers" do
317
+ it_behaves_like 'expands', {
318
+ '{&var:3}' => '&var=val',
319
+ '{&list}' => '&list=red,green,blue',
320
+ '{&list*}' => '&list=red&list=green&list=blue',
321
+ '{&keys}' => [
322
+ '&keys=semi,%3B,dot,.,comma,%2C',
323
+ '&keys=dot,.,semi,%3B,comma,%2C',
324
+ '&keys=comma,%2C,semi,%3B,dot,.',
325
+ '&keys=semi,%3B,comma,%2C,dot,.',
326
+ '&keys=dot,.,comma,%2C,semi,%3B',
327
+ '&keys=comma,%2C,dot,.,semi,%3B'
328
+ ],
329
+ '{&keys*}' => [
330
+ '&semi=%3B&dot=.&comma=%2C',
331
+ '&dot=.&semi=%3B&comma=%2C',
332
+ '&comma=%2C&semi=%3B&dot=.',
333
+ '&semi=%3B&comma=%2C&dot=.',
334
+ '&dot=.&comma=%2C&semi=%3B',
335
+ '&comma=%2C&dot=.&semi=%3B'
336
+ ]
337
+ }
338
+ end
339
+ end
340
+ describe "Modifiers" do
341
+ subject{
342
+ {
343
+ :var => "value",
344
+ :semi => ";",
345
+ :year => %w(1965 2000 2012),
346
+ :dom => %w(example com)
347
+ }
348
+ }
349
+ context "length" do
350
+ it_behaves_like 'expands', {
351
+ '{var:3}' => 'val',
352
+ '{var:30}' => 'value',
353
+ '{var}' => 'value',
354
+ '{semi}' => '%3B',
355
+ '{semi:2}' => '%3B'
356
+ }
357
+ end
358
+ context "explode" do
359
+ it_behaves_like 'expands', {
360
+ 'find{?year*}' => 'find?year=1965&year=2000&year=2012',
361
+ 'www{.dom*}' => 'www.example.com',
362
+ }
363
+ end
364
+ end
365
+ describe "Expansion" do
366
+ subject{
367
+ {
368
+ :count => ["one", "two", "three"],
369
+ :dom => ["example", "com"],
370
+ :dub => "me/too",
371
+ :hello => "Hello World!",
372
+ :half => "50%",
373
+ :var => "value",
374
+ :who => "fred",
375
+ :base => "http://example.com/home/",
376
+ :path => "/foo/bar",
377
+ :list => ["red", "green", "blue"],
378
+ :keys => {"semi" => ";","dot" => ".","comma" => ","},
379
+ :v => "6",
380
+ :x => "1024",
381
+ :y => "768",
382
+ :empty => "",
383
+ :empty_keys => {},
384
+ :undef => nil
385
+ }
386
+ }
387
+ context "concatenation" do
388
+ it_behaves_like 'expands', {
389
+ '{count}' => 'one,two,three',
390
+ '{count*}' => 'one,two,three',
391
+ '{/count}' => '/one,two,three',
392
+ '{/count*}' => '/one/two/three',
393
+ '{;count}' => ';count=one,two,three',
394
+ '{;count*}' => ';count=one;count=two;count=three',
395
+ '{?count}' => '?count=one,two,three',
396
+ '{?count*}' => '?count=one&count=two&count=three',
397
+ '{&count*}' => '&count=one&count=two&count=three'
398
+ }
399
+ end
400
+ context "simple expansion" do
401
+ it_behaves_like 'expands', {
402
+ '{var}' => 'value',
403
+ '{hello}' => 'Hello%20World%21',
404
+ '{half}' => '50%25',
405
+ 'O{empty}X' => 'OX',
406
+ 'O{undef}X' => 'OX',
407
+ '{x,y}' => '1024,768',
408
+ '{x,hello,y}' => '1024,Hello%20World%21,768',
409
+ '?{x,empty}' => '?1024,',
410
+ '?{x,undef}' => '?1024',
411
+ '?{undef,y}' => '?768',
412
+ '{var:3}' => 'val',
413
+ '{var:30}' => 'value',
414
+ '{list}' => 'red,green,blue',
415
+ '{list*}' => 'red,green,blue',
416
+ '{keys}' => [
417
+ 'semi,%3B,dot,.,comma,%2C',
418
+ 'dot,.,semi,%3B,comma,%2C',
419
+ 'comma,%2C,semi,%3B,dot,.',
420
+ 'semi,%3B,comma,%2C,dot,.',
421
+ 'dot,.,comma,%2C,semi,%3B',
422
+ 'comma,%2C,dot,.,semi,%3B'
423
+ ],
424
+ '{keys*}' => [
425
+ 'semi=%3B,dot=.,comma=%2C',
426
+ 'dot=.,semi=%3B,comma=%2C',
427
+ 'comma=%2C,semi=%3B,dot=.',
428
+ 'semi=%3B,comma=%2C,dot=.',
429
+ 'dot=.,comma=%2C,semi=%3B',
430
+ 'comma=%2C,dot=.,semi=%3B'
431
+ ]
432
+ }
433
+ end
434
+ context "reserved expansion (+)" do
435
+ it_behaves_like 'expands', {
436
+ '{+var}' => 'value',
437
+ '{+hello}' => 'Hello%20World!',
438
+ '{+half}' => '50%25',
439
+ '{base}index' => 'http%3A%2F%2Fexample.com%2Fhome%2Findex',
440
+ '{+base}index' => 'http://example.com/home/index',
441
+ 'O{+empty}X' => 'OX',
442
+ 'O{+undef}X' => 'OX',
443
+ '{+path}/here' => '/foo/bar/here',
444
+ 'here?ref={+path}' => 'here?ref=/foo/bar',
445
+ 'up{+path}{var}/here' => 'up/foo/barvalue/here',
446
+ '{+x,hello,y}' => '1024,Hello%20World!,768',
447
+ '{+path,x}/here' => '/foo/bar,1024/here',
448
+ '{+path:6}/here' => '/foo/b/here',
449
+ '{+list}' => 'red,green,blue',
450
+ '{+list*}' => 'red,green,blue',
451
+ '{+keys}' => [
452
+ 'semi,;,dot,.,comma,,',
453
+ 'dot,.,semi,;,comma,,',
454
+ 'comma,,,semi,;,dot,.',
455
+ 'semi,;,comma,,,dot,.',
456
+ 'dot,.,comma,,,semi,;',
457
+ 'comma,,,dot,.,semi,;'
458
+ ],
459
+ '{+keys*}' => [
460
+ 'semi=;,dot=.,comma=,',
461
+ 'dot=.,semi=;,comma=,',
462
+ 'comma=,,semi=;,dot=.',
463
+ 'semi=;,comma=,,dot=.',
464
+ 'dot=.,comma=,,semi=;',
465
+ 'comma=,,dot=.,semi=;'
466
+ ]
467
+ }
468
+ end
469
+ context "fragment expansion (#)" do
470
+ it_behaves_like 'expands', {
471
+ '{#var}' => '#value',
472
+ '{#hello}' => '#Hello%20World!',
473
+ '{#half}' => '#50%25',
474
+ 'foo{#empty}' => 'foo#',
475
+ 'foo{#undef}' => 'foo',
476
+ '{#x,hello,y}' => '#1024,Hello%20World!,768',
477
+ '{#path,x}/here' => '#/foo/bar,1024/here',
478
+ '{#path:6}/here' => '#/foo/b/here',
479
+ '{#list}' => '#red,green,blue',
480
+ '{#list*}' => '#red,green,blue',
481
+ '{#keys}' => [
482
+ '#semi,;,dot,.,comma,,',
483
+ '#dot,.,semi,;,comma,,',
484
+ '#comma,,,semi,;,dot,.',
485
+ '#semi,;,comma,,,dot,.',
486
+ '#dot,.,comma,,,semi,;',
487
+ '#comma,,,dot,.,semi,;'
488
+ ],
489
+ '{#keys*}' => [
490
+ '#semi=;,dot=.,comma=,',
491
+ '#dot=.,semi=;,comma=,',
492
+ '#comma=,,semi=;,dot=.',
493
+ '#semi=;,comma=,,dot=.',
494
+ '#dot=.,comma=,,semi=;',
495
+ '#comma=,,dot=.,semi=;'
496
+ ]
497
+ }
498
+ end
499
+ context "label expansion (.)" do
500
+ it_behaves_like 'expands', {
501
+ '{.who}' => '.fred',
502
+ '{.who,who}' => '.fred.fred',
503
+ '{.half,who}' => '.50%25.fred',
504
+ 'www{.dom*}' => 'www.example.com',
505
+ 'X{.var}' => 'X.value',
506
+ 'X{.empty}' => 'X.',
507
+ 'X{.undef}' => 'X',
508
+ 'X{.var:3}' => 'X.val',
509
+ 'X{.list}' => 'X.red,green,blue',
510
+ 'X{.list*}' => 'X.red.green.blue',
511
+ 'X{.keys}' => [
512
+ 'X.semi,%3B,dot,.,comma,%2C',
513
+ 'X.dot,.,semi,%3B,comma,%2C',
514
+ 'X.comma,%2C,semi,%3B,dot,.',
515
+ 'X.semi,%3B,comma,%2C,dot,.',
516
+ 'X.dot,.,comma,%2C,semi,%3B',
517
+ 'X.comma,%2C,dot,.,semi,%3B'
518
+ ],
519
+ 'X{.keys*}' => [
520
+ 'X.semi=%3B.dot=..comma=%2C',
521
+ 'X.dot=..semi=%3B.comma=%2C',
522
+ 'X.comma=%2C.semi=%3B.dot=.',
523
+ 'X.semi=%3B.comma=%2C.dot=.',
524
+ 'X.dot=..comma=%2C.semi=%3B',
525
+ 'X.comma=%2C.dot=..semi=%3B'
526
+ ],
527
+ 'X{.empty_keys}' => 'X',
528
+ 'X{.empty_keys*}' => 'X'
529
+ }
530
+ end
531
+ context "path expansion (/)" do
532
+ it_behaves_like 'expands', {
533
+ '{/who}' => '/fred',
534
+ '{/who,who}' => '/fred/fred',
535
+ '{/half,who}' => '/50%25/fred',
536
+ '{/who,dub}' => '/fred/me%2Ftoo',
537
+ '{/var}' => '/value',
538
+ '{/var,empty}' => '/value/',
539
+ '{/var,undef}' => '/value',
540
+ '{/var,x}/here' => '/value/1024/here',
541
+ '{/var:1,var}' => '/v/value',
542
+ '{/list}' => '/red,green,blue',
543
+ '{/list*}' => '/red/green/blue',
544
+ '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
545
+ '{/keys}' => [
546
+ '/semi,%3B,dot,.,comma,%2C',
547
+ '/dot,.,semi,%3B,comma,%2C',
548
+ '/comma,%2C,semi,%3B,dot,.',
549
+ '/semi,%3B,comma,%2C,dot,.',
550
+ '/dot,.,comma,%2C,semi,%3B',
551
+ '/comma,%2C,dot,.,semi,%3B'
552
+ ],
553
+ '{/keys*}' => [
554
+ '/semi=%3B/dot=./comma=%2C',
555
+ '/dot=./semi=%3B/comma=%2C',
556
+ '/comma=%2C/semi=%3B/dot=.',
557
+ '/semi=%3B/comma=%2C/dot=.',
558
+ '/dot=./comma=%2C/semi=%3B',
559
+ '/comma=%2C/dot=./semi=%3B'
560
+ ]
561
+ }
562
+ end
563
+ context "path-style expansion (;)" do
564
+ it_behaves_like 'expands', {
565
+ '{;who}' => ';who=fred',
566
+ '{;half}' => ';half=50%25',
567
+ '{;empty}' => ';empty',
568
+ '{;v,empty,who}' => ';v=6;empty;who=fred',
569
+ '{;v,bar,who}' => ';v=6;who=fred',
570
+ '{;x,y}' => ';x=1024;y=768',
571
+ '{;x,y,empty}' => ';x=1024;y=768;empty',
572
+ '{;x,y,undef}' => ';x=1024;y=768',
573
+ '{;hello:5}' => ';hello=Hello',
574
+ '{;list}' => ';list=red,green,blue',
575
+ '{;list*}' => ';list=red;list=green;list=blue',
576
+ '{;keys}' => [
577
+ ';keys=semi,%3B,dot,.,comma,%2C',
578
+ ';keys=dot,.,semi,%3B,comma,%2C',
579
+ ';keys=comma,%2C,semi,%3B,dot,.',
580
+ ';keys=semi,%3B,comma,%2C,dot,.',
581
+ ';keys=dot,.,comma,%2C,semi,%3B',
582
+ ';keys=comma,%2C,dot,.,semi,%3B'
583
+ ],
584
+ '{;keys*}' => [
585
+ ';semi=%3B;dot=.;comma=%2C',
586
+ ';dot=.;semi=%3B;comma=%2C',
587
+ ';comma=%2C;semi=%3B;dot=.',
588
+ ';semi=%3B;comma=%2C;dot=.',
589
+ ';dot=.;comma=%2C;semi=%3B',
590
+ ';comma=%2C;dot=.;semi=%3B'
591
+ ]
592
+ }
593
+ end
594
+ context "form query expansion (?)" do
595
+ it_behaves_like 'expands', {
596
+ '{?who}' => '?who=fred',
597
+ '{?half}' => '?half=50%25',
598
+ '{?x,y}' => '?x=1024&y=768',
599
+ '{?x,y,empty}' => '?x=1024&y=768&empty=',
600
+ '{?x,y,undef}' => '?x=1024&y=768',
601
+ '{?var:3}' => '?var=val',
602
+ '{?list}' => '?list=red,green,blue',
603
+ '{?list*}' => '?list=red&list=green&list=blue',
604
+ '{?keys}' => [
605
+ '?keys=semi,%3B,dot,.,comma,%2C',
606
+ '?keys=dot,.,semi,%3B,comma,%2C',
607
+ '?keys=comma,%2C,semi,%3B,dot,.',
608
+ '?keys=semi,%3B,comma,%2C,dot,.',
609
+ '?keys=dot,.,comma,%2C,semi,%3B',
610
+ '?keys=comma,%2C,dot,.,semi,%3B'
611
+ ],
612
+ '{?keys*}' => [
613
+ '?semi=%3B&dot=.&comma=%2C',
614
+ '?dot=.&semi=%3B&comma=%2C',
615
+ '?comma=%2C&semi=%3B&dot=.',
616
+ '?semi=%3B&comma=%2C&dot=.',
617
+ '?dot=.&comma=%2C&semi=%3B',
618
+ '?comma=%2C&dot=.&semi=%3B'
619
+ ]
620
+ }
621
+ end
622
+ context "form query expansion (&)" do
623
+ it_behaves_like 'expands', {
624
+ '{&who}' => '&who=fred',
625
+ '{&half}' => '&half=50%25',
626
+ '?fixed=yes{&x}' => '?fixed=yes&x=1024',
627
+ '{&x,y,empty}' => '&x=1024&y=768&empty=',
628
+ '{&x,y,undef}' => '&x=1024&y=768',
629
+ '{&var:3}' => '&var=val',
630
+ '{&list}' => '&list=red,green,blue',
631
+ '{&list*}' => '&list=red&list=green&list=blue',
632
+ '{&keys}' => [
633
+ '&keys=semi,%3B,dot,.,comma,%2C',
634
+ '&keys=dot,.,semi,%3B,comma,%2C',
635
+ '&keys=comma,%2C,semi,%3B,dot,.',
636
+ '&keys=semi,%3B,comma,%2C,dot,.',
637
+ '&keys=dot,.,comma,%2C,semi,%3B',
638
+ '&keys=comma,%2C,dot,.,semi,%3B'
639
+ ],
640
+ '{&keys*}' => [
641
+ '&semi=%3B&dot=.&comma=%2C',
642
+ '&dot=.&semi=%3B&comma=%2C',
643
+ '&comma=%2C&semi=%3B&dot=.',
644
+ '&semi=%3B&comma=%2C&dot=.',
645
+ '&dot=.&comma=%2C&semi=%3B',
646
+ '&comma=%2C&dot=.&semi=%3B'
647
+ ]
648
+ }
649
+ end
650
+ end
651
+
652
+ class ExampleTwoProcessor
653
+ def self.restore(name, value)
654
+ return value.gsub(/-/, " ") if name == "query"
655
+ return value
656
+ end
657
+
658
+ def self.match(name)
659
+ return ".*?" if name == "first"
660
+ return ".*"
661
+ end
662
+ def self.validate(name, value)
663
+ return !!(value =~ /^[\w ]+$/) if name == "query"
664
+ return true
665
+ end
666
+
667
+ def self.transform(name, value)
668
+ return value.gsub(/ /, "+") if name == "query"
669
+ return value
670
+ end
671
+ end
672
+
673
+
674
+ describe Addressable::Template do
675
+ describe "Matching" do
676
+ let(:uri){
677
+ Addressable::URI.parse(
678
+ "http://example.com/search/an-example-search-query/"
679
+ )
680
+ }
681
+ let(:uri2){
682
+ Addressable::URI.parse("http://example.com/a/b/c/")
683
+ }
684
+ let(:uri3){
685
+ Addressable::URI.parse("http://example.com/;a=1;b=2;c=3;first=foo")
686
+ }
687
+ let(:uri4){
688
+ Addressable::URI.parse("http://example.com/?a=1&b=2&c=3&first=foo")
689
+ }
690
+ context "first uri with ExampleTwoProcessor" do
691
+ subject{
692
+ match = Addressable::Template.new(
693
+ "http://example.com/search/{query}/"
694
+ ).match(uri, ExampleTwoProcessor)
695
+ }
696
+ its(:variables){ should == ["query"]}
697
+ its(:captures){ should == ["an example search query"]}
698
+ end
699
+
700
+ context "second uri with ExampleTwoProcessor" do
701
+ subject{
702
+ match = Addressable::Template.new(
703
+ "http://example.com/{first}/{+second}/"
704
+ ).match(uri2, ExampleTwoProcessor)
705
+ }
706
+ its(:variables){ should == ["first", "second"]}
707
+ its(:captures){ should == ["a", "b/c"] }
708
+ end
709
+ context "second uri" do
710
+ subject{
711
+ match = Addressable::Template.new(
712
+ "http://example.com/{first}{/second*}/"
713
+ ).match(uri2)
714
+ }
715
+ its(:variables){ should == ["first", "second"]}
716
+ its(:captures){ should == ["a", ["b","c"]] }
717
+ end
718
+ context "third uri" do
719
+ subject{
720
+ match = Addressable::Template.new(
721
+ "http://example.com/{;hash*,first}"
722
+ ).match(uri3)
723
+ }
724
+ its(:variables){ should == ["hash", "first"]}
725
+ its(:captures){ should == [
726
+ {"a" => "1", "b" => "2", "c" => "3", "first" => "foo"}, nil] }
727
+ end
728
+ context "fourth uri" do
729
+ subject{
730
+ match = Addressable::Template.new(
731
+ "http://example.com/{?hash*,first}"
732
+ ).match(uri4)
733
+ }
734
+ its(:variables){ should == ["hash", "first"]}
735
+ its(:captures){ should == [
736
+ {"a" => "1", "b" => "2", "c" => "3", "first"=> "foo"}, nil] }
737
+ end
738
+ end
739
+ describe "extract" do
740
+ let(:template) {
741
+ Addressable::Template.new(
742
+ "http://{host}{/segments*}/{?one,two,bogus}{#fragment}"
743
+ )
744
+ }
745
+ let(:uri){ "http://example.com/a/b/c/?one=1&two=2#foo" }
746
+ it "should be able to extract" do
747
+ template.extract(uri).should == {
748
+ "host" => "example.com",
749
+ "segments" => %w(a b c),
750
+ "one" => "1",
751
+ "bogus" => nil,
752
+ "two" => "2",
753
+ "fragment" => "foo"
754
+ }
755
+ end
756
+ end
757
+ describe "Partial expand with symbols" do
758
+ context "partial_expand with two simple values" do
759
+ subject{
760
+ Addressable::Template.new("http://example.com/{one}/{two}/")
761
+ }
762
+ it "builds a new pattern" do
763
+ subject.partial_expand(:one => "1").pattern.should ==
764
+ "http://example.com/1/{two}/"
765
+ end
766
+ end
767
+ context "partial_expand query with missing param in middle" do
768
+ subject{
769
+ Addressable::Template.new("http://example.com/{?one,two,three}/")
770
+ }
771
+ it "builds a new pattern" do
772
+ subject.partial_expand(:one => "1", :three => "3").pattern.should ==
773
+ "http://example.com/?one=1{&two}&three=3/"
774
+ end
775
+ end
776
+ context "partial_expand with query string" do
777
+ subject{
778
+ Addressable::Template.new("http://example.com/{?two,one}/")
779
+ }
780
+ it "builds a new pattern" do
781
+ subject.partial_expand(:one => "1").pattern.should ==
782
+ "http://example.com/{?two}&one=1/"
783
+ end
784
+ end
785
+ context "partial_expand with path operator" do
786
+ subject{
787
+ Addressable::Template.new("http://example.com{/one,two}/")
788
+ }
789
+ it "builds a new pattern" do
790
+ subject.partial_expand(:one => "1").pattern.should ==
791
+ "http://example.com/1{/two}/"
792
+ end
793
+ end
794
+ end
795
+ describe "Partial expand with strings" do
796
+ context "partial_expand with two simple values" do
797
+ subject{
798
+ Addressable::Template.new("http://example.com/{one}/{two}/")
799
+ }
800
+ it "builds a new pattern" do
801
+ subject.partial_expand("one" => "1").pattern.should ==
802
+ "http://example.com/1/{two}/"
803
+ end
804
+ end
805
+ context "partial_expand query with missing param in middle" do
806
+ subject{
807
+ Addressable::Template.new("http://example.com/{?one,two,three}/")
808
+ }
809
+ it "builds a new pattern" do
810
+ subject.partial_expand("one" => "1", "three" => "3").pattern.should ==
811
+ "http://example.com/?one=1{&two}&three=3/"
812
+ end
813
+ end
814
+ context "partial_expand with query string" do
815
+ subject{
816
+ Addressable::Template.new("http://example.com/{?two,one}/")
817
+ }
818
+ it "builds a new pattern" do
819
+ subject.partial_expand("one" => "1").pattern.should ==
820
+ "http://example.com/{?two}&one=1/"
821
+ end
822
+ end
823
+ context "partial_expand with path operator" do
824
+ subject{
825
+ Addressable::Template.new("http://example.com{/one,two}/")
826
+ }
827
+ it "builds a new pattern" do
828
+ subject.partial_expand("one" => "1").pattern.should ==
829
+ "http://example.com/1{/two}/"
830
+ end
831
+ end
832
+ end
833
+ describe "Expand" do
834
+ context "expand with a processor" do
835
+ subject{
836
+ Addressable::Template.new("http://example.com/search/{query}/")
837
+ }
838
+ it "processes spaces" do
839
+ subject.expand({"query" => "an example search query"},
840
+ ExampleTwoProcessor).to_str.should ==
841
+ "http://example.com/search/an+example+search+query/"
842
+ end
843
+ it "validates" do
844
+ lambda{
845
+ subject.expand({"query" => "Bogus!"},
846
+ ExampleTwoProcessor).to_str
847
+ }.should raise_error(Addressable::Template::InvalidTemplateValueError)
848
+ end
849
+ end
850
+ context "partial_expand query with missing param in middle" do
851
+ subject{
852
+ Addressable::Template.new("http://example.com/{?one,two,three}/")
853
+ }
854
+ it "builds a new pattern" do
855
+ subject.partial_expand("one" => "1", "three" => "3").pattern.should ==
856
+ "http://example.com/?one=1{&two}&three=3/"
857
+ end
858
+ end
859
+ context "partial_expand with query string" do
860
+ subject{
861
+ Addressable::Template.new("http://example.com/{?two,one}/")
862
+ }
863
+ it "builds a new pattern" do
864
+ subject.partial_expand("one" => "1").pattern.should ==
865
+ "http://example.com/{?two}&one=1/"
866
+ end
867
+ end
868
+ context "partial_expand with path operator" do
869
+ subject{
870
+ Addressable::Template.new("http://example.com{/one,two}/")
871
+ }
872
+ it "builds a new pattern" do
873
+ subject.partial_expand("one" => "1").pattern.should ==
874
+ "http://example.com/1{/two}/"
875
+ end
876
+ end
877
+ end
878
+ context "Matching with operators" do
879
+ describe "Level 1:" do
880
+ subject { Addressable::Template.new("foo{foo}/{bar}baz") }
881
+ it "can match" do
882
+ data = subject.match("foofoo/bananabaz")
883
+ data.mapping["foo"].should == "foo"
884
+ data.mapping["bar"].should == "banana"
885
+ end
886
+ it "can fail" do
887
+ subject.match("bar/foo").should be_nil
888
+ subject.match("foobaz").should be_nil
889
+ end
890
+ it "can match empty" do
891
+ data = subject.match("foo/baz")
892
+ data.mapping["foo"].should == ""
893
+ data.mapping["bar"].should == ""
894
+ end
895
+ it "lists vars" do
896
+ subject.variables.should == ["foo", "bar"]
897
+ end
898
+ end
899
+
900
+ describe "Level 2:" do
901
+ subject { Addressable::Template.new("foo{+foo}{#bar}baz") }
902
+ it "can match" do
903
+ data = subject.match("foo/test/banana#bazbaz")
904
+ data.mapping["foo"].should == "/test/banana"
905
+ data.mapping["bar"].should == "baz"
906
+ end
907
+ it "lists vars" do
908
+ subject.variables.should == ["foo", "bar"]
909
+ end
910
+ end
911
+
912
+ describe "Level 3:" do
913
+ context "no operator" do
914
+ subject { Addressable::Template.new("foo{foo,bar}baz") }
915
+ it "can match" do
916
+ data = subject.match("foofoo,barbaz")
917
+ data.mapping["foo"].should == "foo"
918
+ data.mapping["bar"].should == "bar"
919
+ end
920
+ it "lists vars" do
921
+ subject.variables.should == ["foo", "bar"]
922
+ end
923
+ end
924
+ context "+ operator" do
925
+ subject { Addressable::Template.new("foo{+foo,bar}baz") }
926
+ it "can match" do
927
+ data = subject.match("foofoo/bar,barbaz")
928
+ data.mapping["bar"].should == "foo/bar,bar"
929
+ data.mapping["foo"].should == ""
930
+ end
931
+ it "lists vars" do
932
+ subject.variables.should == ["foo", "bar"]
933
+ end
934
+ end
935
+ context ". operator" do
936
+ subject { Addressable::Template.new("foo{.foo,bar}baz") }
937
+ it "can match" do
938
+ data = subject.match("foo.foo.barbaz")
939
+ data.mapping["foo"].should == "foo"
940
+ data.mapping["bar"].should == "bar"
941
+ end
942
+ it "lists vars" do
943
+ subject.variables.should == ["foo", "bar"]
944
+ end
945
+ end
946
+ context "/ operator" do
947
+ subject { Addressable::Template.new("foo{/foo,bar}baz") }
948
+ it "can match" do
949
+ data = subject.match("foo/foo/barbaz")
950
+ data.mapping["foo"].should == "foo"
951
+ data.mapping["bar"].should == "bar"
952
+ end
953
+ it "lists vars" do
954
+ subject.variables.should == ["foo", "bar"]
955
+ end
956
+ end
957
+ context "; operator" do
958
+ subject { Addressable::Template.new("foo{;foo,bar,baz}baz") }
959
+ it "can match" do
960
+ data = subject.match("foo;foo=bar%20baz;bar=foo;bazbaz")
961
+ data.mapping["foo"].should == "bar baz"
962
+ data.mapping["bar"].should == "foo"
963
+ data.mapping["baz"].should == ""
964
+ end
965
+ it "lists vars" do
966
+ subject.variables.should == %w(foo bar baz)
967
+ end
968
+ end
969
+ context "? operator" do
970
+ context "test" do
971
+ subject { Addressable::Template.new("foo{?foo,bar}baz") }
972
+ it "can match" do
973
+ data = subject.match("foo?foo=bar%20baz&bar=foobaz")
974
+ data.mapping["foo"].should == "bar baz"
975
+ data.mapping["bar"].should == "foo"
976
+ end
977
+ it "lists vars" do
978
+ subject.variables.should == %w(foo bar)
979
+ end
980
+ end
981
+ context "issue #71" do
982
+ subject { Addressable::Template.new("http://cyberscore.dev/api/users{?username}") }
983
+ it "can match" do
984
+ data = subject.match("http://cyberscore.dev/api/users?username=foobaz")
985
+ data.mapping["username"].should == "foobaz"
986
+ end
987
+ it "lists vars" do
988
+ subject.variables.should == %w(username)
989
+ subject.keys.should == %w(username)
990
+ end
991
+ end
992
+ end
993
+ context "& operator" do
994
+ subject { Addressable::Template.new("foo{&foo,bar}baz") }
995
+ it "can match" do
996
+ data = subject.match("foo&foo=bar%20baz&bar=foobaz")
997
+ data.mapping["foo"].should == "bar baz"
998
+ data.mapping["bar"].should == "foo"
999
+ end
1000
+ it "lists vars" do
1001
+ subject.variables.should == %w(foo bar)
1002
+ end
1003
+ end
1004
+ end
1005
+ end
1006
+
1007
+ context "support regexes:" do
1008
+ context "EXPRESSION" do
1009
+ subject { Addressable::Template::EXPRESSION }
1010
+ it "should be able to match an expression" do
1011
+ subject.should match("{foo}")
1012
+ subject.should match("{foo,9}")
1013
+ subject.should match("{foo.bar,baz}")
1014
+ subject.should match("{+foo.bar,baz}")
1015
+ subject.should match("{foo,foo%20bar}")
1016
+ subject.should match("{#foo:20,baz*}")
1017
+ subject.should match("stuff{#foo:20,baz*}things")
1018
+ end
1019
+ it "should fail on non vars" do
1020
+ subject.should_not match("!{foo")
1021
+ subject.should_not match("{foo.bar.}")
1022
+ subject.should_not match("!{}")
1023
+ end
1024
+ end
1025
+ context "VARNAME" do
1026
+ subject { Addressable::Template::VARNAME }
1027
+ it "should be able to match a variable" do
1028
+ subject.should match("foo")
1029
+ subject.should match("9")
1030
+ subject.should match("foo.bar")
1031
+ subject.should match("foo_bar")
1032
+ subject.should match("foo_bar.baz")
1033
+ subject.should match("foo%20bar")
1034
+ subject.should match("foo%20bar.baz")
1035
+ end
1036
+ it "should fail on non vars" do
1037
+ subject.should_not match("!foo")
1038
+ subject.should_not match("foo.bar.")
1039
+ subject.should_not match("foo%2%00bar")
1040
+ subject.should_not match("foo_ba%r")
1041
+ subject.should_not match("foo_bar*")
1042
+ subject.should_not match("foo_bar:20")
1043
+ end
1044
+ end
1045
+ context "VARIABLE_LIST" do
1046
+ subject { Addressable::Template::VARIABLE_LIST }
1047
+ it "should be able to match a variable list" do
1048
+ subject.should match("foo,bar")
1049
+ subject.should match("foo")
1050
+ subject.should match("foo,bar*,baz")
1051
+ subject.should match("foo.bar,bar_baz*,baz:12")
1052
+ end
1053
+ it "should fail on non vars" do
1054
+ subject.should_not match(",foo,bar*,baz")
1055
+ subject.should_not match("foo,*bar,baz")
1056
+ subject.should_not match("foo,,bar*,baz")
1057
+ end
1058
+ end
1059
+ context "VARSPEC" do
1060
+ subject { Addressable::Template::VARSPEC }
1061
+ it "should be able to match a variable with modifier" do
1062
+ subject.should match("9:8")
1063
+ subject.should match("foo.bar*")
1064
+ subject.should match("foo_bar:12")
1065
+ subject.should match("foo_bar.baz*")
1066
+ subject.should match("foo%20bar:12")
1067
+ subject.should match("foo%20bar.baz*")
1068
+ end
1069
+ it "should fail on non vars" do
1070
+ subject.should_not match("!foo")
1071
+ subject.should_not match("*foo")
1072
+ subject.should_not match("fo*o")
1073
+ subject.should_not match("fo:o")
1074
+ subject.should_not match("foo:")
1075
+ end
1076
+ end
1077
+ end
1078
+ end
1079
+
1080
+ describe Addressable::Template::MatchData do
1081
+ let(:template) { Addressable::Template.new('{foo}/{bar}') }
1082
+ subject(:its) { template.match('ab/cd') }
1083
+ its(:uri) { should == Addressable::URI.parse('ab/cd') }
1084
+ its(:template) { should == template }
1085
+ its(:mapping) { should == { 'foo' => 'ab', 'bar' => 'cd' } }
1086
+ its(:variables) { should == ['foo', 'bar'] }
1087
+ its(:keys) { should == its.variables }
1088
+ its(:names) { should == its.variables }
1089
+ its(:values) { should == ['ab', 'cd'] }
1090
+ its(:captures) { should == its.values }
1091
+ its(:to_a) { should == ['ab/cd', 'ab', 'cd'] }
1092
+ its(:to_s) { should == 'ab/cd' }
1093
+ its(:string) { should == its.to_s }
1094
+ its(:pre_match) { should == "" }
1095
+ its(:post_match) { should == "" }
1096
+
1097
+ describe 'values_at' do
1098
+ it 'returns an array with the values' do
1099
+ its.values_at(0, 2).should == ['ab/cd', 'cd']
1100
+ end
1101
+ it 'allows mixing integer an string keys' do
1102
+ its.values_at('foo', 1).should == ['ab', 'ab']
1103
+ end
1104
+ it 'accepts unknown keys' do
1105
+ its.values_at('baz', 'foo').should == [nil, 'ab']
1106
+ end
1107
+ end
1108
+
1109
+ describe '[]' do
1110
+ context 'string key' do
1111
+ it 'returns the corresponding capture' do
1112
+ its['foo'].should == 'ab'
1113
+ its['bar'].should == 'cd'
1114
+ end
1115
+ it 'returns nil for unknown keys' do
1116
+ its['baz'].should be_nil
1117
+ end
1118
+ end
1119
+ context 'symbol key' do
1120
+ it 'returns the corresponding capture' do
1121
+ its[:foo].should == 'ab'
1122
+ its[:bar].should == 'cd'
1123
+ end
1124
+ it 'returns nil for unknown keys' do
1125
+ its[:baz].should be_nil
1126
+ end
1127
+ end
1128
+ context 'integer key' do
1129
+ it 'returns the full URI for index 0' do
1130
+ its[0].should == 'ab/cd'
1131
+ end
1132
+ it 'returns the corresponding capture' do
1133
+ its[1].should == 'ab'
1134
+ its[2].should == 'cd'
1135
+ end
1136
+ it 'returns nil for unknown keys' do
1137
+ its[3].should be_nil
1138
+ end
1139
+ end
1140
+ context 'other key' do
1141
+ it 'raises an exception' do
1142
+ expect { its[Object.new] }.to raise_error(TypeError)
1143
+ end
1144
+ end
1145
+ context 'with length' do
1146
+ it 'returns an array starting at index with given length' do
1147
+ its[0, 2].should == ['ab/cd', 'ab']
1148
+ its[2, 1].should == ['cd']
1149
+ end
1150
+ end
1151
+ end
1152
+ end