usher 0.5.3 → 0.5.4

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 CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 5
3
- :patch: 3
2
+ :patch: 4
4
3
  :major: 0
4
+ :minor: 5
@@ -25,6 +25,15 @@ class Usher
25
25
  instance_eval(&blk) if blk
26
26
  end
27
27
 
28
+ def dup
29
+ new_one = super
30
+ original = self
31
+ new_one.instance_eval do
32
+ @router = router.dup
33
+ end
34
+ new_one
35
+ end
36
+
28
37
  def add(path, options = nil)
29
38
  @router.add_route(path, options)
30
39
  end
data/lib/usher/node.rb CHANGED
@@ -6,23 +6,40 @@ class Usher
6
6
 
7
7
  Response = Struct.new(:path, :params, :remaining_path, :matched_path)
8
8
 
9
- attr_reader :lookup, :greedy_lookup
10
- attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods
9
+ attr_reader :normal, :greedy, :request
10
+ attr_accessor :terminates, :request_method_type, :parent, :value, :request_methods
11
11
 
12
12
  def initialize(parent, value)
13
13
  @parent = parent
14
14
  @value = value
15
- @lookup = Hash.new
16
- @greedy_lookup = Hash.new
17
- @exclusive_type = nil
15
+ @request = nil
16
+ @normal = nil
17
+ @greedy = nil
18
+ @request_method_type = nil
19
+ end
20
+
21
+ def activate_normal!
22
+ @normal ||= Hash.new
23
+ end
24
+
25
+ def activate_greedy!
26
+ @greedy ||= Hash.new
27
+ end
28
+
29
+ def activate_request!
30
+ @request ||= Hash.new
18
31
  end
19
32
 
20
- def upgrade_lookup
21
- @lookup = FuzzyHash.new(@lookup)
33
+ def upgrade_normal!
34
+ @normal = FuzzyHash.new(@normal)
22
35
  end
23
36
 
24
- def upgrade_greedy_lookup
25
- @greedy_lookup = FuzzyHash.new(@greedy_lookup)
37
+ def upgrade_greedy!
38
+ @greedy = FuzzyHash.new(@greedy)
39
+ end
40
+
41
+ def upgrade_request!
42
+ @request = FuzzyHash.new(@request)
26
43
  end
27
44
 
28
45
  def depth
@@ -30,7 +47,7 @@ class Usher
30
47
  end
31
48
 
32
49
  def greedy?
33
- !@greedy_lookup.empty?
50
+ @greedy && !@greedy.empty?
34
51
  end
35
52
 
36
53
  def self.root(route_set, request_methods)
@@ -45,12 +62,22 @@ class Usher
45
62
 
46
63
  def pp
47
64
  $stdout << " " * depth
48
- $stdout << "#{depth}: #{value.inspect} #{!!terminates?}\n"
49
- @lookup.each do |k,v|
65
+ $stdout << "#{terminates? ? '* ' : ''}#{depth}: #{value.inspect}\n"
66
+ normal.each do |k,v|
50
67
  $stdout << " " * (depth + 1)
51
- $stdout << "#{k} ==> \n"
68
+ $stdout << ". #{k.inspect} ==> \n"
52
69
  v.pp
53
- end
70
+ end if normal
71
+ greedy.each do |k,v|
72
+ $stdout << " " * (depth + 1)
73
+ $stdout << "g #{k.inspect} ==> \n"
74
+ v.pp
75
+ end if greedy
76
+ request.each do |k,v|
77
+ $stdout << " " * (depth + 1)
78
+ $stdout << "r #{k.inspect} ==> \n"
79
+ v.pp
80
+ end if request
54
81
  end
55
82
 
56
83
  def add(route)
@@ -67,37 +94,38 @@ class Usher
67
94
 
68
95
  def unique_routes(node = self, routes = [])
69
96
  routes << node.terminates.route if node.terminates
70
- node.lookup.values.each do |v|
97
+ node.normal.values.each do |v|
71
98
  unique_routes(v, routes)
