uri_template 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/lib/uri_template.rb +10 -35
- data/lib/uri_template/colon.rb +0 -0
- data/lib/uri_template/draft2.rb +0 -0
- data/lib/uri_template/draft7.rb +211 -184
- data/uri_template.gemspec +3 -3
- metadata +10 -9
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.1.0 - 2.11.2011
|
2
|
+
- Removed Sections. They made too many headaches.
|
3
|
+
+ Made draft7 template concatenateable. This should replace sections.
|
4
|
+
* BUGFIX: multiline uris were matched
|
5
|
+
* BUGFIX: variablenames were decoded when this was not appreciated
|
6
|
+
|
1
7
|
# 0.0.2 - 1.11.2011
|
2
8
|
* BUGFIX: Concatenating empty sections no more leads to catch-all templates, when an emtpy template was appreciated.
|
3
9
|
+ The extracted variables now contains the keys :suffix and :prefix if the match didn't consume the whole uri.
|
data/lib/uri_template.rb
CHANGED
@@ -18,6 +18,7 @@
|
|
18
18
|
# A base module for all implementations of a uri template.
|
19
19
|
module URITemplate
|
20
20
|
|
21
|
+
autoload :Utils, 'uri_template/utils'
|
21
22
|
autoload :Draft7, 'uri_template/draft7'
|
22
23
|
|
23
24
|
# A hash with all available implementations.
|
@@ -39,6 +40,7 @@ module URITemplate
|
|
39
40
|
# URITemplate.resolve_class("template",:draft7) #=> [ URITemplate::Draft7, ["template"] ]
|
40
41
|
#
|
41
42
|
# @raise ArgumentError when no class was found.
|
43
|
+
#
|
42
44
|
def self.resolve_class(*args)
|
43
45
|
symbols, rest = args.partition{|x| x.kind_of? Symbol }
|
44
46
|
version = symbols.fetch(0, :default)
|
@@ -66,45 +68,18 @@ module URITemplate
|
|
66
68
|
module Invalid
|
67
69
|
end
|
68
70
|
|
69
|
-
# A base module for all implementation of a template section.
|
70
|
-
# Sections are a custom extension to the uri template spec.
|
71
|
-
# A template section ( in comparison to a template ) can be unbounded on its ends. Therefore they don't necessarily match a whole uri and can be concatenated.
|
72
|
-
module Section
|
73
|
-
|
74
|
-
include URITemplate
|
75
|
-
|
76
|
-
# Same as {URITemplate.new} but for sections
|
77
|
-
def self.new(*args)
|
78
|
-
klass, rest = URITemplate.resolve_class(*args)
|
79
|
-
return klass::Section.new(*rest)
|
80
|
-
end
|
81
|
-
|
82
|
-
# @abstract
|
83
|
-
# Concatenates this section with an other section.
|
84
|
-
def >>(other)
|
85
|
-
raise "Please implement #>> on #{self.class.inspect}"
|
86
|
-
end
|
87
|
-
|
88
|
-
# @abstract
|
89
|
-
# Is this section left bounded?
|
90
|
-
def left_bound?
|
91
|
-
raise "Please implement #left_bound? on #{self.class.inspect}"
|
92
|
-
end
|
93
|
-
|
94
|
-
# @abstract
|
95
|
-
# Is this section right bounded?
|
96
|
-
def right_bound?
|
97
|
-
raise "Please implement #right_bound? on #{self.class.inspect}"
|
98
|
-
end
|
99
|
-
|
100
|
-
end
|
101
|
-
|
102
71
|
# @abstract
|
103
72
|
# Expands this uri template with the given variables.
|
104
73
|
# The variables should be converted to strings using {Utils#object_to_param}.
|
105
|
-
# @raise Unconvertable if a variable could not be converted.
|
74
|
+
# @raise {Unconvertable} if a variable could not be converted to a string.
|
106
75
|
def expand(variables={})
|
107
|
-
raise "Please implement #expand on #{self.class.inspect}"
|
76
|
+
raise "Please implement #expand on #{self.class.inspect}."
|
77
|
+
end
|
78
|
+
|
79
|
+
# @abstract
|
80
|
+
# Returns the type of this template. The type is a symbol which can be used in {.resolve_class} to resolve the type of this template.
|
81
|
+
def type
|
82
|
+
raise "Please implement #type on #{self.class.inspect}."
|
108
83
|
end
|
109
84
|
|
110
85
|
end
|
File without changes
|
File without changes
|
data/lib/uri_template/draft7.rb
CHANGED
@@ -34,7 +34,7 @@ class URITemplate::Draft7
|
|
34
34
|
Utils = URITemplate::Utils
|
35
35
|
|
36
36
|
# @private
|
37
|
-
LITERAL = /^([^"'%<>\\^`{|}\s]|%\h\h)+/
|
37
|
+
LITERAL = /^([^"'%<>\\^`{|}\s\p{Cc}]|%\h\h)+/
|
38
38
|
|
39
39
|
# @private
|
40
40
|
CHARACTER_CLASSES = {
|
@@ -60,19 +60,19 @@ class URITemplate::Draft7
|
|
60
60
|
}
|
61
61
|
|
62
62
|
# Specifies that no processing should be done upon extraction.
|
63
|
-
# @see extract
|
63
|
+
# @see #extract
|
64
64
|
NO_PROCESSING = []
|
65
65
|
|
66
66
|
# Specifies that the extracted values should be processed.
|
67
|
-
# @see extract
|
67
|
+
# @see #extract
|
68
68
|
CONVERT_VALUES = [:convert_values]
|
69
69
|
|
70
70
|
# Specifies that the extracted variable list should be processed.
|
71
|
-
# @see extract
|
71
|
+
# @see #extract
|
72
72
|
CONVERT_RESULT = [:convert_result]
|
73
73
|
|
74
74
|
# Default processing. Means: convert values and the list itself.
|
75
|
-
# @see extract
|
75
|
+
# @see #extract
|
76
76
|
DEFAULT_PROCESSING = CONVERT_VALUES + CONVERT_RESULT
|
77
77
|
|
78
78
|
# @private
|
@@ -99,11 +99,15 @@ __REGEXP__
|
|
99
99
|
(?<varchar> [a-zA-Z_]|%[0-9a-fA-F]{2}){0}
|
100
100
|
(?<varname> \g<varchar>(?:\g<varchar>|\.)*){0}
|
101
101
|
(?<varspec> \g<varname>\*?(?::\d+)?){0}
|
102
|
-
^(([^"'%<>^`{|}\s]|%\h\h)+|\{\g<operator>(?<vars>\g<varspec>(?:,\g<varspec>)*)\})*$
|
102
|
+
^(([^"'%<>^`{|}\s\p{Cc}]|%\h\h)+|\{\g<operator>(?<vars>\g<varspec>(?:,\g<varspec>)*)\})*$
|
103
103
|
__REGEXP__
|
104
104
|
|
105
105
|
# @private
|
106
|
-
class
|
106
|
+
class Token
|
107
|
+
end
|
108
|
+
|
109
|
+
# @private
|
110
|
+
class Literal < Token
|
107
111
|
|
108
112
|
attr_reader :string
|
109
113
|
|
@@ -115,6 +119,10 @@ __REGEXP__
|
|
115
119
|
0
|
116
120
|
end
|
117
121
|
|
122
|
+
def level
|
123
|
+
1
|
124
|
+
end
|
125
|
+
|
118
126
|
def expand(*_)
|
119
127
|
return @string
|
120
128
|
end
|
@@ -128,58 +136,9 @@ __REGEXP__
|
|
128
136
|
end
|
129
137
|
|
130
138
|
end
|
131
|
-
|
132
|
-
# @private
|
133
|
-
class Terminal
|
134
|
-
def expand(*_)
|
135
|
-
''
|
136
|
-
end
|
137
|
-
|
138
|
-
def size
|
139
|
-
0
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# @private
|
144
|
-
class LeftBound < Terminal
|
145
|
-
|
146
|
-
def to_r_source(*_)
|
147
|
-
'^'
|
148
|
-
end
|
149
|
-
|
150
|
-
def to_s
|
151
|
-
''
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
# @private
|
157
|
-
class RightBound < Terminal
|
158
|
-
|
159
|
-
def to_r_source(*_)
|
160
|
-
'$'
|
161
|
-
end
|
162
|
-
|
163
|
-
def to_s
|
164
|
-
''
|
165
|
-
end
|
166
|
-
|
167
|
-
end
|
168
|
-
|
169
|
-
# @private
|
170
|
-
class Open < Terminal
|
171
|
-
|
172
|
-
def to_r_source(*_)
|
173
|
-
''
|
174
|
-
end
|
175
|
-
|
176
|
-
def to_s
|
177
|
-
"\u2026"
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
139
|
+
|
181
140
|
# @private
|
182
|
-
class Expression
|
141
|
+
class Expression < Token
|
183
142
|
|
184
143
|
attr_reader :variables, :max_length
|
185
144
|
|
@@ -192,6 +151,7 @@ __REGEXP__
|
|
192
151
|
PAIR_CONNECTOR = '='.freeze
|
193
152
|
PAIR_IF_EMPTY = true
|
194
153
|
LIST_CONNECTOR = ','.freeze
|
154
|
+
BASE_LEVEL = 1
|
195
155
|
|
196
156
|
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved]
|
197
157
|
|
@@ -202,6 +162,18 @@ __REGEXP__
|
|
202
162
|
@variables.size
|
203
163
|
end
|
204
164
|
|
165
|
+
def level
|
166
|
+
if @variables.none?{|_,expand,ml| expand || (ml > 0) }
|
167
|
+
if @variables.size == 1
|
168
|
+
return self.class::BASE_LEVEL
|
169
|
+
else
|
170
|
+
return 3
|
171
|
+
end
|
172
|
+
else
|
173
|
+
return 4
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
205
177
|
def expand( vars, options )
|
206
178
|
result = []
|
207
179
|
variables.each{| var, expand , max_length |
|
@@ -243,7 +215,7 @@ __REGEXP__
|
|
243
215
|
value = "(?:\\g<#{self.class::CHARACTER_CLASS[:class_name]}>|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
|
244
216
|
if expand
|
245
217
|
#if self.class::PAIR_IF_EMPTY
|
246
|
-
pair = "
|
218
|
+
pair = "(?:\\g<c_vn_>#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
247
219
|
|
248
220
|
if first
|
249
221
|
source << "(?<v#{base_counter + i}>(?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
@@ -274,7 +246,7 @@ __REGEXP__
|
|
274
246
|
# could be list or map, too
|
275
247
|
value = "\\g<#{self.class::CHARACTER_CLASS[:class_name]}>#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
|
276
248
|
|
277
|
-
pair = "
|
249
|
+
pair = "(?:\\g<c_vn_>#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
278
250
|
|
279
251
|
value = "#{pair}(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*"
|
280
252
|
elsif last
|
@@ -323,12 +295,12 @@ __REGEXP__
|
|
323
295
|
found_value = true
|
324
296
|
splitted << [ match['name'][0..-2], decode(match['value'] + rest , false) ]
|
325
297
|
else
|
326
|
-
splitted << [
|
298
|
+
splitted << [ match['value'] + rest, nil ]
|
327
299
|
end
|
328
300
|
rest = match.post_match
|
329
301
|
end
|
330
302
|
if !found_value
|
331
|
-
return [ [ name, splitted.map{|n,v|
|
303
|
+
return [ [ name, splitted.map{|n,v| decode(n , false) } ] ]
|
332
304
|
else
|
333
305
|
return [ [ name, splitted ] ]
|
334
306
|
end
|
@@ -433,6 +405,7 @@ __REGEXP__
|
|
433
405
|
|
434
406
|
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
|
435
407
|
OPERATOR = '+'.freeze
|
408
|
+
BASE_LEVEL = 2
|
436
409
|
|
437
410
|
end
|
438
411
|
|
@@ -441,6 +414,7 @@ __REGEXP__
|
|
441
414
|
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
|
442
415
|
PREFIX = '#'.freeze
|
443
416
|
OPERATOR = '#'.freeze
|
417
|
+
BASE_LEVEL = 2
|
444
418
|
|
445
419
|
end
|
446
420
|
|
@@ -449,6 +423,7 @@ __REGEXP__
|
|
449
423
|
SEPARATOR = '.'.freeze
|
450
424
|
PREFIX = '.'.freeze
|
451
425
|
OPERATOR = '.'.freeze
|
426
|
+
BASE_LEVEL = 3
|
452
427
|
|
453
428
|
end
|
454
429
|
|
@@ -457,6 +432,7 @@ __REGEXP__
|
|
457
432
|
SEPARATOR = '/'.freeze
|
458
433
|
PREFIX = '/'.freeze
|
459
434
|
OPERATOR = '/'.freeze
|
435
|
+
BASE_LEVEL = 3
|
460
436
|
|
461
437
|
end
|
462
438
|
|
@@ -467,6 +443,7 @@ __REGEXP__
|
|
467
443
|
NAMED = true
|
468
444
|
PAIR_IF_EMPTY = false
|
469
445
|
OPERATOR = ';'.freeze
|
446
|
+
BASE_LEVEL = 3
|
470
447
|
|
471
448
|
end
|
472
449
|
|
@@ -476,6 +453,7 @@ __REGEXP__
|
|
476
453
|
PREFIX = '?'.freeze
|
477
454
|
NAMED = true
|
478
455
|
OPERATOR = '?'.freeze
|
456
|
+
BASE_LEVEL = 3
|
479
457
|
|
480
458
|
end
|
481
459
|
|
@@ -485,6 +463,7 @@ __REGEXP__
|
|
485
463
|
PREFIX = '&'.freeze
|
486
464
|
NAMED = true
|
487
465
|
OPERATOR = '&'.freeze
|
466
|
+
BASE_LEVEL = 3
|
488
467
|
|
489
468
|
end
|
490
469
|
|
@@ -559,6 +538,7 @@ __REGEXP__
|
|
559
538
|
|
560
539
|
# Tries to convert the given param in to a instance of {Draft7}
|
561
540
|
# It basically passes thru instances of that class, parses strings and return nil on everything else.
|
541
|
+
#
|
562
542
|
# @example
|
563
543
|
# URITemplate::Draft7.try_convert( Object.new ) #=> nil
|
564
544
|
# tpl = URITemplate::Draft7.new('{foo}')
|
@@ -566,6 +546,7 @@ __REGEXP__
|
|
566
546
|
# URITemplate::Draft7.try_convert('{foo}') #=> tpl
|
567
547
|
# # This pattern is invalid, so it wont be parsed:
|
568
548
|
# URITemplate::Draft7.try_convert('{foo') #=> nil
|
549
|
+
#
|
569
550
|
def try_convert(x)
|
570
551
|
if x.kind_of? self
|
571
552
|
return x
|
@@ -576,6 +557,19 @@ __REGEXP__
|
|
576
557
|
end
|
577
558
|
end
|
578
559
|
|
560
|
+
|
561
|
+
# Like {.try_convert}, but raises an ArgumentError, when the conversion failed.
|
562
|
+
#
|
563
|
+
# @raise ArgumentError
|
564
|
+
def convert(x)
|
565
|
+
o = self.try_convert(x)
|
566
|
+
if o.nil?
|
567
|
+
raise ArgumentError, "Expected to receive something that can be converted to an #{self.class}, but got: #{x.inspect}."
|
568
|
+
else
|
569
|
+
return o
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
579
573
|
# Tests whether a given pattern is a valid template pattern.
|
580
574
|
# @example
|
581
575
|
# URITemplate::Draft7.valid? 'foo' #=> true
|
@@ -663,28 +657,49 @@ __REGEXP__
|
|
663
657
|
def to_r
|
664
658
|
classes = CHARACTER_CLASSES.map{|_,v| v[:class]+"{0}\n" }
|
665
659
|
bc = 0
|
666
|
-
@regexp ||= Regexp.new(classes.join + tokens.map{|part|
|
660
|
+
@regexp ||= Regexp.new(classes.join + '\A' + tokens.map{|part|
|
667
661
|
r = part.to_r_source(bc)
|
668
662
|
bc += part.size
|
669
663
|
r
|
670
|
-
}.join, Regexp::EXTENDED)
|
664
|
+
}.join + '\z' , Regexp::EXTENDED)
|
671
665
|
end
|
672
666
|
|
673
667
|
|
674
668
|
# Extracts variables from a uri ( given as string ) or an instance of MatchData ( which was matched by the regexp of this template.
|
675
|
-
# The actual result depends on the value of
|
669
|
+
# The actual result depends on the value of post_processing.
|
676
670
|
# This argument specifies whether pair arrays should be converted to hashes.
|
677
671
|
#
|
678
|
-
# @example
|
672
|
+
# @example Default Processing
|
679
673
|
# URITemplate::Draft7.new('{var}').extract('value') #=> {'var'=>'value'}
|
680
674
|
# URITemplate::Draft7.new('{&args*}').extract('&a=1&b=2') #=> {'args'=>{'a'=>'1','b'=>'2'}}
|
681
675
|
# URITemplate::Draft7.new('{&arg,arg}').extract('&arg=1&arg=2') #=> {'arg'=>'2'}
|
682
676
|
#
|
683
|
-
# @example
|
677
|
+
# @example No Processing
|
684
678
|
# URITemplate::Draft7.new('{var}').extract('value', URITemplate::Draft7::NO_PROCESSING) #=> [['var','value']]
|
685
679
|
# URITemplate::Draft7.new('{&args*}').extract('&a=1&b=2', URITemplate::Draft7::NO_PROCESSING) #=> [['args',[['a','1'],['b','2']]]]
|
686
680
|
# URITemplate::Draft7.new('{&arg,arg}').extract('&arg=1&arg=2', URITemplate::Draft7::NO_PROCESSING) #=> [['arg','1'],['arg','2']]
|
687
681
|
#
|
682
|
+
# @raise Encoding::InvalidByteSequenceError when the given uri was not properly encoded.
|
683
|
+
# @raise Encoding::UndefinedConversionError when the given uri could not be converted to utf-8.
|
684
|
+
# @raise Encoding::CompatibilityError when the given uri could not be converted to utf-8.
|
685
|
+
#
|
686
|
+
# @param [String,MatchData] Uri_or_MatchData A uri or a matchdata from which the variables should be extracted.
|
687
|
+
# @param [Array] Processing Specifies which processing should be done.
|
688
|
+
#
|
689
|
+
# @note
|
690
|
+
# Don't expect that an extraction can fully recover the expanded variables. Extract rather generates a variable list which should expand to the uri from which it were extracted. In general the following equation should hold true:
|
691
|
+
# a_tpl.expand( a_tpl.extract( an_uri ) ) == an_uri
|
692
|
+
#
|
693
|
+
# @example Extraction cruces
|
694
|
+
# two_lists = URITemplate::Draft7.new('{listA*,listB*}')
|
695
|
+
# uri = two_lists.expand('listA'=>[1,2],'listB'=>[3,4]) #=> "1,2,3,4"
|
696
|
+
# variables = two_lists.extract( uri ) #=> {'listA'=>["1","2","3","4"],'listB'=>nil}
|
697
|
+
# # However, like said in the note:
|
698
|
+
# two_lists.expand( variables ) == uri #=> true
|
699
|
+
#
|
700
|
+
# @note
|
701
|
+
# The current implementation drops duplicated variables instead of checking them.
|
702
|
+
#
|
688
703
|
#
|
689
704
|
def extract(uri_or_match, post_processing = DEFAULT_PROCESSING )
|
690
705
|
if uri_or_match.kind_of? String
|
@@ -703,12 +718,6 @@ __REGEXP__
|
|
703
718
|
return nil
|
704
719
|
else
|
705
720
|
result = extract_matchdata(m)
|
706
|
-
if m.pre_match and m.pre_match.size > 0
|
707
|
-
result.unshift( [:prefix, m.pre_match] )
|
708
|
-
end
|
709
|
-
if m.post_match and m.post_match.size > 0
|
710
|
-
result.push( [:suffix, m.post_match] )
|
711
|
-
end
|
712
721
|
if post_processing.include? :convert_values
|
713
722
|
result.map!{|k,v| [k, Utils.pair_array_to_hash(v)] }
|
714
723
|
end
|
@@ -738,140 +747,158 @@ __REGEXP__
|
|
738
747
|
end
|
739
748
|
|
740
749
|
alias to_s pattern
|
750
|
+
|
751
|
+
# Compares two template patterns.
|
752
|
+
def ==(tpl)
|
753
|
+
return false if self.class != tpl.class
|
754
|
+
return self.pattern == tpl.pattern
|
755
|
+
end
|
756
|
+
|
757
|
+
# @method ===(uri)
|
758
|
+
# Alias for to_r.=== . Tests whether this template matches a given uri.
|
759
|
+
# @return TrueClass, FalseClass
|
760
|
+
def_delegators :to_r, :===
|
761
|
+
|
762
|
+
# @method match(uri)
|
763
|
+
# Alias for to_r.match . Matches this template against the given uri.
|
764
|
+
# @yield MatchData
|
765
|
+
# @return MatchData, Object
|
766
|
+
def_delegators :to_r, :match
|
741
767
|
|
742
|
-
#
|
743
|
-
#
|
744
|
-
# Unboundedness is denoted with unicode character \u2026 ( … ).
|
745
|
-
#
|
746
|
-
# @example
|
747
|
-
# prefix = URITemplate::Draft7::Section.new('/prefix…')
|
748
|
-
# template = URITemplate::Draft7.new('/prefix')
|
749
|
-
# prefix === '/prefix/something completly different' #=> true
|
750
|
-
# template === '/prefix/something completly different' #=> false
|
751
|
-
# prefix.to_r.match('/prefix/something completly different').post_match #=> '/something completly different'
|
752
|
-
#
|
768
|
+
# The type of this template.
|
769
|
+
#
|
753
770
|
# @example
|
754
|
-
#
|
755
|
-
#
|
756
|
-
#
|
757
|
-
#
|
758
|
-
#
|
771
|
+
# tpl1 = URITemplate::Draft7.new('/foo')
|
772
|
+
# tpl2 = URITemplate.new( tpl1.pattern, tpl1.type )
|
773
|
+
# tpl1 == tpl2 #=> true
|
774
|
+
#
|
775
|
+
# @see {URITemplate#type}
|
776
|
+
def type
|
777
|
+
:draft7
|
778
|
+
end
|
779
|
+
|
780
|
+
# Returns the level of this template according to the draft ( http://tools.ietf.org/html/draft-gregorio-uritemplate-07#section-1.2 ). Higher level means higher complexity.
|
781
|
+
# Basically this is defined as:
|
759
782
|
#
|
783
|
+
# * Level 1: no operators, one variable per expansion, no variable modifiers
|
784
|
+
# * Level 2: '+' and '#' operators, one variable per expansion, no variable modifiers
|
785
|
+
# * Level 3: all operators, multiple variables per expansion, no variable modifiers
|
786
|
+
# * Level 4: all operators, multiple variables per expansion, all variable modifiers
|
787
|
+
#
|
760
788
|
# @example
|
761
|
-
#
|
762
|
-
#
|
763
|
-
#
|
764
|
-
#
|
765
|
-
#
|
766
|
-
#
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
#
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
# end
|
775
|
-
# route( 'app_a/do_something' ) #=> "app_a: do_something with {:suffix=>\"do_something\"}"
|
776
|
-
# route( 'app_b/1337/something_else' ) #=> "app_b: something_else with {\"x\"=>\"1337\", :suffix=>\"something_else\"}"
|
777
|
-
# route( 'bla' ) #=> 'not found'
|
778
|
-
#
|
779
|
-
class Section < self
|
780
|
-
|
781
|
-
include URITemplate::Section
|
789
|
+
# URITemplate::Draft7.new('/foo/').level #=> 1
|
790
|
+
# URITemplate::Draft7.new('/foo{bar}').level #=> 1
|
791
|
+
# URITemplate::Draft7.new('/foo{#bar}').level #=> 2
|
792
|
+
# URITemplate::Draft7.new('/foo{.bar}').level #=> 3
|
793
|
+
# URITemplate::Draft7.new('/foo{bar,baz}').level #=> 3
|
794
|
+
# URITemplate::Draft7.new('/foo{bar:20}').level #=> 4
|
795
|
+
# URITemplate::Draft7.new('/foo{bar*}').level #=> 4
|
796
|
+
#
|
797
|
+
# Templates of lower levels might be convertible to other formats while templates of higher levels might be incompatible. Level 1 for example should be convertible to any other format since it just contains simple expansions.
|
798
|
+
#
|
799
|
+
def level
|
800
|
+
tokens.map(&:level).max
|
801
|
+
end
|
782
802
|
|
783
|
-
|
784
|
-
|
803
|
+
# Tries to conatenate two templates, as if they were path segments.
|
804
|
+
# Removes double slashes or insert one if they are missing.
|
805
|
+
#
|
806
|
+
# @example
|
807
|
+
# tpl = URITemplate::Draft7.new('/xy/')
|
808
|
+
# (tpl / '/z/' ).pattern #=> '/xy/z/'
|
809
|
+
# (tpl / 'z/' ).pattern #=> '/xy/z/'
|
810
|
+
# (tpl / '{/z}' ).pattern #=> '/xy{/z}'
|
811
|
+
# (tpl / 'a' / 'b' ).pattern #=> '/xy/a/b'
|
812
|
+
#
|
813
|
+
def /(o)
|
814
|
+
other = self.class.convert(o)
|
785
815
|
|
786
|
-
|
787
|
-
|
788
|
-
tokens.first.kind_of? LeftBound
|
816
|
+
if other.absolute?
|
817
|
+
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."
|
789
818
|
end
|
790
819
|
|
791
|
-
|
792
|
-
|
793
|
-
tokens.last.kind_of? RightBound
|
820
|
+
if other.pattern == ''
|
821
|
+
return self
|
794
822
|
end
|
795
|
-
|
796
|
-
#
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
# sect = URITemplate::Draft7::Section.new('…')
|
808
|
-
# combo = sect >> '…'
|
809
|
-
# combo.pattern #=> ""
|
810
|
-
# combo === "" #=> true
|
811
|
-
# combo === "/foo" #=> false
|
812
|
-
#
|
813
|
-
# @return Section
|
814
|
-
def >>(other)
|
815
|
-
o = self.class.try_convert(other)
|
816
|
-
if o.kind_of? Section
|
817
|
-
if !self.right_bound?
|
818
|
-
if o.pattern == ELLIPSIS
|
819
|
-
return self.class.new("", o.options)
|
820
|
-
elsif !o.left_bound?
|
821
|
-
#if self.tokens.size == 1
|
822
|
-
# return o
|
823
|
-
#end
|
824
|
-
tkns = self.tokens[0..-2] + o.tokens[1..-1]
|
825
|
-
unless tkns.first.kind_of? Terminal
|
826
|
-
tkns.unshift(LeftBound.new)
|
827
|
-
end
|
828
|
-
unless tkns.last.kind_of? Terminal
|
829
|
-
tkns.push(RightBound.new)
|
830
|
-
end
|
831
|
-
return self.class.new(tkns, o.options)
|
823
|
+
# Merge!
|
824
|
+
# Analyze the last token of this an the first token of the next and try to merge them
|
825
|
+
if self.tokens.last.kind_of?(Literal)
|
826
|
+
if self.tokens.last.string[-1] == '/' # the last token ends with an /
|
827
|
+
if other.tokens.first.kind_of? Literal
|
828
|
+
# both seems to be paths, merge them!
|
829
|
+
if other.tokens.first.string[0] == '/'
|
830
|
+
# strip one '/'
|
831
|
+
return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string + other.tokens.first.string[1..-1]) ] + other.tokens[1..-1] )
|
832
|
+
else
|
833
|
+
# no problem, but we can merge them
|
834
|
+
return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string + other.tokens.first.string) ] + other.tokens[1..-1] )
|
832
835
|
end
|
836
|
+
elsif other.tokens.first.kind_of? Expression::Path
|
837
|
+
# this will automatically insert '/'
|
838
|
+
# so we can strip one '/'
|
839
|
+
return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string[0..-2]) ] + other.tokens )
|
833
840
|
end
|
834
|
-
raise ArgumentError, "Expected something that could be converted to a URITemplate section, but got #{other.inspect}"
|
835
841
|
end
|
836
842
|
end
|
843
|
+
if other.tokens.first.kind_of?(Expression::Path) or (other.tokens.first.kind_of?(Literal) and other.tokens.first.string[0] == '/')
|
844
|
+
return self.class.new( self.tokens + other.tokens )
|
845
|
+
else
|
846
|
+
return self.class.new( self.tokens + [Literal.new('/')] + other.tokens )
|
847
|
+
end
|
848
|
+
end
|
849
|
+
|
850
|
+
|
851
|
+
#
|
852
|
+
# should be relative:
|
853
|
+
# xxx ...
|
854
|
+
# {xxx}x ...
|
855
|
+
#
|
856
|
+
# should not be relative:
|
857
|
+
# {proto}:// ...
|
858
|
+
# http:// ...
|
859
|
+
# http{ssl}:// ...
|
860
|
+
#
|
861
|
+
def absolute?
|
862
|
+
read_chars = ""
|
837
863
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
864
|
+
tokens.each do |token|
|
865
|
+
if token.kind_of? Expression
|
866
|
+
if token.class::OPERATOR == ''
|
867
|
+
read_chars << "x"
|
868
|
+
else
|
869
|
+
return false
|
870
|
+
end
|
871
|
+
elsif token.kind_of? Literal
|
872
|
+
read_chars << token.string
|
873
|
+
end
|
874
|
+
if read_chars =~ /^[a-z]+:\/\//i
|
875
|
+
return true
|
876
|
+
elsif read_chars =~ /(?<!:|\/)\/(?!\/)/
|
877
|
+
return false
|
847
878
|
end
|
848
|
-
pat = pat[ (lb ? 0 : 1)..(rb ? -1 : -2) ]
|
849
|
-
[lb ? LeftBound.new : Open.new] + Tokenizer.new(pat).to_a + [rb ? RightBound.new : Open.new]
|
850
879
|
end
|
851
|
-
|
880
|
+
|
881
|
+
return false
|
852
882
|
end
|
853
883
|
|
854
|
-
#
|
855
|
-
|
856
|
-
|
857
|
-
|
884
|
+
# Returns the number of static characters in this template.
|
885
|
+
# This method is useful for routing, since it's often pointful to use the url with fewer variable characters.
|
886
|
+
# For example 'static' and 'sta{var}' both match 'static', but in most cases 'static' should be prefered over 'sta{var}' since it's more specific.
|
887
|
+
#
|
888
|
+
# @example
|
889
|
+
# URITemplate::Draft7.new('/xy/').static_characters #=> 4
|
890
|
+
# URITemplate::Draft7.new('{foo}').static_characters #=> 0
|
891
|
+
# URITemplate::Draft7.new('a{foo}b').static_characters #=> 2
|
892
|
+
#
|
893
|
+
# @return Numeric
|
894
|
+
def static_characters
|
895
|
+
@static_characters ||= tokens.select{|t| t.kind_of?(Literal) }.map{|t| t.string.size }.inject(0,:+)
|
858
896
|
end
|
859
|
-
|
860
|
-
# @method ===(uri)
|
861
|
-
# Alias for to_r.=== . Tests whether this template matches a given uri.
|
862
|
-
# @return TrueClass, FalseClass
|
863
|
-
def_delegators :to_r, :===
|
864
|
-
|
865
|
-
# @method match(uri)
|
866
|
-
# Alias for to_r.match . Matches this template against the given uri.
|
867
|
-
# @yield MatchData
|
868
|
-
# @return MatchData, Object
|
869
|
-
def_delegators :to_r, :match
|
870
897
|
|
871
898
|
protected
|
872
899
|
# @private
|
873
900
|
def tokenize!
|
874
|
-
|
901
|
+
Tokenizer.new(pattern).to_a
|
875
902
|
end
|
876
903
|
|
877
904
|
def tokens
|
data/uri_template.gemspec
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'uri_template'
|
3
|
-
s.version = '0.0
|
4
|
-
s.date = '2011-11-
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2011-11-02'
|
5
5
|
s.authors = ["HannesG"]
|
6
6
|
s.email = %q{hannes.georg@googlemail.com}
|
7
7
|
s.summary = 'A templating system for URIs.'
|
8
8
|
s.homepage = 'http://github.com/hannesg/uri_template'
|
9
|
-
s.description = 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07 . An implementation of an older version of that spec is known as addressable. This
|
9
|
+
s.description = 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07 . An implementation of an older version of that spec is known as addressable. This gem however is intended to be extended when newer specs evolve. For now only draft 7 is supported. Downside: only for 1.9 compatible since it uses Oniguruma regexp.'
|
10
10
|
|
11
11
|
s.require_paths = ['lib']
|
12
12
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uri_template
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-11-
|
12
|
+
date: 2011-11-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &14484700 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *14484700
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: yard
|
27
|
-
requirement: &
|
27
|
+
requirement: &14482080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,18 +32,19 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *14482080
|
36
36
|
description: ! 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07
|
37
37
|
. An implementation of an older version of that spec is known as addressable. This
|
38
|
-
|
39
|
-
|
40
|
-
regexp.'
|
38
|
+
gem however is intended to be extended when newer specs evolve. For now only draft
|
39
|
+
7 is supported. Downside: only for 1.9 compatible since it uses Oniguruma regexp.'
|
41
40
|
email: hannes.georg@googlemail.com
|
42
41
|
executables: []
|
43
42
|
extensions: []
|
44
43
|
extra_rdoc_files: []
|
45
44
|
files:
|
46
45
|
- lib/uri_template.rb
|
46
|
+
- lib/uri_template/colon.rb
|
47
|
+
- lib/uri_template/draft2.rb
|
47
48
|
- lib/uri_template/draft7.rb
|
48
49
|
- lib/uri_template/utils.rb
|
49
50
|
- uri_template.gemspec
|