joshbuddy-usher 0.5.1 → 0.5.2

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