72
- end
73
- node.greedy_lookup.values.each do |v|
99
+ end if node.normal
100
+ node.greedy.values.each do |v|
74
101
  unique_routes(v, routes)
75
- end
102
+ end if node.greedy
103
+ node.request.values.each do |v|
104
+ unique_routes(v, routes)
105
+ end if node.request
76
106
  routes.uniq!
77
107
  routes
78
108
  end
79
109
 
80
- def find(usher, request, original_path, path, params = [], position = 0)
81
- if exclusive_type
82
- [lookup[request.send(exclusive_type)], lookup[nil]].each do |n|
83
- if n && (ret = n.find(usher, request, original_path, path.dup, params.dup, position))
84
- return ret
85
- end
86
- end
87
- nil
88
- elsif terminates? && (path.size.zero? || terminates.route.partial_match?)
89
- if terminates.route.partial_match?
90
- Response.new(terminates, params, original_path[position, original_path.size], original_path[0, position])
110
+ def find(usher, request_object, original_path, path, params = [], position = 0)
111
+ if request_method_type
112
+ if (specific_node = request[request_object.send(request_method_type)]) && (ret = specific_node.find(usher, request_object, original_path, path.dup, params.dup, position))
113
+ ret
114
+ elsif (general_node = request[nil]) && (ret = general_node.find(usher, request_object, original_path, path.dup, params.dup, position))
115
+ ret
91
116
  else
92
- Response.new(terminates, params, nil, original_path)
117
+ nil
93
118
  end
94
-
95
- elsif !path.size.zero? && (greedy? && (match_with_result_output = greedy_lookup.match_with_result(whole_path = original_path[position, original_path.size])))
119
+ elsif terminates? && (path.empty? || terminates.route.partial_match?)
120
+ terminates.route.partial_match? ?
121
+ Response.new(terminates, params, original_path[position, original_path.size], original_path[0, position]) :
122
+ Response.new(terminates, params, nil, original_path)
123
+ elsif !path.empty? && (greedy? && (match_with_result_output = greedy.match_with_result(whole_path = original_path[position, original_path.size])))
96
124
  next_path, matched_part = match_with_result_output
97
125
  position += matched_part.size
98
126
  params << [next_path.value.name, whole_path.slice!(0, matched_part.size)]
99
- next_path.find(usher, request, original_path, whole_path.size.zero? ? whole_path : usher.splitter.url_split(whole_path), params, position)
100
- elsif !path.size.zero? && (next_part = lookup[part = path.shift] || lookup[nil])
127
+ next_path.find(usher, request_object, original_path, whole_path.empty? ? whole_path : usher.splitter.url_split(whole_path), params, position)
128
+ elsif !path.empty? && normal && (next_part = normal[part = path.shift] || normal[nil])
101
129
  position += part.size
102
130
  case next_part.value
103
131
  when Route::Variable::Glob
@@ -115,7 +143,7 @@ class Usher
115
143
  next_part.value.valid!(part)
116
144
  params.last.last << part
117
145
  end
118
- if path.size.zero?
146
+ if path.empty?
119
147
  break
120
148
  else
121
149
  part = path.shift
@@ -131,59 +159,107 @@ class Usher
131
159
  params.last.last << next_path_part
132
160
  end
133
161
  end
134
- next_part.find(usher, request, original_path, path, params, position)
162
+ next_part.find(usher, request_object, original_path, path, params, position)
135
163
  else
136
164
  nil
137
165
  end
138
166
  end
139
167
 
140
168
  private
169
+
141
170
  def set_path_with_destination(path, destination = path)
142
- parts = path.parts.dup
143
- request_methods.each do |type|
144
- parts.push(Route::RequestMethod.new(type, path.route.conditions[type])) if path.route.conditions && path.route.conditions.key?(type)
171
+ node = path.parts.inject(self){ |node, key| process_path_part(node, key) }
172
+ node = process_request_parts(node, request_methods_for_path(path))
173
+
174
+ while node.request_method_type
175
+ node = (node.request[nil] ||= Node.new(node, Route::RequestMethod.new(node.request_method_type, nil)))
145
176
  end
