usher 0.8.0 → 0.8.1

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