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.
@@ -0,0 +1,374 @@
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
+ require 'uri_template/rfc6570'
19
+
20
+ class URITemplate::RFC6570
21
+
22
+ # @private
23
+ class Expression < Token
24
+
25
+ include URITemplate::Expression
26
+
27
+ attr_reader :variables
28
+
29
+ def initialize(vars)
30
+ @variable_specs = vars
31
+ @variables = vars.map(&:first)
32
+ @variables.uniq!
33
+ end
34
+
35
+ PREFIX = ''.freeze
36
+ SEPARATOR = ','.freeze
37
+ PAIR_CONNECTOR = '='.freeze
38
+ PAIR_IF_EMPTY = true
39
+ LIST_CONNECTOR = ','.freeze
40
+ BASE_LEVEL = 1
41
+
42
+ CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved]
43
+
44
+ NAMED = false
45
+ OPERATOR = ''
46
+
47
+ def level
48
+ if @variable_specs.none?{|_,expand,ml| expand || (ml > 0) }
49
+ if @variable_specs.size == 1
50
+ return self.class::BASE_LEVEL
51
+ else
52
+ return 3
53
+ end
54
+ else
55
+ return 4
56
+ end
57
+ end
58
+
59
+ def expands?
60
+ @variable_specs.any?{|_,expand,_| expand }
61
+ end
62
+
63
+ def arity
64
+ @variable_specs.size
65
+ end
66
+
67
+ def expand( vars )
68
+ result = []
69
+ @variable_specs.each{| var, expand , max_length |
70
+ unless vars[var].nil?
71
+ result.push(*expand_one(var, vars[var], expand, max_length))
72
+ end
73
+ }
74
+ if result.any?
75
+ return (self.class::PREFIX + result.join(self.class::SEPARATOR))
76
+ else
77
+ return ''
78
+ end
79
+ end
80
+
81
+ def extract(position,matched)
82
+ name, expand, max_length = @variable_specs[position]
83
+ if matched.nil?
84
+ return [[ name , extracted_nil ]]
85
+ end
86
+ if expand
87
+ it = URITemplate::RegexpEnumerator.new(self.class.hash_extractor(max_length), :rest => :raise)
88
+ if position == 0
89
+ matched = "#{self.class::SEPARATOR}#{matched}"
90
+ end
91
+ splitted = it.each(matched)\
92
+ .map do |match|
93
+ raise match.inspect if match.kind_of? String
94
+ [ decode(match[1]), decode(match[2], false) ]
95
+ end
96
+ return after_expand(name, splitted)
97
+ end
98
+
99
+ return [ [ name, decode( matched ) ] ]
100
+ end
101
+
102
+ def to_s
103
+ return '{' + self.class::OPERATOR + @variable_specs.map{|name,expand,max_length| name + (expand ? '*': '') + (max_length > 0 ? (':' + max_length.to_s) : '') }.join(',') + '}'
104
+ end
105
+
106
+ private
107
+
108
+ def expand_one( name, value, expand, max_length)
109
+ if length_limited?(max_length)
110
+ if value.kind_of?(Array) or value.kind_of?(Hash)
111
+ raise InvalidValue::LengthLimitInapplicable.new(name,value)
112
+ end
113
+ end
114
+ if value.kind_of?(Hash) or Utils.pair_array?(value)
115
+ return transform_hash(name, value, expand, max_length)
116
+ elsif value.kind_of? Array
117
+ return transform_array(name, value, expand, max_length)
118
+ else
119
+ return self_pair(name, value, max_length)
120
+ end
121
+ end
122
+
123
+ def length_limited?(max_length)
124
+ max_length > 0
125
+ end
126
+
127
+ def extracted_nil
128
+ nil
129
+ end
130
+
131
+ protected
132
+
133
+ module ClassMethods
134
+
135
+ def hash_extractors
136
+ @hash_extractors ||= Hash.new{|hsh, key| hsh[key] = generate_hash_extractor(key) }
137
+ end
138
+
139
+ def hash_extractor(max_length)
140
+ return hash_extractors[max_length]
141
+ end
142
+
143
+ def generate_hash_extractor(max_length)
144
+ source = regex_builder
145
+ source.push('\\A')
146
+ source.escaped_separator
147
+ source.capture do
148
+ source.character_class('+').reluctant
149
+ end
150
+ source.group do
151
+ source.escaped_pair_connector
152
+ source.capture do
153
+ source.character_class(max_length,0).reluctant
154
+ end
155
+ end.length('?')
156
+ source.lookahead do
157
+ source.push '\\z'
158
+ source.push '|'
159
+ source.escaped_separator
160
+ source.push '[^'
161
+ source.escaped_separator
162
+ source.push ']'
163
+ end
164
+ return Regexp.new( source.join , Utils::KCODE_UTF8)
165
+ end
166
+
167
+ def regex_builder
168
+ RegexBuilder.new(self)
169
+ end
170
+
171
+ end
172
+
173
+ extend ClassMethods
174
+
175
+ def escape(x)
176
+ Utils.escape_url(Utils.object_to_param(x))
177
+ end
178
+
179
+ def unescape(x)
180
+ Utils.unescape_url(x)
181
+ end
182
+
183
+ def regex_builder
184
+ self.class.regex_builder
185
+ end
186
+
187
+ SPLITTER = /^(,+)|([^,]+)/
188
+
189
+ COMMA = ",".freeze
190
+
191
+ def decode(x, split = true)
192
+ if x.nil?
193
+ return extracted_nil
194
+ elsif split
195
+ result = []
196
+ # Add a comma if the last character is a comma
197
+ # seems weird but is more compatible than changing
198
+ # the regex.
199
+ x += COMMA if x[-1..-1] == COMMA
200
+ URITemplate::RegexpEnumerator.new(SPLITTER, :rest => :raise).each(x) do |match|
201
+ if match[1]
202
+ next if match[1].size == 1
203
+ result << match[1][0..-3]
204
+ elsif match[2]
205
+ result << unescape(match[2])
206
+ end
207
+ end
208
+ case(result.size)
209
+ when 0 then ''
210
+ when 1 then result.first
211
+ else result
212
+ end
213
+ else
214
+ unescape(x)
215
+ end
216
+ end
217
+
218
+ def cut(str,chars)
219
+ if chars > 0
220
+ md = Regexp.compile("\\A#{self.class::CHARACTER_CLASS[:class]}{0,#{chars.to_s}}", Utils::KCODE_UTF8).match(str)
221
+ return md[0]
222
+ else
223
+ return str
224
+ end
225
+ end
226
+
227
+ def pair(key, value, max_length = 0, &block)
228
+ ek = key
229
+ if block
230
+ ev = value.map(&block).join(self.class::LIST_CONNECTOR)
231
+ else
232
+ ev = escape(value)
233
+ end
234
+ if !self.class::PAIR_IF_EMPTY and ev.size == 0
235
+ return ek
236
+ else
237
+ return ek + self.class::PAIR_CONNECTOR + cut( ev, max_length )
238
+ end
239
+ end
240
+
241
+ def transform_hash(name, hsh, expand , max_length)
242
+ if expand
243
+ hsh.map{|key,value| pair(escape(key),value) }
244
+ elsif hsh.none? && !self.class::NAMED
245
+ []
246
+ else
247
+ [ self_pair(name,hsh){|key,value| escape(key)+self.class::LIST_CONNECTOR+escape(value)} ]
248
+ end
249
+ end
250
+
251
+ def transform_array(name, ary, expand , max_length)
252
+ if expand
253
+ ary.map{|value| self_pair(name,value) }
254
+ elsif ary.none? && !self.class::NAMED
255
+ []
256
+ else
257
+ [ self_pair(name, ary){|value| escape(value) } ]
258
+ end
259
+ end
260
+ end
261
+
262
+ require 'uri_template/rfc6570/expression/named'
263
+ require 'uri_template/rfc6570/expression/unnamed'
264
+
265
+ class Expression::Basic < Expression::Unnamed
266
+ end
267
+
268
+ class Expression::Reserved < Expression::Unnamed
269
+
270
+ CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
271
+ OPERATOR = '+'.freeze
272
+ BASE_LEVEL = 2
273
+
274
+ def escape(x)
275
+ Utils.escape_uri(Utils.object_to_param(x))
276
+ end
277
+
278
+ def unescape(x)
279
+ Utils.unescape_uri(x)
280
+ end
281
+
282
+ def scheme?
283
+ true
284
+ end
285
+
286
+ def host?
287
+ true
288
+ end
289
+
290
+ end
291
+
292
+ class Expression::Fragment < Expression::Unnamed
293
+
294
+ CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
295
+ PREFIX = '#'.freeze
296
+ OPERATOR = '#'.freeze
297
+ BASE_LEVEL = 2
298
+
299
+ def escape(x)
300
+ Utils.escape_uri(Utils.object_to_param(x))
301
+ end
302
+
303
+ def unescape(x)
304
+ Utils.unescape_uri(x)
305
+ end
306
+
307
+ end
308
+
309
+ class Expression::Label < Expression::Unnamed
310
+
311
+ SEPARATOR = '.'.freeze
312
+ PREFIX = '.'.freeze
313
+ OPERATOR = '.'.freeze
314
+ BASE_LEVEL = 3
315
+
316
+ end
317
+
318
+ class Expression::Path < Expression::Unnamed
319
+
320
+ SEPARATOR = '/'.freeze
321
+ PREFIX = '/'.freeze
322
+ OPERATOR = '/'.freeze
323
+ BASE_LEVEL = 3
324
+
325
+ def starts_with_slash?
326
+ true
327
+ end
328
+
329
+ end
330
+
331
+ class Expression::PathParameters < Expression::Named
332
+
333
+ SEPARATOR = ';'.freeze
334
+ PREFIX = ';'.freeze
335
+ NAMED = true
336
+ PAIR_IF_EMPTY = false
337
+ OPERATOR = ';'.freeze
338
+ BASE_LEVEL = 3
339
+
340
+ end
341
+
342
+ class Expression::FormQuery < Expression::Named
343
+
344
+ SEPARATOR = '&'.freeze
345
+ PREFIX = '?'.freeze
346
+ NAMED = true
347
+ OPERATOR = '?'.freeze
348
+ BASE_LEVEL = 3
349
+
350
+ end
351
+
352
+ class Expression::FormQueryContinuation < Expression::Named
353
+
354
+ SEPARATOR = '&'.freeze
355
+ PREFIX = '&'.freeze
356
+ NAMED = true
357
+ OPERATOR = '&'.freeze
358
+ BASE_LEVEL = 3
359
+
360
+ end
361
+
362
+ # @private
363
+ OPERATORS = {
364
+ '' => Expression::Basic,
365
+ '+' => Expression::Reserved,
366
+ '#' => Expression::Fragment,
367
+ '.' => Expression::Label,
368
+ '/' => Expression::Path,
369
+ ';' => Expression::PathParameters,
370
+ '?' => Expression::FormQuery,
371
+ '&' => Expression::FormQueryContinuation
372
+ }
373
+
374
+ end
@@ -0,0 +1,117 @@
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
+ require 'uri_template/rfc6570'
19
+
20
+ class URITemplate::RFC6570
21
+
22
+ class RegexBuilder
23
+
24
+ def initialize(expression_class)
25
+ @expression_class = expression_class
26
+ @source = []
27
+ end
28
+
29
+ def <<(arg)
30
+ @source << arg
31
+ self
32
+ end
33
+
34
+ def push(*args)
35
+ @source.push(*args)
36
+ self
37
+ end
38
+
39
+ def escaped_pair_connector
40
+ self << Regexp.escape(@expression_class::PAIR_CONNECTOR)
41
+ end
42
+
43
+ def escaped_separator
44
+ self << Regexp.escape(@expression_class::SEPARATOR)
45
+ end
46
+
47
+ def escaped_prefix
48
+ self << Regexp.escape(@expression_class::PREFIX)
49
+ end
50
+
51
+ def join
52
+ return @source.join
53
+ end
54
+
55
+ def length(*args)
56
+ self << format_length(*args)
57
+ end
58
+
59
+ def character_class_with_comma(max_length=0, min = 0)
60
+ self << @expression_class::CHARACTER_CLASS[:class_with_comma] << format_length(max_length, min)
61
+ end
62
+
63
+ def character_class(max_length=0, min = 0)
64
+ self << @expression_class::CHARACTER_CLASS[:class] << format_length(max_length, min)
65
+ end
66
+
67
+ def reluctant
68
+ self << '?'
69
+ end
70
+
71
+ def group(capture = false)
72
+ self << '('
73
+ self << '?:' unless capture
74
+ yield
75
+ self << ')'
76
+ end
77
+
78
+ def negative_lookahead
79
+ self << '(?!'
80
+ yield
81
+ self << ')'
82
+ end
83
+
84
+ def lookahead
85
+ self << '(?='
86
+ yield
87
+ self << ')'
88
+ end
89
+
90
+ def capture(&block)
91
+ group(true, &block)
92
+ end
93
+
94
+ def separated_list(first = true, length = 0, min = 1, &block)
95
+ if first
96
+ yield
97
+ min -= 1
98
+ end
99
+ self.push('(?:').escaped_separator
100
+ yield
101
+ self.push(')').length(length, min)
102
+ end
103
+
104
+ private
105
+
106
+ def format_length(len, min = 0)
107
+ return len if len.kind_of? String
108
+ return '{'+min.to_s+','+len.to_s+'}' if len.kind_of?(Numeric) and len > 0
109
+ return '*' if min == 0
110
+ return '+' if min == 1
111
+ return '{'+min.to_s+',}'
112
+ end
113
+
114
+
115
+ end
116
+
117
+ end