146
-
147
- current_node = self
148
- while !parts.size.zero? || current_node.exclusive_type
177
+
178
+ node.terminates = destination
179
+ end
180
+
181
+ def request_method_index(type)
182
+ request_methods.index(type)
183
+ end
184
+
185
+ def process_request_parts(node, parts)
186
+ while parts.any?{ |p| !p.trivial? }
149
187
  key = parts.shift
150
- target_node = case key
151
- when Route::RequestMethod
152
- current_node.upgrade_lookup if key.value.is_a?(Regexp)
153
- if current_node.exclusive_type == key.type
154
- current_node.lookup[key.value] ||= Node.new(current_node, key)
155
- elsif current_node.lookup.empty?
156
- current_node.exclusive_type = key.type
157
- current_node.lookup[key.value] ||= Node.new(current_node, key)
158
- else
188
+
189
+ next if key.trivial?
190
+
191
+ node.activate_request!
192
+
193
+ node = if node.request_method_type.nil?
194
+ node.request_method_type = key.type
195
+ node.upgrade_request! if key.value.is_a?(Regexp)
196
+ node.request[key.value] ||= Node.new(node, key)
197
+ else
198
+ case request_method_index(node.request_method_type) <=> request_method_index(key.type)
199
+ when -1
159
200
  parts.unshift(key)
160
- current_node.lookup[nil] ||= Node.new(current_node, Route::RequestMethod.new(current_node.exclusive_type, nil))
161
- end
162
- when Route::Variable
163
- upgrade_method, lookup_method = case key
164
- when Route::Variable::Greedy
165
- [:upgrade_greedy_lookup, :greedy_lookup]
166
- else
167
- [:upgrade_lookup, :lookup]
201
+ node.request[key.value] ||= Node.new(node, Route::RequestMethod.new(node.request_method_type, nil))
202
+ when 0
203
+ node.upgrade_request! if key.value.is_a?(Regexp)
204
+ node.request[key.value] ||= Node.new(node, key)
205
+ when 1
206
+ previous_node = node.parent
207
+ current_node_entry_key = nil
208
+ current_node_entry_lookup = nil
209
+ [previous_node.normal, previous_node.greedy, previous_node.request].compact.each do |l|
210
+ current_node_entry_key = l.each{|k,v| break k if node == v}
211
+ current_node_entry_lookup = l and break if current_node_entry_key
212
+ end
213
+
214
+ current_node_entry_lookup.respond_to?(:delete_value) ?
215
+ current_node_entry_lookup.delete_value(node) : current_node_entry_lookup.delete_if{|k,v| v == node}
216
+
217
+ new_node = Node.new(previous_node, Route::RequestMethod.new(key.type, nil))
218
+ new_node.activate_request!
219
+ new_node.request_method_type = key.type
220
+ current_node_entry_lookup[current_node_entry_key] = new_node
221
+ node.parent = new_node
222
+ new_node.request[nil] = node
223
+ parts.unshift(key)
224
+
225
+ new_node
168
226
  end
169
-
170
- if key.regex_matcher
171
- current_node.send(upgrade_method)
172
- current_node.send(lookup_method)[key.regex_matcher] ||= Node.new(current_node, key)
173
- else
174
- current_node.send(lookup_method)[nil] ||= Node.new(current_node, key)
175
- end
176
- else
177
-
178
- current_node.upgrade_lookup if key.is_a?(Regexp)
179
- current_node.lookup[key] ||= Node.new(current_node, key)
180
227
  end
181
- current_node = target_node
182
228
  end
229
+ node
230
+ end
183
231
 
