uri_template 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/lib/uri_template.rb +43 -35
- data/lib/uri_template/colon.rb +21 -21
- data/lib/uri_template/draft7.rb +137 -141
- data/lib/uri_template/utils.rb +47 -49
- data/uri_template.gemspec +3 -3
- metadata +12 -12
data/CHANGELOG
CHANGED
data/lib/uri_template.rb
CHANGED
@@ -18,66 +18,74 @@
|
|
18
18
|
# A base module for all implementations of a uri template.
|
19
19
|
module URITemplate
|
20
20
|
|
21
|
+
# @private
|
22
|
+
# Should we use \u.... or \x.. in regexps?
|
23
|
+
SUPPORTS_UNICODE_CHARS = begin
|
24
|
+
rx = eval('/\u0020/')
|
25
|
+
!!(rx =~ " ")
|
26
|
+
rescue SyntaxError
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
21
30
|
# This should make it possible to do basic analysis independently from the concrete type.
|
22
31
|
module Token
|
23
|
-
|
32
|
+
|
24
33
|
def size
|
25
34
|
variables.size
|
26
35
|
end
|
27
|
-
|
36
|
+
|
28
37
|
end
|
29
|
-
|
38
|
+
|
30
39
|
# A module which all literal tokens should include.
|
31
40
|
module Literal
|
32
|
-
|
41
|
+
|
33
42
|
include Token
|
34
|
-
|
43
|
+
|
35
44
|
attr_reader :string
|
36
|
-
|
45
|
+
|
37
46
|
def literal?
|
38
47
|
true
|
39
48
|
end
|
40
|
-
|
49
|
+
|
41
50
|
def expression?
|
42
51
|
false
|
43
52
|
end
|
44
|
-
|
53
|
+
|
45
54
|
def variables
|
46
55
|
[]
|
47
56
|
end
|
48
|
-
|
57
|
+
|
49
58
|
def size
|
50
59
|
0
|
51
60
|
end
|
52
|
-
|
61
|
+
|
53
62
|
def expand(*_)
|
54
63
|
return string
|
55
64
|
end
|
56
|
-
|
65
|
+
|
57
66
|
end
|
58
|
-
|
59
|
-
|
67
|
+
|
60
68
|
# A module which all non-literal tokens should include.
|
61
69
|
module Expression
|
62
|
-
|
70
|
+
|
63
71
|
include Token
|
64
|
-
|
72
|
+
|
65
73
|
attr_reader :variables
|
66
|
-
|
74
|
+
|
67
75
|
def literal?
|
68
76
|
false
|
69
77
|
end
|
70
|
-
|
78
|
+
|
71
79
|
def expression?
|
72
80
|
true
|
73
81
|
end
|
74
|
-
|
82
|
+
|
75
83
|
end
|
76
84
|
|
77
85
|
autoload :Utils, 'uri_template/utils'
|
78
86
|
autoload :Draft7, 'uri_template/draft7'
|
79
87
|
autoload :Colon, 'uri_template/colon'
|
80
|
-
|
88
|
+
|
81
89
|
# A hash with all available implementations.
|
82
90
|
# Currently the only implementation is :draft7. But there also aliases :default and :latest available. This should make it possible to add newer specs later.
|
83
91
|
# @see resolve_class
|
@@ -87,7 +95,7 @@ module URITemplate
|
|
87
95
|
:colon => :Colon,
|
88
96
|
:latest => :Draft7
|
89
97
|
}
|
90
|
-
|
98
|
+
|
91
99
|
# Looks up which implementation to use.
|
92
100
|
# Extracts all symbols from args and looks up the first in {VERSIONS}.
|
93
101
|
#
|
@@ -105,7 +113,7 @@ module URITemplate
|
|
105
113
|
raise ArgumentError, "Unknown template version #{version.inspect}, defined versions: #{VERSIONS.keys.inspect}" unless VERSIONS.key?(version)
|
106
114
|
return self.const_get(VERSIONS[version]), rest
|
107
115
|
end
|
108
|
-
|
116
|
+
|
109
117
|
# Creates an uri template using an implementation.
|
110
118
|
# The args should at least contain a pattern string.
|
111
119
|
# Symbols in the args are used to determine the actual implementation.
|
@@ -121,7 +129,7 @@ module URITemplate
|
|
121
129
|
klass, rest = resolve_class(*args)
|
122
130
|
return klass.new(*rest)
|
123
131
|
end
|
124
|
-
|
132
|
+
|
125
133
|
# Tries to convert the given argument into an {URITemplate}.
|
126
134
|
# Returns nil if this fails.
|
127
135
|
#
|
@@ -135,7 +143,7 @@ module URITemplate
|
|
135
143
|
return nil
|
136
144
|
end
|
137
145
|
end
|
138
|
-
|
146
|
+
|
139
147
|
# Same as {.try_convert} but raises an ArgumentError when the given argument could not be converted.
|
140
148
|
#
|
141
149
|
# @raise ArgumentError if the argument is unconvertable
|
@@ -147,7 +155,7 @@ module URITemplate
|
|
147
155
|
end
|
148
156
|
return o
|
149
157
|
end
|
150
|
-
|
158
|
+
|
151
159
|
# Tries to coerce two URITemplates into a common representation.
|
152
160
|
# Returns an array with two {URITemplate}s and two booleans indicating which of the two were converted or raises an ArgumentError.
|
153
161
|
#
|
@@ -180,7 +188,7 @@ module URITemplate
|
|
180
188
|
raise ArgumentError, "Expected at least on URITemplate, but got #{a.inspect} and #{b.inspect}" unless a.kind_of? URITemplate or b.kind_of? URITemplate
|
181
189
|
raise ArgumentError, "Cannot coerce #{a.inspect} and #{b.inspect} into a common representation."
|
182
190
|
end
|
183
|
-
|
191
|
+
|
184
192
|
# Applies a method to a URITemplate with another URITemplate as argument.
|
185
193
|
# This is a useful shorthand since both URITemplates are automatically coerced.
|
186
194
|
#
|
@@ -194,11 +202,11 @@ module URITemplate
|
|
194
202
|
a,b,_,_ = self.coerce(a,b)
|
195
203
|
a.send(method,b,*args)
|
196
204
|
end
|
197
|
-
|
205
|
+
|
198
206
|
# A base class for all errors which will be raised upon invalid syntax.
|
199
207
|
module Invalid
|
200
208
|
end
|
201
|
-
|
209
|
+
|
202
210
|
# Expands this uri template with the given variables.
|
203
211
|
# The variables should be converted to strings using {Utils#object_to_param}.
|
204
212
|
# @raise {Unconvertable} if a variable could not be converted to a string.
|
@@ -209,19 +217,19 @@ module URITemplate
|
|
209
217
|
part.expand(variables)
|
210
218
|
}.join
|
211
219
|
end
|
212
|
-
|
220
|
+
|
213
221
|
# @abstract
|
214
222
|
# 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.
|
215
223
|
def type
|
216
224
|
raise "Please implement #type on #{self.class.inspect}."
|
217
225
|
end
|
218
|
-
|
226
|
+
|
219
227
|
# @abstract
|
220
228
|
# Returns the tokens of this templates. Tokens should include either {Literal} or {Expression}.
|
221
229
|
def tokens
|
222
230
|
raise "Please implement #tokens on #{self.class.inspect}."
|
223
231
|
end
|
224
|
-
|
232
|
+
|
225
233
|
# Returns an array containing all variables. Repeated variables are ignored. The concrete order of the variables may change.
|
226
234
|
# @example
|
227
235
|
# URITemplate.new('{foo}{bar}{baz}').variables #=> ['foo','bar','baz']
|
@@ -231,7 +239,7 @@ module URITemplate
|
|
231
239
|
def variables
|
232
240
|
@variables ||= tokens.select(&:expression?).map(&:variables).flatten.uniq
|
233
241
|
end
|
234
|
-
|
242
|
+
|
235
243
|
# Returns the number of static characters in this template.
|
236
244
|
# This method is useful for routing, since it's often pointful to use the url with fewer variable characters.
|
237
245
|
# 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.
|
@@ -245,7 +253,7 @@ module URITemplate
|
|
245
253
|
def static_characters
|
246
254
|
@static_characters ||= tokens.select(&:literal?).map{|t| t.string.size }.inject(0,:+)
|
247
255
|
end
|
248
|
-
|
256
|
+
|
249
257
|
# Returns whether this uri-template is absolute.
|
250
258
|
# This is detected by checking for "://".
|
251
259
|
#
|
@@ -260,14 +268,14 @@ module URITemplate
|
|
260
268
|
end
|
261
269
|
if read_chars =~ /^[a-z]+:\/\//i
|
262
270
|
return @absolute = true
|
263
|
-
elsif read_chars =~ /(
|
271
|
+
elsif read_chars =~ /(^|[^:\/])\/(?!\/)/
|
264
272
|
return @absolute = false
|
265
273
|
end
|
266
274
|
end
|
267
|
-
|
275
|
+
|
268
276
|
return @absolute = false
|
269
277
|
end
|
270
|
-
|
278
|
+
|
271
279
|
# Opposite of {#absolute?}
|
272
280
|
def relative?
|
273
281
|
!absolute?
|
data/lib/uri_template/colon.rb
CHANGED
@@ -23,7 +23,7 @@ require 'uri_template/utils'
|
|
23
23
|
# A colon based template denotes variables with a colon.
|
24
24
|
# This template type is realy basic but having just on template type was a bit weird.
|
25
25
|
module URITemplate
|
26
|
-
|
26
|
+
|
27
27
|
class Colon
|
28
28
|
|
29
29
|
include URITemplate
|
@@ -31,46 +31,46 @@ class Colon
|
|
31
31
|
VAR = /(?:\{:([a-z]+)\}|:([a-z]+)(?![a-z]))/u
|
32
32
|
|
33
33
|
class Token
|
34
|
-
|
34
|
+
|
35
35
|
class Variable < self
|
36
|
-
|
36
|
+
|
37
37
|
include URITemplate::Expression
|
38
|
-
|
38
|
+
|
39
39
|
attr_reader :name
|
40
|
-
|
40
|
+
|
41
41
|
def initialize(name)
|
42
42
|
@name = name
|
43
43
|
@variables = [name]
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def expand(vars)
|
47
47
|
return Utils.escape_url(Utils.object_to_param(vars[@name]))
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def to_r
|
51
51
|
return ['([^/]*?)'].join
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
class Static < self
|
57
|
-
|
57
|
+
|
58
58
|
include URITemplate::Literal
|
59
|
-
|
59
|
+
|
60
60
|
def initialize(str)
|
61
61
|
@string = str
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
def expand(_)
|
65
65
|
return @string
|
66
66
|
end
|
67
|
-
|
67
|
+
|
68
68
|
def to_r
|
69
69
|
return Regexp.escape(@string)
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
end
|
75
75
|
|
76
76
|
attr_reader :pattern
|
@@ -94,7 +94,7 @@ class Colon
|
|
94
94
|
def initialize(pattern)
|
95
95
|
@pattern = pattern
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
# Extracts variables from an uri.
|
99
99
|
#
|
100
100
|
# @param String uri
|
@@ -106,19 +106,19 @@ class Colon
|
|
106
106
|
[v, Utils.unescape_url(md[i+1])]
|
107
107
|
}.flatten(1) ]
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
def type
|
111
111
|
:colon
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
def to_r
|
115
115
|
@regexp ||= Regexp.new('\A' + tokens.map(&:to_r).join + '\z', Utils::KCODE_UTF8)
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
def tokens
|
119
119
|
@tokens ||= tokenize!
|
120
120
|
end
|
121
|
-
|
121
|
+
|
122
122
|
# Tries to concatenate two templates, as if they were path segments.
|
123
123
|
# Removes double slashes or inserts one if they are missing.
|
124
124
|
#
|
@@ -136,7 +136,7 @@ class Colon
|
|
136
136
|
end
|
137
137
|
return self.class.new( File.join( this.pattern, other.pattern ) )
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
protected
|
141
141
|
|
142
142
|
def tokenize!
|
data/lib/uri_template/draft7.rb
CHANGED
@@ -29,127 +29,127 @@ class URITemplate::Draft7
|
|
29
29
|
|
30
30
|
include URITemplate
|
31
31
|
extend Forwardable
|
32
|
-
|
32
|
+
|
33
33
|
# @private
|
34
34
|
Utils = URITemplate::Utils
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
|
36
|
+
if SUPPORTS_UNICODE_CHARS
|
37
|
+
# @private
|
38
|
+
# \/ - unicode ctrl-chars
|
39
|
+
LITERAL = /([^"'%<>\\^`{|}\u0000-\u001F\u007F-\u009F\s]|%[0-9a-fA-F]{2})+/u
|
40
|
+
else
|
41
|
+
# @private
|
42
|
+
LITERAL = Regexp.compile('([^"\'%<>\\\\^`{|}\x00-\x1F\x7F-\x9F\s]|%[0-9a-fA-F]{2})+',Utils::KCODE_UTF8)
|
43
|
+
end
|
44
|
+
|
40
45
|
# @private
|
41
46
|
CHARACTER_CLASSES = {
|
42
|
-
|
47
|
+
|
43
48
|
:unreserved => {
|
44
|
-
:
|
45
|
-
:class => '(?:[A-Za-z0-9\-\._]|%\h\h)',
|
46
|
-
|
47
|
-
:class_name => 'c_u_',
|
49
|
+
:class => '(?:[A-Za-z0-9\-\._]|%[0-9a-fA-F]{2})',
|
48
50
|
:grabs_comma => false
|
49
51
|
},
|
50
52
|
:unreserved_reserved_pct => {
|
51
|
-
:
|
52
|
-
:class => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%\h\h)',
|
53
|
-
:class_name => 'c_urp_',
|
53
|
+
:class => '(?:[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%[0-9a-fA-F]{2})',
|
54
54
|
:grabs_comma => true
|
55
55
|
},
|
56
|
-
|
56
|
+
|
57
57
|
:varname => {
|
58
58
|
:class => '(?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*?',
|
59
59
|
:class_name => 'c_vn_'
|
60
60
|
}
|
61
|
-
|
61
|
+
|
62
62
|
}
|
63
|
-
|
63
|
+
|
64
64
|
# Specifies that no processing should be done upon extraction.
|
65
65
|
# @see #extract
|
66
66
|
NO_PROCESSING = []
|
67
|
-
|
67
|
+
|
68
68
|
# Specifies that the extracted values should be processed.
|
69
69
|
# @see #extract
|
70
70
|
CONVERT_VALUES = [:convert_values]
|
71
|
-
|
71
|
+
|
72
72
|
# Specifies that the extracted variable list should be processed.
|
73
73
|
# @see #extract
|
74
74
|
CONVERT_RESULT = [:convert_result]
|
75
|
-
|
75
|
+
|
76
76
|
# Default processing. Means: convert values and the list itself.
|
77
77
|
# @see #extract
|
78
78
|
DEFAULT_PROCESSING = CONVERT_VALUES + CONVERT_RESULT
|
79
|
-
|
79
|
+
|
80
80
|
# @private
|
81
81
|
VAR = Regexp.compile(<<'__REGEXP__'.strip, Utils::KCODE_UTF8)
|
82
82
|
((?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*)(\*)?(?::(\d+))?
|
83
83
|
__REGEXP__
|
84
|
-
|
84
|
+
|
85
85
|
# @private
|
86
86
|
EXPRESSION = Regexp.compile(<<'__REGEXP__'.strip, Utils::KCODE_UTF8)
|
87
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+)?)*)\}
|
88
88
|
__REGEXP__
|
89
89
|
|
90
90
|
# @private
|
91
|
-
URI = Regexp.compile(<<
|
92
|
-
|
91
|
+
URI = Regexp.compile(<<__REGEXP__.strip, Utils::KCODE_UTF8)
|
92
|
+
\\A(#{LITERAL.source}|#{EXPRESSION.source})*\\z
|
93
93
|
__REGEXP__
|
94
94
|
|
95
95
|
SLASH = ?/
|
96
|
-
|
96
|
+
|
97
97
|
# @private
|
98
98
|
class Token
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
# @private
|
102
102
|
class Literal < Token
|
103
|
-
|
103
|
+
|
104
104
|
include URITemplate::Literal
|
105
|
-
|
105
|
+
|
106
106
|
def initialize(string)
|
107
107
|
@string = string
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
def level
|
111
111
|
1
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
def arity
|
115
115
|
0
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
def to_r_source(*_)
|
119
119
|
Regexp.escape(@string)
|
120
120
|
end
|
121
|
-
|
121
|
+
|
122
122
|
def to_s
|
123
123
|
@string
|
124
124
|
end
|
125
|
-
|
125
|
+
|
126
126
|
end
|
127
127
|
|
128
128
|
# @private
|
129
129
|
class Expression < Token
|
130
|
-
|
130
|
+
|
131
131
|
include URITemplate::Expression
|
132
|
-
|
132
|
+
|
133
133
|
attr_reader :variables, :max_length
|
134
|
-
|
134
|
+
|
135
135
|
def initialize(vars)
|
136
136
|
@variable_specs = vars
|
137
137
|
@variables = vars.map(&:first)
|
138
138
|
@variables.uniq!
|
139
139
|
end
|
140
|
-
|
140
|
+
|
141
141
|
PREFIX = ''.freeze
|
142
142
|
SEPARATOR = ','.freeze
|
143
143
|
PAIR_CONNECTOR = '='.freeze
|
144
144
|
PAIR_IF_EMPTY = true
|
145
145
|
LIST_CONNECTOR = ','.freeze
|
146
146
|
BASE_LEVEL = 1
|
147
|
-
|
147
|
+
|
148
148
|
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved]
|
149
|
-
|
149
|
+
|
150
150
|
NAMED = false
|
151
151
|
OPERATOR = ''
|
152
|
-
|
152
|
+
|
153
153
|
def level
|
154
154
|
if @variable_specs.none?{|_,expand,ml| expand || (ml > 0) }
|
155
155
|
if @variable_specs.size == 1
|
@@ -161,16 +161,16 @@ __REGEXP__
|
|
161
161
|
return 4
|
162
162
|
end
|
163
163
|
end
|
164
|
-
|
164
|
+
|
165
165
|
def arity
|
166
166
|
@variable_specs.size
|
167
167
|
end
|
168
|
-
|
168
|
+
|
169
169
|
def expand( vars )
|
170
170
|
result = []
|
171
171
|
@variable_specs.each{| var, expand , max_length |
|
172
172
|
unless vars[var].nil?
|
173
|
-
if vars[var].kind_of?
|
173
|
+
if vars[var].kind_of?(Hash) or Utils.pair_array?(vars[var])
|
174
174
|
result.push( *transform_hash(var, vars[var], expand, max_length) )
|
175
175
|
elsif vars[var].kind_of? Array
|
176
176
|
result.push( *transform_array(var, vars[var], expand, max_length) )
|
@@ -189,11 +189,11 @@ __REGEXP__
|
|
189
189
|
return ''
|
190
190
|
end
|
191
191
|
end
|
192
|
-
|
192
|
+
|
193
193
|
def to_s
|
194
194
|
return '{' + self.class::OPERATOR + @variable_specs.map{|name,expand,max_length| name + (expand ? '*': '') + (max_length > 0 ? (':' + max_length.to_s) : '') }.join(',') + '}'
|
195
195
|
end
|
196
|
-
|
196
|
+
|
197
197
|
#TODO: certain things after a slurpy variable will never get matched. therefore, it's pointless to add expressions for them
|
198
198
|
#TODO: variables, which appear twice could be compacted, don't they?
|
199
199
|
def to_r_source
|
@@ -203,11 +203,11 @@ __REGEXP__
|
|
203
203
|
i = 0
|
204
204
|
if self.class::NAMED
|
205
205
|
@variable_specs.each{| var, expand , max_length |
|
206
|
-
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
|
206
|
+
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*'}"
|
207
207
|
if expand
|
208
208
|
#if self.class::PAIR_IF_EMPTY
|
209
209
|
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
210
|
-
|
210
|
+
|
211
211
|
if first
|
212
212
|
source << "((?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
|
213
213
|
else
|
@@ -219,14 +219,14 @@ __REGEXP__
|
|
219
219
|
else
|
220
220
|
pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value}|)"
|
221
221
|
end
|
222
|
-
|
222
|
+
|
223
223
|
if first
|
224
224
|
source << "(?:#{pair})"
|
225
225
|
else
|
226
226
|
source << "(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})?"
|
227
227
|
end
|
228
228
|
end
|
229
|
-
|
229
|
+
|
230
230
|
first = false
|
231
231
|
i = i+1
|
232
232
|
}
|
@@ -235,20 +235,20 @@ __REGEXP__
|
|
235
235
|
last = (vs == i)
|
236
236
|
if expand
|
237
237
|
# could be list or map, too
|
238
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
|
239
|
-
|
238
|
+
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*'}"
|
239
|
+
|
240
240
|
pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
|
241
|
-
|
241
|
+
|
242
242
|
value = "#{pair}(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*"
|
243
243
|
elsif last
|
244
244
|
# the last will slurp lists
|
245
245
|
if self.class::CHARACTER_CLASS[:grabs_comma]
|
246
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
246
|
+
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
247
247
|
else
|
248
|
-
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
248
|
+
value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
249
249
|
end
|
250
250
|
else
|
251
|
-
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
251
|
+
value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
252
252
|
end
|
253
253
|
if first
|
254
254
|
source << "(#{value})"
|
@@ -261,7 +261,7 @@ __REGEXP__
|
|
261
261
|
end
|
262
262
|
return '(?:' + Regexp.escape(self.class::PREFIX) + source.join + ')?'
|
263
263
|
end
|
264
|
-
|
264
|
+
|
265
265
|
def extract(position,matched)
|
266
266
|
name, expand, max_length = @variable_specs[position]
|
267
267
|
if matched.nil?
|
@@ -302,38 +302,38 @@ __REGEXP__
|
|
302
302
|
elsif self.class::NAMED
|
303
303
|
return [ [ name, decode( matched[1..-1] ) ] ]
|
304
304
|
end
|
305
|
-
|
305
|
+
|
306
306
|
return [ [ name, decode( matched ) ] ]
|
307
307
|
end
|
308
|
-
|
308
|
+
|
309
309
|
protected
|
310
|
-
|
310
|
+
|
311
311
|
module ClassMethods
|
312
|
-
|
312
|
+
|
313
313
|
def hash_extractor(max_length)
|
314
314
|
@hash_extractors ||= {}
|
315
315
|
@hash_extractors[max_length] ||= begin
|
316
|
-
value = "#{self::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
|
316
|
+
value = "#{self::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{0,'+max_length.to_s+'}':'*?'}"
|
317
317
|
pair = "(#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self::PAIR_CONNECTOR)})?(#{value})"
|
318
318
|
source = "\\A#{Regexp.escape(self::SEPARATOR)}?" + pair + "(\\z|#{Regexp.escape(self::SEPARATOR)}(?!#{Regexp.escape(self::SEPARATOR)}))"
|
319
319
|
Regexp.new( source , Utils::KCODE_UTF8)
|
320
320
|
end
|
321
321
|
end
|
322
|
-
|
322
|
+
|
323
323
|
end
|
324
|
-
|
324
|
+
|
325
325
|
extend ClassMethods
|
326
|
-
|
326
|
+
|
327
327
|
def escape(x)
|
328
328
|
Utils.escape_url(Utils.object_to_param(x))
|
329
329
|
end
|
330
|
-
|
330
|
+
|
331
331
|
def unescape(x)
|
332
332
|
Utils.unescape_url(x)
|
333
333
|
end
|
334
|
-
|
334
|
+
|
335
335
|
SPLITTER = /^(?:,(,*)|([^,]+))/
|
336
|
-
|
336
|
+
|
337
337
|
def decode(x, split = true)
|
338
338
|
if x.nil?
|
339
339
|
if self.class::PAIR_IF_EMPTY
|
@@ -362,17 +362,17 @@ __REGEXP__
|
|
362
362
|
unescape(x)
|
363
363
|
end
|
364
364
|
end
|
365
|
-
|
365
|
+
|
366
366
|
def cut(str,chars)
|
367
367
|
if chars > 0
|
368
|
-
md = Regexp.compile("\\A#{self.class::CHARACTER_CLASS[:class]}{,#{chars.to_s}}", Utils::KCODE_UTF8).match(str)
|
368
|
+
md = Regexp.compile("\\A#{self.class::CHARACTER_CLASS[:class]}{0,#{chars.to_s}}", Utils::KCODE_UTF8).match(str)
|
369
369
|
#TODO: handle invalid matches
|
370
370
|
return md[0]
|
371
371
|
else
|
372
372
|
return str
|
373
373
|
end
|
374
374
|
end
|
375
|
-
|
375
|
+
|
376
376
|
def pair(key, value, max_length = 0)
|
377
377
|
ek = escape(key)
|
378
378
|
ev = escape(value)
|
@@ -382,7 +382,7 @@ __REGEXP__
|
|
382
382
|
return ek + self.class::PAIR_CONNECTOR + cut( ev, max_length )
|
383
383
|
end
|
384
384
|
end
|
385
|
-
|
385
|
+
|
386
386
|
def transform_hash(name, hsh, expand , max_length)
|
387
387
|
if expand
|
388
388
|
hsh.map{|key,value| pair(key,value) }
|
@@ -392,7 +392,7 @@ __REGEXP__
|
|
392
392
|
[ (self.class::NAMED ? escape(name)+self.class::PAIR_CONNECTOR : '' ) + hsh.map{|key,value| escape(key)+self.class::LIST_CONNECTOR+escape(value) }.join(self.class::LIST_CONNECTOR) ]
|
393
393
|
end
|
394
394
|
end
|
395
|
-
|
395
|
+
|
396
396
|
def transform_array(name, ary, expand , max_length)
|
397
397
|
if expand
|
398
398
|
ary.map{|value| escape(value) }
|
@@ -402,91 +402,91 @@ __REGEXP__
|
|
402
402
|
[ (self.class::NAMED ? escape(name)+self.class::PAIR_CONNECTOR : '' ) + ary.map{|value| escape(value) }.join(self.class::LIST_CONNECTOR) ]
|
403
403
|
end
|
404
404
|
end
|
405
|
-
|
405
|
+
|
406
406
|
class Reserved < self
|
407
|
-
|
407
|
+
|
408
408
|
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
|
409
409
|
OPERATOR = '+'.freeze
|
410
410
|
BASE_LEVEL = 2
|
411
|
-
|
411
|
+
|
412
412
|
def escape(x)
|
413
413
|
Utils.escape_uri(Utils.object_to_param(x))
|
414
414
|
end
|
415
|
-
|
415
|
+
|
416
416
|
def unescape(x)
|
417
417
|
Utils.unescape_uri(x)
|
418
418
|
end
|
419
|
-
|
419
|
+
|
420
420
|
end
|
421
|
-
|
421
|
+
|
422
422
|
class Fragment < self
|
423
|
-
|
423
|
+
|
424
424
|
CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
|
425
425
|
PREFIX = '#'.freeze
|
426
426
|
OPERATOR = '#'.freeze
|
427
427
|
BASE_LEVEL = 2
|
428
|
-
|
428
|
+
|
429
429
|
def escape(x)
|
430
430
|
Utils.escape_uri(Utils.object_to_param(x))
|
431
431
|
end
|
432
|
-
|
432
|
+
|
433
433
|
def unescape(x)
|
434
434
|
Utils.unescape_uri(x)
|
435
435
|
end
|
436
|
-
|
436
|
+
|
437
437
|
end
|
438
|
-
|
438
|
+
|
439
439
|
class Label < self
|
440
|
-
|
440
|
+
|
441
441
|
SEPARATOR = '.'.freeze
|
442
442
|
PREFIX = '.'.freeze
|
443
443
|
OPERATOR = '.'.freeze
|
444
444
|
BASE_LEVEL = 3
|
445
|
-
|
445
|
+
|
446
446
|
end
|
447
|
-
|
447
|
+
|
448
448
|
class Path < self
|
449
|
-
|
449
|
+
|
450
450
|
SEPARATOR = '/'.freeze
|
451
451
|
PREFIX = '/'.freeze
|
452
452
|
OPERATOR = '/'.freeze
|
453
453
|
BASE_LEVEL = 3
|
454
|
-
|
454
|
+
|
455
455
|
end
|
456
|
-
|
456
|
+
|
457
457
|
class PathParameters < self
|
458
|
-
|
458
|
+
|
459
459
|
SEPARATOR = ';'.freeze
|
460
460
|
PREFIX = ';'.freeze
|
461
461
|
NAMED = true
|
462
462
|
PAIR_IF_EMPTY = false
|
463
463
|
OPERATOR = ';'.freeze
|
464
464
|
BASE_LEVEL = 3
|
465
|
-
|
465
|
+
|
466
466
|
end
|
467
|
-
|
467
|
+
|
468
468
|
class FormQuery < self
|
469
|
-
|
469
|
+
|
470
470
|
SEPARATOR = '&'.freeze
|
471
471
|
PREFIX = '?'.freeze
|
472
472
|
NAMED = true
|
473
473
|
OPERATOR = '?'.freeze
|
474
474
|
BASE_LEVEL = 3
|
475
|
-
|
475
|
+
|
476
476
|
end
|
477
|
-
|
477
|
+
|
478
478
|
class FormQueryContinuation < self
|
479
|
-
|
479
|
+
|
480
480
|
SEPARATOR = '&'.freeze
|
481
481
|
PREFIX = '&'.freeze
|
482
482
|
NAMED = true
|
483
483
|
OPERATOR = '&'.freeze
|
484
484
|
BASE_LEVEL = 3
|
485
|
-
|
485
|
+
|
486
486
|
end
|
487
|
-
|
487
|
+
|
488
488
|
end
|
489
|
-
|
489
|
+
|
490
490
|
# @private
|
491
491
|
OPERATORS = {
|
492
492
|
'' => Expression,
|
@@ -498,33 +498,33 @@ __REGEXP__
|
|
498
498
|
'?' => Expression::FormQuery,
|
499
499
|
'&' => Expression::FormQueryContinuation
|
500
500
|
}
|
501
|
-
|
501
|
+
|
502
502
|
# This error is raised when an invalid pattern was given.
|
503
503
|
class Invalid < StandardError
|
504
|
-
|
504
|
+
|
505
505
|
include URITemplate::Invalid
|
506
|
-
|
506
|
+
|
507
507
|
attr_reader :pattern, :position
|
508
|
-
|
508
|
+
|
509
509
|
def initialize(source, position)
|
510
510
|
@pattern = pattern
|
511
511
|
@position = position
|
512
512
|
super("Invalid expression found in #{source.inspect} at #{position}: '#{source[position..-1]}'")
|
513
513
|
end
|
514
|
-
|
514
|
+
|
515
515
|
end
|
516
|
-
|
516
|
+
|
517
517
|
# @private
|
518
518
|
class Tokenizer
|
519
|
-
|
519
|
+
|
520
520
|
include Enumerable
|
521
|
-
|
521
|
+
|
522
522
|
attr_reader :source
|
523
|
-
|
523
|
+
|
524
524
|
def initialize(source)
|
525
525
|
@source = source
|
526
526
|
end
|
527
|
-
|
527
|
+
|
528
528
|
def each
|
529
529
|
if !block_given?
|
530
530
|
return Enumerator.new(self)
|
@@ -551,12 +551,12 @@ __REGEXP__
|
|
551
551
|
end
|
552
552
|
end
|
553
553
|
end
|
554
|
-
|
554
|
+
|
555
555
|
end
|
556
|
-
|
556
|
+
|
557
557
|
# The class methods for all draft7 templates.
|
558
558
|
module ClassMethods
|
559
|
-
|
559
|
+
|
560
560
|
# Tries to convert the given param in to a instance of {Draft7}
|
561
561
|
# It basically passes thru instances of that class, parses strings and return nil on everything else.
|
562
562
|
#
|
@@ -586,8 +586,7 @@ __REGEXP__
|
|
586
586
|
return nil
|
587
587
|
end
|
588
588
|
end
|
589
|
-
|
590
|
-
|
589
|
+
|
591
590
|
# Like {.try_convert}, but raises an ArgumentError, when the conversion failed.
|
592
591
|
#
|
593
592
|
# @raise ArgumentError
|
@@ -599,7 +598,7 @@ __REGEXP__
|
|
599
598
|
return o
|
600
599
|
end
|
601
600
|
end
|
602
|
-
|
601
|
+
|
603
602
|
# Tests whether a given pattern is a valid template pattern.
|
604
603
|
# @example
|
605
604
|
# URITemplate::Draft7.valid? 'foo' #=> true
|
@@ -608,13 +607,13 @@ __REGEXP__
|
|
608
607
|
def valid?(pattern)
|
609
608
|
URI === pattern
|
610
609
|
end
|
611
|
-
|
610
|
+
|
612
611
|
end
|
613
|
-
|
612
|
+
|
614
613
|
extend ClassMethods
|
615
|
-
|
614
|
+
|
616
615
|
attr_reader :options
|
617
|
-
|
616
|
+
|
618
617
|
# @param String,Array either a pattern as String or an Array of tokens
|
619
618
|
# @param Hash some options
|
620
619
|
# @option :lazy If true the pattern will be parsed on first access, this also means that syntax errors will not be detected unless accessed.
|
@@ -633,7 +632,7 @@ __REGEXP__
|
|
633
632
|
raise ArgumentError, "Expected to receive a pattern string, but got #{pattern_or_tokens.inspect}"
|
634
633
|
end
|
635
634
|
end
|
636
|
-
|
635
|
+
|
637
636
|
# @method expand(variables = {})
|
638
637
|
# Expands the template with the given variables.
|
639
638
|
# The expansion should be compatible to uritemplate spec draft 7 ( http://tools.ietf.org/html/draft-gregorio-uritemplate-07 ).
|
@@ -648,7 +647,7 @@ __REGEXP__
|
|
648
647
|
#
|
649
648
|
# @param variables Hash
|
650
649
|
# @return String
|
651
|
-
|
650
|
+
|
652
651
|
# Compiles this template into a regular expression which can be used to test whether a given uri matches this template. This template is also used for {#===}.
|
653
652
|
#
|
654
653
|
# @example
|
@@ -666,8 +665,7 @@ __REGEXP__
|
|
666
665
|
Regexp.new( source.join, Utils::KCODE_UTF8)
|
667
666
|
end
|
668
667
|
end
|
669
|
-
|
670
|
-
|
668
|
+
|
671
669
|
# Extracts variables from a uri ( given as string ) or an instance of MatchData ( which was matched by the regexp of this template.
|
672
670
|
# The actual result depends on the value of post_processing.
|
673
671
|
# This argument specifies whether pair arrays should be converted to hashes.
|
@@ -724,25 +722,25 @@ __REGEXP__
|
|
724
722
|
if block_given?
|
725
723
|
return yield result
|
726
724
|
end
|
727
|
-
|
725
|
+
|
728
726
|
return result
|
729
727
|
end
|
730
728
|
end
|
731
|
-
|
729
|
+
|
732
730
|
# Extracts variables without any proccessing.
|
733
731
|
# This is equivalent to {#extract} with options {NO_PROCESSING}.
|
734
732
|
# @see #extract
|
735
733
|
def extract_simple(uri_or_match)
|
736
734
|
extract( uri_or_match, NO_PROCESSING )
|
737
735
|
end
|
738
|
-
|
736
|
+
|
739
737
|
# Returns the pattern for this template.
|
740
738
|
def pattern
|
741
739
|
@pattern ||= tokens.map(&:to_s).join
|
742
740
|
end
|
743
|
-
|
741
|
+
|
744
742
|
alias to_s pattern
|
745
|
-
|
743
|
+
|
746
744
|
# Compares two template patterns.
|
747
745
|
def ==(o)
|
748
746
|
this, other, this_converted, _ = URITemplate.coerce( self, o )
|
@@ -751,12 +749,12 @@ __REGEXP__
|
|
751
749
|
end
|
752
750
|
return this.pattern == other.pattern
|
753
751
|
end
|
754
|
-
|
752
|
+
|
755
753
|
# @method ===(uri)
|
756
754
|
# Alias for to_r.=== . Tests whether this template matches a given uri.
|
757
755
|
# @return TrueClass, FalseClass
|
758
756
|
def_delegators :to_r, :===
|
759
|
-
|
757
|
+
|
760
758
|
# @method match(uri)
|
761
759
|
# Alias for to_r.match . Matches this template against the given uri.
|
762
760
|
# @yield MatchData
|
@@ -774,7 +772,7 @@ __REGEXP__
|
|
774
772
|
def type
|
775
773
|
:draft7
|
776
774
|
end
|
777
|
-
|
775
|
+
|
778
776
|
# 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.
|
779
777
|
# Basically this is defined as:
|
780
778
|
#
|
@@ -797,7 +795,7 @@ __REGEXP__
|
|
797
795
|
def level
|
798
796
|
tokens.map(&:level).max
|
799
797
|
end
|
800
|
-
|
798
|
+
|
801
799
|
# Tries to concatenate two templates, as if they were path segments.
|
802
800
|
# Removes double slashes or insert one if they are missing.
|
803
801
|
#
|
@@ -813,11 +811,11 @@ __REGEXP__
|
|
813
811
|
if this_converted
|
814
812
|
return this / other
|
815
813
|
end
|
816
|
-
|
814
|
+
|
817
815
|
if other.absolute?
|
818
816
|
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."
|
819
817
|
end
|
820
|
-
|
818
|
+
|
821
819
|
if other.pattern == ''
|
822
820
|
return self
|
823
821
|
end
|
@@ -848,7 +846,7 @@ __REGEXP__
|
|
848
846
|
end
|
849
847
|
end
|
850
848
|
end
|
851
|
-
|
849
|
+
|
852
850
|
if other.tokens.first.kind_of?(Literal)
|
853
851
|
if other.tokens.first.string[0] == SLASH
|
854
852
|
return self.class.new( self.tokens + other.tokens )
|
@@ -861,7 +859,7 @@ __REGEXP__
|
|
861
859
|
return self.class.new( self.tokens + [Literal.new('/')] + other.tokens )
|
862
860
|
end
|
863
861
|
end
|
864
|
-
|
862
|
+
|
865
863
|
# Returns an array containing a the template tokens.
|
866
864
|
def tokens
|
867
865
|
@tokens ||= tokenize!
|
@@ -872,11 +870,11 @@ protected
|
|
872
870
|
def tokenize!
|
873
871
|
Tokenizer.new(pattern).to_a
|
874
872
|
end
|
875
|
-
|
873
|
+
|
876
874
|
def arity
|
877
875
|
@arity ||= tokens.inject(0){|a,t| a + t.arity }
|
878
876
|
end
|
879
|
-
|
877
|
+
|
880
878
|
# @private
|
881
879
|
def extract_matchdata(matchdata, post_processing)
|
882
880
|
bc = 1
|
@@ -908,7 +906,5 @@ protected
|
|
908
906
|
end
|
909
907
|
end
|
910
908
|
end
|
911
|
-
|
912
|
-
end
|
913
|
-
|
914
909
|
|
910
|
+
end
|