usher 0.8.0 → 0.8.1

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.markdown CHANGED
@@ -143,7 +143,7 @@ In your config/initializers/usher.rb (create if it doesn't exist) add:
143
143
 
144
144
  ## Sinatra
145
145
 
146
- In Sinatra, you get the extra method, +generate+, which lets you generate a url. Name your routes with `:name` when you define them.
146
+ In Sinatra, you get the extra method, `generate`, which lets you generate a url. Name your routes with `:name` when you define them.
147
147
 
148
148
  require 'rubygems'
149
149
  require 'usher'
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ YARD::Rake::YardocTask.new do |t|
9
9
  t.options = ['--markup=markdown'] # optional
10
10
  end
11
11
 
12
- task :spec => ['spec:private', 'spec:rails2_2:cleanup', 'spec:rails2_3:cleanup']
12
+ task :spec => ['spec:private', 'spec:rails2_2:spec', 'spec:rails2_3:spec']
13
13
  namespace(:spec) do
14
14
  Spec::Rake::SpecTask.new(:private) do |t|
15
15
  t.spec_opts ||= []
@@ -24,7 +24,7 @@ namespace(:spec) do
24
24
  sh('unzip -qq spec/rails2_2/vendor.zip -dspec/rails2_2')
25
25
  end
26
26
 
27
- Spec::Rake::SpecTask.new(:spec) do |t|
27
+ Spec::Rake::SpecTask.new(:only_spec) do |t|
28
28
  t.spec_opts ||= []
29
29
  t.spec_opts << "-rubygems"
30
30
  t.spec_opts << "--options" << "spec/spec.opts"
@@ -34,9 +34,8 @@ namespace(:spec) do
34
34
  task :cleanup do
35
35
  sh('rm -rf spec/rails2_2/vendor')
36
36
  end
37
-
38
- task :spec => :unzip
39
- task :cleanup => :spec
37
+
38
+ task :spec => [:unzip, :only_spec, :cleanup]
40
39
  end
41
40
 
42
41
  namespace(:rails2_3) do
@@ -45,7 +44,7 @@ namespace(:spec) do
45
44
  sh('unzip -qq spec/rails2_3/vendor.zip -dspec/rails2_3')
46
45
  end
47
46
 
48
- Spec::Rake::SpecTask.new(:spec) do |t|
47
+ Spec::Rake::SpecTask.new(:only_spec) do |t|
49
48
  t.spec_opts ||= []
50
49
  t.spec_opts << "-rubygems"
51
50
  t.spec_opts << "--options" << "spec/spec.opts"
@@ -55,8 +54,7 @@ namespace(:spec) do
55
54
  sh('rm -rf spec/rails2_3/vendor')
56
55
  end
57
56
 
58
- task :spec => :unzip
59
- task :cleanup => :spec
57
+ task :spec => [:unzip, :only_spec, :cleanup]
60
58
  end
61
59
 
62
60
 
data/lib/usher.rb CHANGED
@@ -252,6 +252,11 @@ class Usher
252
252
  route
253
253
  end
254
254
 
255
+ def add_meta(meta, path, options = nil)
256
+ route = get_route(path, options)
257
+ root.add_meta(route, meta)
258
+ end
259
+
255
260
  # Recognizes a `request`
256
261
  # @param request [#path] The request object. Must minimally respond to #path if no path argument is supplied here.
257
262
  # @param path [String] The path to be recognized.
@@ -23,7 +23,7 @@ class Usher
23
23
  # * <tt>generator</tt> - Route generator to use. (Default <tt>Usher::Util::Generators::URL.new</tt>)
24
24
  # * <tt>allow_identical_variable_names</tt> - Option to prevent routes with identical variable names to be added. eg, /:variable/:variable would raise an exception if this option is not enabled. (Default <tt>false</tt>)
25
25
  def initialize(options = {}, &blk)
26
- @_app = options[:default_app] || proc{|env| ::Rack::Response.new("No route found", 404).finish }
26
+ @_app = options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
27
27
  @use_destinations = options.key?(:use_destinations) ? options.delete(:use_destinations) : true
28
28
  @router_key = options.delete(:router_key) || ENV_KEY_DEFAULT_ROUTER
29
29
  request_methods = options.delete(:request_methods) || [:request_method, :host, :port, :scheme]
@@ -56,14 +56,14 @@ class Usher
56
56
  new_one
57
57
  end
58
58
 
59
- # Adds a route to the route set with a +path+ and optional +options+.
59
+ # Adds a route to the route set with a `path` and optional `options`.
60
60
  # See <tt>Usher#add_route</tt> for more details about the format of the route and options accepted here.
61
61
  def add(path, options = nil)
62
62
  @router.add_route(path, options)
63
63
  end
64
64
  alias_method :path, :add
65
65
 
66
- # Sets the default application when route matching is unsuccessful. Accepts either an application +app+ or a block to call.
66
+ # Sets the default application when route matching is unsuccessful. Accepts either an application `app` or a block to call.
67
67
  #
68
68
  # default { |env| ... }
69
69
  # default DefaultApp
@@ -79,27 +79,27 @@ class Usher
79
79
  # it returns route, and because you may want to work with the route,
80
80
  # for example give it a name, we returns the route with GET request
81
81
 
82
- # Convenience method for adding a route that only matches request method +GET+.
82
+ # Convenience method for adding a route that only matches request method `GET`.
83
83
  def only_get(path, options = {})
84
84
  add(path, options.merge!(:conditions => {:request_method => ["GET"]}))
85
85
  end
86
86
 
87
- # Convenience method for adding a route that only matches request methods +GET+ and +HEAD+.
87
+ # Convenience method for adding a route that only matches request methods `GET` and `HEAD`.
88
88
  def get(path, options = {})
89
89
  add(path, options.merge!(:conditions => {:request_method => ["HEAD", "GET"]}))
90
90
  end
91
91
 
92
- # Convenience method for adding a route that only matches request method +POST+.
92
+ # Convenience method for adding a route that only matches request method `POST`.
93
93
  def post(path, options = {})
94
94
  add(path, options.merge!(:conditions => {:request_method => "POST"}))
95
95
  end
96
96
 
97
- # Convenience method for adding a route that only matches request method +PUT+.
97
+ # Convenience method for adding a route that only matches request method `PUT`.
98
98
  def put(path, options = {})
99
99
  add(path, options.merge!(:conditions => {:request_method => "PUT"}))
100
100
  end
101
101
 
102
- # Convenience method for adding a route that only matches request method +DELETE+.
102
+ # Convenience method for adding a route that only matches request method `DELETE`.
103
103
  def delete(path, options = {})
104
104
  add(path, options.merge!(:conditions => {:request_method => "DELETE"}))
105
105
  end
@@ -167,7 +167,7 @@ class Usher
167
167
  elsif usable_response && response.destination.respond_to?(:args) && response.destination.args.first.respond_to?(:call)
168
168
  response.args.first
169
169
  elsif !response.succeeded? && response.request_method?
170
- rack_response = ::Rack::Response.new("Method not allowed", 405)
170
+ rack_response = ::Rack::Response.new("Method Not Allowed", 405)
171
171
  rack_response['Allow'] = response.acceptable_responses_only_strings.join(", ")
172
172
  proc { |env| rack_response.finish }
173
173
  else
@@ -177,8 +177,8 @@ class Usher
177
177
 
178
178
  # Consume the path from path_info to script_name
179
179
  def consume_path!(request, response)
180
- request.env["SCRIPT_NAME"] = (request.env["SCRIPT_NAME"] + response.matched_path) || ""
181
- request.env["PATH_INFO"] = response.remaining_path || ""
180
+ request.env["SCRIPT_NAME"] = (request.env["SCRIPT_NAME"] + response.matched_path)
181
+ request.env["PATH_INFO"] = response.remaining_path || ""
182
182
  end
183
183
 
184
184
  def default_app
@@ -4,7 +4,7 @@ class Usher
4
4
  # Route specific for Rack with redirection support built in.
5
5
  class Route < Usher::Route
6
6
 
7
- # Redirect route to some other path.
7
+ # Redirect route to some other path.
8
8
  def redirect(path, status = 302)
9
9
  unless (300..399).include?(status)
10
10
  raise ArgumentError, "Status has to be an integer between 300 and 399"
@@ -18,9 +18,14 @@ class Usher
18
18
  self
19
19
  end
20
20
 
21
+ # Serves either files from a directory, or a single static file.
21
22
  def serves_static_from(root)
22
- match_partially!
23
- @destination = ::Rack::File.new(root)
23
+ if File.directory?(root)
24
+ match_partially!
25
+ @destination = ::Rack::File.new(root)
26
+ else
27
+ @destination = proc{|env| env['PATH_INFO'] = File.basename(root); ::Rack::File.new(File.dirname(root)).call(env)}
28
+ end
24
29
  end
25
30
  end
26
31
  end
@@ -93,11 +93,11 @@ class Usher
93
93
  Array(destinations).each do |d| d.module_eval { include Helpers }
94
94
  @router.named_routes.keys.each do |name|
95
95
  @module.module_eval <<-end_eval # We use module_eval to avoid leaks
96
- def #{name}_url(options = {})
97
- ActionController::Routing::Routes.generate(options, {}, :generate, :#{name})
96
+ def #{name}_url(*args)
97
+ UsherRailsRouter.generate(args, {}, :generate, :#{name})
98
98
  end
99
- def #{name}_path(options = {})
100
- ActionController::Routing::Routes.generate(options, {}, :generate, :#{name})
99
+ def #{name}_path(*args)
100
+ UsherRailsRouter.generate(args, {}, :generate, :#{name})
101
101
  end
102
102
  end_eval
103
103
  end
@@ -107,12 +107,24 @@ class Usher
107
107
  { }
108
108
  end
109
109
  "
110
+ unless @module.const_defined?(:UsherRailsRouter)
111
+ @module.const_set(:UsherRailsRouter, self)
112
+ end
113
+
110
114
  @router.named_routes.helpers.__send__(:extend, @module)
111
115
  end
112
116
  end
113
117
 
114
- def generate(options, recall = {}, method = :generate, route_name = nil)
115
- route = if(route_name)
118
+ def generate(args, recall = {}, method = :generate, route_name = nil)
119
+ if args.is_a?(Hash)
120
+ options = args
121
+ args = nil
122
+ else
123
+ args = Array(args)
124
+ options = args.last.is_a?(Hash) ? args.pop : {}
125
+ end
126
+
127
+ route = if route_name
116
128
  @router.named_routes[route_name]
117
129
  else
118
130
  merged_options = options
@@ -125,7 +137,7 @@ class Usher
125
137
  case method
126
138
  when :generate
127
139
  merged_options ||= recall.merge(options)
128
- url = generate_url(route, merged_options)
140
+ url = generate_url(route, args ? args << merged_options : merged_options)
129
141
  url.slice!(-1) if url[-1] == ?/
130
142
  url
131
143
  else
@@ -35,7 +35,7 @@ class Usher
35
35
 
36
36
  # Generates a url from Rack env and identifiers or significant keys.
37
37
  #
38
- # To generate a url by named route, pass the name in as a +Symbol+.
38
+ # To generate a url by named route, pass the name in as a `Symbol`.
39
39
  # url(env, :dashboard) # => "/dashboard"
40
40
  #
41
41
  # Additional parameters can be passed in as a hash
data/lib/usher/node.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  require File.join('usher', 'node', 'root')
2
4
  require File.join('usher', 'node', 'root_ignoring_trailing_delimiters')
3
5
  require File.join('usher', 'node', 'response')
@@ -19,8 +21,10 @@ class Usher
19
21
  # @see Root
20
22
  class Node
21
23
 
22
- attr_reader :normal, :greedy, :request
23
- attr_accessor :terminates, :request_method_type, :parent, :value, :request_methods
24
+ Terminates = Struct.new(:choices, :default)
25
+
26
+ attr_reader :normal, :greedy, :request, :terminates, :unique_terminating_routes, :meta
27
+ attr_accessor :default_terminates, :request_method_type, :parent, :value, :request_methods
24
28
 
25
29
  def initialize(parent, value)
26
30
  @parent, @value = parent, value
@@ -29,7 +33,7 @@ class Usher
29
33
  def inspect
30
34
  out = ''
31
35
  out << " " * depth
32
- out << "#{terminates? ? '* ' : ''}#{depth}: #{value.inspect}\n"
36
+ out << "#{terminates ? '* ' : ''}#{depth}: #{value.inspect}\n"
33
37
  [:normal, :greedy, :request].each do |node_type|
34
38
  send(node_type).each do |k,v|
35
39
  out << (" " * (depth + 1)) << "#{node_type.to_s[0].chr} #{k.inspect} ==> \n" << v.inspect
@@ -38,15 +42,44 @@ class Usher
38
42
  out
39
43
  end
40
44
 
41
- protected
45
+ def add_meta(obj)
46
+ create_meta << obj
47
+ end
42
48
 
49
+ def add_terminate(path)
50
+ if path.route.when_proc
51
+ create_terminate.choices << path
52
+ else
53
+ create_terminate.default = path
54
+ end
55
+ unique_terminating_routes << path.route
56
+ end
43
57
 
58
+ def remove_terminate(path)
59
+ if terminates
60
+ terminates.choices.delete(path)
61
+ terminates.default = nil if terminates.default == path
62
+ end
63
+ unique_terminating_routes.delete_if{|r| r == path.route}
64
+ end
65
+
66
+ protected
67
+
68
+ def create_terminate
69
+ @unique_terminating_routes ||= Set.new
70
+ @terminates ||= Terminates.new([], nil)
71
+ end
72
+
73
+ def create_meta
74
+ @meta ||= []
75
+ end
76
+
44
77
  def depth
45
78
  @depth ||= parent.is_a?(Node) ? parent.depth + 1 : 0
46
79
  end
47
80
 
48
- def terminates?
49
- @terminates && @terminates.route.recognizable?
81
+ def pick_terminate(request_object)
82
+ @terminates.choices.find{|(p, t)| p.call(request_object) && t && t.route.recognizable?} || (@terminates.default && @terminates.default.route.recognizable? ? @terminates.default : nil) if @terminates
50
83
  end
51
84
 
52
85
  def ancestors
@@ -69,19 +102,19 @@ class Usher
69
102
  @route_set ||= root.route_set
70
103
  end
71
104
 
72
- def find(request_object, original_path, path, params = [])
105
+ def find(request_object, original_path, path, params = [], gathered_meta = nil)
106
+ (gathered_meta ||= []).concat(meta) if meta
73
107
  # terminates or is partial
74
-
75
- if terminates? && (path.empty? || terminates.route.partial_match?)
76
- response = terminates.route.partial_match? ?
77
- Response.new(terminates, params, path.join, original_path[0, original_path.size - path.join.size]) :
78
- Response.new(terminates, params, nil, original_path)
108
+ if terminating_path = pick_terminate(request_object) and (path.empty? || terminating_path.route.partial_match?)
109
+ response = terminating_path.route.partial_match? ?
110
+ Response.new(terminating_path, params, path.join, original_path[0, original_path.size - path.join.size], false, gathered_meta) :
111
+ Response.new(terminating_path, params, nil, original_path, false, gathered_meta)
79
112
  # terminates or is partial
80
113
  elsif !path.empty? and greedy and match_with_result_output = greedy.match_with_result(whole_path = path.join)
81
114
  child_node, matched_part = match_with_result_output
82
115
  whole_path.slice!(0, matched_part.size)
83
116
  params << matched_part if child_node.value.is_a?(Route::Variable)
84
- child_node.find(request_object, original_path, whole_path.empty? ? whole_path : route_set.splitter.split(whole_path), params)
117
+ child_node.find(request_object, original_path, whole_path.empty? ? whole_path : route_set.splitter.split(whole_path), params, gathered_meta)
85
118
  elsif !path.empty? and normal and child_node = normal[path.first] || normal[nil]
86
119
  part = path.shift
87
120
  case child_node.value
@@ -93,7 +126,7 @@ class Usher
93
126
  next_path_part = path.shift # and until they are satified,
94
127
  part << next_path_part
95
128
  end if variable.look_ahead
96
- params << part # because its a variable, we need to add it to the params array
129
+ params << part # because its a variable, we need to add it to the params array
97
130
  when Route::Variable::Glob
98
131
  params << []
99
132
  loop do
@@ -110,22 +143,22 @@ class Usher
110
143
  end
111
144
  end
112
145
  end
113
- child_node.find(request_object, original_path, path, params)
146
+ child_node.find(request_object, original_path, path, params, gathered_meta)
114
147
  elsif request_method_type
115
148
  if route_set.priority_lookups?
116
149
  route_candidates = []
117
- if specific_node = request[request_object.send(request_method_type)] and ret = specific_node.find(request_object, original_path, path.dup, params && params.dup)
150
+ if specific_node = request[request_object.send(request_method_type)] and ret = specific_node.find(request_object, original_path, path.dup, params.dup, gathered_meta && gathered_meta.dup)
118
151
  route_candidates << ret
119
152
  end
120
- if general_node = request[nil] and ret = general_node.find(request_object, original_path, path.dup, params && params.dup)
153
+ if general_node = request[nil] and ret = general_node.find(request_object, original_path, path.dup, params.dup, gathered_meta && gathered_meta.dup)
121
154
  route_candidates << ret
122
155
  end
123
156
  route_candidates.sort!{|r1, r2| r1.path.route.priority <=> r2.path.route.priority}
124
157
  request_method_respond(route_candidates.last, request_method_type)
125
158
  else
126
- if specific_node = request[request_object.send(request_method_type)] and ret = specific_node.find(request_object, original_path, path.dup, params && params.dup)
159
+ if specific_node = request[request_object.send(request_method_type)] and ret = specific_node.find(request_object, original_path, path.dup, params.dup, gathered_meta && gathered_meta.dup)
127
160
  ret
128
- elsif general_node = request[nil] and ret = general_node.find(request_object, original_path, path.dup, params && params.dup)
161
+ elsif general_node = request[nil] and ret = general_node.find(request_object, original_path, path.dup, params.dup, gathered_meta && gathered_meta.dup)
129
162
  request_method_respond(ret, request_method_type)
130
163
  else
131
164
  request_method_respond(nil, request_method_type)
@@ -1,7 +1,7 @@
1
1
  class Usher
2
2
  class Node
3
3
  # The response from {Usher::Node::Root#lookup}. Adds some convenience methods for common parameter manipulation.
4
- class Response < Struct.new(:path, :params_as_array, :remaining_path, :matched_path, :only_trailing_delimiters)
4
+ class Response < Struct.new(:path, :params_as_array, :remaining_path, :matched_path, :only_trailing_delimiters, :meta)
5
5
 
6
6
  # The success of the response
7
7
  # @return [Boolean] Always returns true
@@ -12,15 +12,19 @@ class Usher
12
12
  end
13
13
 
14
14
  def add(route)
15
- route.paths.each { |path| set_path_with_destination(path) }
15
+ get_nodes_for_route(route) {|p, n| n.add_terminate(p)}
16
16
  end
17
17
 
18
18
  def delete(route)
19
- route.paths.each { |path| set_path_with_destination(path, nil) }
19
+ get_nodes_for_route(route) {|p, n| n.remove_terminate(p)}
20
+ end
21
+
22
+ def add_meta(route, obj)
23
+ get_nodes_for_route(route) {|p, n| n.add_meta(obj)}
20
24
  end
21
25
 
22
26
  def unique_routes(node = self, routes = [])
23
- routes << node.terminates.route if node.terminates
27
+ routes.concat(node.unique_terminating_routes.to_a) if node.unique_terminating_routes
24
28
  [:normal, :greedy, :request].each { |type| node.send(type).values.each { |v| unique_routes(v, routes) } if node.send(type) }
25
29
  routes.uniq!
26
30
  routes
@@ -33,16 +37,22 @@ class Usher
33
37
  private
34
38
 
35
39
  def set_path_with_destination(path, destination = path)
36
- nodes = [path.parts ? path.parts.inject(self){ |node, key| process_path_part(node, key)} : self]
37
- nodes = process_request_parts(nodes, request_methods_for_path(path)) if request_methods
38
- nodes.each do |node|
39
- while node.request_method_type
40
- node = (node.request[nil] ||= Node.new(node, Route::RequestMethod.new(node.request_method_type, nil)))
40
+ get_nodes(path) {|n| n.add_terminate(destination)}
41
+ end
42
+
43
+ def get_nodes_for_route(route)
44
+ route.paths.each do |path|
45
+ nodes = [path.parts ? path.parts.inject(self){ |node, key| process_path_part(node, key)} : self]
46
+ nodes = process_request_parts(nodes, request_methods_for_path(path)) if request_methods
47
+ nodes.each do |node|
48
+ while node.request_method_type
49
+ node = (node.request[nil] ||= Node.new(node, Route::RequestMethod.new(node.request_method_type, nil)))
50
+ end
51
+ yield path, node
41
52
  end
42
- node.terminates = destination
43
53
  end
44
54
  end
45
-
55
+
46
56
  def request_method_index(type)
47
57
  request_methods.index(type)
48
58
  end
data/lib/usher/route.rb CHANGED
@@ -6,7 +6,7 @@ require File.join('usher', 'route', 'request_method')
6
6
 
7
7
  class Usher
8
8
  class Route
9
- attr_reader :original_path, :paths, :requirements, :conditions, :named, :generate_with, :default_values, :match_partially, :destination, :priority
9
+ attr_reader :original_path, :paths, :requirements, :conditions, :named, :generate_with, :default_values, :match_partially, :destination, :priority, :when_proc
10
10
  attr_accessor :parent_route, :router, :recognizable
11
11
 
12
12
  class GenerateWith < Struct.new(:scheme, :port, :host)
@@ -15,6 +15,12 @@ class Usher
15
15
  end
16
16
  end
17
17
 
18
+ def ==(other_route)
19
+ if other_route.is_a?(Route)
20
+ original_path == other_route.original_path && requirements == other_route.requirements && conditions == other_route.conditions && match_partially == other_route.match_partially && recognizable == other_route.recognizable && parent_route == other_route.parent_route && generate_with == other_route.generate_with
21
+ end
22
+ end
23
+
18
24
  def initialize(original_path, parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially, priority)
19
25
  @original_path, @router, @requirements, @conditions, @default_values, @match_partially, @priority = original_path, router, requirements, conditions, default_values, match_partially, priority
20
26
  @recognizable = true
@@ -22,6 +28,11 @@ class Usher
22
28
  @generate_with = GenerateWith.new(generate_with[:scheme], generate_with[:port], generate_with[:host]) if generate_with
23
29
  end
24
30
 
31
+ def when(&block)
32
+ @when_proc = block
33
+ self
34
+ end
35
+
25
36
  def destination_keys
26
37
  @destination_keys ||= case
27
38
  when Hash
@@ -11,6 +11,10 @@ class Usher
11
11
  build_generator
12
12
  end
13
13
 
14
+ def ==(other_path)
15
+ other_path.is_a?(Path) ? route == other_path.route && parts == other_path.parts : nil
16
+ end
17
+
14
18
  def convert_params_array(ary)
15
19
  ary.empty? ? ary : dynamic_keys.zip(ary)
16
20
  end
@@ -79,35 +83,39 @@ class Usher
79
83
  @dynamic = @parts && @parts.any?{|p| p.is_a?(Variable)}
80
84
  end
81
85
 
82
- def build_generator
83
- if parts
84
- interpolating_path = ''
85
-
86
+ def interpolating_path
87
+ unless @interpolating_path
88
+ @interpolating_path = ''
86
89
  parts.each_with_index do |part, index|
87
90
  case part
88
91
  when String
89
- interpolating_path << part
92
+ @interpolating_path << part
90
93
  when Static::Greedy
91
- interpolating_path << part.generate_with
94
+ @interpolating_path << part.generate_with
92
95
  when Variable::Glob
93
- interpolating_path << '#{('
94
- interpolating_path << "Array(arg#{index})"
96
+ @interpolating_path << '#{('
97
+ @interpolating_path << "Array(arg#{index})"
95
98
  if part.default_value
96
- interpolating_path << ' || '
97
- interpolating_path << part.default_value.inspect
99
+ @interpolating_path << ' || '
100
+ @interpolating_path << part.default_value.inspect
98
101
  end
99
- interpolating_path << ').join(route.router.delimiters.first)}'
102
+ @interpolating_path << ').join(route.router.delimiters.first)}'
100
103
  when Variable
101
- interpolating_path << '#{'
102
- interpolating_path << "arg#{index}"
104
+ @interpolating_path << '#{'
105
+ @interpolating_path << "arg#{index}"
103
106
  if part.default_value
104
- interpolating_path << ' || '
105
- interpolating_path << part.default_value.inspect
107
+ @interpolating_path << ' || '
108
+ @interpolating_path << part.default_value.inspect
106
109
  end
107
- interpolating_path << '}'
110
+ @interpolating_path << '}'
108
111
  end
109
112
  end
110
-
113
+ end
114
+ @interpolating_path
115
+ end
116
+
117
+ def build_generator
118
+ if parts
111
119
  generating_method = "def generate"
112
120
  if dynamic?
113
121
  generating_method << "("
@@ -75,7 +75,7 @@ class Usher
75
75
  result << generate_path(path, params)
76
76
  end
77
77
 
78
- # Generates a completed URL based on a +route+ or set of optional +params+
78
+ # Generates a completed URL based on a `route` or set of optional `params`
79
79
  #
80
80
  # set = Usher.new
81
81
  # route = set.add_named_route(:test_route, '/:controller/:action')
@@ -43,14 +43,16 @@ class Usher
43
43
  part.default_value = default_values[part.name] if part.is_a?(Usher::Route::Variable) && default_values && default_values[part.name]
44
44
  case part
45
45
  when Usher::Route::Variable::Glob
46
- if part.look_ahead && !part.look_ahead_priority
46
+ possible_look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !router.delimiters.unescaped.include?(p)} || nil
47
+ if part.look_ahead && !part.look_ahead_priority && possible_look_ahead != part.look_ahead
47
48
  part.look_ahead = nil
48
49
  part.look_ahead_priority = true
49
50
  else
50
51
  part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !router.delimiters.unescaped.include?(p)} || nil
51
52
  end
52
53
  when Usher::Route::Variable
53
- if part.look_ahead && !part.look_ahead_priority
54
+ possible_look_ahead = router.delimiters.first_in(path[index + 1, path.size]) || router.delimiters.unescaped.first
55
+ if part.look_ahead && !part.look_ahead_priority && possible_look_ahead != part.look_ahead
54
56
  part.look_ahead = nil
55
57
  part.look_ahead_priority = true
56
58
  else
@@ -10,16 +10,18 @@ class Usher
10
10
  ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails23)"
11
11
 
12
12
  when '2.2'
13
- class Usher::Interface::Rails22::Mapper
14
- include ActionController::Resources
15
- end
13
+ Usher::Interface::Rails22::Mapper.module_eval("include ActionController::Resources")
16
14
  ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails22)"
17
15
 
18
16
  when '2.0'
19
- class Usher::Interface::Rails20::Mapper
20
- include ActionController::Resources
21
- end
22
-
17
+ Usher::Interface::Rails20::Mapper.module_eval("include ActionController::Resources")
18
+ ActionController::Routing.module_eval <<-CODE
19
+ remove_const(:Routes);
20
+ interface = Usher::Interface.for(:rails20);
21
+ interface.configuration_file = File.join(RAILS_ROOT, 'config', 'routes.rb')
22
+ Routes = interface;
23
+ CODE
24
+ when '3.0'
23
25
  ActionController::Routing.module_eval <<-CODE
24
26
  remove_const(:Routes);
25
27
  interface = Usher::Interface.for(:rails20);
@@ -0,0 +1,23 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+ require "usher"
3
+
4
+ describe "Usher metadata" do
5
+
6
+ it "should add meta data to a path" do
7
+ usher = Usher.new
8
+ usher.add_route('/test')
9
+ usher.add_route('/test/test2')
10
+ usher.add_route('/test/test3')
11
+ usher.add_route('/test/test2/:variable')
12
+ usher.add_meta(:test, '/test')
13
+ usher.add_meta(:test2, '/test/test2')
14
+ usher.add_meta(:test3, '/test/test2/:something')
15
+
16
+ usher.recognize_path('/test').meta.should == [:test]
17
+ usher.recognize_path('/test/test3').meta.should == [:test]
18
+ usher.recognize_path('/test/test2').meta.should == [:test, :test2]
19
+ usher.recognize_path('/test/test2/variable1').meta.should == [:test, :test2, :test3]
20
+ usher.recognize_path('/test/test2/variable2').meta.should == [:test, :test2, :test3]
21
+
22
+ end
23
+ end
@@ -36,7 +36,7 @@ describe "Usher route adding" do
36
36
  route_set.add_named_route(:route, '/bad/route', :controller => 'sample').should == route_set.named_routes[:route]
37
37
  end
38
38
 
39
- it "should allow named routes to be added" do
39
+ it "should allow named routes to be deleted" do
40
40
  route_set.add_named_route(:route, '/bad/route', :controller => 'sample').should == route_set.named_routes[:route]
41
41
  route_set.route_count.should == 1
42
42
  route_set.named_routes.size == 1
@@ -37,6 +37,13 @@ describe "Rack interface extensions for Usher::Route" do
37
37
  status, headers, body = @route_set.call(@env)
38
38
  body.path.should == File.join(File.dirname(__FILE__), File.basename(__FILE__))
39
39
  end
40
+
41
+ it "should serve a specific file" do
42
+ @route_set.get("/static-file").serves_static_from(__FILE__)
43
+ @env = Rack::MockRequest.env_for("/static-file")
44
+ status, headers, body = @route_set.call(@env)
45
+ body.path.should == __FILE__
46
+ end
40
47
  end
41
48
 
42
49
  describe "chaining" do
@@ -84,6 +84,15 @@ describe "Usher route recognition" do
84
84
  end
85
85
  end
86
86
 
87
+ describe 'when proc' do
88
+
89
+ it "pick the correct route" do
90
+ not_target_route = @route_set.add_route('/sample').when{|r| r.protocol == 'https'}
91
+ target_route = @route_set.add_route('/sample').when{|r| r.protocol == 'http'}
92
+ @route_set.recognize(build_request({:method => 'get', :path => '/sample', :protocol => 'http'})).path.route.should == target_route
93
+ end
94
+ end
95
+
87
96
  it "should recognize path with a trailing slash" do
88
97
  @route_set = Usher.new(:request_methods => [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains], :ignore_trailing_delimiters => true)
89
98
 
@@ -399,14 +408,14 @@ describe "Usher route recognition" do
399
408
 
400
409
  it "should not add routes added to the dup to the original" do
401
410
  @r2.add_route("/bar", :bar => "bar")
402
- @r2.recognize( build_request(:path => "/bar")).path.route.destination.should == {:bar => "bar"}
403
- @route_set.recognize( build_request(:path => "/bar")).should == nil
411
+ @r2.recognize( build_request(:path => "/bar")).path.route.destination.should == {:bar => "bar"}
412
+ @route_set.recognize(build_request(:path => "/bar")).should == nil
404
413
  end
405
414
 
406
415
  it "should not delete routes added to the dup to the original" do
407
416
  @r2.delete_route("/foo")
408
- @route_set.recognize( build_request(:path => "/foo")).path.route.destination.should == {:foo => "foo"}
409
- @r2.recognize( build_request(:path => "/foo")).should == nil
417
+ @route_set.recognize(build_request(:path => "/foo")).path.route.destination.should == {:foo => "foo"}
418
+ @r2.recognize( build_request(:path => "/foo")).should == nil
410
419
  end
411
420
 
412
421
 
@@ -106,6 +106,16 @@ describe "Usher (for Sinatra) route recognition" do
106
106
  response.status.should == 200
107
107
  response.body.should == "/hi-18"
108
108
  end
109
+
110
+ it "should map route with complex params" do
111
+ @app.get('/hi/:foo/:bar/:baz(.:format)') { "/#{params[:foo]}/#{params[:bar]}/#{params[:baz]}/#{params[:format]}" }
112
+ response = @app.call_with_mock_request('/hi/foo/bar/baz')
113
+ response.status.should == 200
114
+ response.body.should == "/foo/bar/baz/"
115
+ response = @app.call_with_mock_request('/hi/foo/bar-bax/baz')
116
+ response.status.should == 200
117
+ response.body.should == "/foo/bar-bax/baz/"
118
+ end
109
119
  end
110
120
 
111
121
  describe "not found" do
@@ -128,5 +138,4 @@ describe "Usher (for Sinatra) route recognition" do
128
138
  response.body.should_not match(/__sinatra__/)
129
139
  end
130
140
  end
131
-
132
141
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  libdir = File.expand_path("lib")
2
2
  $:.unshift(libdir) unless $:.include?(libdir)
3
3
 
4
+ require 'usher'
5
+
4
6
  module CallWithMockRequestMixin
5
7
  def call_with_mock_request(url = "/sample", method = "GET", params = Hash.new)
6
8
  params.merge!(:method => method)
data/usher.gemspec CHANGED
@@ -5,7 +5,7 @@ require "base64"
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "usher"
8
- s.version = "0.8.0"
8
+ s.version = "0.8.1"
9
9
  s.authors = ["Daniel Neighman", "Daniel Vartanov", "Jakub Šťastný", "Joshua Hull", "Davide D'Agostino"].sort
10
10
  s.homepage = "http://github.com/joshbuddy/usher"
11
11
  s.summary = "Pure ruby general purpose router with interfaces for rails, rack, email or choose your own adventure"
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 8
8
- - 0
9
- version: 0.8.0
8
+ - 1
9
+ version: 0.8.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Daniel Neighman
@@ -19,7 +19,7 @@ authors:
19
19
  autorequire:
20
20
  bindir: bin
21
21
  cert_chain:
22
- date: 2010-05-05 00:00:00 -04:00
22
+ date: 2010-05-20 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -142,6 +142,7 @@ files:
142
142
  - spec/private/generate_spec.rb
143
143
  - spec/private/generate_with_spec.rb
144
144
  - spec/private/grapher_spec.rb
145
+ - spec/private/meta_spec.rb
145
146
  - spec/private/parser_spec.rb
146
147
  - spec/private/path_spec.rb
147
148
  - spec/private/rack/dispatch_spec.rb