uri_template 0.0.2 → 0.1.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 +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
|