joshbuddy-usher 0.5.4 → 0.5.6

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/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)