184
- current_node.terminates = destination
232
+
233
+ def process_path_part(node, key)
234
+ case key
235
+ when Route::Variable::Greedy
236
+ node.activate_greedy!
237
+ if key.regex_matcher
238
+ node.upgrade_greedy!
239
+ node.greedy[key.regex_matcher] ||= Node.new(node, key)
240
+ else
241
+ node.greedy[nil] ||= Node.new(node, key)
242
+ end
243
+ when Route::Variable
244
+ node.activate_normal!
245
+ if key.regex_matcher
246
+ node.upgrade_normal!
247
+ node.normal[key.regex_matcher] ||= Node.new(node, key)
248
+ else
249
+ node.normal[nil] ||= Node.new(node, key)
250
+ end
251
+ else
252
+ node.activate_normal!
253
+ node.upgrade_normal! if key.is_a?(Regexp)
254
+ node.normal[key] ||= Node.new(node, key)
255
+ end
185
256
  end
186
257
 
258
+ def request_methods_for_path(path)
259
+ request_methods.collect do |type|
260
+ Route::RequestMethod.new(type, path.route.conditions && path.route.conditions[type])
261
+ end
262
+ end
187
263
 
188
264
  end
189
265
  end
@@ -17,6 +17,11 @@ class Usher
17
17
  o.is_a?(self.class) && o.type == type && o.value == value
18
18
  end
19
19
  alias == eql?
20
+
21
+ def trivial?
22
+ value.nil?
23
+ end
24
+
20
25
  end
21
26
  end
22
27
  end
data/lib/usher.rb CHANGED
@@ -221,6 +221,20 @@ class Usher
221
221
  routes.each{|r| r.parent_route = route}
222
222
  end
223
223
 
224
+ def dup
225
+ replacement = super
226
+ original = self
227
+ replacement.instance_eval do
228
+ reset!
229
+ original.routes.each do |route|
230
+ @root.add(route)
231
+ @routes << route
232
+ end
233
+ rebuild_grapher!
234
+ end
235
+ replacement
236
+ end
237
+
224
238
  private
225
239
 
226
240
  attr_accessor :request_methods
@@ -255,7 +269,7 @@ class Usher
255
269
  if options
256
270
  options.delete_if do |k, v|
257
271
  if v.is_a?(Regexp) || v.is_a?(Proc)
258
- (requirements ||= {})[k] = v
272
+ (requirements ||= {})[k] = v
259
273
  true
260
274
  end
261
275
  end
@@ -48,32 +48,9 @@ describe "Usher route adding" do
48
48
  it "should calculate depths for nodes" do
49
49
  route_set.add_named_route(:route, '/bad/route/three/four')
50
50
  route_set.root.depth.should == 0
51
- route_set.root.lookup['/'].depth.should == 1
51
+ route_set.root.normal['/'].depth.should == 1
52
52
  end
53
53
 
54
- it "should pp for nodes" do
55
- route_set.add_named_route(:route, '/bad/route/three/four')
56
- route_set.root.depth.should == 0
57
- old_out = $stdout
58
- $stdout = (output = StringIO.new)
59
- route_set.root.lookup['/'].lookup['bad'].lookup['/'].pp
60
- $stdout = old_out
61
- output.rewind
62
- output.read.should == <<-HEREDOC
63
- 3: "/" false
64
- route ==>
65
- 4: "route" false
66
- / ==>
67
- 5: "/" false
68
- three ==>
69
- 6: "three" false
70
- / ==>
71
- 7: "/" false
72
- four ==>
73
- 8: "four" true
74
- HEREDOC
75
- end
76
-
77
54
  describe "merging paths" do
78
55
  before do
79
56
  @r1 = route_set.add_route("/foo/bar")
@@ -119,5 +119,55 @@ describe "Usher (for rack) route dispatching" do
119
119
 
120
120
  end
121
121
 
