uri_template 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/README
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
= URITemplate - a uri template library
|
2
|
-
|
3
|
-
{<img src="https://secure.travis-ci.org/hannesg/uri_template.png" />}[http://travis-ci.org/hannesg/uri_template] {<img src="https://gemnasium.com/hannesg/uri_template.png" />}[https://gemnasium.com/hannesg/uri_template]
|
4
|
-
|
5
|
-
With URITemplate you can generate URI based on simple templates. The syntax if defined by RFC 6570 ( http://tools.ietf.org/html/rfc6570 ).
|
6
|
-
|
7
|
-
For backward compatibility, there is an implementation based on draft 7 of the uri template spec ( http://tools.ietf.org/html/draft-gregorio-uritemplate-07 ), too. For the syntax read that spec. Draft 2 of that specification is implemented as addressable gem.
|
8
|
-
|
9
|
-
It's currently planed to add newer versions of that spec when they emerge. Therefore the module URITemplate just defines one abstract method ( expand ) and some methods to access the actual implementations.
|
10
|
-
|
11
|
-
From version 0.2.0, it will use escape_utils if available. This will significantly boost uri-escape/unescape performance if more characters need to be escaped ( may be slightly slower in trivial cases. working on that ... ), but does not run everywhere. To enable this, do the following:
|
12
|
-
|
13
|
-
````ruby
|
14
|
-
# escape_utils has to be loaded when uri_templates is loaded
|
15
|
-
gem 'escape_utils'
|
16
|
-
require 'escape_utils'
|
17
|
-
|
18
|
-
gem 'uri_template'
|
19
|
-
require 'uri_template'
|
20
|
-
|
21
|
-
UriTemplate::Utils.using_escape_utils? #=> true
|
22
|
-
|
23
|
-
|
24
|
-
== Examples
|
25
|
-
|
26
|
-
require 'uri_template'
|
27
|
-
|
28
|
-
tpl = URITemplate.new('http://{host}{/segments*}/{file}{.extensions*}')
|
29
|
-
|
30
|
-
# This will give: http://www.host.com/path/to/a/file.x.y
|
31
|
-
tpl.expand('host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y'])
|
32
|
-
|
33
|
-
# This will give: { 'host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y']}
|
34
|
-
tpl.extract('http://www.host.com/path/to/a/file.x.y')
|
35
|
-
|
36
|
-
|
37
|
-
== Benchmarks
|
38
|
-
|
39
|
-
* System: Core 2 Duo T9300, 4 gb ram, ubuntu 12.04 64 bit, ruby 1.9.3, 100_000 repetitions
|
40
|
-
* Implementation: RFC6570 ( version 0.3.0 ) vs. Addressable ( 2.2.8 )
|
41
|
-
* Results marked with * means that the template object is reused.
|
42
|
-
|
43
|
-
Addressable | Addressable* | RFC6570 | RFC6570* |
|
44
|
-
--Expansion-----------------------------------------------------------------------------------------
|
45
|
-
Empty string 8.505 | 8.439 | 0.539 | 0.064 |
|
46
|
-
One simple variable 19.717 | 19.721 | 3.031 | 1.169 |
|
47
|
-
One escaped variable 21.873 | 22.017 | 3.573 | 1.705 |
|
48
|
-
One missing variable 9.676 | 11.981 | 2.633 | 0.352 |
|
49
|
-
Path segments 29.901 | 31.698 | 4.929 | 3.051 |
|
50
|
-
Arguments 36.584 | 36.531 | 9.540 | 5.982 |
|
51
|
-
Full URI 109.102 | 116.458 | 19.806 | 10.548 |
|
52
|
-
Segments and Arguments 108.103 | 107.750 | 14.059 | 7.593 |
|
53
|
-
total 343.461 | 354.596 | 58.109 | 30.464 |
|
54
|
-
--Extraction----------------------------------------------------------------------------------------
|
55
|
-
Empty string 21.422 | 21.843 | 3.122 | 0.804 |
|
56
|
-
One simple variable 39.860 | 43.840 | 10.671 | 2.874 |
|
57
|
-
One escaped variable 51.321 | 50.790 | 10.963 | 2.040 |
|
58
|
-
One missing variable 26.125 | 26.320 | 9.400 | 1.847 |
|
59
|
-
Path segments 62.712 | 64.191 | 18.502 | 4.518 |
|
60
|
-
Arguments 81.350 | 81.258 | 20.762 | 5.401 |
|
61
|
-
Full URI 145.339 | 141.795 | 45.306 | 11.096 |
|
62
|
-
Segments and Arguments 124.431 | 122.885 | 34.208 | 8.126 |
|
63
|
-
Segments and Arguments ( not extractable ) 34.183 | 34.200 | 26.542 | 0.410 |
|
64
|
-
total 586.743 | 587.122 | 179.476 | 37.116 |
|
65
|
-
|
66
|
-
SUCCESS - URITemplate was faster in every test!
|
67
|
-
|
data/lib/uri_template/draft7.rb
DELETED
@@ -1,266 +0,0 @@
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
2
|
-
# This program is free software: you can redistribute it and/or modify
|
3
|
-
# it under the terms of the Affero GNU General Public License as published by
|
4
|
-
# the Free Software Foundation, either version 3 of the License, or
|
5
|
-
# (at your option) any later version.
|
6
|
-
#
|
7
|
-
# This program is distributed in the hope that it will be useful,
|
8
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
9
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
10
|
-
# GNU General Public License for more details.
|
11
|
-
#
|
12
|
-
# You should have received a copy of the GNU General Public License
|
13
|
-
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
14
|
-
#
|
15
|
-
# (c) 2011 - 2012 by Hannes Georg
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
# A uri template which should comply with the uri template spec draft 7 ( http://tools.ietf.org/html/draft-gregorio-uritemplate-07 ).
|
20
|
-
# This class is here for backward compatibility. There is already a newer draft of the spec and an rfc.
|
21
|
-
class URITemplate::Draft7 < URITemplate::RFC6570
|
22
|
-
|
23
|
-
TYPE = :draft7
|
24
|
-
|
25
|
-
CHARACTER_CLASSES = URITemplate::RFC6570::CHARACTER_CLASSES
|
26
|
-
|
27
|
-
Utils = URITemplate::Utils
|
28
|
-
|
29
|
-
class Expression < URITemplate::RFC6570::Expression
|
30
|
-
|
31
|
-
def extract(position,matched)
|
32
|
-
name, expand, max_length = @variable_specs[position]
|
33
|
-
if matched.nil?
|
34
|
-
return [[ name , matched ]]
|
35
|
-
end
|
36
|
-
if expand
|
37
|
-
#TODO: do we really need this? - this could be stolen from rack
|
38
|
-
ex = self.class.hash_extractor(max_length)
|
39
|
-
rest = matched
|
40
|
-
splitted = []
|
41
|
-
found_value = false
|
42
|
-
# 1 = name
|
43
|
-
# 2 = value
|
44
|
-
# 3 = rest
|
45
|
-
until rest.size == 0
|
46
|
-
match = ex.match(rest)
|
47
|
-
if match.nil?
|
48
|
-
raise "Couldn't match #{rest.inspect} againts the hash extractor. This is definitly a Bug. Please report this ASAP!"
|
49
|
-
end
|
50
|
-
if match.post_match.size == 0
|
51
|
-
rest = match[3].to_s
|
52
|
-
else
|
53
|
-
rest = ''
|
54
|
-
end
|
55
|
-
if match[1]
|
56
|
-
found_value = true
|
57
|
-
splitted << [ match[1][0..-2], decode(match[2] + rest , false) ]
|
58
|
-
else
|
59
|
-
splitted << [ match[2] + rest, nil ]
|
60
|
-
end
|
61
|
-
rest = match.post_match
|
62
|
-
end
|
63
|
-
if !found_value
|
64
|
-
return [ [ name, splitted.map{|n,v| decode(n , false) } ] ]
|
65
|
-
else
|
66
|
-
return [ [ name, splitted ] ]
|
67
|
-
end
|
68
|
-
elsif self.class::NAMED
|
69
|
-
return [ [ name, decode( matched[1..-1] ) ] ]
|
70
|
-
end
|
71
|
-
|
72
|
-
return [ [ name, decode( matched ) ] ]
|
73
|
-
end
|
74
|
-
|
75
|
-
def to_r_source
|
76
|
-
source = []
|
77
|
-
first = true
|
78
|
-
vs = @variable_specs.size - 1
|
79
|
-
i = 0
|
80
|
-
if self.class::NAMED
|
81
|
-
@variable_specs.each{| var, expand , max_length |
|
82
|
-
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*'}"
|
83
|
-
if expand
|
84
|
-
#if self.class::PAIR_IF_EMPTY
|
85
|
-
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
86
|
-
|
87
|
-
if first
|
88
|
-
source << "((?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
89
|
-
else
|
90
|
-
source << "((?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
91
|
-
end
|
92
|
-
else
|
93
|
-
if self.class::PAIR_IF_EMPTY
|
94
|
-
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})"
|
95
|
-
else
|
96
|
-
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value}|)"
|
97
|
-
end
|
98
|
-
|
99
|
-
if first
|
100
|
-
source << "(?:#{pair})"
|
101
|
-
else
|
102
|
-
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})?"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
first = false
|
107
|
-
i = i+1
|
108
|
-
}
|
109
|
-
else
|
110
|
-
@variable_specs.each{| var, expand , max_length |
|
111
|
-
last = (vs == i)
|
112
|
-
if expand
|
113
|
-
# could be list or map, too
|
114
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*'}"
|
115
|
-
|
116
|
-
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
117
|
-
|
118
|
-
value = "#{pair}(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*"
|
119
|
-
elsif last
|
120
|
-
# the last will slurp lists
|
121
|
-
if self.class::CHARACTER_CLASS[:grabs_comma]
|
122
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
123
|
-
else
|
124
|
-
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
125
|
-
end
|
126
|
-
else
|
127
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
128
|
-
end
|
129
|
-
if first
|
130
|
-
source << "(#{value})"
|
131
|
-
first = false
|
132
|
-
else
|
133
|
-
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}(#{value}))?"
|
134
|
-
end
|
135
|
-
i = i+1
|
136
|
-
}
|
137
|
-
end
|
138
|
-
return '(?:' + Regexp.escape(self.class::PREFIX) + source.join + ')?'
|
139
|
-
end
|
140
|
-
|
141
|
-
protected
|
142
|
-
|
143
|
-
def transform_array(name, ary, expand , max_length)
|
144
|
-
if expand
|
145
|
-
ary.map{|value| escape(value) }
|
146
|
-
elsif ary.none?
|
147
|
-
[]
|
148
|
-
else
|
149
|
-
[ (self.class::NAMED ? escape(name)+self.class::PAIR_CONNECTOR : '' ) + ary.map{|value| escape(value) }.join(self.class::LIST_CONNECTOR) ]
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
module ClassMethods
|
154
|
-
|
155
|
-
def hash_extractor(max_length)
|
156
|
-
@hash_extractors ||= {}
|
157
|
-
@hash_extractors[max_length] ||= begin
|
158
|
-
value = "#{self::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
159
|
-
pair = "(#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self::PAIR_CONNECTOR)})?(#{value})"
|
160
|
-
source = "\\A#{Regexp.escape(self::SEPARATOR)}?" + pair + "(\\z|#{Regexp.escape(self::SEPARATOR)}(?!#{Regexp.escape(self::SEPARATOR)}))"
|
161
|
-
Regexp.new( source , Utils::KCODE_UTF8)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
end
|
166
|
-
|
167
|
-
extend ClassMethods
|
168
|
-
|
169
|
-
class Reserved < self
|
170
|
-
|
171
|
-
CHARACTER_CLASS = URITemplate::RFC6570::CHARACTER_CLASSES[:unreserved_reserved_pct]
|
172
|
-
OPERATOR = '+'.freeze
|
173
|
-
BASE_LEVEL = 2
|
174
|
-
|
175
|
-
def escape(x)
|
176
|
-
Utils.escape_uri(Utils.object_to_param(x))
|
177
|
-
end
|
178
|
-
|
179
|
-
def unescape(x)
|
180
|
-
Utils.unescape_uri(x)
|
181
|
-
end
|
182
|
-
|
183
|
-
end
|
184
|
-
|
185
|
-
class Fragment < self
|
186
|
-
|
187
|
-
CHARACTER_CLASS = URITemplate::RFC6570::CHARACTER_CLASSES[:unreserved_reserved_pct]
|
188
|
-
PREFIX = '#'.freeze
|
189
|
-
OPERATOR = '#'.freeze
|
190
|
-
BASE_LEVEL = 2
|
191
|
-
|
192
|
-
def escape(x)
|
193
|
-
Utils.escape_uri(Utils.object_to_param(x))
|
194
|
-
end
|
195
|
-
|
196
|
-
def unescape(x)
|
197
|
-
Utils.unescape_uri(x)
|
198
|
-
end
|
199
|
-
|
200
|
-
end
|
201
|
-
|
202
|
-
class Label < self
|
203
|
-
|
204
|
-
SEPARATOR = '.'.freeze
|
205
|
-
PREFIX = '.'.freeze
|
206
|
-
OPERATOR = '.'.freeze
|
207
|
-
BASE_LEVEL = 3
|
208
|
-
|
209
|
-
end
|
210
|
-
|
211
|
-
class Path < self
|
212
|
-
|
213
|
-
SEPARATOR = '/'.freeze
|
214
|
-
PREFIX = '/'.freeze
|
215
|
-
OPERATOR = '/'.freeze
|
216
|
-
BASE_LEVEL = 3
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
class PathParameters < self
|
221
|
-
|
222
|
-
SEPARATOR = ';'.freeze
|
223
|
-
PREFIX = ';'.freeze
|
224
|
-
NAMED = true
|
225
|
-
PAIR_IF_EMPTY = false
|
226
|
-
OPERATOR = ';'.freeze
|
227
|
-
BASE_LEVEL = 3
|
228
|
-
|
229
|
-
end
|
230
|
-
|
231
|
-
class FormQuery < self
|
232
|
-
|
233
|
-
SEPARATOR = '&'.freeze
|
234
|
-
PREFIX = '?'.freeze
|
235
|
-
NAMED = true
|
236
|
-
OPERATOR = '?'.freeze
|
237
|
-
BASE_LEVEL = 3
|
238
|
-
|
239
|
-
end
|
240
|
-
|
241
|
-
class FormQueryContinuation < self
|
242
|
-
|
243
|
-
SEPARATOR = '&'.freeze
|
244
|
-
PREFIX = '&'.freeze
|
245
|
-
NAMED = true
|
246
|
-
OPERATOR = '&'.freeze
|
247
|
-
BASE_LEVEL = 3
|
248
|
-
|
249
|
-
end
|
250
|
-
|
251
|
-
|
252
|
-
end
|
253
|
-
|
254
|
-
# @private
|
255
|
-
OPERATORS = {
|
256
|
-
'' => Expression,
|
257
|
-
'+' => Expression::Reserved,
|
258
|
-
'#' => Expression::Fragment,
|
259
|
-
'.' => Expression::Label,
|
260
|
-
'/' => Expression::Path,
|
261
|
-
';' => Expression::PathParameters,
|
262
|
-
'?' => Expression::FormQuery,
|
263
|
-
'&' => Expression::FormQueryContinuation
|
264
|
-
}
|
265
|
-
|
266
|
-
end
|