uri_template 0.1.4 → 0.2.0

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.2.0 - 03.12.2011
2
+ * Reworked the escaping mechanism
3
+ + escape_utils can now be used to boost escape/unescape performance
4
+
1
5
  # 0.1.4 - 19.11.2011
2
6
  * Compatiblity: Works now with MRI 1.9.3, Rubinius and JRuby
3
7
  * Various (significant!) performance improvements
data/README CHANGED
@@ -6,6 +6,19 @@ It's currently planed to add newer versions of that spec when they emerge. There
6
6
 
7
7
  Some implementations might be able to extract variables, too ( Draft 7 is! ).
8
8
 
9
+ From version 0.2.0, it will use escape_utils if available. This will significantly boost uri-escape/unescape performance if more characters need to be escaped ( may be slightly slower in trivial cases. working on that ... ), but does not run everywhere. To enable this, do the following:
10
+
11
+ ````ruby
12
+ # escape_utils has to be loaded when uri_templates is loaded
13
+ gem 'escape_utils'
14
+ require 'escape_utils'
15
+
16
+ gem 'uri_template'
17
+ require 'uri_template'
18
+
19
+ UriTemplate::Utils.using_escape_utils? #=> true
20
+
21
+
9
22
  == Examples
10
23
 
11
24
  require 'uri_template'
data/lib/uri_template.rb CHANGED
@@ -149,7 +149,7 @@ module URITemplate
149
149
  end
150
150
 
151
151
  # Tries to coerce two URITemplates into a common representation.
152
- # Returns an array with two {URITemplate}s and two booleans indicating which of the two were converted or raises an {ArgumentError}.
152
+ # Returns an array with two {URITemplate}s and two booleans indicating which of the two were converted or raises an ArgumentError.
153
153
  #
154
154
  # @example
155
155
  # URITemplate.coerce( URITemplate.new(:draft7,'{x}'), '{y}' ) #=> [URITemplate.new(:draft7,'{x}'), URITemplate.new(:draft7,'{y}'), false, true]
@@ -217,7 +217,7 @@ module URITemplate
217
217
  end
218
218
 
219
219
  # @abstract
220
- # Returns the tokens of this templates. Tokens should include either {Static} or {Expression}.
220
+ # Returns the tokens of this templates. Tokens should include either {Literal} or {Expression}.
221
221
  def tokens
222
222
  raise "Please implement #tokens on #{self.class.inspect}."
223
223
  end
@@ -234,7 +234,7 @@ module URITemplate
234
234
 
235
235
  # Returns the number of static characters in this template.
236
236
  # This method is useful for routing, since it's often pointful to use the url with fewer variable characters.
237
- # 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.
237
+ # 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.
238
238
  #
239
239
  # @example
240
240
  # URITemplate.new('/xy/').static_characters #=> 4
@@ -44,7 +44,7 @@ class Colon
44
44
  end
45
45
 
46
46
  def expand(vars)
47
- return Utils.pct(vars[@name])
47
+ return Utils.escape_url(Utils.object_to_param(vars[@name]))
48
48
  end
49
49
 
50
50
  def to_r
@@ -103,7 +103,7 @@ class Colon
103
103
  md = self.to_r.match(uri)
104
104
  return nil unless md
105
105
  return Hash[ *self.variables.each_with_index.map{|v,i|
106
- [v, Utils.dpct(md[i+1])]
106
+ [v, Utils.unescape_url(md[i+1])]
107
107
  }.flatten(1) ]
108
108
  end
109
109
 
@@ -178,7 +178,7 @@ __REGEXP__
178
178
  if self.class::NAMED
179
179
  result.push( pair(var, vars[var], max_length) )
180
180
  else
181
- result.push( cut( encode(vars[var]), max_length ) )
181
+ result.push( cut( escape(vars[var]), max_length ) )
182
182
  end
183
183
  end
184
184
  end
@@ -203,7 +203,6 @@ __REGEXP__
203
203
  i = 0
204
204
  if self.class::NAMED