122
+ describe "dupping" do
123
+ before do
124
+ @app = mock("app")
125
+ @u1 = Usher::Interface.for(:rack)
126
+ @u2 = Usher::Interface.for(:rack)
127
+
128
+ @u1.add("/one", :default_values => {:one => :one}).to(@app)
129
+ @u1.add("/mount").match_partially!.to(@u2)
130
+
131
+ @u2.add("/app", :default_values => {:foo => :bar}).to(@app)
132
+
133
+ end
134
+
135
+ it "should allow me to dup the router" do
136
+ @app.should_receive(:call).twice.with{|e| e['usher.params'].should == {:one => :one}}
137
+ @u1.call(Rack::MockRequest.env_for("/one"))
138
+ u1_dash = @u1.dup
139
+ u1_dash.call(Rack::MockRequest.env_for("/one"))
140
+ end
141
+
142
+ it "should allow me to dup the router and add a new route without polluting the original" do
143
+ @app.should_receive(:call).with{|e| e['usher.params'].should == {:foo => :bar}}
144
+ u1_dash = @u1.dup
145
+ u1_dash.add("/foo", :default_values => {:foo => :bar}).to(@app)
146
+ u1_dash.call(Rack::MockRequest.env_for("/foo"))
147
+ @app.should_not_receive(:call)
148
+ @u1.call(Rack::MockRequest.env_for("/foo"))
149
+ end
150
+
151
+ it "should allow me to dup the router and nested routers should remain intact" do
152
+ @app.should_receive(:call).with{|e| e['usher.params'].should == {:foo => :bar}}
153
+ u1_dash = @u1.dup
154
+ u1_dash.call(Rack::MockRequest.env_for("/mount/app"))
155
+ end
156
+
157
+ it "should allow me to dup the router and add more routes" do
158
+ @app.should_receive(:call).with{|e| e['usher.params'].should == {:another => :bar}}
159
+
160
+ u3 = Usher::Interface.for(:rack)
161
+ u1_dash = @u1.dup
162
+
163
+ u3.add("/another_bar", :default_values => {:another => :bar}).to(@app)
164
+ u1_dash.add("/some/mount").match_partially!.to(u3)
165
+
166
+ u1_dash.call(Rack::MockRequest.env_for("/some/mount/another_bar"))
167
+
168
+ @app.should_not_receive(:call)
169
+ @u1.call(Rack::MockRequest.env_for("/some/mount/another_bar"))
170
+ end
171
+ end
122
172
  end
123
173
  end
@@ -31,14 +31,17 @@ describe "Usher route recognition" do
31
31
  end
32
32
 
33
33
  it "should recognize a specific route when several http-style restrictions are used" do
34
+ target_route_http_admin_generic = route_set.add_route('/sample', :conditions => {:domain => 'admin.spec.com'})
34
35
  target_route_http_admin = route_set.add_route('/sample', :conditions => {:protocol => 'http', :domain => 'admin.spec.com'})
35
36
  target_route_http_www = route_set.add_route('/sample', :conditions => {:protocol => 'http', :domain => 'www.spec.com'})
36
- target_route_https_msie = route_set.add_route('/sample', :conditions => {:protocol => 'https', :user_agent => 'MSIE 6.0'})
37
+ target_route_https_msie = route_set.add_route('/sample', :conditions => {:protocol => 'https', :domain => 'admin.spec.com', :user_agent => 'MSIE 6.0'})
37
38
  target_route_https_admin = route_set.add_route('/sample', :conditions => {:protocol => 'https', :domain => 'admin.spec.com'})
38
39
  route_set.recognize(build_request({:method => 'get', :path => '/sample', :protocol => 'http', :domain => 'admin.spec.com', :user_agent => nil})).path.route.should == target_route_http_admin
39
40
  route_set.recognize(build_request({:method => 'get', :path => '/sample', :protocol => 'http', :domain => 'www.spec.com', :user_agent => nil})).path.route.should == target_route_http_www
40
41
  route_set.recognize(build_request({:method => 'get', :path => '/sample', :protocol => 'https', :domain => 'admin.spec.com', :user_agent => 'MSIE 6.0'})).path.route.should == target_route_https_msie
