joshbuddy-usher 0.5.4 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Usher
1
+ = Usher
2
2
 
3
3
  Tree-based router library. Useful for (specifically) for Rails and Rack, but probably generally useful for anyone interested in doing routing. Based on Ilya Grigorik suggestion, turns out looking up in a hash and following a tree is faster than Krauter's massive regex approach.
4
4
 
@@ -19,13 +19,13 @@ Tree-based router library. Useful for (specifically) for Rails and Rack, but pro
19
19
  From the rdoc:
20
20
 
21
21
  Creates a route from +path+ and +options+
22
-
22
+
23
23
  === +path+
24
24
  A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
25
25
 
26
26
  ==== Dynamic
27
27
  Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
28
- more parts.
28
+ more parts.
29
29
 
30
30
  <b>Example:</b>
31
31
  <tt>/path/:variable/path</tt> would match
@@ -35,7 +35,7 @@ more parts.
35
35
  * <tt>/path/one_more/path</tt>
36
36
 
37
37
  In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
38
- However, <tt>/path/test/one_more/path</tt> would not be matched.
38
+ However, <tt>/path/test/one_more/path</tt> would not be matched.
39
39
 
40
40
  <b>Example:</b>
41
41
  <tt>/path/*variable/path</tt> would match
@@ -59,7 +59,7 @@ But not
59
59
  As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
60
60
  actually be bound to the variable
61
61
 
62
- Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
62
+ Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
63
63
  regex allows.
64
64
 
65
65
  <b>Example:</b>
@@ -110,7 +110,7 @@ For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <t
110
110
  [body] # Response body
111
111
  ]
112
112
  end
113
-
113
+
114
114
  routes = Usher::Interface.for(:rack) do
115
115
  add('/hello/:name').to(app)
116
116
  end
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 4
3
- :major: 0
4
2
  :minor: 5
3
+ :patch: 6
4
+ :major: 0
data/lib/usher.rb CHANGED
@@ -7,12 +7,12 @@ require File.join(File.dirname(__FILE__), 'usher', 'exceptions')
7
7
  require File.join(File.dirname(__FILE__), 'usher', 'util')
8
8
 
9
9
  class Usher
10
- attr_reader :root, :named_routes, :routes, :splitter,
11
- :delimiters, :delimiter_chars, :delimiters_regex,
10
+ attr_reader :root, :named_routes, :routes, :splitter,
11
+ :delimiters, :delimiter_chars, :delimiters_regex,
12
12
  :parent_route, :generator
13
-
13
+
14
14
  # Returns whether the route set is empty
15
- #
15
+ #
16
16
  # set = Usher.new
17
17
  # set.empty? => true
18
18
  # set.add_route('/test')
@@ -20,13 +20,13 @@ class Usher
20
20
  def empty?
21
21
  @routes.empty?
22
22
  end
23
-
23
+
24
24
  def route_count
25
25
  @routes.size
26
26
  end
27
-
27
+
28
28
  # Resets the route set back to its initial state
29
- #
29
+ #
30
30
  # set = Usher.new
31
31
  # set.add_route('/test')
32
32
  # set.empty? => false
@@ -39,14 +39,14 @@ class Usher
39
39
  @grapher = Grapher.new
40
40
  end
41
41
  alias clear! reset!
42
-
42
+
43
43
  # Creates a route set, with options
44
- #
44
+ #
45
45
  # <tt>:delimiters</tt>: Array of Strings. (default <tt>['/', '.']</tt>). Delimiters used in path separation. Array must be single character strings.
46
- #
46
+ #
47
47
  # <tt>:valid_regex</tt>: String. (default <tt>'[0-9A-Za-z\$\-_\+!\*\',]+'</tt>). String that can be interpolated into regex to match
48
48
  # valid character sequences within path.
49
- #
49
+ #
50
50
  # <tt>:request_methods</tt>: Array of Symbols. (default <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]</tt>)
51
51
  # Array of methods called against the request object for the purposes of matching route requirements.
52
52
  def initialize(options = nil)
@@ -64,13 +64,13 @@ class Usher
64
64
  def can_generate?
65
65
  !@generator.nil?
66
66
  end
67
-
67
+
68
68
  def generator
69
69
  @generator
70
70
  end
71
-
71
+
72
72
  # Adds a route referencable by +name+. See add_route for format +path+ and +options+.
73
- #
73
+ #
74
74
  # set = Usher.new
75
75
  # set.add_named_route(:test_route, '/test')
76
76
  def add_named_route(name, path, options = nil)
@@ -78,7 +78,7 @@ class Usher
78
78
  end
79
79
 
80
80
  # Deletes a route referencable by +name+. At least the path and conditions have to match the route you intend to delete.
81
- #
81
+ #
82
82
  # set = Usher.new
83
83
  # set.delete_named_route(:test_route, '/test')
84
84
  def delete_named_route(name, path, options = nil)
@@ -87,7 +87,7 @@ class Usher
87
87
  end
88
88
 
89
89
  # Attaches a +route+ to a +name+
90
- #
90
+ #
91
91
  # set = Usher.new
92
92
  # route = set.add_route('/test')
93
93
  # set.name(:test, route)
@@ -97,13 +97,13 @@ class Usher
97
97
  end
98
98
 
99
99
  # Creates a route from +path+ and +options+
100
- #
100
+ #
101
101
  # === +path+
102
102
  # A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
103
103
  #
104
104
  # ==== Dynamic
105
105
  # Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
106
- # more parts.
106
+ # more parts.
107
107
  #
108
108
  # <b>Example:</b>
109
109
  # <tt>/path/:variable/path</tt> would match
@@ -113,7 +113,7 @@ class Usher
113
113
  # * <tt>/path/one_more/path</tt>
114
114
  #
115
115
  # In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
116
- # However, <tt>/path/test/one_more/path</tt> would not be matched.
116
+ # However, <tt>/path/test/one_more/path</tt> would not be matched.
117
117
  #
118
118
  # <b>Example:</b>
119
119
  # <tt>/path/*variable/path</tt> would match
@@ -127,22 +127,22 @@ class Usher
127
127
  #
128
128
  # <b>Example:</b>
129
129
  # <tt>/product/{:id,\d+}</tt> would match
130
- #
130
+ #
131
131
  # * <tt>/product/123</tt>
132
132
  # * <tt>/product/4521</tt>
133
- #
133
+ #
134
134
  # But not
135
135
  # * <tt>/product/AE-35</tt>
136
- #
136
+ #
137
137
  # As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
138
138
  # actually be bound to the variable
139
139
  #
140
- # Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
140
+ # Variables can also have a greedy regex matcher. These matchers ignore all delimiters, and continue matching for as long as much as their
141
141
  # regex allows.
142
142
  #
143
143
  # <b>Example:</b>
144
144
  # <tt>/product/{!id,hello/world|hello}</tt> would match
145
- #
145
+ #
146
146
  # * <tt>/product/hello/world</tt>
147
147
  # * <tt>/product/hello</tt>
148
148
  #
@@ -176,7 +176,7 @@ class Usher
176
176
  end
177
177
 
178
178
  # Deletes a route. At least the path and conditions have to match the route you intend to delete.
179
- #
179
+ #
180
180
  # set = Usher.new
181
181
  # set.delete_route('/test')
182
182
  def delete_route(path, options = nil)
@@ -188,7 +188,7 @@ class Usher
188
188
  end
189
189
 
190
190
  # Recognizes a +request+ 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.
191
- #
191
+ #
192
192
  # Request = Struct.new(:path)
193
193
  # set = Usher.new
194
194
  # route = set.add_route('/test')
@@ -198,7 +198,7 @@ class Usher
198
198
  end
199
199
 
200
200
  # 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.
201
- #
201
+ #
202
202
  # Request = Struct.new(:path)
203
203
  # set = Usher.new
204
204
  # route = set.add_route('/test')
@@ -208,35 +208,43 @@ class Usher
208
208
  end
209
209
 
210
210
  # Recognizes a set of +parameters+ and gets the closest matching Usher::Route::Path or +nil+ if no route exists.
211
- #
211
+ #
212
212
  # set = Usher.new
213
213
  # route = set.add_route('/:controller/:action')
214
214
  # set.path_for_options({:controller => 'test', :action => 'action'}) == path.route => true
215
215
  def path_for_options(options)
216
216
  @grapher.find_matching_path(options)
217
217
  end
218
-
218
+
219
219
  def parent_route=(route)
220
220
  @parent_route = route
221
221
  routes.each{|r| r.parent_route = route}
222
222
  end
223
-
223
+
224
224
  def dup
225
225
  replacement = super
226
226
  original = self
227
+ inverted_named_routes = original.named_routes.invert
227
228
  replacement.instance_eval do
229
+ @parser = nil
228
230
  reset!
229
231
  original.routes.each do |route|
230
- @root.add(route)
231
- @routes << route
232
+ new_route = route.dup
233
+ new_route.router = self
234
+ @root.add(new_route)
235
+ @routes << new_route
236
+ if name = inverted_named_routes[route]
237
+ @named_routes[name] = new_route
238
+ end
232
239
  end
240
+ send(:generator=, original.generator.class.new) if original.can_generate?
233
241
  rebuild_grapher!
234
242
  end
235
243
  replacement
236
244
  end
237
-
245
+
238
246
  private
239
-
247
+
240
248
  attr_accessor :request_methods
241
249
  attr_reader :valid_regex
242
250
 
@@ -247,7 +255,7 @@ class Usher
247
255
  end
248
256
  @generator
249
257
  end
250
-
258
+
251
259
  def delimiters=(delimiters)
252
260
  @delimiters = delimiters
253
261
  @delimiter_chars = @delimiters.collect{|d| d[0]}
@@ -274,12 +282,12 @@ class Usher
274
282
  end
275
283
  end
276
284
  end
277
-
285
+
278
286
  route = parser.generate_route(path, conditions, requirements, default_values, generate_with)
279
287
  route.to(options) if options && !options.empty?
280
288
  route
281
289
  end
282
-
290
+
283
291
  def rebuild_grapher!
284
292
  @grapher = Grapher.new
285
293
  @routes.each{|r| @grapher.add_route(r)}
@@ -6,6 +6,7 @@ class Usher
6
6
  autoload :RackInterface, File.join(File.dirname(__FILE__), 'interface', 'rack_interface')
7
7
  autoload :EmailInterface, File.join(File.dirname(__FILE__), 'interface', 'email_interface')
8
8
  autoload :Rails3Interface, File.join(File.dirname(__FILE__), 'interface', 'rails3_interface')
9
+ autoload :TextInterface, File.join(File.dirname(__FILE__), 'interface', 'text_interface')
9
10
 
10
11
  def self.for(type, &blk)
11
12
  class_for(type).new(&blk)
@@ -25,6 +26,8 @@ class Usher
25
26
  EmailInterface
26
27
  when :rails3
27
28
  Rails3Interface
29
+ when :text
30
+ TextInterface
28
31
  end
29
32
 
30
33
  end
@@ -3,28 +3,34 @@ require 'rack'
3
3
  class Usher
4
4
  module Interface
5
5
  class RackInterface
6
-
6
+
7
7
  attr_reader :router
8
-
8
+ attr_accessor :app
9
+
10
+ DEFAULT_APPLICATION = lambda do |env|
11
+ Rack::Response.new("No route found", 404).finish
12
+ end
13
+
9
14
  class Builder < Rack::Builder
10
-
15
+
11
16
  def initialize(&block)
12
17
  @usher = Usher::Interface::RackInterface.new
13
18
  super
14
19
  end
15
-
20
+
16
21
  def map(path, options = nil, &block)
17
22
  @usher.add(path, options).to(&block)
18
23
  @ins << @usher unless @ins.last == @usher
19
24
  end
20
-
25
+
21
26
  end
22
-
23
- def initialize(&blk)
27
+
28
+ def initialize(app = nil, &blk)
29
+ @app = app || DEFAULT_APPLICATION
24
30
  @router = Usher.new(:request_methods => [:request_method, :host, :port, :scheme], :generator => Usher::Util::Generators::URL.new)
25
31
  instance_eval(&blk) if blk
26
32
  end
27
-
33
+
28
34
  def dup
29
35
  new_one = super
30
36
  original = self
@@ -33,15 +39,15 @@ class Usher
33
39
  end
34
40
  new_one
35
41
  end
36
-
42
+
37
43
  def add(path, options = nil)
38
44
  @router.add_route(path, options)
39
- end
40
-
45
+ end
46
+
41
47
  def parent_route=(route)
42
48
  @router.parent_route = route
43
49
  end
44
-
50
+
45
51
  def parent_route
46
52
  @router.parent_route
47
53
  end
@@ -51,30 +57,55 @@ class Usher
51
57
  end
52
58
 
53
59
  def call(env)
54
- env['usher.params'] ||= {}
55
60
  response = @router.recognize(request = Rack::Request.new(env), request.path_info)
56
- if response.nil?
57
- body = "No route found"
58
- headers = {"Content-Type" => "text/plain", "Content-Length" => body.length.to_s}
59
- [404, headers, [body]]
61
+ after_match(env, response) if response
62
+ determine_respondant(response).call(env)
63
+ end
64
+
65
+ def generate(route, options = nil)
66
+ @router.generator.generate(route, options)
67
+ end
68
+
69
+ # Allows a hook to be placed for sub classes to make use of between matching
70
+ # and calling the application
71
+ #
72
+ # @api plugin
73
+ def after_match(env, response)
74
+ params = response.path.route.default_values ?
75
+ response.path.route.default_values.merge(Hash[response.params]) :
76
+ Hash[response.params]
77
+
78
+ env['usher.params'] ?
79
+ env['usher.params'].merge!(params) :
80
+ env['usher.params'] = params
81
+
82
+ # consume the path_info to the script_name
83
+ # response.remaining_path
84
+ consume_path!(env, response) if response.partial_match?
85
+ end
86
+
87
+ # Determines which application to respond with.
88
+ #
89
+ # Within the request when determine respondant is called
90
+ # If there is a matching route to an application, that
91
+ # application is called, Otherwise the middleware application is called.
92
+ #
93
+ # @api private
94
+ def determine_respondant(response)
95
+ unless response
96
+ app
60
97
  else
61
- params = response.path.route.default_values || {}
62
- response.params.each{ |hk| params[hk.first] = hk.last}
63
-
64
- # consume the path_info to the script_name response.remaining_path
65
- env["SCRIPT_NAME"] << response.matched_path || ""
66
- env["PATH_INFO"] = response.remaining_path || ""
67
-
68
- env['usher.params'].merge!(params)
69
-
70
- response.path.route.destination.call(env)
98
+ respondant = response.path.route.destination
99
+ respondant = app unless respondant.respond_to?(:call)
100
+ respondant
71
101
  end
72
102
  end
73
103
 
74
- def generate(route, params = nil, options = nil)
75
- @usher.generator.generate(route, params, options)
104
+ # Consume the path from path_info to script_name
105
+ def consume_path!(env, response)
106
+ env["SCRIPT_NAME"] = (env["SCRIPT_NAME"] + response.matched_path) || ""
107
+ env["PATH_INFO"] = response.remaining_path || ""
76
108
  end
77
-
78
109
  end
79
110
  end
80
111
  end
@@ -26,12 +26,13 @@ class Usher
26
26
  @configurations_files << file
27
27
  end
28
28
 
29
- def reload!
29
+ def reload
30
30
  @usher.reset!
31
31
  @configurations_files.each do |c|
32
32
  Kernel.load(c)
33
33
  end
34
34
  end
35
+ alias_method :reload!, :reload
35
36
 
36
37
  def call(env)
37
38
  request = ActionDispatch::Request.new(env)