205
205
  @variable_specs.each{| var, expand , max_length |
206
- last = (vs == i)
207
206
  value = "(?:#{self.class::CHARACTER_CLASS[:class]}|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
208
207
  if expand
209
208
  #if self.class::PAIR_IF_EMPTY
@@ -269,6 +268,7 @@ __REGEXP__
269
268
  return [[ name , matched ]]
270
269
  end
271
270
  if expand
271
+ #TODO: do we really need this? - this could be stolen from rack
272
272
  ex = self.class.hash_extractor(max_length)
273
273
  rest = matched
274
274
  splitted = []
@@ -324,8 +324,12 @@ __REGEXP__
324
324
 
325
325
  extend ClassMethods
326
326
 
327
- def encode(x)
328
- Utils.pct(Utils.object_to_param(x), self.class::CHARACTER_CLASS[:unencoded])
327
+ def escape(x)
328
+ Utils.escape_url(Utils.object_to_param(x))
329
+ end
330
+
331
+ def unescape(x)
332
+ Utils.unescape_url(x)
329
333
  end
330
334
 
331
335
  SPLITTER = /^(?:,(,*)|([^,]+))/
@@ -345,7 +349,7 @@ __REGEXP__
345
349
  if m[1] and m[1].size > 0
346
350
  r << m[1]
347
351
  elsif m[2]
348
- r << Utils.dpct(m[2])
352
+ r << unescape(m[2])
349
353
  end
350
354
  v = m.post_match
351
355
  end
@@ -355,7 +359,7 @@ __REGEXP__
355
359
  else r
356
360
  end
357
361
  else
358
- Utils.dpct(x)
362
+ unescape(x)
359
363
  end
360
364
  end
361
365
 
@@ -370,8 +374,8 @@ __REGEXP__
370
374
  end
371
375
 
372
376
  def pair(key, value, max_length = 0)
373
- ek = encode(key)
374
- ev = encode(value)
377
+ ek = escape(key)
378
+ ev = escape(value)
375
379
  if !self.class::PAIR_IF_EMPTY and ev.size == 0
376
380
  return ek
377
381
  else
@@ -385,17 +389,17 @@ __REGEXP__
385
389
  elsif hsh.none?
386
390
  []
387
391
  else
388
- [ (self.class::NAMED ? encode(name)+self.class::PAIR_CONNECTOR : '' ) + hsh.map{|key,value| encode(key)+self.class::LIST_CONNECTOR+encode(value) }.join(self.class::LIST_CONNECTOR) ]
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) ]
389
393
  end
390
394
  end
391
395
 
392
396
  def transform_array(name, ary, expand , max_length)
393
397
  if expand
394
- ary.map{|value| encode(value) }
398
+ ary.map{|value| escape(value) }
395
399
  elsif ary.none?
396
400
  []
397
401
  else
398
- [ (self.class::NAMED ? encode(name)+self.class::PAIR_CONNECTOR : '' ) + ary.map{|value| encode(value) }.join(self.class::LIST_CONNECTOR) ]
402
+ [ (self.class::NAMED ? escape(name)+self.class::PAIR_CONNECTOR : '' ) + ary.map{|value| escape(value) }.join(self.class::LIST_CONNECTOR) ]
399
403
  end
400
404
  end
401
405
 
@@ -404,6 +408,14 @@ __REGEXP__
404
408
  CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
405
409
  OPERATOR = '+'.freeze
406
410
  BASE_LEVEL = 2
411
+
412
+ def escape(x)
413
+ Utils.escape_uri(Utils.object_to_param(x))
414
+ end
415
+
416
+ def unescape(x)
417
+ Utils.unescape_uri(x)
418
+ end
407
419
 
408
420
  end
409
421
 
@@ -413,6 +425,14 @@ __REGEXP__
413
425
  PREFIX = '#'.freeze
414
426
  OPERATOR = '#'.freeze
415
427
  BASE_LEVEL = 2
428
+
429
+ def escape(x)
430
+ Utils.escape_uri(Utils.object_to_param(x))
431
+ end
432
+
433
+ def unescape(x)
434
+ Utils.unescape_uri(x)
435
+ end
416
436
 
417
437
  end
418
438
 
@@ -593,8 +613,6 @@ __REGEXP__
593
613
 
594
614
  extend ClassMethods
595
615
 
596
- attr_reader :pattern
597
-
598
616
  attr_reader :options
599
617
 
600
618
  # @param String,Array either a pattern as String or an Array of tokens
@@ -642,8 +660,6 @@ __REGEXP__
642
660
  # @return Regexp
643
661
  def to_r
644
662
  @regexp ||= begin
645
- #classes = CHARACTER_CLASSES.map{|_,v| v[:class]+"{0}\n" }
646
- bc = 0
647
663
  source = tokens.map(&:to_r_source)