41
42
  route_set.recognize(build_request({:method => 'get', :path => '/sample', :protocol => 'https', :domain => 'admin.spec.com', :user_agent => nil})).path.route.should == target_route_https_admin
43
+ route_set.recognize(build_request({:method => 'put', :path => '/sample', :protocol => 'wacky', :domain => 'admin.spec.com', :user_agent => nil})).path.route.should == target_route_http_admin_generic
44
+
42
45
  end
43
46
 
44
47
  it "should correctly fix that tree if conditionals are used later" do
@@ -178,8 +181,8 @@ describe "Usher route recognition" do
178
181
 
179
182
  it "should use a requirement (proc) on incoming variables" do
180
183
  route_set.add_route('/:controller/:action/:id', :id => proc{|v| Integer(v)})
181
- proc {route_set.recognize(build_request({:method => 'get', :path => '/products/show/123', :domain => 'admin.host.com'}))}.should_not raise_error Usher::ValidationException
182
- proc {route_set.recognize(build_request({:method => 'get', :path => '/products/show/123asd', :domain => 'admin.host.com'}))}.should raise_error Usher::ValidationException
184
+ proc {route_set.recognize(build_request({:method => 'get', :path => '/products/show/123', :domain => 'admin.host.com'}))}.should_not raise_error(Usher::ValidationException)
185
+ proc {route_set.recognize(build_request({:method => 'get', :path => '/products/show/123asd', :domain => 'admin.host.com'}))}.should raise_error(Usher::ValidationException)
183
186
  end
184
187
 
185
188
  it "shouldn't care about mildly weird characters in the URL" do
@@ -213,4 +216,51 @@ describe "Usher route recognition" do
213
216
  end
214
217
 
215
218
  end
219
+
220
+ describe "dup safety" do
221
+ before do
222
+ route_set.add_route("/foo", :foo => "foo")
223
+ @r2 = route_set.dup
224
+ end
225
+
226
+ it "should provide a different object" do
227
+ route_set.should_not eql(@r2)
228
+ end
229
+
230
+ it "should recognize the originals routes in the dup" do
231
+ route_set.recognize(build_request(:path => "/foo")).path.route.destination.should == {:foo =>"foo"}
232
+ @r2.recognize( build_request(:path => "/foo")).path.route.destination.should == {:foo =>"foo"}
233
+ end
234
+
235
+ it "should not add routes added to the dup to the original" do
236
+ @r2.add_route("/bar", :bar => "bar")
237
+ @r2.recognize( build_request(:path => "/bar")).path.route.destination.should == {:bar => "bar"}
238
+ route_set.recognize( build_request(:path => "/bar")).should == nil
239
+ end
240
+
241
+ it "should not delete routes added to the dup to the original" do
242
+ @r2.delete_route("/foo")
243
+ route_set.recognize( build_request(:path => "/foo")).path.route.destination.should == {:foo => "foo"}
244
+ @r2.recognize( build_request(:path => "/foo")).should == nil
245
+ end
246
+
247
+
248
+ it "should safely dup with nested ushers" do
249
+ r1 = Usher.new
250
+ r2 = Usher.new
251
+ r3 = Usher.new
252
+
253
+ r1.add_route("/mounted" ).match_partially!.to(r2)
254
+ r2.add_route("/inner" ).match_partially!.to(r3)
255
+ r3.add_route("/baz", :baz => :baz )
256
+
257
+ r1.recognize(build_request(:path => "/mounted/inner")).path.route.destination.should == r2
258
+ r4 = r1.dup
259
+ r4.recognize(build_request(:path => "/mounted/inner")).path.route.destination.should == r2
260
+ r4.add_route("/r3").match_partially!.to(r3)
261
+ r4.recognize(build_request(:path => "/r3")).path.route.destination.should == r3
262
+ r1.recognize(build_request(:path => "/r3")).should be_nil
263
+ end
264
+
265
+ end
216
266
  end
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.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Hull
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-27 00:00:00 -04:00
12
+ date: 2009-08-28 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency