uri_template 0.1.4 → 0.2.0

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 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