uri_template 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.1.4 - 19.11.2011
2
+ * Compatiblity: Works now with MRI 1.9.3, Rubinius and JRuby
3
+ * Various (significant!) performance improvements
4
+
1
5
  # 0.1.3 - 15.11.2011
2
6
  * BUGFIX: Draft7./ now concatenates literals correctly
3
7
  * BUGFIX: Draft7.tokens is now public
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 T9600, 4 gb ram, Fedora 15 64 bit, 10_000 repetitions
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?}
@@ -28,7 +28,7 @@ class Colon
28
28
 
29
29
  include URITemplate
30
30
 
31
- VAR = /(?:\{:(?<name>[a-z]+)\}|:(?<name>[a-z]+)(?![a-z]))/
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 ['(?<', name, '>[^/]*?)'].join
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
- return self.to_r.match(uri) do |md|
104
- Hash[ *self.variables.map{|v|
105
- [v, Utils.dpct(md[v])]
106
- }.flatten(1) ]
107
- end
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
- Token::Variable.new(x['name'])
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
@@ -34,26 +34,28 @@ class URITemplate::Draft7
34
34
  Utils = URITemplate::Utils
35
35
 
36
36
  # @private
37
- LITERAL = /^([^"'%<>\\^`{|}\s\p{Cc}]|%\h\h)+/
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 => '(?<c_u_>[A-Za-z0-9\-\._]|%\h\h)',
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 => '(?<c_urp_>[A-Za-z0-9\-\._:\/?#\[\]@!\$%\'\(\)*+,;=]|%\h\h)',
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 => '(?<c_vn_> (?:[a-zA-Z_]|%[0-9a-fA-F]{2})(?:[a-zA-Z_\.]|%[0-9a-fA-F]{2})*?)',
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, Regexp::EXTENDED)
80
- (?<operator> [+#\./;?&]?){0}
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, Regexp::EXTENDED)
89
- (?<operator> [+#\./;?&]?){0}
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, Regexp::EXTENDED)
98
- (?<operator> [+#\./;?&]?){0}
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 + @variable_specs.map{|name,expand,max_length| name +(expand ? '*': '') + (max_length > 0 ? ':'+max_length.to_s : '') }.join(',') + '}'
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(base_counter = 0)
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 = "(?:\\g<#{self.class::CHARACTER_CLASS[:class_name]}>|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
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 = "(?:\\g<c_vn_>#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
210
+ pair = "(?:#{CHARACTER_CLASSES[:varname][:class]}#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
219
211
 
220
212
  if first
221
- source << "(?<v#{base_counter + i}>(?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
213
+ source << "((?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
222
214
  else
223
- source << "(?<v#{base_counter + i}>(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
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)}(?<v#{base_counter + i}>#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})?"
219
+ pair = "#{Regexp.escape(var)}(#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})"
228
220
  else
229
- pair = "#{Regexp.escape(var)}(?<v#{base_counter + i}>#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value}|)"
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 = "\\g<#{self.class::CHARACTER_CLASS[:class_name]}>#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
239
+ value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
248
240
 
249
- pair = "(?:\\g<c_vn_>#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
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 = "(?:\\g<#{self.class::CHARACTER_CLASS[:class_name]}>)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
247
+ value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
256
248
  else
257
- value = "(?:\\g<#{self.class::CHARACTER_CLASS[:class_name]}>|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
249
+ value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
258
250
  end
259
251
  else
260
- value = "\\g<#{self.class::CHARACTER_CLASS[:class_name]}>#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
252
+ value = "#{self.class::CHARACTER_CLASS[:class]}#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
261
253
  end
262
254
  if first
263
- source << "(?<v#{base_counter + i}>#{value})"
255
+ source << "(#{value})"
264
256
  first = false
265
257
  else
266
- source << "(?:#{Regexp.escape(self.class::SEPARATOR)}(?<v#{base_counter + i}>#{value}))?"
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['rest'].to_s
285
+ rest = match[3].to_s
291
286
  else
292
287
  rest = ''
293
288
  end
294
- if match['name']
289
+ if match[1]
295
290
  found_value = true
296
- splitted << [ match['name'][0..-2], decode(match['value'] + rest , false) ]
291
+ splitted << [ match[1][0..-2], decode(match[2] + rest , false) ]
297
292
  else
298
- splitted << [ match['value'] + rest, nil ]
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 = "\\g<#{self::CHARACTER_CLASS[:class_name]}>#{(max_length > 0)?'{,'+max_length.to_s+'}':'*?'}"
322
- pair = "(?<name>\\g<c_vn_>#{Regexp.escape(self::PAIR_CONNECTOR)})?(?<value>#{value})"
323
-
324
- Regexp.new( CHARACTER_CLASSES[:varname][:class] + "{0}\n" + self::CHARACTER_CLASS[:class] + "{0}\n" + "^#{Regexp.escape(self::SEPARATOR)}?" + pair + "(?<rest>$|#{Regexp.escape(self::SEPARATOR)}(?!#{Regexp.escape(self::SEPARATOR)}))" ,Regexp::EXTENDED)
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("^#{self.class::CHARACTER_CLASS[:class]}{,#{chars.to_s}}", Regexp::EXTENDED).match(str)
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[5].split(',').map{|name|
516
+ vars = scanner[2].split(',').map{|name|
522
517
  match = VAR.match(name)
523
- [ match['varname'], match['explode'] == '*', match['length'].to_i ]
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
- classes = CHARACTER_CLASSES.map{|_,v| v[:class]+"{0}\n" }
647
- bc = 0
648
- @regexp ||= Regexp.new(classes.join + '\A' + tokens.map{|part|
649
- r = part.to_r_source(bc)
650
- bc += part.arity
651
- r
652
- }.join + '\z' , Regexp::EXTENDED)
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] == '/' # the last token ends with an /
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 = 0
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
- while i < part.arity
876
- vars.push(*part.extract(i, matchdata["v#{bc}"]))
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
- return vars
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
@@ -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.encode('UTF-8').gsub(m){
126
+ to_ascii( s.to_s.gsub(m){
86
127
  '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
87
- }.encode('ASCII')
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.encode('ASCII').gsub(PCT){
139
+ to_utf8(s.to_s.gsub(PCT){
99
140
  $1.to_i(16).chr
100
- }.encode('UTF-8')
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
- def pair_array_to_hash(a)
147
- if pair_array?(a)
148
- return Hash[ *a.map{ |k,v| [k,pair_array_to_hash(v)] }.flatten(1) ]
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 a
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.3'
4
- s.date = '2011-11-15'
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: only for 1.9 compatible since it uses Oniguruma regexp.'
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.3
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-15 00:00:00.000000000Z
12
+ date: 2011-11-19 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &26050760 !ruby/object:Gem::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: *26050760
24
+ version_requirements: *16477840
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yard
27
- requirement: &26050320 !ruby/object:Gem::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: *26050320
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: only for 1.9 compatible since it uses Oniguruma regexp.'
61
+ 7 is supported. Downside: not for 1.8.7.'
40
62
  email: hannes.georg@googlemail.com
41
63
  executables: []
42
64
  extensions: []