joshbuddy-usher 0.5.1 → 0.5.2

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
@@ -12,6 +12,7 @@ Tree-based router library. Useful for (specifically) for Rails and Rack, but pro
12
12
  * Really, really fast
13
13
  * Relatively light and happy code-base, should be easy and fun to alter (it hovers around 1,000 LOC, 800 for the core)
14
14
  * Interface and implementation are separate, encouraging cross-pollination
15
+ * Works in 1.9!
15
16
 
16
17
  == Route format
17
18
 
@@ -67,6 +68,7 @@ regex allows.
67
68
  * <tt>/product/hello/world</tt>
68
69
  * <tt>/product/hello</tt>
69
70
 
71
+
70
72
  ==== Static
71
73
 
72
74
  Static parts of literal character sequences. For instance, <tt>/path/something.html</tt> would match only the same path.
data/Rakefile CHANGED
@@ -10,6 +10,11 @@ begin
10
10
  s.authors = ["Joshua Hull"]
11
11
  s.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"]
12
12
  s.add_dependency 'fuzzyhash', '>=0.0.6'
13
+ s.rubyforge_project = 'joshbuddy-usher'
14
+ end
15
+ Jeweler::RubyforgeTasks.new do |rubyforge|
16
+ rubyforge.doc_task = "rdoc"
17
+ rubyforge.remote_doc_path = ''
13
18
  end
14
19
  rescue LoadError
15
20
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
@@ -42,30 +47,3 @@ Rake::RDocTask.new do |rd|
42
47
  rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
43
48
  rd.rdoc_dir = 'rdoc'
44
49
  end
45
-
46
- # These are new tasks
47
- begin
48
- require 'rake/contrib/sshpublisher'
49
- namespace :rubyforge do
50
-
51
- desc "Release gem and RDoc documentation to RubyForge"
52
- task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
53
-
54
- namespace :release do
55
- desc "Publish RDoc to RubyForge."
56
- task :docs => [:rdoc] do
57
- config = YAML.load(
58
- File.read(File.expand_path('~/.rubyforge/user-config.yml'))
59
- )
60
-
61
- host = "#{config['username']}@rubyforge.org"
62
- remote_dir = "/var/www/gforge-projects/joshbuddy-usher/"
63
- local_dir = 'rdoc'
64
-
65
- Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
66
- end
67
- end
68
- end
69
- rescue LoadError
70
- puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
71
- end
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :major: 0
3
2
  :minor: 5
4
- :patch: 1
3
+ :patch: 2
4
+ :major: 0
data/lib/usher.rb CHANGED
@@ -7,11 +7,9 @@ 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
-
11
-
12
- attr_reader :tree, :named_routes, :route_count, :routes, :splitter, :delimiters, :delimiter_chars, :delimiters_regex
13
-
14
- SymbolArraySorter = proc {|a,b| a.hash <=> b.hash} #:nodoc:
10
+ attr_reader :root, :named_routes, :routes, :splitter,
11
+ :delimiters, :delimiter_chars, :delimiters_regex,
12
+ :parent_route, :generator
15
13
 
16
14
  # Returns whether the route set is empty
17
15
  #
@@ -20,9 +18,13 @@ class Usher
20
18
  # set.add_route('/test')
21
19
  # set.empty? => false
22
20
  def empty?
23
- @route_count.zero?
21
+ @routes.empty?
24
22
  end
25
-
23
+
24
+ def route_count
25
+ @routes.size
26
+ end
27
+
26
28
  # Resets the route set back to its initial state
27
29
  #
28
30
  # set = Usher.new
@@ -31,10 +33,9 @@ class Usher
31
33
  # set.reset!
32
34
  # set.empty? => true
33
35
  def reset!
34
- @tree = Node.root(self, @request_methods)
36
+ @root = Node.root(self, request_methods)
35
37
  @named_routes = {}
36
38
  @routes = []
37
- @route_count = 0
38
39
  @grapher = Grapher.new
39
40
  end
40
41
  alias clear! reset!
@@ -49,20 +50,26 @@ class Usher
49
50
  # <tt>:request_methods</tt>: Array of Symbols. (default <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]</tt>)
50
51
  # Array of methods called against the request object for the purposes of matching route requirements.
