uri_template 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +53 -0
- data/README.md +56 -0
- data/lib/uri_template/colon.rb +88 -29
- data/lib/uri_template/expression.rb +33 -0
- data/lib/uri_template/literal.rb +53 -0
- data/lib/uri_template/rfc6570/expression/named.rb +72 -0
- data/lib/uri_template/rfc6570/expression/unnamed.rb +76 -0
- data/lib/uri_template/rfc6570/expression.rb +374 -0
- data/lib/uri_template/rfc6570/regex_builder.rb +117 -0
- data/lib/uri_template/rfc6570.rb +18 -536
- data/lib/uri_template/token.rb +64 -0
- data/lib/uri_template/utils.rb +47 -10
- data/lib/uri_template.rb +189 -88
- data/uri_template.gemspec +4 -4
- metadata +14 -10
- data/CHANGELOG +0 -46
- data/README +0 -67
- data/lib/uri_template/draft7.rb +0 -266
data/lib/uri_template/rfc6570.rb
CHANGED
@@ -35,7 +35,7 @@ class URITemplate::RFC6570
|
|
35
35
|
# @private
|
36
36
|
Utils = URITemplate::Utils
|
37
37
|
|
38
|
-
if
|
38
|
+
if Utils.use_unicode?
|
39
39
|
# @private
|
40
40
|
# \/ - unicode ctrl-chars
|
41
41
|
LITERAL = /([^"'%<>\\^`{|}\u0000-\u001F\u007F-\u009F\s]|%[0-9a-fA-F]{2})+/u
|
@@ -48,16 +48,20 @@ class URITemplate::RFC6570
|
|
48
48
|
CHARACTER_CLASSES = {
|
49
49
|
|
50
50
|
:unreserved => {
|
51
|
-
:class => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})',
|
51
|
+
:class => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})',
|
52
|
+
:class_with_comma => '(?:[A-Za-z0-9\-\._,]|%[0-9a-fA-F]{2})',
|
53
|
+
:class_without_comma => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})',
|
52
54
|
:grabs_comma => false
|
53
55
|
},
|
54
56
|
:unreserved_reserved_pct => {
|
55
57
|
:class => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%[0-9a-fA-F]{2})',
|
58
|
+
:class_with_comma => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%[0-9a-fA-F]{2})',
|
59
|
+
:class_without_comma => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+;=]|%[0-9a-fA-F]{2})',
|
56
60
|
:grabs_comma => true
|
57
61
|
},
|
58
62
|
|
59
63
|
:varname => {
|
60
|
-
:class => '(?:[
|
64
|
+
:class => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})+',
|
61
65
|
:class_name => 'c_vn_'
|
62
66
|
}
|
63
67
|
|
@@ -94,8 +98,6 @@ __REGEXP__
|
|
94
98
|
\\A(#{LITERAL.source}|#{EXPRESSION.source})*\\z
|
95
99
|
__REGEXP__
|
96
100
|
|
97
|
-
SLASH = ?/
|
98
|
-
|
99
101
|
# @private
|
100
102
|
class Token
|
101
103
|
end
|
@@ -127,424 +129,6 @@ __REGEXP__
|
|
127
129
|
|
128
130
|
end
|
129
131
|
|
130
|
-
|
131
|
-
# @private
|
132
|
-
class Expression < Token
|
133
|
-
|
134
|
-
include URITemplate::Expression
|
135
|
-
|
136
|
-
attr_reader :variables, :max_length
|
137
|
-
|
138
|
-
def initialize(vars)
|
139
|
-
@variable_specs = vars
|
140
|
-
@variables = vars.map(&:first)
|
141
|
-
@variables.uniq!
|
142
|
-
end
|
143
|
-
|
144
|
-
PREFIX = ''.freeze
|
145
|
-
SEPARATOR = ','.freeze
|
146
|
-
PAIR_CONNECTOR = '='.freeze
|
147
|
-
PAIR_IF_EMPTY = true
|
148
|
-
LIST_CONNECTOR = ','.freeze
|
149
|
-
BASE_LEVEL = 1
|
150
|
-
|
151
|
-
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved]
|
152
|
-
|
153
|
-
NAMED = false
|
154
|
-
OPERATOR = ''
|
155
|
-
|
156
|
-
def level
|
157
|
-
if @variable_specs.none?{|_,expand,ml| expand || (ml > 0) }
|
158
|
-
if @variable_specs.size == 1
|
159
|
-
return self.class::BASE_LEVEL
|
160
|
-
else
|
161
|
-
return 3
|
162
|
-
end
|
163
|
-
else
|
164
|
-
return 4
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def expands?
|
169
|
-
@variable_specs.any?{|_,expand,_| expand }
|
170
|
-
end
|
171
|
-
|
172
|
-
def arity
|
173
|
-
@variable_specs.size
|
174
|
-
end
|
175
|
-
|
176
|
-
def expand( vars )
|
177
|
-
result = []
|
178
|
-
@variable_specs.each{| var, expand , max_length |
|
179
|
-
unless vars[var].nil?
|
180
|
-
if vars[var].kind_of?(Hash) or Utils.pair_array?(vars[var])
|
181
|
-
if max_length && max_length > 0
|
182
|
-
raise InvalidValue::LengthLimitInapplicable.new(var,vars[var])
|
183
|
-
end
|
184
|
-
result.push( *transform_hash(var, vars[var], expand, max_length) )
|
185
|
-
elsif vars[var].kind_of? Array
|
186
|
-
if max_length && max_length > 0
|
187
|
-
raise InvalidValue::LengthLimitInapplicable.new(var,vars[var])
|
188
|
-
end
|
189
|
-
result.push( *transform_array(var, vars[var], expand, max_length) )
|
190
|
-
else
|
191
|
-
if self.class::NAMED
|
192
|
-
result.push( pair(var, vars[var], max_length) )
|
193
|
-
else
|
194
|
-
result.push( cut( escape(vars[var]), max_length ) )
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
}
|
199
|
-
if result.any?
|
200
|
-
return (self.class::PREFIX + result.join(self.class::SEPARATOR))
|
201
|
-
else
|
202
|
-
return ''
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def to_s
|
207
|
-
return '{' + self.class::OPERATOR + @variable_specs.map{|name,expand,max_length| name + (expand ? '*': '') + (max_length > 0 ? (':' + max_length.to_s) : '') }.join(',') + '}'
|
208
|
-
end
|
209
|
-
|
210
|
-
#TODO: certain things after a slurpy variable will never get matched. therefore, it's pointless to add expressions for them
|
211
|
-
#TODO: variables, which appear twice could be compacted, don't they?
|
212
|
-
def to_r_source
|
213
|
-
source = []
|
214
|
-
first = true
|
215
|
-
vs = @variable_specs.size - 1
|
216
|
-
i = 0
|
217
|
-
if self.class::NAMED
|
218
|
-
@variable_specs.each{| var, expand , max_length |
|
219
|
-
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*'}"
|
220
|
-
if expand
|
221
|
-
#if self.class::PAIR_IF_EMPTY
|
222
|
-
pair = "#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value}"
|
223
|
-
|
224
|
-
if first
|
225
|
-
source << "((?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
226
|
-
else
|
227
|
-
source << "((?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
228
|
-
end
|
229
|
-
else
|
230
|
-
if self.class::PAIR_IF_EMPTY
|
231
|
-
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})"
|
232
|
-
else
|
233
|
-
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value}|)"
|
234
|
-
end
|
235
|
-
|
236
|
-
if first
|
237
|
-
source << "(?:#{pair})"
|
238
|
-
else
|
239
|
-
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})?"
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
first = false
|
244
|
-
i = i+1
|
245
|
-
}
|
246
|
-
else
|
247
|
-
@variable_specs.each{| var, expand , max_length |
|
248
|
-
last = (vs == i)
|
249
|
-
if expand
|
250
|
-
# could be list or map, too
|
251
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*'}"
|
252
|
-
|
253
|
-
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
254
|
-
|
255
|
-
value = "#{pair}(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*"
|
256
|
-
elsif last
|
257
|
-
# the last will slurp lists
|
258
|
-
if self.class::CHARACTER_CLASS[:grabs_comma]
|
259
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
260
|
-
else
|
261
|
-
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
262
|
-
end
|
263
|
-
else
|
264
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
265
|
-
end
|
266
|
-
if first
|
267
|
-
source << "(#{value})"
|
268
|
-
first = false
|
269
|
-
else
|
270
|
-
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}(#{value}))?"
|
271
|
-
end
|
272
|
-
i = i+1
|
273
|
-
}
|
274
|
-
end
|
275
|
-
return '(?:' + Regexp.escape(self.class::PREFIX) + source.join + ')?'
|
276
|
-
end
|
277
|
-
|
278
|
-
def extract(position,matched)
|
279
|
-
name, expand, max_length = @variable_specs[position]
|
280
|
-
if matched.nil?
|
281
|
-
return [[ name , matched ]]
|
282
|
-
end
|
283
|
-
if expand
|
284
|
-
#TODO: do we really need this? - this could be stolen from rack
|
285
|
-
ex = self.class.hash_extractor(max_length)
|
286
|
-
rest = matched
|
287
|
-
splitted = []
|
288
|
-
if self.class::NAMED
|
289
|
-
# 1 = name
|
290
|
-
# 2 = value
|
291
|
-
# 3 = rest
|
292
|
-
until rest.size == 0
|
293
|
-
match = ex.match(rest)
|
294
|
-
if match.nil?
|
295
|
-
raise "Couldn't match #{rest.inspect} againts the hash extractor. This is definitly a Bug. Please report this ASAP!"
|
296
|
-
end
|
297
|
-
if match.post_match.size == 0
|
298
|
-
rest = match[3].to_s
|
299
|
-
else
|
300
|
-
rest = ''
|
301
|
-
end
|
302
|
-
splitted << [ match[1], decode(match[2] + rest , false) ]
|
303
|
-
rest = match.post_match
|
304
|
-
end
|
305
|
-
result = Utils.pair_array_to_hash2( splitted )
|
306
|
-
if result.size == 1 && result[0][0] == name
|
307
|
-
return result
|
308
|
-
else
|
309
|
-
return [ [ name , result ] ]
|
310
|
-
end
|
311
|
-
else
|
312
|
-
found_value = false
|
313
|
-
# 1 = name and seperator
|
314
|
-
# 2 = value
|
315
|
-
# 3 = rest
|
316
|
-
until rest.size == 0
|
317
|
-
match = ex.match(rest)
|
318
|
-
if match.nil?
|
319
|
-
raise "Couldn't match #{rest.inspect} againts the hash extractor. This is definitly a Bug. Please report this ASAP!"
|
320
|
-
end
|
321
|
-
if match.post_match.size == 0
|
322
|
-
rest = match[3].to_s
|
323
|
-
else
|
324
|
-
rest = ''
|
325
|
-
end
|
326
|
-
if match[1]
|
327
|
-
found_value = true
|
328
|
-
splitted << [ match[1][0..-2], decode(match[2] + rest , false) ]
|
329
|
-
else
|
330
|
-
splitted << [ match[2] + rest, nil ]
|
331
|
-
end
|
332
|
-
rest = match.post_match
|
333
|
-
end
|
334
|
-
if !found_value
|
335
|
-
return [ [ name, splitted.map{|n,v| decode(n , false) } ] ]
|
336
|
-
else
|
337
|
-
return [ [ name, splitted ] ]
|
338
|
-
end
|
339
|
-
end
|
340
|
-
elsif self.class::NAMED
|
341
|
-
return [ [ name, decode( matched[1..-1] ) ] ]
|
342
|
-
end
|
343
|
-
|
344
|
-
return [ [ name, decode( matched ) ] ]
|
345
|
-
end
|
346
|
-
|
347
|
-
protected
|
348
|
-
|
349
|
-
module ClassMethods
|
350
|
-
|
351
|
-
def hash_extractor(max_length)
|
352
|
-
@hash_extractors ||= {}
|
353
|
-
@hash_extractors[max_length] ||= begin
|
354
|
-
value = "#{self::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
355
|
-
if self::NAMED
|
356
|
-
pair = "(#{CHARACTER_CLASSES[:varname][:class]})#{Regexp.escape(self::PAIR_CONNECTOR)}(#{value})"
|
357
|
-
else
|
358
|
-
pair = "(#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self::PAIR_CONNECTOR)})?(#{value})"
|
359
|
-
end
|
360
|
-
source = "\\A#{Regexp.escape(self::SEPARATOR)}?" + pair + "(\\z|#{Regexp.escape(self::SEPARATOR)}(?!#{Regexp.escape(self::SEPARATOR)}))"
|
361
|
-
Regexp.new( source , Utils::KCODE_UTF8)
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
end
|
366
|
-
|
367
|
-
extend ClassMethods
|
368
|
-
|
369
|
-
def escape(x)
|
370
|
-
Utils.escape_url(Utils.object_to_param(x))
|
371
|
-
end
|
372
|
-
|
373
|
-
def unescape(x)
|
374
|
-
Utils.unescape_url(x)
|
375
|
-
end
|
376
|
-
|
377
|
-
SPLITTER = /^(?:,(,*)|([^,]+))/
|
378
|
-
|
379
|
-
def decode(x, split = true)
|
380
|
-
if x.nil?
|
381
|
-
if self.class::PAIR_IF_EMPTY
|
382
|
-
return x
|
383
|
-
else
|
384
|
-
return ''
|
385
|
-
end
|
386
|
-
elsif split
|
387
|
-
r = []
|
388
|
-
v = x
|
389
|
-
until v.size == 0
|
390
|
-
m = SPLITTER.match(v)
|
391
|
-
if m[1] and m[1].size > 0
|
392
|
-
if m.post_match.size == 0
|
393
|
-
r << m[1]
|
394
|
-
else
|
395
|
-
r << m[1][0..-2]
|
396
|
-
end
|
397
|
-
elsif m[2]
|
398
|
-
r << unescape(m[2])
|
399
|
-
end
|
400
|
-
v = m.post_match
|
401
|
-
end
|
402
|
-
case(r.size)
|
403
|
-
when 0 then ''
|
404
|
-
when 1 then r.first
|
405
|
-
else r
|
406
|
-
end
|
407
|
-
else
|
408
|
-
unescape(x)
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
def cut(str,chars)
|
413
|
-
if chars > 0
|
414
|
-
md = Regexp.compile("\\A#{self.class::CHARACTER_CLASS[:class]}{0,#{chars.to_s}}", Utils::KCODE_UTF8).match(str)
|
415
|
-
#TODO: handle invalid matches
|
416
|
-
return md[0]
|
417
|
-
else
|
418
|
-
return str
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
def pair(key, value, max_length = 0)
|
423
|
-
ek = escape(key)
|
424
|
-
ev = escape(value)
|
425
|
-
if !self.class::PAIR_IF_EMPTY and ev.size == 0
|
426
|
-
return ek
|
427
|
-
else
|
428
|
-
return ek + self.class::PAIR_CONNECTOR + cut( ev, max_length )
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
def transform_hash(name, hsh, expand , max_length)
|
433
|
-
if expand
|
434
|
-
hsh.map{|key,value| pair(key,value) }
|
435
|
-
elsif hsh.none?
|
436
|
-
[]
|
437
|
-
else
|
438
|
-
[ (self.class::NAMED ? escape(name)+self.class::PAIR_CONNECTOR : '' ) + hsh.map{|key,value| escape(key)+self.class::LIST_CONNECTOR+escape(value) }.join(self.class::LIST_CONNECTOR) ]
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
def transform_array(name, ary, expand , max_length)
|
443
|
-
if expand
|
444
|
-
self.class::NAMED ? ary.map{|value| pair(name,value) } : ary.map{|value| escape(value) }
|
445
|
-
elsif ary.none?
|
446
|
-
[]
|
447
|
-
else
|
448
|
-
[ (self.class::NAMED ? escape(name)+self.class::PAIR_CONNECTOR : '' ) + ary.map{|value| escape(value) }.join(self.class::LIST_CONNECTOR) ]
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
class Reserved < self
|
453
|
-
|
454
|
-
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
|
455
|
-
OPERATOR = '+'.freeze
|
456
|
-
BASE_LEVEL = 2
|
457
|
-
|
458
|
-
def escape(x)
|
459
|
-
Utils.escape_uri(Utils.object_to_param(x))
|
460
|
-
end
|
461
|
-
|
462
|
-
def unescape(x)
|
463
|
-
Utils.unescape_uri(x)
|
464
|
-
end
|
465
|
-
|
466
|
-
end
|
467
|
-
|
468
|
-
class Fragment < self
|
469
|
-
|
470
|
-
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
|
471
|
-
PREFIX = '#'.freeze
|
472
|
-
OPERATOR = '#'.freeze
|
473
|
-
BASE_LEVEL = 2
|
474
|
-
|
475
|
-
def escape(x)
|
476
|
-
Utils.escape_uri(Utils.object_to_param(x))
|
477
|
-
end
|
478
|
-
|
479
|
-
def unescape(x)
|
480
|
-
Utils.unescape_uri(x)
|
481
|
-
end
|
482
|
-
|
483
|
-
end
|
484
|
-
|
485
|
-
class Label < self
|
486
|
-
|
487
|
-
SEPARATOR = '.'.freeze
|
488
|
-
PREFIX = '.'.freeze
|
489
|
-
OPERATOR = '.'.freeze
|
490
|
-
BASE_LEVEL = 3
|
491
|
-
|
492
|
-
end
|
493
|
-
|
494
|
-
class Path < self
|
495
|
-
|
496
|
-
SEPARATOR = '/'.freeze
|
497
|
-
PREFIX = '/'.freeze
|
498
|
-
OPERATOR = '/'.freeze
|
499
|
-
BASE_LEVEL = 3
|
500
|
-
|
501
|
-
end
|
502
|
-
|
503
|
-
class PathParameters < self
|
504
|
-
|
505
|
-
SEPARATOR = ';'.freeze
|
506
|
-
PREFIX = ';'.freeze
|
507
|
-
NAMED = true
|
508
|
-
PAIR_IF_EMPTY = false
|
509
|
-
OPERATOR = ';'.freeze
|
510
|
-
BASE_LEVEL = 3
|
511
|
-
|
512
|
-
end
|
513
|
-
|
514
|
-
class FormQuery < self
|
515
|
-
|
516
|
-
SEPARATOR = '&'.freeze
|
517
|
-
PREFIX = '?'.freeze
|
518
|
-
NAMED = true
|
519
|
-
OPERATOR = '?'.freeze
|
520
|
-
BASE_LEVEL = 3
|
521
|
-
|
522
|
-
end
|
523
|
-
|
524
|
-
class FormQueryContinuation < self
|
525
|
-
|
526
|
-
SEPARATOR = '&'.freeze
|
527
|
-
PREFIX = '&'.freeze
|
528
|
-
NAMED = true
|
529
|
-
OPERATOR = '&'.freeze
|
530
|
-
BASE_LEVEL = 3
|
531
|
-
|
532
|
-
end
|
533
|
-
|
534
|
-
end
|
535
|
-
|
536
|
-
# @private
|
537
|
-
OPERATORS = {
|
538
|
-
'' => Expression,
|
539
|
-
'+' => Expression::Reserved,
|
540
|
-
'#' => Expression::Fragment,
|
541
|
-
'.' => Expression::Label,
|
542
|
-
'/' => Expression::Path,
|
543
|
-
';' => Expression::PathParameters,
|
544
|
-
'?' => Expression::FormQuery,
|
545
|
-
'&' => Expression::FormQueryContinuation
|
546
|
-
}
|
547
|
-
|
548
132
|
# This error is raised when an invalid pattern was given.
|
549
133
|
class Invalid < StandardError
|
550
134
|
|
@@ -601,9 +185,6 @@ __REGEXP__
|
|
601
185
|
end
|
602
186
|
|
603
187
|
def each
|
604
|
-
if !block_given?
|
605
|
-
return Enumerator.new(self)
|
606
|
-
end
|
607
188
|
scanner = StringScanner.new(@source)
|
608
189
|
until scanner.eos?
|
609
190
|
expression = scanner.scan(EXPRESSION)
|
@@ -641,9 +222,6 @@ __REGEXP__
|
|
641
222
|
# URITemplate::RFC6570.try_convert( tpl ) #=> tpl
|
642
223
|
# URITemplate::RFC6570.try_convert('{foo}') #=> tpl
|
643
224
|
# URITemplate::RFC6570.try_convert(URITemplate.new(:colon, ':foo')) #=> tpl
|
644
|
-
# URITemplate::RFC6570.try_convert(URITemplate.new(:draft7, '{foo}')) #=> tpl
|
645
|
-
# # Draft7 and RFC6570 handle expansion of named variables a bit differently:
|
646
|
-
# URITemplate::RFC6570.try_convert(URITemplate.new(:draft7, '{?list*}')) #=> nil
|
647
225
|
# # This pattern is invalid, so it wont be parsed:
|
648
226
|
# URITemplate::RFC6570.try_convert('{foo') #=> nil
|
649
227
|
#
|
@@ -653,6 +231,7 @@ __REGEXP__
|
|
653
231
|
elsif x.kind_of? String and valid? x
|
654
232
|
return new(x)
|
655
233
|
elsif x.kind_of? URITemplate::Colon
|
234
|
+
return nil if x.tokens.any?{|tk| tk.kind_of? URITemplate::Colon::Token::Splat }
|
656
235
|
return new( x.tokens.map{|tk|
|
657
236
|
if tk.literal?
|
658
237
|
Literal.new(tk.string)
|
@@ -660,27 +239,11 @@ __REGEXP__
|
|
660
239
|
Expression.new([[tk.variables.first, false, 0]])
|
661
240
|
end
|
662
241
|
})
|
663
|
-
elsif (x.class == URITemplate::Draft7 and self == URITemplate::RFC6570) or (x.class == URITemplate::RFC6570 and self == URITemplate::Draft7)
|
664
|
-
if x.tokens.none?{|t| t.class::NAMED and t.expands? }
|
665
|
-
return self.new(x.to_s)
|
666
|
-
end
|
667
242
|
else
|
668
243
|
return nil
|
669
244
|
end
|
670
245
|
end
|
671
246
|
|
672
|
-
# Like {.try_convert}, but raises an ArgumentError, when the conversion failed.
|
673
|
-
#
|
674
|
-
# @raise ArgumentError
|
675
|
-
def convert(x)
|
676
|
-
o = self.try_convert(x)
|
677
|
-
if o.nil?
|
678
|
-
raise ArgumentError, "Expected to receive something that can be converted to an #{self.class}, but got: #{x.inspect}."
|
679
|
-
else
|
680
|
-
return o
|
681
|
-
end
|
682
|
-
end
|
683
|
-
|
684
247
|
# Tests whether a given pattern is a valid template pattern.
|
685
248
|
# @example
|
686
249
|
# URITemplate::RFC6570.valid? 'foo' #=> true
|
@@ -717,7 +280,7 @@ __REGEXP__
|
|
717
280
|
|
718
281
|
# @method expand(variables = {})
|
719
282
|
# Expands the template with the given variables.
|
720
|
-
# The expansion should be compatible to uritemplate spec
|
283
|
+
# The expansion should be compatible to uritemplate spec rfc 6570 ( http://tools.ietf.org/html/rfc6570 ).
|
721
284
|
# @note
|
722
285
|
# All keys of the supplied hash should be strings as anything else won't be recognised.
|
723
286
|
# @note
|
@@ -776,14 +339,13 @@ __REGEXP__
|
|
776
339
|
# @example Extraction cruces
|
777
340
|
# two_lists = URITemplate::RFC6570.new('{listA*,listB*}')
|
778
341
|
# uri = two_lists.expand('listA'=>[1,2],'listB'=>[3,4]) #=> "1,2,3,4"
|
779
|
-
# variables = two_lists.extract( uri ) #=> {'listA'=>["1","2","3"
|
342
|
+
# variables = two_lists.extract( uri ) #=> {'listA'=>["1","2","3"],'listB'=>["4"]}
|
780
343
|
# # However, like said in the note:
|
781
344
|
# two_lists.expand( variables ) == uri #=> true
|
782
345
|
#
|
783
346
|
# @note
|
784
347
|
# The current implementation drops duplicated variables instead of checking them.
|
785
348
|
#
|
786
|
-
#
|
787
349
|
def extract(uri_or_match, post_processing = DEFAULT_PROCESSING )
|
788
350
|
if uri_or_match.kind_of? String
|
789
351
|
m = self.to_r.match(uri_or_match)
|
@@ -816,22 +378,6 @@ __REGEXP__
|
|
816
378
|
extract( uri_or_match, NO_PROCESSING )
|
817
379
|
end
|
818
380
|
|
819
|
-
# Returns the pattern for this template.
|
820
|
-
def pattern
|
821
|
-
@pattern ||= tokens.map(&:to_s).join
|
822
|
-
end
|
823
|
-
|
824
|
-
alias to_s pattern
|
825
|
-
|
826
|
-
# Compares two template patterns.
|
827
|
-
def ==(o)
|
828
|
-
this, other, this_converted, _ = URITemplate.coerce( self, o )
|
829
|
-
if this_converted
|
830
|
-
return this == other
|
831
|
-
end
|
832
|
-
return this.pattern == other.pattern
|
833
|
-
end
|
834
|
-
|
835
381
|
# @method ===(uri)
|
836
382
|
# Alias for to_r.=== . Tests whether this template matches a given uri.
|
837
383
|
# @return TrueClass, FalseClass
|
@@ -855,7 +401,7 @@ __REGEXP__
|
|
855
401
|
self.class::TYPE
|
856
402
|
end
|
857
403
|
|
858
|
-
# Returns the level of this template according to the
|
404
|
+
# Returns the level of this template according to the rfc 6570 ( http://tools.ietf.org/html/rfc6570#section-1.2 ). Higher level means higher complexity.
|
859
405
|
# Basically this is defined as:
|
860
406
|
#
|
861
407
|
# * Level 1: no operators, one variable per expansion, no variable modifiers
|
@@ -878,70 +424,6 @@ __REGEXP__
|
|
878
424
|
tokens.map(&:level).max
|
879
425
|
end
|
880
426
|
|
881
|
-
# Tries to concatenate two templates, as if they were path segments.
|
882
|
-
# Removes double slashes or insert one if they are missing.
|
883
|
-
#
|
884
|
-
# @example
|
885
|
-
# tpl = URITemplate::RFC6570.new('/xy/')
|
886
|
-
# (tpl / '/z/' ).pattern #=> '/xy/z/'
|
887
|
-
# (tpl / 'z/' ).pattern #=> '/xy/z/'
|
888
|
-
# (tpl / '{/z}' ).pattern #=> '/xy{/z}'
|
889
|
-
# (tpl / 'a' / 'b' ).pattern #=> '/xy/a/b'
|
890
|
-
#
|
891
|
-
def /(o)
|
892
|
-
this, other, this_converted, _ = URITemplate.coerce( self, o )
|
893
|
-
if this_converted
|
894
|
-
return this / other
|
895
|
-
end
|
896
|
-
klass = self.class
|
897
|
-
if other.absolute?
|
898
|
-
raise ArgumentError, "Expected to receive a relative template but got an absoulte one: #{other.inspect}. If you think this is a bug, please report it."
|
899
|
-
end
|
900
|
-
|
901
|
-
if other.pattern == ''
|
902
|
-
return self
|
903
|
-
end
|
904
|
-
# Merge!
|
905
|
-
# Analyze the last token of this an the first token of the next and try to merge them
|
906
|
-
if self.tokens.last.kind_of?(klass::Literal)
|
907
|
-
if self.tokens.last.string[-1] == SLASH # the last token ends with an /
|
908
|
-
if other.tokens.first.kind_of? klass::Literal
|
909
|
-
# both seems to be paths, merge them!
|
910
|
-
if other.tokens.first.string[0] == SLASH
|
911
|
-
# strip one '/'
|
912
|
-
return self.class.new( self.tokens[0..-2] + [ klass::Literal.new(self.tokens.last.string + other.tokens.first.string[1..-1]) ] + other.tokens[1..-1] )
|
913
|
-
else
|
914
|
-
# no problem, but we can merge them
|
915
|
-
return self.class.new( self.tokens[0..-2] + [ klass::Literal.new(self.tokens.last.string + other.tokens.first.string) ] + other.tokens[1..-1] )
|
916
|
-
end
|
917
|
-
elsif other.tokens.first.kind_of? klass::Expression::Path
|
918
|
-
# this will automatically insert '/'
|
919
|
-
# so we can strip one '/'
|
920
|
-
return self.class.new( self.tokens[0..-2] + [ klass::Literal.new(self.tokens.last.string[0..-2]) ] + other.tokens )
|
921
|
-
end
|
922
|
-
elsif other.tokens.first.kind_of? klass::Literal
|
923
|
-
# okay, this template does not end with /, but the next starts with a literal => merge them!
|
924
|
-
if other.tokens.first.string[0] == SLASH
|
925
|
-
return self.class.new( self.tokens[0..-2] + [ klass::Literal.new(self.tokens.last.string + other.tokens.first.string)] + other.tokens[1..-1] )
|
926
|
-
else
|
927
|
-
return self.class.new( self.tokens[0..-2] + [ klass::Literal.new(self.tokens.last.string + '/' + other.tokens.first.string)] + other.tokens[1..-1] )
|
928
|
-
end
|
929
|
-
end
|
930
|
-
end
|
931
|
-
|
932
|
-
if other.tokens.first.kind_of?(klass::Literal)
|
933
|
-
if other.tokens.first.string[0] == SLASH
|
934
|
-
return self.class.new( self.tokens + other.tokens )
|
935
|
-
else
|
936
|
-
return self.class.new( self.tokens + [ klass::Literal.new('/' + other.tokens.first.string)]+ other.tokens[1..-1] )
|
937
|
-
end
|
938
|
-
elsif other.tokens.first.kind_of?(klass::Expression::Path)
|
939
|
-
return self.class.new( self.tokens + other.tokens )
|
940
|
-
else
|
941
|
-
return self.class.new( self.tokens + [ klass::Literal.new('/')] + other.tokens )
|
942
|
-
end
|
943
|
-
end
|
944
|
-
|
945
427
|
# Returns an array containing a the template tokens.
|
946
428
|
def tokens
|
947
429
|
@tokens ||= tokenize!
|
@@ -966,27 +448,27 @@ protected
|
|
966
448
|
i = 0
|
967
449
|
pa = part.arity
|
968
450
|
while i < pa
|
969
|
-
vars
|
451
|
+
vars.push( *part.extract(i, matchdata[bc]) )
|
970
452
|
bc += 1
|
971
453
|
i += 1
|
972
454
|
end
|
973
455
|
}
|
974
456
|
if post_processing.include? :convert_result
|
975
457
|
if post_processing.include? :convert_values
|
976
|
-
vars.
|
977
|
-
return Hash[*vars.map!{|k,v| [k,Utils.pair_array_to_hash(v)] }.flatten(1) ]
|
458
|
+
return Hash[ vars.map!{|k,v| [k,Utils.pair_array_to_hash(v)] } ]
|
978
459
|
else
|
979
|
-
vars
|
980
|
-
return Hash[*vars]
|
460
|
+
return Hash[vars]
|
981
461
|
end
|
982
462
|
else
|
983
463
|
if post_processing.include? :convert_value
|
984
|
-
vars.flatten!(1)
|
985
464
|
return vars.collect{|k,v| [k,Utils.pair_array_to_hash(v)] }
|
986
465
|
else
|
987
|
-
return vars
|
466
|
+
return vars
|
988
467
|
end
|
989
468
|
end
|
990
469
|
end
|
991
470
|
|
992
471
|
end
|
472
|
+
|
473
|
+
require 'uri_template/rfc6570/regex_builder.rb'
|
474
|
+
require 'uri_template/rfc6570/expression.rb'
|