648
664
  source.unshift('\A')
649
665
  source.push('\z')
@@ -729,7 +745,7 @@ __REGEXP__
729
745
 
730
746
  # Compares two template patterns.
731
747
  def ==(o)
732
- this, other, this_converted, other_converted = URITemplate.coerce( self, o )
748
+ this, other, this_converted, _ = URITemplate.coerce( self, o )
733
749
  if this_converted
734
750
  return this == other
735
751
  end
@@ -793,7 +809,7 @@ __REGEXP__
793
809
  # (tpl / 'a' / 'b' ).pattern #=> '/xy/a/b'
794
810
  #
795
811
  def /(o)
796
- this, other, this_converted, other_converted = URITemplate.coerce( self, o )
812
+ this, other, this_converted, _ = URITemplate.coerce( self, o )
797
813
  if this_converted
798
814
  return this / other
799
815
  end
@@ -64,81 +64,156 @@ module URITemplate
64
64
 
65
65
  end
66
66
 
67
- # A collection of some utility methods
67
+ # A collection of some utility methods.
68
+ # The most methods are used to parse or generate uri-parameters.
69
+ # I will use the escape_utils library if available, but runs happily without.
70
+ #
68
71
  module Utils
69
72
 
70
73
  KCODE_UTF8 = (Regexp::KCODE_UTF8 rescue 0)
71
-
72
- # @private
73
- PCT = /%(\h\h)/.freeze
74
74
 
75
- # A regexp which match all non-simple characters.
76
- NOT_SIMPLE_CHARS = /([^A-Za-z0-9\-\._])/.freeze
75
+ # Bundles some string encoding methods.
76
+ module StringEncoding
77
+
78
+ # @method to_ascii(string)
79
+ # converts a string to ascii
80
+ #
81
+ # This method checks which encoding method is available.
82
+ # @param String
83
+ # @return String
84
+ # @visibility public
85
+ def to_ascii_encode(str)
86
+ str.encode(Encoding::ASCII)
87
+ end
88
+
89
+ # @method to_utf8(string)
90
+ # converts a string to utf8
91
+ #
92
+ # This method checks which encoding method is available.
93
+ # @param String
94
+ # @return String
95
+ # @visibility public
96
+ def to_utf8_encode(str)
97
+ str.encode(Encoding::UTF_8)
98
+ end
99
+
100
+ def to_ascii_fallback(str)
101
+ str
102
+ end
103
+
104
+ def to_utf8_fallback(str)
105
+ str
106
+ end
107
+
108
+ if "".respond_to?(:encode)
109
+ # @private
110
+ alias_method :to_ascii, :to_ascii_encode
111
+ # @private
112
+ alias_method :to_utf8, :to_utf8_encode
113
+ else
114
+ # @private
115
+ alias_method :to_ascii, :to_ascii_fallback
116
+ # @private
117
+ alias_method :to_utf8, :to_utf8_fallback
118
+ end
119
+
120
+ public :to_ascii
121
+ public :to_utf8
122
+
123
+ end
77
124
 
78
- ASCII = 'ASCII'.freeze
79
- UTF8 = 'UTF-8'.freeze
125
+ module Escaping
80
126
 
81
- def to_ascii_force_encoding(str)
82
- str.force_encoding(ASCII)
83
- end
127
+ # A pure escaping module, which implements escaping methods in pure ruby.
128
+ # The performance is acceptable, but could be better with escape_utils.
129
+ module Pure
84
130
 
85
- def to_utf8_force_encoding(str)
86
- str.force_encoding(UTF8)
87
- end
131
+ include StringEncoding
88
132
 
