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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # 0.5.0 - 23.09.2012
2
+ - - Removed draft7
3
+ - - splitted absoulte? method into host? and scheme?
4
+ - + the URITemplate interface is now much stronger
5
+ - * code quality _significantly_ improved
6
+ - + concat method
7
+
8
+ # 0.4.0 - 06.07.2012
9
+ - + expand now accepts symbols as keys ( thanks to @peterhellber )
10
+ - + expand now accepts arrays of pairs ( thanks to @peterhellber )
11
+ - * fixed some testing bugs
12
+
13
+ # 0.3.0 - 24.05.2012
14
+ - + Implemented the final version. Default implementation is now RFC 6570
15
+ - * BUGFIX: variables with terminal dots were allowed
16
+ - * BUGFIX: lists of commas were parsed incorrectly
17
+
18
+ # 0.2.1 - 30.12.2011
19
+ - * Compatibility: Works now with MRI 1.8.7 and REE
20
+
21
+ # 0.2.0 - 03.12.2011
22
+ - * Reworked the escaping mechanism
23
+ - + escape_utils can now be used to boost escape/unescape performance
24
+
25
+ # 0.1.4 - 19.11.2011
26
+ - * Compatiblity: Works now with MRI 1.9.3, Rubinius and JRuby
27
+ - * Various (significant!) performance improvements
28
+
29
+ # 0.1.3 - 15.11.2011
30
+ - * BUGFIX: Draft7./ now concatenates literals correctly
31
+ - * BUGFIX: Draft7.tokens is now public
32
+
33
+ # 0.1.2 - 10.11.2011
34
+ - + added a new template-type: Colon
35
+ this should allow (some day) to rails-like routing tables
36
+ - + made the tokens-method mandatory and added two interfaces for tokens.
37
+ this allows cross-type features like variable anaylisis
38
+
39
+ # 0.1.1 - 4.11.2011
40
+ - + added a bunch of useful helper methods
41
+
42
+ # 0.1.0 - 2.11.2011
43
+ - - Removed Sections. They made too many headaches.
44
+ - + Made draft7 template concatenateable. This should replace sections.
45
+ - * BUGFIX: multiline uris were matched
46
+ - * BUGFIX: variablenames were decoded when this was not appreciated
47
+
48
+ # 0.0.2 - 1.11.2011
49
+ - * BUGFIX: Concatenating empty sections no more leads to catch-all templates, when an emtpy template was appreciated.
50
+ - + The extracted variables now contains the keys :suffix and :prefix if the match didn't consume the whole uri.
51
+
52
+ # 0.0.1 - 30.10.2011
53
+ - Initial version
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ URITemplate - a uri template library
2
+ ========================
3
+
4
+ [![Build Status](https://secure.travis-ci.org/hannesg/uri_template.png)](http://travis-ci.org/hannesg/uri_template)
5
+ [![Dependency Status](https://gemnasium.com/hannesg/uri_template.png)](https://gemnasium.com/hannesg/uri_template)
6
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/hannesg/uri_template)
7
+
8
+ With URITemplate you can generate URIs based on simple templates and extract variables from URIs using the same templates. There are currently two syntaxes defined. Namely the one defined in [RFC 6570]( http://tools.ietf.org/html/rfc6570 ) and a colon based syntax, similiar to the one used by sinatra.
9
+
10
+ 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:
11
+
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
+
22
+ Examples
23
+ -------------------
24
+
25
+ require 'uri_template'
26
+
27
+ tpl = URITemplate.new('http://{host}{/segments*}/{file}{.extensions*}')
28
+
29
+ # This will give: http://www.host.com/path/to/a/file.x.y
30
+ tpl.expand('host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y'])
31
+
32
+ # This will give: { 'host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y']}
33
+ tpl.extract('http://www.host.com/path/to/a/file.x.y')
34
+
35
+ # If you like colon templates more:
36
+ tpl2 = URITemplate.new(:colon, '/:x/y')
37
+
38
+ # This will give: {'x' => 'z'}
39
+ tpl2.extract('/z/y')
40
+
41
+
42
+ RFC 6570 Syntax
43
+ --------------------
44
+
45
+ The syntax defined by [RFC 6570]( http://tools.ietf.org/html/rfc6570 ) is pretty straight forward. Basically anything surrounded by curly brackets is interpreted as variable.
46
+
47
+ URITemplate.new('{variable}').expand('variable' => 'value') #=> "value"
48
+
49
+ The way variables are inserted can be modified using operators. The operator is the first character between the curly brackets. There are seven operators defined `#`, `+`, `;`, `?`, `&`, `/` and `.`. So if you want to create a form-style query do this:
50
+
51
+ URITemplate.new('{?variable}').expand('variable' => 'value') #=> "?variable=value"
52
+
53
+ Benchmarks
54
+ -----------------------
55
+
56
+ I have assembled one benchmark based on the uritemplate-test examples. You can find them in the "benchmarks" folder. The short result: uri_template is 2-10x faster than addressable on ruby 1.9.3.
@@ -20,15 +20,45 @@ require 'forwardable'
20
20
  require 'uri_template'
21
21
  require 'uri_template/utils'
22
22
 
23
- # A colon based template denotes variables with a colon.
24
- # This template type is realy basic but having just on template type was a bit weird.
25
23
  module URITemplate
26
24
 
25
+ # A colon based template denotes variables with a colon.
26
+ #
27
+ # This template type is somewhat compatible with sinatra.
28
+ #
29
+ # @example
30
+ # tpl = URITemplate::Colon.new('/foo/:bar')
31
+ # tpl.extract('/foo/baz') #=> {'bar'=>'baz'}
32
+ # tpl.expand('bar'=>'boom') #=> '/foo/boom'
33
+ #
27
34
  class Colon
28
35
 
29
36
  include URITemplate
30
37
 
31
- VAR = /(?:\{:([a-z]+)\}|:([a-z]+)(?![a-z]))/u
38
+ VAR = /(?:\{:([a-z]+)\}|:([a-z]+)(?![a-z])|\*)/u
39
+
40
+ class InvalidValue < StandardError
41
+
42
+ include URITemplate::InvalidValue
43
+
44
+ attr_reader :variable, :value
45
+
46
+ def initialize(variable, value)
47
+ @variable = variable
48
+ @value = value
49
+ super(generate_message())
50
+ end
51
+
52
+ class SplatIsNotAnArray < self
53
+ end
54
+
55
+ protected
56
+
57
+ def generate_message()
58
+ return "The template variable " + variable.inspect + " cannot expand the given value "+ value.inspect
59
+ end
60
+
61
+ end
32
62
 
33
63
  class Token
34
64
 
@@ -44,11 +74,41 @@ class Colon
44
74
  end
45
75
 
46
76
  def expand(vars)
47
- return Utils.escape_url(Utils.object_to_param(vars[@name]))
77
+ return Utils.escape_url(Utils.object_to_param(vars[name]))
78
+ end
79
+
80
+ def to_r
81
+ return '([^/]*?)'
82
+ end
83
+
84
+ def to_s
85
+ return ":#{name}"
86
+ end
87
+
88
+ end
89
+
90
+ class Splat < Variable
91
+
92
+ SPLAT = 'splat'.freeze
93
+
94
+ attr_reader :index
95
+
96
+ def initialize(index)
97
+ @index = index
98
+ super(SPLAT)
99
+ end
100
+
101
+ def expand(vars)
102
+ var = vars[name]
103
+ if Array === var
104
+ return Utils.escape_uri(Utils.object_to_param(var[index]))
105
+ else
106
+ raise InvalidValue::SplatIsNotAnArray.new(name,var)
107
+ end
48
108
  end
49
109
 
50
110
  def to_r
51
- return ['([^/]*?)'].join
111
+ return '(.+?)'
52
112
  end
53
113
 
54
114
  end
@@ -78,13 +138,13 @@ class Colon
78
138
  # Tries to convert the value into a colon-template.
79
139
  # @example
80
140
  # URITemplate::Colon.try_convert('/foo/:bar/').pattern #=> '/foo/:bar/'
81
- # URITemplate::Colon.try_convert(URITemplate::Draft7.new('/foo/{bar}/')).pattern #=> '/foo/{:bar}/'
141
+ # URITemplate::Colon.try_convert(URITemplate.new(:rfc6570, '/foo/{bar}/')).pattern #=> '/foo/{:bar}/'
82
142
  def self.try_convert(x)
83
143
  if x.kind_of? String
84
144
  return new(x)
85
145
  elsif x.kind_of? self
86
146
  return x
87
- elsif x.kind_of? URITemplate::Draft7 and x.level == 1
147
+ elsif x.kind_of? URITemplate::RFC6570 and x.level == 1
88
148
  return new( x.pattern.gsub(/\{(.*?)\}/u){ "{:#{$1}}" } )
89
149
  else
90
150
  return nil
@@ -92,6 +152,7 @@ class Colon
92
152
  end
93
153
 
94
154
  def initialize(pattern)
155
+ raise ArgumentError,"Expected a String but got #{pattern.inspect}" unless pattern.kind_of? String
95
156
  @pattern = pattern
96
157
  end
97
158
 
@@ -102,9 +163,20 @@ class Colon
102
163
  def extract(uri)
103
164
  md = self.to_r.match(uri)
104
165
  return nil unless md
105
- return Hash[ *self.variables.each_with_index.map{|v,i|
106
- [v, Utils.unescape_url(md[i+1])]
107
- }.flatten(1) ]
166
+ result = {}
167
+ splat = []
168
+ self.tokens.select{|tk| tk.kind_of? URITemplate::Expression }.each_with_index do |tk,i|
169
+ if tk.kind_of? Token::Splat
170
+ splat << md[i+1]
171
+ result['splat'] = splat unless result.key? 'splat'
172
+ else
173
+ result[tk.name] = Utils.unescape_url( md[i+1] )
174
+ end
175
+ end
176
+ if block_given?
177
+ return yield(result)
178
+ end
179
+ return result
108
180
  end
109
181
 
110
182
  def type
@@ -119,30 +191,17 @@ class Colon
119
191
  @tokens ||= tokenize!
120
192
  end
121
193
 
122
- # Tries to concatenate two templates, as if they were path segments.
123
- # Removes double slashes or inserts one if they are missing.
124
- #
125
- # @example
126
- # tpl = URITemplate::Colon.new('/xy/')
127
- # (tpl / '/z/' ).pattern #=> '/xy/z/'
128
- # (tpl / 'z/' ).pattern #=> '/xy/z/'
129
- # (tpl / ':z' ).pattern #=> '/xy/:z'
130
- # (tpl / ':a' / 'b' ).pattern #=> '/xy/:a/b'
131
- #
132
- def /(o)
133
- this, other, this_converted, other_converted = URITemplate.coerce( self, o )
134
- if this_converted
135
- return this / other
136
- end
137
- return self.class.new( File.join( this.pattern, other.pattern ) )
138
- end
139
-
140
194
  protected
141
195
 
142
196
  def tokenize!
197
+ number_of_splats = 0
143
198
  RegexpEnumerator.new(VAR).each(@pattern).map{|x|
144
199
  if x.kind_of? String
145
- Token::Static.new(x)
200
+ Token::Static.new(Utils.escape_uri(x))
201
+ elsif x[0] == '*'
202
+ n = number_of_splats
203
+ number_of_splats = number_of_splats + 1
204
+ Token::Splat.new(n)
146
205
  else
147
206
  # TODO: when rubinius supports ambigious names this could be replaced with x['name'] *sigh*
148
207
  Token::Variable.new(x[1] || x[2])
@@ -0,0 +1,33 @@
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
+ # A module which all non-literal tokens should include.
19
+ module URITemplate::Expression
20
+
21
+ include URITemplate::Token
22
+
23
+ attr_reader :variables
24
+
25
+ def literal?
26
+ false
27
+ end
28
+
29
+ def expression?
30
+ true
31
+ end
32
+
33
+ end
@@ -0,0 +1,53 @@
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
+ # A module which all literal tokens should include.
19
+ module URITemplate::Literal
20
+
21
+ include URITemplate::Token
22
+
23
+ SLASH = ?/
24
+
25
+ attr_reader :string
26
+
27
+ def literal?
28
+ true
29
+ end
30
+
31
+ def expression?
32
+ false
33
+ end
34
+
35
+ def size
36
+ 0
37
+ end
38
+
39
+ def expand(_)
40
+ return string
41
+ end
42
+
43
+ def starts_with_slash?
44
+ string[0] == SLASH
45
+ end
46
+
47
+ def ends_with_slash?
48
+ string[-1] == SLASH
49
+ end
50
+
51
+ alias to_s string
52
+
53
+ end
@@ -0,0 +1,72 @@
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::Expression::Named < URITemplate::RFC6570::Expression
21
+
22
+ alias self_pair pair
23
+
24
+ def to_r_source
25
+ source = regex_builder
26
+ source.group do
27
+ source.escaped_prefix
28
+ first = true
29
+ @variable_specs.each do | var, expand , max_length |
30
+ if expand
31
+ source.capture do
32
+ source.separated_list(first) do
33
+ source.character_class('+')\
34
+ .escaped_pair_connector\
35
+ .character_class_with_comma(max_length)
36
+ end
37
+ end
38
+ else
39
+ source.group do
40
+ source.escaped_separator unless first
41
+ source << Regexp.escape(var)
42
+ source.group do
43
+ source.escaped_pair_connector
44
+ source.capture do
45
+ source.character_class_with_comma(max_length)
46
+ end
47
+ source << '|' unless self.class::PAIR_IF_EMPTY
48
+ end
49
+ end.length('?')
50
+ end
51
+ first = false
52
+ end
53
+ end.length('?')
54
+ return source.join
55
+ end
56
+
57
+ private
58
+
59
+ def extracted_nil
60
+ self.class::PAIR_IF_EMPTY ? nil : ""
61
+ end
62
+
63
+ def after_expand(name, splitted)
64
+ result = URITemplate::Utils.pair_array_to_hash2( splitted )
65
+ if result.size == 1 && result[0][0] == name
66
+ return result
67
+ else
68
+ return [ [ name , result ] ]
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,76 @@
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::Expression::Unnamed < URITemplate::RFC6570::Expression
21
+
22
+ def self_pair(_, value, max_length = 0,&block)
23
+ if block
24
+ ev = value.map(&block).join(self.class::LIST_CONNECTOR)
25
+ else
26
+ ev = escape(value)
27
+ end
28
+ cut( ev, max_length ,&block)
29
+ end
30
+
31
+ def to_r_source
32
+ vs = @variable_specs.size - 1
33
+ i = 0
34
+ source = regex_builder
35
+ source.group do
36
+ source.escaped_prefix
37
+ @variable_specs.each do | var, expand , max_length |
38
+ last = (vs == i)
39
+ first = (i == 0)
40
+ if expand
41
+ source.group(true) do
42
+ source.separated_list(first) do
43
+ source.group do
44
+ source.character_class('+').reluctant
45
+ source.escaped_pair_connector
46
+ end.length('?')
47
+ source.character_class(max_length)
48
+ end
49
+ end
50
+ else
51
+ source.escaped_separator unless first
52
+ source.group(true) do
53
+ if last
54
+ source.character_class_with_comma(max_length)
55
+ else
56
+ source.character_class(max_length)
57
+ end
58
+ end
59
+ end
60
+ i = i+1
61
+ end
62
+ end.length('?')
63
+ return source.join
64
+ end
65
+
66
+ private
67
+
68
+ def after_expand(name, splitted)
69
+ if splitted.none?{|_,b| b }
70
+ return [ [ name, splitted.map{|a,_| a } ] ]
71
+ else
72
+ return [ [ name, splitted ] ]
73
+ end
74
+ end
75
+
76
+ end