usher 0.6.2 → 0.6.3
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/VERSION.yml +1 -1
- data/lib/usher/exceptions.rb +1 -0
- data/lib/usher/interface/rack.rb +1 -1
- data/lib/usher/node.rb +8 -8
- data/lib/usher/route/path.rb +6 -2
- data/lib/usher/route/variable.rb +20 -19
- data/lib/usher/splitter.rb +3 -5
- data/lib/usher/util/generate.rb +55 -27
- data/lib/usher.rb +14 -8
- data/spec/private/recognize_spec.rb +7 -0
- metadata +2 -2
data/VERSION.yml
CHANGED
data/lib/usher/exceptions.rb
CHANGED
data/lib/usher/interface/rack.rb
CHANGED
@@ -57,7 +57,7 @@ class Usher
|
|
57
57
|
|
58
58
|
def initialize(app = nil, options = nil, &blk)
|
59
59
|
@_app = app || lambda { |env| ::Rack::Response.new("No route found", 404).finish }
|
60
|
-
@router = Usher.new(:request_methods => [:request_method, :host, :port, :scheme], :generator => Usher::Util::Generators::URL.new)
|
60
|
+
@router = Usher.new(:request_methods => [:request_method, :host, :port, :scheme], :generator => Usher::Util::Generators::URL.new, :allow_identical_variable_names => false)
|
61
61
|
@use_destinations = options && options.key?(:use_destinations) ? options[:use_destinations] : true
|
62
62
|
instance_eval(&blk) if blk
|
63
63
|
end
|
data/lib/usher/node.rb
CHANGED
@@ -122,14 +122,14 @@ class Usher
|
|
122
122
|
def find(usher, request_object, original_path, path, params = [], position = 0)
|
123
123
|
if terminates? && (path.empty? || terminates.route.partial_match? || (usher.ignore_trailing_delimiters? && path.all?{|p| usher.delimiters.include?(p)}))
|
124
124
|
terminates.route.partial_match? ?
|
125
|
-
Response.new(terminates, params, original_path[position, original_path.size], original_path[0, position]) :
|
126
|
-
Response.new(terminates, params, nil, original_path)
|
125
|
+
Response.new(terminates, terminates.convert_params_array(params), original_path[position, original_path.size], original_path[0, position]) :
|
126
|
+
Response.new(terminates, terminates.convert_params_array(params), nil, original_path)
|
127
127
|
elsif !path.empty? and greedy and match_with_result_output = greedy.match_with_result(whole_path = original_path[position, original_path.size])
|
128
128
|
next_path, matched_part = match_with_result_output
|
129
129
|
position += matched_part.size
|
130
130
|
whole_path.slice!(0, matched_part.size)
|
131
|
-
params <<
|
132
|
-
next_path.find(usher, request_object, original_path, whole_path.empty? ? whole_path : usher.splitter.
|
131
|
+
params << matched_part if next_path.value.is_a?(Route::Variable)
|
132
|
+
next_path.find(usher, request_object, original_path, whole_path.empty? ? whole_path : usher.splitter.split(whole_path), params, position)
|
133
133
|
elsif !path.empty? and normal and next_part = normal[path.first] || normal[nil]
|
134
134
|
part = path.shift
|
135
135
|
position += part.size
|
@@ -142,14 +142,14 @@ class Usher
|
|
142
142
|
# do a validity check
|
143
143
|
var.valid!(part)
|
144
144
|
# because its a variable, we need to add it to the params array
|
145
|
-
params <<
|
145
|
+
params << part
|
146
146
|
until path.empty? || (var.look_ahead === path.first) # variables have a look ahead notion,
|
147
147
|
next_path_part = path.shift # and until they are satified,
|
148
148
|
position += next_path_part.size # keep appending to the value in params
|
149
|
-
params.last
|
149
|
+
params.last << next_path_part
|
150
150
|
end if var.look_ahead && usher.delimiters.size > 1
|
151
151
|
when Route::Variable::Glob
|
152
|
-
params << [
|
152
|
+
params << []
|
153
153
|
while true
|
154
154
|
if (next_part.value.look_ahead === part || (!usher.delimiters.unescaped.include?(part) && next_part.value.regex_matcher && !next_part.value.regex_matcher.match(part)))
|
155
155
|
path.unshift(part)
|
@@ -161,7 +161,7 @@ class Usher
|
|
161
161
|
break
|
162
162
|
elsif !usher.delimiters.unescaped.include?(part)
|
163
163
|
next_part.value.valid!(part)
|
164
|
-
params.last
|
164
|
+
params.last << part
|
165
165
|
end
|
166
166
|
if path.empty?
|
167
167
|
break
|
data/lib/usher/route/path.rb
CHANGED
@@ -9,7 +9,11 @@ class Usher
|
|
9
9
|
self.route = route
|
10
10
|
self.parts = parts
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
|
+
def convert_params_array(ary)
|
14
|
+
ary.empty? ? ary : dynamic_keys.zip(ary)
|
15
|
+
end
|
16
|
+
|
13
17
|
def dynamic_indicies
|
14
18
|
unless dynamic? && @dynamic_indicies
|
15
19
|
@dynamic_indicies = []
|
@@ -31,7 +35,7 @@ class Usher
|
|
31
35
|
end
|
32
36
|
|
33
37
|
def dynamic_keys
|
34
|
-
@dynamic_keys ||=
|
38
|
+
@dynamic_keys ||= dynamic_parts.map{|dp| dp.name} if dynamic?
|
35
39
|
end
|
36
40
|
|
37
41
|
def dynamic_required_keys
|
data/lib/usher/route/variable.rb
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
class Usher
|
2
2
|
class Route
|
3
3
|
class Variable
|
4
|
+
|
5
|
+
module ProcValidator
|
6
|
+
def valid!(val)
|
7
|
+
begin
|
8
|
+
@validator.call(val)
|
9
|
+
rescue Exception => e
|
10
|
+
raise ValidationException.new("#{val} does not conform to #{@validator}, root cause #{e.inspect}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module CaseEqualsValidator
|
16
|
+
def valid!(val)
|
17
|
+
@validator === val or raise(ValidationException.new("#{val} does not conform to #{@validator}"))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
4
21
|
attr_reader :type, :name, :validator, :regex_matcher
|
5
22
|
attr_accessor :look_ahead, :default_value, :look_ahead_priority
|
6
23
|
|
@@ -10,12 +27,12 @@ class Usher
|
|
10
27
|
@regex_matcher = regex_matcher
|
11
28
|
|
12
29
|
case @validator
|
13
|
-
when Proc
|
14
|
-
self.extend(ProcValidator)
|
15
30
|
when nil
|
16
31
|
# do nothing
|
32
|
+
when Proc
|
33
|
+
extend(ProcValidator)
|
17
34
|
else
|
18
|
-
|
35
|
+
extend(CaseEqualsValidator)
|
19
36
|
end
|
20
37
|
end
|
21
38
|
private :initialize
|
@@ -23,22 +40,6 @@ class Usher
|
|
23
40
|
def valid!(val)
|
24
41
|
end
|
25
42
|
|
26
|
-
module ProcValidator
|
27
|
-
def valid!(val)
|
28
|
-
begin
|
29
|
-
@validator.call(val)
|
30
|
-
rescue Exception => e
|
31
|
-
raise ValidationException.new("#{val} does not conform to #{@validator}, root cause #{e.inspect}")
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
module CaseEqualsValidator
|
37
|
-
def valid!(val)
|
38
|
-
@validator === val or raise(ValidationException.new("#{val} does not conform to #{@validator}"))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
43
|
def ==(o)
|
43
44
|
o && (o.class == self.class && o.name == @name && o.validator == @validator)
|
44
45
|
end
|
data/lib/usher/splitter.rb
CHANGED
@@ -14,10 +14,9 @@ class Usher
|
|
14
14
|
@url_split_regex = Regexp.new("[#{delimiters.collect{|d| Regexp.quote(d)}.join}]|[^#{delimiters.collect{|d| Regexp.quote(d)}.join}]+")
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def split(path)
|
18
18
|
path.scan(@url_split_regex)
|
19
19
|
end
|
20
|
-
alias split url_split
|
21
20
|
end
|
22
21
|
|
23
22
|
class MultiCharacterSplitterInstance
|
@@ -26,12 +25,11 @@ class Usher
|
|
26
25
|
@delimiters = delimiters
|
27
26
|
end
|
28
27
|
|
29
|
-
def
|
28
|
+
def split(path)
|
30
29
|
split_path = path.split(delimiters_regexp)
|
31
|
-
split_path.reject!{|s| s.
|
30
|
+
split_path.reject!{|s| s.empty? }
|
32
31
|
split_path
|
33
32
|
end
|
34
|
-
alias split url_split
|
35
33
|
|
36
34
|
protected
|
37
35
|
|
data/lib/usher/util/generate.rb
CHANGED
@@ -12,28 +12,50 @@ class Usher
|
|
12
12
|
|
13
13
|
def generate_path_for_base_params(path, params)
|
14
14
|
raise UnrecognizedException.new unless path
|
15
|
-
|
16
15
|
result = ''
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
result <<
|
24
|
-
|
16
|
+
|
17
|
+
case params
|
18
|
+
when nil, Hash
|
19
|
+
path.parts.each do |part|
|
20
|
+
case part
|
21
|
+
when String
|
22
|
+
result << part
|
23
|
+
when Route::Variable::Glob
|
24
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
25
|
+
value.each_with_index do |current_value, index|
|
26
|
+
part.valid!(current_value)
|
27
|
+
result << current_value.to_s
|
28
|
+
result << usher.delimiters.first if index != value.size - 1
|
29
|
+
end
|
30
|
+
when Route::Variable
|
31
|
+
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
32
|
+
part.valid!(value)
|
33
|
+
result << value.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
else
|
37
|
+
params = Array(params)
|
38
|
+
path.parts.each do |part|
|
39
|
+
case part
|
40
|
+
when String
|
41
|
+
result << part
|
42
|
+
when Route::Variable::Glob
|
43
|
+
value = (params && params.shift) || part.default_value || raise(MissingParameterException.new)
|
44
|
+
value.each_with_index do |current_value, index|
|
45
|
+
part.valid!(current_value)
|
46
|
+
result << current_value.to_s
|
47
|
+
result << usher.delimiters.first if index != value.size - 1
|
48
|
+
end
|
49
|
+
when Route::Variable
|
50
|
+
value = (params && params.shift) || part.default_value || raise(MissingParameterException.new)
|
51
|
+
part.valid!(value)
|
52
|
+
result << value.to_s
|
25
53
|
end
|
26
|
-
when Route::Variable
|
27
|
-
value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
|
28
|
-
part.valid!(value)
|
29
|
-
result << value.to_s
|
30
|
-
else
|
31
|
-
result << part
|
32
54
|
end
|
33
55
|
end
|
34
56
|
result
|
35
57
|
end
|
36
|
-
|
58
|
+
|
37
59
|
end
|
38
60
|
|
39
61
|
class URL < Generic
|
@@ -104,26 +126,30 @@ class Usher
|
|
104
126
|
|
105
127
|
def generate_path(path, params = nil, generate_extra = true)
|
106
128
|
params = Array(params) if params.is_a?(String)
|
129
|
+
extra_params = nil
|
107
130
|
case params
|
108
131
|
when nil
|
109
132
|
params = path && path.route.default_values
|
110
133
|
when Hash
|
111
134
|
params = path.route.default_values.merge(params) if path && path.route.default_values
|
112
|
-
|
135
|
+
else
|
113
136
|
params = Array(params)
|
114
|
-
given_size = params.size
|
115
137
|
extra_params = params.last.is_a?(Hash) ? params.pop : nil
|
116
|
-
|
117
|
-
params.merge!(extra_params) if extra_params
|
138
|
+
raise MissingParameterException.new("got #{params.size}, expected #{path.dynamic_parts.size} parameters") unless path.dynamic_parts.size == params.size
|
118
139
|
end
|
119
140
|
|
120
141
|
result = Rack::Utils.uri_escape(generate_path_for_base_params(path, params))
|
142
|
+
|
143
|
+
params = extra_params if extra_params
|
144
|
+
|
121
145
|
unless !generate_extra || params.nil? || params.empty?
|
122
146
|
if usher.consider_destination_keys? && path.route.destination_keys
|
123
147
|
params.delete_if{|k, v| path.route.destination_keys.include?(k)}
|
124
148
|
end
|
125
|
-
|
126
|
-
|
149
|
+
unless params.empty?
|
150
|
+
result << '?' unless result[??]
|
151
|
+
result << generate_extra_params(params)
|
152
|
+
end
|
127
153
|
end
|
128
154
|
result
|
129
155
|
end
|
@@ -188,6 +214,8 @@ class Usher
|
|
188
214
|
|
189
215
|
def path_for_routing_lookup(routing_lookup, params = {})
|
190
216
|
path = case routing_lookup
|
217
|
+
when Route::Path
|
218
|
+
routing_lookup
|
191
219
|
when Symbol
|
192
220
|
route = @usher.named_routes[routing_lookup]
|
193
221
|
raise UnrecognizedException unless route
|
@@ -196,23 +224,23 @@ class Usher
|
|
196
224
|
routing_lookup.find_matching_path(params)
|
197
225
|
when nil
|
198
226
|
params.is_a?(Hash) ? usher.path_for_options(params) : raise
|
199
|
-
when Route::Path
|
200
|
-
routing_lookup
|
201
227
|
end
|
202
228
|
end
|
203
229
|
|
204
230
|
|
205
|
-
def generate_extra_params(params
|
231
|
+
def generate_extra_params(params)
|
206
232
|
extra_params_result = ''
|
207
233
|
|
208
234
|
params.each do |k,v|
|
209
235
|
case v
|
210
236
|
when Array
|
211
237
|
v.each do |v_part|
|
212
|
-
extra_params_result <<
|
238
|
+
extra_params_result << '&' unless extra_params_result.empty?
|
239
|
+
extra_params_result << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
|
213
240
|
end
|
214
241
|
else
|
215
|
-
extra_params_result <<
|
242
|
+
extra_params_result << '&' unless extra_params_result.empty?
|
243
|
+
extra_params_result << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
|
216
244
|
end
|
217
245
|
end
|
218
246
|
extra_params_result
|
data/lib/usher.rb
CHANGED
@@ -67,15 +67,20 @@ class Usher
|
|
67
67
|
# Example, you create a route with a destination of :controller => 'test', :action => 'action'. If you made a call to generator with :controller => 'test',
|
68
68
|
# :action => 'action', it would pick that route to use for generation.
|
69
69
|
def initialize(options = nil)
|
70
|
-
self.generator
|
71
|
-
self.delimiters
|
72
|
-
self.valid_regex
|
73
|
-
self.request_methods
|
74
|
-
self.ignore_trailing_delimiters
|
75
|
-
self.consider_destination_keys
|
70
|
+
self.generator = options && options.delete(:generator)
|
71
|
+
self.delimiters = Delimiters.new(options && options.delete(:delimiters) || ['/', '.'])
|
72
|
+
self.valid_regex = options && options.delete(:valid_regex) || '[0-9A-Za-z\$\-_\+!\*\',]+'
|
73
|
+
self.request_methods = options && options.delete(:request_methods)
|
74
|
+
self.ignore_trailing_delimiters = options && options.key?(:ignore_trailing_delimiters) ? options.delete(:ignore_trailing_delimiters) : false
|
75
|
+
self.consider_destination_keys = options && options.key?(:consider_destination_keys) ? options.delete(:consider_destination_keys) : false
|
76
|
+
self.allow_identical_variable_names = options && options.key?(:allow_identical_variable_names) ? options.delete(:allow_identical_variable_names) : true
|
76
77
|
reset!
|
77
78
|
end
|
78
79
|
|
80
|
+
def allow_identical_variable_names?
|
81
|
+
@allow_identical_variable_names
|
82
|
+
end
|
83
|
+
|
79
84
|
def ignore_trailing_delimiters?
|
80
85
|
@ignore_trailing_delimiters
|
81
86
|
end
|
@@ -223,7 +228,7 @@ class Usher
|
|
223
228
|
# route = set.add_route('/test')
|
224
229
|
# set.recognize(Request.new('/test')).path.route == route => true
|
225
230
|
def recognize(request, path = request.path)
|
226
|
-
@root.find(self, request, path, @splitter.
|
231
|
+
@root.find(self, request, path, @splitter.split(path))
|
227
232
|
end
|
228
233
|
|
229
234
|
# Recognizes a +path+ and returns +nil+ or an Usher::Node::Response, which is a struct containing a Usher::Route::Path and an array of arrays containing the extracted parameters. Convenience method for when recognizing on the request object is unneeded.
|
@@ -282,7 +287,7 @@ class Usher
|
|
282
287
|
|
283
288
|
private
|
284
289
|
|
285
|
-
attr_accessor :request_methods, :ignore_trailing_delimiters, :consider_destination_keys
|
290
|
+
attr_accessor :request_methods, :ignore_trailing_delimiters, :consider_destination_keys, :allow_identical_variable_names
|
286
291
|
attr_reader :valid_regex
|
287
292
|
|
288
293
|
def generator=(generator)
|
@@ -333,6 +338,7 @@ class Usher
|
|
333
338
|
end
|
334
339
|
|
335
340
|
route = parser.generate_route(path, conditions, requirements, default_values, generate_with, priority)
|
341
|
+
raise(MultipleParameterException.new) if !allow_identical_variable_names? and route.paths.first.dynamic? and route.paths.first.dynamic_keys.uniq.size != route.paths.first.dynamic_keys.size
|
336
342
|
route.to(options) if options && !options.empty?
|
337
343
|
route
|
338
344
|
end
|
@@ -273,6 +273,13 @@ describe "Usher route recognition" do
|
|
273
273
|
@route_set.recognize(build_request({:path => '/id1/one/id2.html'})).params.should == [[:id1, 'id1'], [:id2, 'id2'], [:format, 'html']]
|
274
274
|
end
|
275
275
|
|
276
|
+
it "should pick the correct variable name when there are two variable names that could be represented" do
|
277
|
+
@route_set.add_route('/:var1')
|
278
|
+
@route_set.add_route('/:var2/foo')
|
279
|
+
@route_set.recognize(build_request({:path => '/foo1'})).params.should == [[:var1, 'foo1']]
|
280
|
+
@route_set.recognize(build_request({:path => '/foo2/foo'})).params.should == [[:var2, 'foo2']]
|
281
|
+
end
|
282
|
+
|
276
283
|
it "should recognize a path with an optional compontnet" do
|
277
284
|
@route_set.add_route("/:name(/:surname)", :conditions => {:method => 'get'})
|
278
285
|
result = @route_set.recognize(build_request({:method => 'get', :path => '/homer'}))
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: usher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Neighman
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-01-
|
17
|
+
date: 2010-01-09 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|