51
52
  def initialize(options = nil)
52
- @delimiters = options && options.delete(:delimiters) || ['/', '.']
53
- @delimiter_chars = @delimiters.collect{|d| d[0]}
54
- @delimiters_regex = @delimiters.collect{|d| Regexp.quote(d)} * '|'
55
- @valid_regex = options && options.delete(:valid_regex) || '[0-9A-Za-z\$\-_\+!\*\',]+'
56
- @request_methods = options && options.delete(:request_methods) || [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]
57
- @splitter = Splitter.for_delimiters(self, @valid_regex)
53
+ self.generator = options && options.delete(:generator)
54
+ self.delimiters = options && options.delete(:delimiters) || ['/', '.']
55
+ self.valid_regex = options && options.delete(:valid_regex) || '[0-9A-Za-z\$\-_\+!\*\',]+'
56
+ self.request_methods = options && options.delete(:request_methods) || [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]
58
57
  reset!
59
58
  end
60
59
 
61
60
  def parser
62
- @parser ||= Util::Parser.for_delimiters(self, @valid_regex)
61
+ @parser ||= Util::Parser.for_delimiters(self, valid_regex)
63
62
  end
64
63
 
65
- # Adds a route referencable by +name+. Sett add_route for format +path+ and +options+.
64
+ def can_generate?
65
+ !@generator.nil?
66
+ end
67
+
68
+ def generator
69
+ @generator
70
+ end
71
+
72
+ # Adds a route referencable by +name+. See add_route for format +path+ and +options+.
66
73
  #
67
74
  # set = Usher.new
68
75
  # set.add_named_route(:test_route, '/test')
@@ -70,6 +77,15 @@ class Usher
70
77
  add_route(path, options).name(name)
71
78
  end
72
79
 
80
+ # Deletes a route referencable by +name+. At least the path and conditions have to match the route you intend to delete.
81
+ #
82
+ # set = Usher.new
83
+ # set.delete_named_route(:test_route, '/test')
84
+ def delete_named_route(name, path, options = nil)
85
+ delete_route(path, options)
86
+ @named_routes.delete(name)
87
+ end
88
+
73
89
  # Attaches a +route+ to a +name+
74
90
  #
75
91
  # set = Usher.new
@@ -150,57 +166,24 @@ class Usher
150
166
  # * +requirements+ - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
151
167
  # * +conditions+ - Accepts any of the +request_methods+ specificied in the construction of Usher. This can be either a <tt>string</tt> or a regular expression.
152
168
  # * Any other key is interpreted as a requirement for the variable of its name.
153
- def add_route(unprocessed_path, options = nil)
154
- conditions = options && options.delete(:conditions) || nil
155
- requirements = options && options.delete(:requirements) || nil
156
- default_values = options && options.delete(:default_values) || nil
157
- generate_with = options && options.delete(:generate_with) || nil
158
- if options
159
- options.delete_if do |k, v|
160
- if v.is_a?(Regexp) || v.is_a?(Proc)
161
- (requirements ||= {})[k] = v
162
- true
163
- end
164
- end
165
- end
166
-
167
- unprocessed_path = parser.parse(unprocessed_path, requirements, default_values) if unprocessed_path.is_a?(String)
168
-
169
- unless unprocessed_path.first.is_a?(Route::Util::Group)
170
- group = Usher::Route::Util::Group.new(:all, nil)
171
- unprocessed_path.each{|p| group << p}
172
- unprocessed_path = group
173
- end
174
-
175
- paths = Route::Util.expand_path(unprocessed_path)
176
-
177
- paths.each do |path|
178
- path.each_with_index do |part, index|
179
- part.default_value = default_values[part.name] if part.is_a?(Usher::Route::Variable) && default_values && default_values[part.name]
180
- case part
181
- when Usher::Route::Variable::Glob
182
- part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !delimiter_chars.include?(p[0])} || nil
183
- when Usher::Route::Variable
184
- part.look_ahead = path[index + 1, path.size].find{|p| delimiter_chars.include?(p[0])} || delimiters.first
185
- end
186
- end
187
- end
188
-
189
- route = Route.new(
190
- paths,
191
- self,
192
- conditions,
193
- requirements,
194
- default_values,
195
- generate_with
196
- )
197
-
198
- route.to(options) if options && !options.empty?
199
-
200
- @tree.add(route)
169
+ def add_route(path, options = nil)
170
+ route = get_route(path, options)
171
+ @root.add(route)
201
172
  @routes << route
