usher 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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