uri_template 0.2.0 → 0.2.1
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 +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
|