89
- def to_ascii_encode(str)
90
- str.encode(ASCII)
91
- end
133
+ # @private
134
+ URL_ESCAPED = /([^A-Za-z0-9\-\._])/.freeze
135
+
136
+ # @private
137
+ URI_ESCAPED = /([^A-Za-z0-9!$&'()*+,.\/:;=?@\[\]_~])/.freeze
138
+
139
+ # @private
140
+ PCT = /%(\h\h)/.freeze
141
+
142
+ def escape_url(s)
143
+ to_utf8(s.to_s).gsub(URL_ESCAPED){
144
+ '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
145
+ }
146
+ end
147
+
148
+ def escape_uri(s)
149
+ to_utf8(s.to_s).gsub(URI_ESCAPED){
150
+ '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
151
+ }
152
+ end
153
+
154
+ def unescape_url(s)
155
+ to_utf8( s.to_s.gsub('+',' ').gsub(PCT){
156
+ $1.to_i(16).chr
157
+ } )
158
+ end
159
+
160
+ def unescape_uri(s)
161
+ to_utf8( s.to_s.gsub(PCT){
162
+ $1.to_i(16).chr
163
+ })
164
+ end
165
+
166
+ def using_escape_utils?
167
+ false
168
+ end
169
+
170
+ end
171
+
172
+ if defined? EscapeUtils
173
+
174
+ # A escaping module, which is backed by escape_utils.
175
+ # The performance is good, espacially for strings with many escaped characters.
176
+ module EscapeUtils
177
+
178
+ include StringEncoding
179
+
180
+ include ::EscapeUtils
92
181
 
93
- def to_utf8_encode(str)
94
- str.encode(UTF8)
95
- end
182
+ def using_escape_utils?
183
+ true
184
+ end
185
+
186
+ def escape_url(s)
187
+ super(to_utf8(s)).gsub('+','%20')
188
+ end
189
+
190
+ def escape_uri(s)
191
+ super(to_utf8(s))
192
+ end
193
+
194
+ def unescape_url(s)
195
+ super(to_ascii(s))
196
+ end
197
+
198
+ def unescape_uri(s)
199
+ super(to_ascii(s))
200
+ end
201
+
202
+ end
96
203
 
97
- def to_ascii_fallback(str)
98
- str
99
204
  end
100
205
 
101
- def to_utf8_fallback(str)
102
- str
103
- end
104
206
 
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
207
  end
115
208
 
116
- # Encodes the given string into a pct-encoded string.
117
- # @param s The string to be encoded.
118
- # @param m A regexp matching all characters, which need to be encoded.
119
- #
120
- # @example
121
- # URITemplate::Utils.pct("abc") #=> "abc"
122
- # URITemplate::Utils.pct("%") #=> "%25"
123
- #
124
- #TODO: is encoding as utf8/ascii really needed?
125
- def pct(s, m=NOT_SIMPLE_CHARS)
126
- to_ascii( s.to_s.gsub(m){
127
- '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
128
- } )
129
- end
209
+ include StringEncoding
130
210
 
131
- # Decodes the given pct-encoded string into a utf-8 string.
132
- # Should be the opposite of #pct.
133
- #
134
- # @example
135
- # URITemplate::Utils.dpct("abc") #=> "abc"
136
- # URITemplate::Utils.dpct("%25") #=> "%"
137
- #
138
- def dpct(s)
139
- to_utf8(s.to_s.gsub(PCT){
140
- $1.to_i(16).chr
141
- })
211
+ if Escaping.const_defined? :EscapeUtils
212
+ include Escaping::EscapeUtils
213
+ puts "Using escape_utils." if $VERBOSE
214
+ else
215
+ include Escaping::Pure
216
+ puts "Not using escape_utils." if $VERBOSE
142
217
  end
143
218
 
144
219
  # Converts an object to a param value.
data/uri_template.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'uri_template'
3
- s.version = '0.1.4'
4
- s.date = '2011-11-19'
3
+ s.version = '0.2.0'
4
+ s.date = '2011-12-03'
5
5
  s.authors = ["HannesG"]
6
6
  s.email = %q{hannes.georg@googlemail.com}
7
7
  s.summary = 'A templating system for URIs.'
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.4
4
+ version: 0.2.0
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-19 00:00:00.000000000Z
12
+ date: 2011-12-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &16477840 !ruby/object:Gem::Requirement
16
+ requirement: &17186480 !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: *16477840
24
+ version_requirements: *17186480
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yard
27
- requirement: &16477400 !ruby/object:Gem::Requirement
27
+ requirement: &17185740 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *16477400
35
+ version_requirements: *17185740
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &16476980 !ruby/object:Gem::Requirement
38
+ requirement: &17184760 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *16476980
46
+ version_requirements: *17184760
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
- requirement: &16476560 !ruby/object:Gem::Requirement
49
+ requirement: &17195720 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *16476560
57
+ version_requirements: *17195720
58
58
  description: ! 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07
59
59
  . An implementation of an older version of that spec is known as addressable. This
60
60
  gem however is intended to be extended when newer specs evolve. For now only draft