uri_template 0.1.3 → 0.1.4
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 +4 -0
- data/README +24 -103
- data/lib/uri_template.rb +4 -4
- data/lib/uri_template/colon.rb +11 -10
- data/lib/uri_template/draft7.rb +88 -76
- data/lib/uri_template/utils.rb +57 -10
- data/uri_template.gemspec +5 -3
- metadata +29 -7
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -21,109 +21,30 @@ Some implementations might be able to extract variables, too ( Draft 7 is! ).
|
|
21
21
|
|
22
22
|
== Benchmarks
|
23
23
|
|
24
|
-
System: Core 2 Duo
|
25
|
-
Implementation: Draft7
|
26
|
-
Results marked with * means that the template object is reused.
|
24
|
+
* System: Core 2 Duo T9300, 4 gb ram, Fedora 16 64 bit, ruby 1.9.3, 100_000 repetitions
|
25
|
+
* Implementation: Draft7 ( version 0.1.3 (pre) ) vs. Addressable ( 2.2.6 )
|
26
|
+
* Results marked with * means that the template object is reused.
|
27
|
+
|
28
|
+
Addressable | Addressable* | Draft7 | Draft7* |
|
29
|
+
--Expansion----------------------------------------------------------------------------------------
|
30
|
+
Empty string 8.601 | 8.529 | 0.451 | 0.057 |
|
31
|
+
One simple variable 19.832 | 19.675 | 2.746 | 0.928 |
|
32
|
+
One escaped variable 22.437 | 22.151 | 6.011 | 4.242 |
|
33
|
+
One missing variable 9.638 | 9.544 | 1.855 | 0.184 |
|
34
|
+
Path segments 25.353 | 25.413 | 3.171 | 1.372 |
|
35
|
+
Arguments 27.140 | 27.144 | 5.293 | 2.715 |
|
36
|
+
Full URI 75.599 | 75.680 | 11.147 | 5.246 |
|
37
|
+
Segments and Arguments 73.360 | 73.334 | 7.694 | 3.604 |
|
38
|
+
--Extraction---------------------------------------------------------------------------------------
|
39
|
+
Empty string 14.031 | 13.993 | 1.849 | 0.576 |
|
40
|
+
One simple variable 28.701 | 28.503 | 6.988 | 1.840 |
|
41
|
+
One escaped variable 33.237 | 33.358 | 8.354 | 3.155 |
|
42
|
+
One missing variable 17.201 | 17.051 | 6.318 | 1.177 |
|
43
|
+
Path segments 43.112 | 43.623 | 12.502 | 2.983 |
|
44
|
+
Arguments 54.448 | 54.090 | 13.772 | 3.394 |
|
45
|
+
Full URI 95.433 | 91.961 | 30.762 | 6.797 |
|
46
|
+
Segments and Arguments 80.508 | 79.570 | 23.204 | 5.210 |
|
47
|
+
Segments and Arguments ( not extractable ) 22.079 | 21.967 | 18.017 | 0.256 |
|
27
48
|
|
28
|
-
=== Expansion
|
29
|
-
"" vs "" => ""
|
30
|
-
user system total real
|
31
|
-
Addressable 0.070000 0.010000 0.080000 ( 0.081990)
|
32
|
-
UriTemplate 0.010000 0.000000 0.010000 ( 0.007668)
|
33
|
-
Addressable* 0.090000 0.000000 0.090000 ( 0.090763)
|
34
|
-
UriTemplate* 0.000000 0.000000 0.000000 ( 0.001633)
|
35
|
-
|
36
|
-
"{simple_string}" vs "{simple_string}" => "noneedtoescape"
|
37
|
-
user system total real
|
38
|
-
Addressable 0.560000 0.000000 0.560000 ( 0.560811)
|
39
|
-
UriTemplate 0.030000 0.000000 0.030000 ( 0.034104)
|
40
|
-
Addressable* 0.560000 0.000000 0.560000 ( 0.558118)
|
41
|
-
UriTemplate* 0.030000 0.000000 0.030000 ( 0.026850)
|
42
|
-
|
43
|
-
"{escaped_string}" vs "{escaped_string}" => "%2F%20%2F%25%2F%20%3F%2B"
|
44
|
-
user system total real
|
45
|
-
Addressable 0.560000 0.000000 0.560000 ( 0.562588)
|
46
|
-
UriTemplate 0.070000 0.000000 0.070000 ( 0.072649)
|
47
|
-
Addressable* 0.560000 0.000000 0.560000 ( 0.556001)
|
48
|
-
UriTemplate* 0.050000 0.000000 0.050000 ( 0.054520)
|
49
|
-
|
50
|
-
"{missing}" vs "{missing}" => ""
|
51
|
-
user system total real
|
52
|
-
Addressable 0.550000 0.010000 0.560000 ( 0.550707)
|
53
|
-
UriTemplate 0.040000 0.000000 0.040000 ( 0.035343)
|
54
|
-
Addressable* 0.550000 0.000000 0.550000 ( 0.556634)
|
55
|
-
UriTemplate* 0.010000 0.000000 0.010000 ( 0.003132)
|
56
|
-
|
57
|
-
"{-prefix|/|segments}" vs "{/segments*}" => "/a/b/c"
|
58
|
-
user system total real
|
59
|
-
Addressable 0.650000 0.000000 0.650000 ( 0.656996)
|
60
|
-
UriTemplate 0.030000 0.000000 0.030000 ( 0.038546)
|
61
|
-
Addressable* 0.660000 0.000000 0.660000 ( 0.655364)
|
62
|
-
UriTemplate* 0.020000 0.000000 0.020000 ( 0.018265)
|
63
|
-
|
64
|
-
"?{-join|&|one,two,three}" vs "{?one,two,three}" => "?one=1&two=2&three=3"
|
65
|
-
user system total real
|
66
|
-
Addressable 0.670000 0.000000 0.670000 ( 0.671649)
|
67
|
-
UriTemplate 0.080000 0.000000 0.080000 ( 0.078588)
|
68
|
-
Addressable* 0.670000 0.000000 0.670000 ( 0.669606)
|
69
|
-
UriTemplate* 0.030000 0.000000 0.030000 ( 0.035886)
|
70
|
-
|
71
|
-
"http://{host}/{-suffix|/|segments}?{-join|&|one,two,bogus}\#{fragment}" vs "http://{host}{/segments*}/{?one,two,bogus}{#fragment}" => "http://example.com/a/b/c/?one=1&two=2#foo"
|
72
|
-
user system total real
|
73
|
-
Addressable 0.800000 0.000000 0.800000 ( 0.802170)
|
74
|
-
UriTemplate 0.150000 0.000000 0.150000 ( 0.147875)
|
75
|
-
Addressable* 0.810000 0.000000 0.810000 ( 0.803928)
|
76
|
-
UriTemplate* 0.060000 0.000000 0.060000 ( 0.064376)
|
77
|
-
|
78
|
-
=== Extraction
|
79
|
-
"" => "" vs ""
|
80
|
-
user system total real
|
81
|
-
Addressable 0.150000 0.000000 0.150000 ( 0.144244)
|
82
|
-
UriTemplate 0.080000 0.000000 0.080000 ( 0.079467)
|
83
|
-
Addressable* 0.140000 0.000000 0.140000 ( 0.149177)
|
84
|
-
UriTemplate* 0.010000 0.000000 0.010000 ( 0.008808)
|
85
|
-
|
86
|
-
"noneedtoescape" => "{simple_string}" vs "{simple_string}"
|
87
|
-
user system total real
|
88
|
-
Addressable 0.350000 0.000000 0.350000 ( 0.345314)
|
89
|
-
UriTemplate 0.110000 0.000000 0.110000 ( 0.116090)
|
90
|
-
Addressable* 0.350000 0.000000 0.350000 ( 0.342613)
|
91
|
-
UriTemplate* 0.020000 0.000000 0.020000 ( 0.025884)
|
92
|
-
|
93
|
-
"%2F%20%2F%25%2F%20%3F%2B" => "{escaped_string}" vs "{escaped_string}"
|
94
|
-
user system total real
|
95
|
-
Addressable 0.410000 0.000000 0.410000 ( 0.404258)
|
96
|
-
UriTemplate 0.140000 0.000000 0.140000 ( 0.143897)
|
97
|
-
Addressable* 0.380000 0.000000 0.380000 ( 0.382331)
|
98
|
-
UriTemplate* 0.060000 0.000000 0.060000 ( 0.053483)
|
99
|
-
|
100
|
-
"" => "{missing}" vs "{missing}"
|
101
|
-
user system total real
|
102
|
-
Addressable 0.220000 0.000000 0.220000 ( 0.222086)
|
103
|
-
UriTemplate 0.100000 0.000000 0.100000 ( 0.104280)
|
104
|
-
Addressable* 0.230000 0.000000 0.230000 ( 0.238814)
|
105
|
-
UriTemplate* 0.020000 0.000000 0.020000 ( 0.017284)
|
106
|
-
|
107
|
-
"/a/b/c" => "{-prefix|/|segments}" vs "{/segments*}"
|
108
|
-
user system total real
|
109
|
-
Addressable 0.490000 0.000000 0.490000 ( 0.489809)
|
110
|
-
UriTemplate 0.230000 0.000000 0.230000 ( 0.232298)
|
111
|
-
Addressable* 0.490000 0.000000 0.490000 ( 0.489334)
|
112
|
-
UriTemplate* 0.120000 0.000000 0.120000 ( 0.119815)
|
113
|
-
|
114
|
-
"?one=1&two=2&three=3" => "?{-join|&|one,two,three}" vs "{?one,two,three}"
|
115
|
-
user system total real
|
116
|
-
Addressable 0.550000 0.000000 0.550000 ( 0.553224)
|
117
|
-
UriTemplate 0.210000 0.000000 0.210000 ( 0.208296)
|
118
|
-
Addressable* 0.550000 0.000000 0.550000 ( 0.551459)
|
119
|
-
UriTemplate* 0.060000 0.000000 0.060000 ( 0.059963)
|
120
|
-
|
121
|
-
"http://example.com/a/b/c/?one=1&two=2#foo" => "http://{host}/{-suffix|/|segments}?{-join|&|one,two,bogus}\#{fragment}" vs "http://{host}{/segments*}/{?one,two,bogus}{#fragment}"
|
122
|
-
user system total real
|
123
|
-
Addressable 0.980000 0.000000 0.980000 ( 0.980292)
|
124
|
-
UriTemplate 0.450000 0.000000 0.450000 ( 0.446143)
|
125
|
-
Addressable* 1.000000 0.000000 1.000000 ( 0.996237)
|
126
|
-
UriTemplate* 0.170000 0.000000 0.170000 ( 0.175239)
|
127
|
-
|
128
49
|
SUCCESS - Draft7 was faster in every test!
|
129
50
|
|
data/lib/uri_template.rb
CHANGED
@@ -250,8 +250,8 @@ module URITemplate
|
|
250
250
|
# This is detected by checking for "://".
|
251
251
|
#
|
252
252
|
def absolute?
|
253
|
+
return @absolute unless @absolute.nil?
|
253
254
|
read_chars = ""
|
254
|
-
|
255
255
|
tokens.each do |token|
|
256
256
|
if token.expression?
|
257
257
|
read_chars << "x"
|
@@ -259,13 +259,13 @@ module URITemplate
|
|
259
259
|
read_chars << token.string
|
260
260
|
end
|
261
261
|
if read_chars =~ /^[a-z]+:\/\//i
|
262
|
-
return true
|
262
|
+
return @absolute = true
|
263
263
|
elsif read_chars =~ /(?<!:|\/)\/(?!\/)/
|
264
|
-
return false
|
264
|
+
return @absolute = false
|
265
265
|
end
|
266
266
|
end
|
267
267
|
|
268
|
-
return false
|
268
|
+
return @absolute = false
|
269
269
|
end
|
270
270
|
|
271
271
|
# Opposite of {#absolute?}
|
data/lib/uri_template/colon.rb
CHANGED
@@ -28,7 +28,7 @@ class Colon
|
|
28
28
|
|
29
29
|
include URITemplate
|
30
30
|
|
31
|
-
VAR = /(?:\{:(
|
31
|
+
VAR = /(?:\{:([a-z]+)\}|:([a-z]+)(?![a-z]))/u
|
32
32
|
|
33
33
|
class Token
|
34
34
|
|
@@ -48,7 +48,7 @@ class Colon
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def to_r
|
51
|
-
return ['(
|
51
|
+
return ['([^/]*?)'].join
|
52
52
|
end
|
53
53
|
|
54
54
|
end
|
@@ -85,7 +85,7 @@ class Colon
|
|
85
85
|
elsif x.kind_of? self
|
86
86
|
return x
|
87
87
|
elsif x.kind_of? URITemplate::Draft7 and x.level == 1
|
88
|
-
return new( x.pattern.gsub(/\{(.*?)\}/){ "{:#{$1}}" } )
|
88
|
+
return new( x.pattern.gsub(/\{(.*?)\}/u){ "{:#{$1}}" } )
|
89
89
|
else
|
90
90
|
return nil
|
91
91
|
end
|
@@ -100,11 +100,11 @@ class Colon
|
|
100
100
|
# @param String uri
|
101
101
|
# @return nil,Hash
|
102
102
|
def extract(uri)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
103
|
+
md = self.to_r.match(uri)
|
104
|
+
return nil unless md
|
105
|
+
return Hash[ *self.variables.each_with_index.map{|v,i|
|
106
|
+
[v, Utils.dpct(md[i+1])]
|
107
|
+
}.flatten(1) ]
|
108
108
|
end
|
109
109
|
|
110
110
|
def type
|
@@ -112,7 +112,7 @@ class Colon
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def to_r
|
115
|
-
@regexp ||= Regexp.new('\A' + tokens.map(&:to_r).join + '\z')
|
115
|
+
@regexp ||= Regexp.new('\A' + tokens.map(&:to_r).join + '\z', Utils::KCODE_UTF8)
|
116
116
|
end
|
117
117
|
|
118
118
|
def tokens
|
@@ -144,7 +144,8 @@ protected
|
|
144
144
|
if x.kind_of? String
|
145
145
|
Token::Static.new(x)
|
146
146
|
else
|
147
|
-
|
147
|
+
# TODO: when rubinius supports ambigious names this could be replaced with x['name'] *sigh*
|
148
|
+
Token::Variable.new(x[1] || x[2])
|
148
149
|
end
|
149
150
|
}.to_a
|
150
151
|
end
|
data/lib/uri_template/draft7.rb
CHANGED
@@ -34,26 +34,28 @@ class URITemplate::Draft7
|
|
34
34
|
Utils = URITemplate::Utils
|
35
35
|
|
36
36
|
# @private
|
37
|
-
|
37
|
+
# \/ - unicode ctrl-chars ( \p{Cc} doen't work with rbx
|
38
|
+
LITERAL = /^([^"'%<>\\^`{|}\s\p{Cntrl}]|%\h\h)+/u
|
38
39
|
|
39
40
|
# @private
|
40
41
|
CHARACTER_CLASSES = {
|
41
42
|
|
42
43
|
:unreserved => {
|
43
|
-
:unencoded => /([^A-Za-z0-9\-\._])
|
44
|
-
:class => '(
|
44
|
+
:unencoded => /([^A-Za-z0-9\-\._])/u,
|
45
|
+
:class => '(?:[A-Za-z0-9\-\._]|%\h\h)',
|
46
|
+
|
45
47
|
:class_name => 'c_u_',
|
46
48
|
:grabs_comma => false
|
47
49
|
},
|
48
50
|
:unreserved_reserved_pct => {
|
49
|
-
:unencoded => /([^A-Za-z0-9\-\._:\/?#\[\]@!\$%'\(\)*+,;=]|%(?!\h\h))
|
50
|
-
:class => '(
|
51
|
+
:unencoded => /([^A-Za-z0-9\-\._:\/?#\[\]@!\$%'\(\)*+,;=]|%(?!\h\h))/u,
|
52
|
+
:class => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%\h\h)',
|
51
53
|
:class_name => 'c_urp_',
|
52
54
|
:grabs_comma => true
|
53
55
|
},
|
54
56
|
|
55
57
|
:varname => {
|
56
|
-
:class => '(
|
58
|
+
:class => '(?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*?',
|
57
59
|
:class_name => 'c_vn_'
|
58
60
|
}
|
59
61
|
|
@@ -76,31 +78,21 @@ class URITemplate::Draft7
|
|
76
78
|
DEFAULT_PROCESSING = CONVERT_VALUES + CONVERT_RESULT
|
77
79
|
|
78
80
|
# @private
|
79
|
-
VAR = Regexp.compile(<<'__REGEXP__'.strip,
|
80
|
-
(
|
81
|
-
(?<varchar> [a-zA-Z_]|%[0-9a-fA-F]{2}){0}
|
82
|
-
(?<varname> \g<varchar>(?:\g<varchar>|\.)*){0}
|
83
|
-
(?<varspec> \g<varname>(?<explode>\*?)(?::(?<length>\d+))?){0}
|
84
|
-
\g<varspec>
|
81
|
+
VAR = Regexp.compile(<<'__REGEXP__'.strip, Utils::KCODE_UTF8)
|
82
|
+
((?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*)(\*)?(?::(\d+))?
|
85
83
|
__REGEXP__
|
86
84
|
|
87
85
|
# @private
|
88
|
-
EXPRESSION = Regexp.compile(<<'__REGEXP__'.strip,
|
89
|
-
(
|
90
|
-
(?<varchar> [a-zA-Z_]|%[0-9a-fA-F]{2}){0}
|
91
|
-
(?<varname> \g<varchar>(?:\g<varchar>|\.)*){0}
|
92
|
-
(?<varspec> \g<varname>\*?(?::\d+)?){0}
|
93
|
-
\{\g<operator>(?<vars>\g<varspec>(?:,\g<varspec>)*)\}
|
86
|
+
EXPRESSION = Regexp.compile(<<'__REGEXP__'.strip, Utils::KCODE_UTF8)
|
87
|
+
\{([+#\./;?&]?)((?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*\*?(?::\d+)?(?:,(?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*\*?(?::\d+)?)*)\}
|
94
88
|
__REGEXP__
|
95
89
|
|
96
90
|
# @private
|
97
|
-
URI = Regexp.compile(<<'__REGEXP__'.strip,
|
98
|
-
(
|
99
|
-
(?<varchar> [a-zA-Z_]|%[0-9a-fA-F]{2}){0}
|
100
|
-
(?<varname> \g<varchar>(?:\g<varchar>|\.)*){0}
|
101
|
-
(?<varspec> \g<varname>\*?(?::\d+)?){0}
|
102
|
-
^(([^"'%<>^`{|}\s\p{Cc}]|%\h\h)+|\{\g<operator>(?<vars>\g<varspec>(?:,\g<varspec>)*)\})*$
|
91
|
+
URI = Regexp.compile(<<'__REGEXP__'.strip, Utils::KCODE_UTF8)
|
92
|
+
\A(([^"'%<>^`{|}\s\p{Cntrl}]|%\h\h)+|\{([+#\./;?&]?)((?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*\*?(?::\d+)?(?:,(?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*\*?(?::\d+)?)*)\})*\z
|
103
93
|
__REGEXP__
|
94
|
+
|
95
|
+
SLASH = ?/
|
104
96
|
|
105
97
|
# @private
|
106
98
|
class Token
|
@@ -199,12 +191,12 @@ __REGEXP__
|
|
199
191
|
end
|
200
192
|
|
201
193
|
def to_s
|
202
|
-
'{' + self.class::OPERATOR +
|
194
|
+
return '{' + self.class::OPERATOR + @variable_specs.map{|name,expand,max_length| name + (expand ? '*': '') + (max_length > 0 ? (':' + max_length.to_s) : '') }.join(',') + '}'
|
203
195
|
end
|
204
196
|
|
205
197
|
#TODO: certain things after a slurpy variable will never get matched. therefore, it's pointless to add expressions for them
|
206
198
|
#TODO: variables, which appear twice could be compacted, don't they?
|
207
|
-
def to_r_source
|
199
|
+
def to_r_source
|
208
200
|
source = []
|
209
201
|
first = true
|
210
202
|
vs = @variable_specs.size - 1
|
@@ -212,21 +204,21 @@ __REGEXP__
|
|
212
204
|
if self.class::NAMED
|
213
205
|
@variable_specs.each{| var, expand , max_length |
|
214
206
|
last = (vs == i)
|
215
|
-
value = "(
|
207
|
+
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
|
216
208
|
if expand
|
217
209
|
#if self.class::PAIR_IF_EMPTY
|
218
|
-
pair = "(
|
210
|
+
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
219
211
|
|
220
212
|
if first
|
221
|
-
source << "(
|
213
|
+
source << "((?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
222
214
|
else
|
223
|
-
source << "(
|
215
|
+
source << "((?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
224
216
|
end
|
225
217
|
else
|
226
218
|
if self.class::PAIR_IF_EMPTY
|
227
|
-
pair = "#{Regexp.escape(var)}(
|
219
|
+
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})"
|
228
220
|
else
|
229
|
-
pair = "#{Regexp.escape(var)}(
|
221
|
+
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value}|)"
|
230
222
|
end
|
231
223
|
|
232
224
|
if first
|
@@ -244,26 +236,26 @@ __REGEXP__
|
|
244
236
|
last = (vs == i)
|
245
237
|
if expand
|
246
238
|
# could be list or map, too
|
247
|
-
value = "
|
239
|
+
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
|
248
240
|
|
249
|
-
pair = "(
|
241
|
+
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
250
242
|
|
251
243
|
value = "#{pair}(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*"
|
252
244
|
elsif last
|
253
245
|
# the last will slurp lists
|
254
246
|
if self.class::CHARACTER_CLASS[:grabs_comma]
|
255
|
-
value = "
|
247
|
+
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
256
248
|
else
|
257
|
-
value = "(
|
249
|
+
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
258
250
|
end
|
259
251
|
else
|
260
|
-
value = "
|
252
|
+
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
261
253
|
end
|
262
254
|
if first
|
263
|
-
source << "(
|
255
|
+
source << "(#{value})"
|
264
256
|
first = false
|
265
257
|
else
|
266
|
-
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}(
|
258
|
+
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}(#{value}))?"
|
267
259
|
end
|
268
260
|
i = i+1
|
269
261
|
}
|
@@ -281,21 +273,24 @@ __REGEXP__
|
|
281
273
|
rest = matched
|
282
274
|
splitted = []
|
283
275
|
found_value = false
|
276
|
+
# 1 = name
|
277
|
+
# 2 = value
|
278
|
+
# 3 = rest
|
284
279
|
until rest.size == 0
|
285
280
|
match = ex.match(rest)
|
286
281
|
if match.nil?
|
287
282
|
raise "Couldn't match #{rest.inspect} againts the hash extractor. This is definitly a Bug. Please report this ASAP!"
|
288
283
|
end
|
289
284
|
if match.post_match.size == 0
|
290
|
-
rest = match[
|
285
|
+
rest = match[3].to_s
|
291
286
|
else
|
292
287
|
rest = ''
|
293
288
|
end
|
294
|
-
if match[
|
289
|
+
if match[1]
|
295
290
|
found_value = true
|
296
|
-
splitted << [ match[
|
291
|
+
splitted << [ match[1][0..-2], decode(match[2] + rest , false) ]
|
297
292
|
else
|
298
|
-
splitted << [ match[
|
293
|
+
splitted << [ match[2] + rest, nil ]
|
299
294
|
end
|
300
295
|
rest = match.post_match
|
301
296
|
end
|
@@ -318,10 +313,10 @@ __REGEXP__
|
|
318
313
|
def hash_extractor(max_length)
|
319
314
|
@hash_extractors ||= {}
|
320
315
|
@hash_extractors[max_length] ||= begin
|
321
|
-
value = "
|
322
|
-
pair = "(
|
323
|
-
|
324
|
-
Regexp.new(
|
316
|
+
value = "#{self::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
317
|
+
pair = "(#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self::PAIR_CONNECTOR)})?(#{value})"
|
318
|
+
source = "\\A#{Regexp.escape(self::SEPARATOR)}?" + pair + "(\\z|#{Regexp.escape(self::SEPARATOR)}(?!#{Regexp.escape(self::SEPARATOR)}))"
|
319
|
+
Regexp.new( source , Utils::KCODE_UTF8)
|
325
320
|
end
|
326
321
|
end
|
327
322
|
|
@@ -366,7 +361,7 @@ __REGEXP__
|
|
366
361
|
|
367
362
|
def cut(str,chars)
|
368
363
|
if chars > 0
|
369
|
-
md = Regexp.compile("
|
364
|
+
md = Regexp.compile("\\A#{self.class::CHARACTER_CLASS[:class]}{,#{chars.to_s}}", Utils::KCODE_UTF8).match(str)
|
370
365
|
#TODO: handle invalid matches
|
371
366
|
return md[0]
|
372
367
|
else
|
@@ -518,9 +513,12 @@ __REGEXP__
|
|
518
513
|
until scanner.eos?
|
519
514
|
expression = scanner.scan(EXPRESSION)
|
520
515
|
if expression
|
521
|
-
vars = scanner[
|
516
|
+
vars = scanner[2].split(',').map{|name|
|
522
517
|
match = VAR.match(name)
|
523
|
-
|
518
|
+
# 1 = varname
|
519
|
+
# 2 = explode
|
520
|
+
# 3 = length
|
521
|
+
[ match[1], match[2] == '*', match[3].to_i ]
|
524
522
|
}
|
525
523
|
yield OPERATORS[scanner[1]].new(vars)
|
526
524
|
else
|
@@ -643,13 +641,14 @@ __REGEXP__
|
|
643
641
|
#
|
644
642
|
# @return Regexp
|
645
643
|
def to_r
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
644
|
+
@regexp ||= begin
|
645
|
+
#classes = CHARACTER_CLASSES.map{|_,v| v[:class]+"{0}\n" }
|
646
|
+
bc = 0
|
647
|
+
source = tokens.map(&:to_r_source)
|
648
|
+
source.unshift('\A')
|
649
|
+
source.push('\z')
|
650
|
+
Regexp.new( source.join, Utils::KCODE_UTF8)
|
651
|
+
end
|
653
652
|
end
|
654
653
|
|
655
654
|
|
@@ -693,7 +692,7 @@ __REGEXP__
|
|
693
692
|
if uri_or_match.kind_of? String
|
694
693
|
m = self.to_r.match(uri_or_match)
|
695
694
|
elsif uri_or_match.kind_of?(MatchData)
|
696
|
-
if uri_or_match.regexp != self.to_r
|
695
|
+
if uri_or_match.respond_to?(:regexp) and uri_or_match.regexp != self.to_r
|
697
696
|
raise ArgumentError, "Trying to extract variables from MatchData which was not generated by this template."
|
698
697
|
end
|
699
698
|
m = uri_or_match
|
@@ -705,15 +704,7 @@ __REGEXP__
|
|
705
704
|
if m.nil?
|
706
705
|
return nil
|
707
706
|
else
|
708
|
-
result = extract_matchdata(m)
|
709
|
-
if post_processing.include? :convert_values
|
710
|
-
result.map!{|k,v| [k, Utils.pair_array_to_hash(v)] }
|
711
|
-
end
|
712
|
-
|
713
|
-
if post_processing.include? :convert_result
|
714
|
-
result = Utils.pair_array_to_hash(result)
|
715
|
-
end
|
716
|
-
|
707
|
+
result = extract_matchdata(m, post_processing)
|
717
708
|
if block_given?
|
718
709
|
return yield result
|
719
710
|
end
|
@@ -817,10 +808,10 @@ __REGEXP__
|
|
817
808
|
# Merge!
|
818
809
|
# Analyze the last token of this an the first token of the next and try to merge them
|
819
810
|
if self.tokens.last.kind_of?(Literal)
|
820
|
-
if self.tokens.last.string[-1] ==
|
811
|
+
if self.tokens.last.string[-1] == SLASH # the last token ends with an /
|
821
812
|
if other.tokens.first.kind_of? Literal
|
822
813
|
# both seems to be paths, merge them!
|
823
|
-
if other.tokens.first.string[0] ==
|
814
|
+
if other.tokens.first.string[0] == SLASH
|
824
815
|
# strip one '/'
|
825
816
|
return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string + other.tokens.first.string[1..-1]) ] + other.tokens[1..-1] )
|
826
817
|
else
|
@@ -834,7 +825,7 @@ __REGEXP__
|
|
834
825
|
end
|
835
826
|
elsif other.tokens.first.kind_of? Literal
|
836
827
|
# okay, this template does not end with /, but the next starts with a literal => merge them!
|
837
|
-
if other.tokens.first.string[0] ==
|
828
|
+
if other.tokens.first.string[0] == SLASH
|
838
829
|
return self.class.new( self.tokens[0..-2] + [Literal.new(self.tokens.last.string + other.tokens.first.string)] + other.tokens[1..-1] )
|
839
830
|
else
|
840
831
|
return self.class.new( self.tokens[0..-2] + [Literal.new(self.tokens.last.string + '/' + other.tokens.first.string)] + other.tokens[1..-1] )
|
@@ -843,7 +834,7 @@ __REGEXP__
|
|
843
834
|
end
|
844
835
|
|
845
836
|
if other.tokens.first.kind_of?(Literal)
|
846
|
-
if other.tokens.first.string[0] ==
|
837
|
+
if other.tokens.first.string[0] == SLASH
|
847
838
|
return self.class.new( self.tokens + other.tokens )
|
848
839
|
else
|
849
840
|
return self.class.new( self.tokens + [Literal.new('/' + other.tokens.first.string)]+ other.tokens[1..-1] )
|
@@ -866,19 +857,40 @@ protected
|
|
866
857
|
Tokenizer.new(pattern).to_a
|
867
858
|
end
|
868
859
|
|
860
|
+
def arity
|
861
|
+
@arity ||= tokens.inject(0){|a,t| a + t.arity }
|
862
|
+
end
|
863
|
+
|
869
864
|
# @private
|
870
|
-
def extract_matchdata(matchdata)
|
871
|
-
bc =
|
865
|
+
def extract_matchdata(matchdata, post_processing)
|
866
|
+
bc = 1
|
872
867
|
vars = []
|
873
868
|
tokens.each{|part|
|
869
|
+
next if part.literal?
|
874
870
|
i = 0
|
875
|
-
|
876
|
-
|
871
|
+
pa = part.arity
|
872
|
+
while i < pa
|
873
|
+
vars << part.extract(i, matchdata[bc])
|
877
874
|
bc += 1
|
878
875
|
i += 1
|
879
876
|
end
|
880
877
|
}
|
881
|
-
|
878
|
+
if post_processing.include? :convert_result
|
879
|
+
if post_processing.include? :convert_values
|
880
|
+
vars.flatten!(1)
|
881
|
+
return Hash[*vars.map!{|k,v| [k,Utils.pair_array_to_hash(v)] }.flatten(1) ]
|
882
|
+
else
|
883
|
+
vars.flatten!(2)
|
884
|
+
return Hash[*vars]
|
885
|
+
end
|
886
|
+
else
|
887
|
+
if post_processing.include? :convert_value
|
888
|
+
vars.flatten!(1)
|
889
|
+
return vars.collect{|k,v| [k,Utils.pair_array_to_hash(v)] }
|
890
|
+
else
|
891
|
+
return vars.flatten(1)
|
892
|
+
end
|
893
|
+
end
|
882
894
|
end
|
883
895
|
|
884
896
|
end
|
data/lib/uri_template/utils.rb
CHANGED
@@ -67,12 +67,52 @@ module URITemplate
|
|
67
67
|
# A collection of some utility methods
|
68
68
|
module Utils
|
69
69
|
|
70
|
+
KCODE_UTF8 = (Regexp::KCODE_UTF8 rescue 0)
|
71
|
+
|
70
72
|
# @private
|
71
73
|
PCT = /%(\h\h)/.freeze
|
72
74
|
|
73
75
|
# A regexp which match all non-simple characters.
|
74
76
|
NOT_SIMPLE_CHARS = /([^A-Za-z0-9\-\._])/.freeze
|
75
77
|
|
78
|
+
ASCII = 'ASCII'.freeze
|
79
|
+
UTF8 = 'UTF-8'.freeze
|
80
|
+
|
81
|
+
def to_ascii_force_encoding(str)
|
82
|
+
str.force_encoding(ASCII)
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_utf8_force_encoding(str)
|
86
|
+
str.force_encoding(UTF8)
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_ascii_encode(str)
|
90
|
+
str.encode(ASCII)
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_utf8_encode(str)
|
94
|
+
str.encode(UTF8)
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_ascii_fallback(str)
|
98
|
+
str
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_utf8_fallback(str)
|
102
|
+
str
|
103
|
+
end
|
104
|
+
|
105
|
+
if "".respond_to?(:force_encoding)
|
106
|
+
alias_method :to_ascii, :to_ascii_force_encoding
|
107
|
+
alias_method :to_utf8, :to_utf8_force_encoding
|
108
|
+
elsif "".respond_to?(:encode)
|
109
|
+
alias_method :to_ascii, :to_ascii_encode
|
110
|
+
alias_method :to_utf8, :to_utf8_encode
|
111
|
+
else
|
112
|
+
alias_method :to_ascii, :to_ascii_fallback
|
113
|
+
alias_method :to_utf8, :to_utf8_fallback
|
114
|
+
end
|
115
|
+
|
76
116
|
# Encodes the given string into a pct-encoded string.
|
77
117
|
# @param s The string to be encoded.
|
78
118
|
# @param m A regexp matching all characters, which need to be encoded.
|
@@ -81,10 +121,11 @@ module URITemplate
|
|
81
121
|
# URITemplate::Utils.pct("abc") #=> "abc"
|
82
122
|
# URITemplate::Utils.pct("%") #=> "%25"
|
83
123
|
#
|
124
|
+
#TODO: is encoding as utf8/ascii really needed?
|
84
125
|
def pct(s, m=NOT_SIMPLE_CHARS)
|
85
|
-
s.to_s.
|
126
|
+
to_ascii( s.to_s.gsub(m){
|
86
127
|
'%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
|
87
|
-
}
|
128
|
+
} )
|
88
129
|
end
|
89
130
|
|
90
131
|
# Decodes the given pct-encoded string into a utf-8 string.
|
@@ -95,9 +136,9 @@ module URITemplate
|
|
95
136
|
# URITemplate::Utils.dpct("%25") #=> "%"
|
96
137
|
#
|
97
138
|
def dpct(s)
|
98
|
-
s.to_s.
|
139
|
+
to_utf8(s.to_s.gsub(PCT){
|
99
140
|
$1.to_i(16).chr
|
100
|
-
}
|
141
|
+
})
|
101
142
|
end
|
102
143
|
|
103
144
|
# Converts an object to a param value.
|
@@ -139,20 +180,26 @@ module URITemplate
|
|
139
180
|
# Turns the given value into a hash if it is an array of pairs.
|
140
181
|
# Otherwise it returns the value.
|
141
182
|
# You can test whether a value will be converted with {#pair_array?}.
|
183
|
+
#
|
142
184
|
# @example
|
143
185
|
# URITemplate::Utils.pair_array_to_hash( 'x' ) #=> 'x'
|
144
186
|
# URITemplate::Utils.pair_array_to_hash( [ ['a',1],['b',2],['c',3] ] ) #=> {'a'=>1,'b'=>2,'c'=>3}
|
145
187
|
# URITemplate::Utils.pair_array_to_hash( [ ['a',1],['a',2],['a',3] ] ) #=> {'a'=>3}
|
146
|
-
|
147
|
-
|
148
|
-
|
188
|
+
#
|
189
|
+
# @example Carful vs. Ignorant
|
190
|
+
# URITemplate::Utils.pair_array_to_hash( [ ['a',1],'foo','bar'], false ) #=> {'a'=>1,'foo'=>'bar'}
|
191
|
+
# URITemplate::Utils.pair_array_to_hash( [ ['a',1],'foo','bar'], true ) #=> [ ['a',1],'foo','bar']
|
192
|
+
#
|
193
|
+
# @param x the value to convert
|
194
|
+
# @param careful [true,false] wheter to check every array item. Use this when you expect array with subarrays which are not pairs. Setting this to false however improves runtime by ~30% even with comparetivly short arrays.
|
195
|
+
def pair_array_to_hash(x, careful = false )
|
196
|
+
if careful ? pair_array?(x) : (x.kind_of?(Array) and x.first.kind_of?(Array))
|
197
|
+
return Hash[ *x.flatten(1) ]
|
149
198
|
else
|
150
|
-
return
|
199
|
+
return x
|
151
200
|
end
|
152
201
|
end
|
153
202
|
|
154
|
-
|
155
|
-
|
156
203
|
extend self
|
157
204
|
|
158
205
|
end
|
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.1.
|
4
|
-
s.date = '2011-11-
|
3
|
+
s.version = '0.1.4'
|
4
|
+
s.date = '2011-11-19'
|
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 gem however is intended to be extended when newer specs evolve. For now only draft 7 is supported. Downside:
|
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: not for 1.8.7.'
|
10
10
|
|
11
11
|
s.require_paths = ['lib']
|
12
12
|
|
@@ -14,4 +14,6 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.add_development_dependency 'rspec'
|
16
16
|
s.add_development_dependency 'yard'
|
17
|
+
s.add_development_dependency 'rake'
|
18
|
+
s.add_development_dependency 'bundler'
|
17
19
|
end
|
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.1.
|
4
|
+
version: 0.1.4
|
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-19 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &16477840 !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: *16477840
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: yard
|
27
|
-
requirement: &
|
27
|
+
requirement: &16477400 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,11 +32,33 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *16477400
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &16476980 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *16476980
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: &16476560 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *16476560
|
36
58
|
description: ! 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07
|
37
59
|
. An implementation of an older version of that spec is known as addressable. This
|
38
60
|
gem however is intended to be extended when newer specs evolve. For now only draft
|
39
|
-
7 is supported. Downside:
|
61
|
+
7 is supported. Downside: not for 1.8.7.'
|
40
62
|
email: hannes.georg@googlemail.com
|
41
63
|
executables: []
|
42
64
|
extensions: []
|