202
173
  @grapher.add_route(route)
203
- @route_count += 1
174
+ route.parent_route = parent_route if parent_route
175
+ route
176
+ end
177
+
178
+ # Deletes a route. At least the path and conditions have to match the route you intend to delete.
179
+ #
180
+ # set = Usher.new
181
+ # set.delete_route('/test')
182
+ def delete_route(path, options = nil)
183
+ route = get_route(path, options)
184
+ @root.delete(route)
185
+ @routes = @root.unique_routes
186
+ rebuild_grapher!
204
187
  route
205
188
  end
206
189
 
@@ -211,7 +194,7 @@ class Usher
211
194
  # route = set.add_route('/test')
212
195
  # set.recognize(Request.new('/test')).path.route == route => true
213
196
  def recognize(request, path = request.path)
214
- @tree.find(self, request, path, @splitter.url_split(path))
197
+ @root.find(self, request, path, @splitter.url_split(path))
215
198
  end
216
199
 
217
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.
@@ -233,4 +216,58 @@ class Usher
233
216
  @grapher.find_matching_path(options)
234
217
  end
235
218
 
219
+ def parent_route=(route)
220
+ @parent_route = route
221
+ routes.each{|r| r.parent_route = route}
222
+ end
223
+
224
+ private
225
+
226
+ attr_accessor :request_methods
227
+ attr_reader :valid_regex
228
+
229
+ def generator=(generator)
230
+ if generator
231
+ @generator = generator
232
+ @generator.usher = self
233
+ end
234
+ @generator
235
+ end
236
+
237
+ def delimiters=(delimiters)
238
+ @delimiters = delimiters
239
+ @delimiter_chars = @delimiters.collect{|d| d[0]}
240
+ @delimiters_regex = @delimiters.collect{|d| Regexp.quote(d)} * '|'
241
+ @delimiters
242
+ end
243
+
244
+ def valid_regex=(valid_regex)
245
+ @valid_regex = valid_regex
246
+ @splitter = Splitter.for_delimiters(self, @valid_regex)
247
+ @valid_regex
248
+ end
249
+
250
+ def get_route(path, options = nil)
251
+ conditions = options && options.delete(:conditions) || nil
252
+ requirements = options && options.delete(:requirements) || nil
253
+ default_values = options && options.delete(:default_values) || nil
254
+ generate_with = options && options.delete(:generate_with) || nil
255
+ if options
256
+ options.delete_if do |k, v|
257
+ if v.is_a?(Regexp) || v.is_a?(Proc)
258
+ (requirements ||= {})[k] = v
259
+ true
260
+ end
261
+ end
262
+ end
263
+
264
+ route = parser.generate_route(path, conditions, requirements, default_values, generate_with)
265
+ route.to(options) if options && !options.empty?
266
+ route
267
+ end
268
+
269
+ def rebuild_grapher!
270
+ @grapher = Grapher.new
271
+ @routes.each{|r| @grapher.add_route(r)}
272
+ end
236
273
  end
data/lib/usher/grapher.rb CHANGED
@@ -58,8 +58,9 @@ class Usher
58
58
  end
59
59
  end
60
60
  end
61
- nil
62
61
  end
62
+ nil
63
63
  end
64
+
64
65
  end
65
66
  end
@@ -5,19 +5,26 @@ class Usher
5
5
  autoload :MerbInterface, File.join(File.dirname(__FILE__), 'interface', 'merb_interface')
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
+ autoload :Rails3Interface, File.join(File.dirname(__FILE__), 'interface', 'rails3_interface')
8
9
 
9
10
  def self.for(type, &blk)
11
+ class_for(type).new(&blk)
12
+ end
13
+
14
+ def self.class_for(type)
10
15
  case type
11
16
  when :rails2_2
12
- Rails2_2Interface.new(&blk)
17
+ Rails2_2Interface
13
18
  when :rails2_3
14
- Rails2_3Interface.new(&blk)
19
+ Rails2_3Interface
15
20
  when :merb
16
- MerbInterface.new(&blk)
21
+ MerbInterface
17
22
  when :rack
18
- RackInterface.new(&blk)
23
+ RackInterface
19
24
  when :email
20
- EmailInterface.new(&blk)
25
+ EmailInterface
26
+ when :rails3
27
+ Rails3Interface
21
28
  end
22
29
 
23
30
  end
@@ -4,34 +4,52 @@ class Usher
4
4
  module Interface
5
5
  class RackInterface
6
6
 
7
- attr_accessor :routes
8
-
9
7
  def initialize(&blk)
10
- @routes = Usher.new(:request_methods => [:method, :host, :port, :scheme])
11
- @generator = Usher::Util::Generators::URL.new(@routes)
8
+ @router = Usher.new(:request_methods => [:request_method, :host, :port, :scheme], :generator => Usher::Util::Generators::URL.new)
12
9
  instance_eval(&blk) if blk
13
10
  end
14
11
 
15
12
  def add(path, options = nil)
16
- @routes.add_route(path, options)
13
+ @router.add_route(path, options)
14
+ end
15
+
16
+ def parent_route=(route)
17
+ @router.parent_route = route
18
+ end
19
+
20
+ def parent_route
21
+ @router.parent_route
17
22
  end
18
23
 
19
24
  def reset!
20
- @routes.reset!
25
+ @router.reset!
21
26
  end
22
27
 
23
28
  def call(env)
24
- response = @routes.recognize(Rack::Request.new(env))
25
- params = {}
26
- response.params.each{ |hk| params[hk.first] = hk.last}
27
- env['usher.params'] = params
28
- response.path.route.destination.call(env)
29
+ env['usher.params'] ||= {}
30
+ response = @router.recognize(request = Rack::Request.new(env), request.path_info)
31
+ if response.nil?
32
+ body = "No route found"
33
+ headers = {"Content-Type" => "text/plain", "Content-Length" => body.length.to_s}
34
+ [404, headers, [body]]
35
+ else
36
+ params = response.path.route.default_values || {}
37
+ response.params.each{ |hk| params[hk.first] = hk.last}
38
+
39
+ # consume the path_info to the script_name response.remaining_path
40
+ env["SCRIPT_NAME"] << response.matched_path || ""
41
+ env["PATH_INFO"] = response.remaining_path || ""
42
+
43
+ env['usher.params'].merge!(params)
44
+
45
+ response.path.route.destination.call(env)
46
+ end
29
47
  end
30
48
 
31
49
  def generate(route, params = nil, options = nil)
32
- @generator.generate(route, params, options)
50
+ @usher.generator.generate(route, params, options)
33
51
  end
34
52
 
35
53
  end
36
54
  end
37
- end
55
+ end
@@ -12,8 +12,7 @@ class Usher
12
12
  end
13
13
 
14
14
  def reset!
15
- @usher ||= Usher.new
16
- @url_generator ||= Usher::Util::Generators::URL.new(@usher)
15
+ @usher ||= Usher.new(:generator => Usher::Util::Generators::URL.new)
17
16
  @module ||= Module.new
18
17
  @module.instance_methods.each do |selector|
19
18
  @module.class_eval { remove_method selector }
@@ -86,7 +85,7 @@ class Usher
86
85
  end
87
86
 
88
87
  def generate_url(route, params)
89
- @url_generator.generate(route, params)
88
+ @usher.generator.generate(route, params)
90
89
  end
91
90
 
92
91
  def path_for_options(options)
@@ -72,8 +72,7 @@ class Usher
72
72
  end
73
73
 
74
74
  def reset!
75
- @router = Usher.new
76
- @url_generator = Usher::Util::Generators::URL.new(@router)
75
+ @router = Usher.new(:generator => Usher::Util::Generators::URL.new)
77
76
  @configuration_files = []
78
77
  @module ||= Module.new
79
78
  @controller_route_added = false
@@ -123,7 +122,7 @@ class Usher
123
122
  end
124
123
 
125
124
  def generate_url(route, params)
126
- @url_generator.generate(route, params)
125
+ @router.generator.generate(route, params)
127
126
  end
128
127
 
129
128
  def path_for_options(options)