http_router 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/http_router.rb CHANGED
@@ -1,243 +1,91 @@
1
- require 'rack'
2
1
  require 'set'
3
- require 'url_mount'
2
+ require 'rack'
4
3
  require 'http_router/node'
5
- require 'http_router/root'
6
- require 'http_router/variable'
7
- require 'http_router/static'
8
- require 'http_router/glob'
4
+ require 'http_router/request'
5
+ require 'http_router/response'
9
6
  require 'http_router/route'
10
7
  require 'http_router/path'
8
+ require 'http_router/regex_route'
11
9
  require 'http_router/optional_compiler'
12
- require 'http_router/parts'
13
- require 'http_router/version'
14
- require 'http_router/rack'
15
10
 
16
11
  class HttpRouter
17
- # Raised when a Route is not able to be generated.
18
- UngeneratableRouteException = Class.new(RuntimeError)
19
- # Raised when a Route is not able to be generated due to a missing parameter.
20
- MissingParameterException = Class.new(RuntimeError)
21
- # Raised when a Route is generated that isn't valid.
22
- InvalidRouteException = Class.new(RuntimeError)
23
- # Raised when a Route is not able to be generated due to too many parameters being passed in.
24
- TooManyParametersException = Class.new(RuntimeError)
25
- # Raised when an already inserted Route has more conditions added.
26
- AlreadyCompiledException = Class.new(RuntimeError)
27
- # Raised when an ambiguous Route is added. For example, this will be raised if you attempt to add "/foo(/:bar)(/:baz)".
28
- AmbiguousRouteException = Class.new(RuntimeError)
29
- # Raised when a request condition is added that is not recognized.
30
- UnsupportedRequestConditionError = Class.new(RuntimeError)
31
- # Raised when there is a potential conflict of variable names within your Route.
32
- AmbiguousVariableException = Class.new(RuntimeError)
12
+
13
+ attr_reader :root, :routes, :known_methods, :named_routes
14
+ attr_accessor :default_app
33
15
 
34
- attr_reader :named_routes, :routes, :root, :request_methods_specified, :variable_names
35
- attr_accessor :url_mount
16
+ UngeneratableRouteException = Class.new(RuntimeError)
17
+ InvalidRouteException = Class.new(RuntimeError)
18
+ MissingParameterException = Class.new(RuntimeError)
36
19
 
37
- # Creates a new HttpRouter.
38
- # Can be called with either <tt>HttpRouter.new(proc{|env| ... }, { .. options .. })</tt> or with the first argument omitted.
39
- # If there is a proc first, then it's used as the default app in the case of a non-match.
40
- # Supported options are
41
- # * :default_app -- Default application used if there is a non-match on #call. Defaults to 404 generator.
42
- # * :ignore_trailing_slash -- Ignore a trailing / when attempting to match. Defaults to +true+.
43
- # * :redirect_trailing_slash -- On trailing /, redirect to the same path without the /. Defaults to +false+.
44
- def initialize(*args, &block)
45
- default_app, options = args.first.is_a?(Hash) ? [nil, args.first] : [args.first, args[1]]
46
- @options = options
47
- @default_app = default_app || options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
48
- @ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
49
- @redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
50
- @init_block = block
51
- @handle_unavailable_route = Proc.new{ raise UngeneratableRouteException }
20
+ def initialize(opts = nil, &blk)
52
21
  reset!
53
- instance_eval(&block) if block
54
- end
55
-
56
- # Ignore trailing slash feature enabled? See #initialize for details.
57
- def ignore_trailing_slash?
58
- @ignore_trailing_slash
59
- end
60
-
61
- # Redirect trailing slash feature enabled? See #initialize for details.
62
- def redirect_trailing_slash?
63
- @redirect_trailing_slash
22
+ @ignore_trailing_slash = opts && opts.key?(:ignore_trailing_slash) ? opts[:ignore_trailing_slash] : true
23
+ instance_eval(&blk) if blk
64
24
  end
65
25
 
66
- # Resets the router to a clean state.
67
- def reset!
68
- @root = Root.new(self)
69
- @request_methods_specified = Set.new
70
- @routes = []
71
- @named_routes = {}
72
- @variable_names = Set.new
73
- end
74
-
75
- # Assigns the default application.
76
- def default(app)
77
- @default_app = app
78
- end
79
-
80
- # Adds a path to be recognized.
81
- #
82
- # To assign a part of the path to a specific variable, use :variable_name within the route.
83
- # For example, <tt>add('/path/:id')</tt> would match <tt>/path/test</tt>, with the variable <tt>:id</tt> having the value <tt>"test"</tt>.
84
- #
85
- # You can receive mulitple parts into a single variable by using the glob syntax.
86
- # For example, <tt>add('/path/*id')</tt> would match <tt>/path/123/456/789</tt>, with the variable <tt>:id</tt> having the value <tt>["123", "456", "789"]</tt>.
87
- #
88
- # As well, paths can end with two optional parts, <tt>*</tt> and <tt>/?</tt>. If it ends with a <tt>*</tt>, it will match partially, returning the part of the path unmatched in the PATH_INFO value of the env. The part matched to will be returned in the SCRIPT_NAME. If it ends with <tt>/?</tt>, then a trailing / on the path will be optionally matched for that specific route. As trailing /'s are ignored by default, you probably don't actually want to use this option that frequently.
89
- #
90
- # Routes can also contain optional parts. There are surrounded with <tt>( )</tt>'s. If you need to match on a bracket in the route itself, you can escape the parentheses with a backslash.
91
- #
92
- # The second argument, options, is an optional hash that can modify the route in further ways. See HttpRouter::Route#with_options for details. Typically, you want to add further options to the route by calling additional methods on it. See HttpRouter::Route for further details.
93
- #
94
- # Returns the route object.
95
- def add(path, options = nil)
96
- add_route route(path.dup).with_options(options)
97
- end
98
-
99
- # Adds a route to be recognized. This must be a HttpRouter::Route object. Returns the route just added.
100
- def add_route(route)
26
+ def add(path, opts = {}, &app)
27
+ route = case path
28
+ when Regexp
29
+ RegexRoute.new(self, path, opts)
30
+ else
31
+ Route.new(self, path, opts)
32
+ end
101
33
  @routes << route
34
+ route.to(app) if app
102
35
  route
103
36
  end
104
37
 
105
- # Adds a path that only responds to the request method +GET+.
106
- #
107
- # Returns the route object.
108
- def get(path, options = nil)
109
- add(path, options).get
38
+ def add_with_request_method(path, method, opts = {}, &app)
39
+ route = add(path, opts).send(method.to_sym)
40
+ route.to(app) if app
41
+ route
110
42
  end
111
43
 
112
- # Adds a path that only responds to the request method +POST+.
113
- #
114
- # Returns the route object.
115
- def post(path, options = nil)
116
- add(path, options).post
44
+ [:post, :get, :delete, :put, :head].each do |rm|
45
+ class_eval "def #{rm}(path, opts = {}, &app); add_with_request_method(path, #{rm.inspect}, opts, &app); end", __FILE__, __LINE__
117
46
  end
118
47
 
119
- # Adds a path that only responds to the request method +PUT+.
120
- #
121
- # Returns the route object.
122
- def put(path, options = nil)
123
- add(path, options).put
48
+ def recognize(env)
49
+ call(env, false)
50
+ end
51
+
52
+ def call(env, perform_call = true)
53
+ rack_request = Rack::Request.new(env)
54
+ request = Request.new(rack_request.path_info, rack_request, perform_call)
55
+ response = catch(:success) { @root[request] }
56
+ if !response
57
+ supported_methods = (@known_methods - [env['REQUEST_METHOD']]).select do |m|
58
+ test_env = Rack::Request.new(rack_request.env.clone)
59
+ test_env.env['REQUEST_METHOD'] = m
60
+ test_request = Request.new(test_env.path_info, test_env, false)
61
+ catch(:success) { @root[test_request] }
62
+ end
63
+ supported_methods.empty? ? @default_app.call(env) : [405, {'Allow' => supported_methods.sort.join(", ")}, []]
64
+ elsif response
65
+ response
66
+ else
67
+ @default_app.call(env)
68
+ end
124
69
  end
125
-
126
- # Adds a path that only responds to the request method +DELETE+.
127
- #
128
- # Returns the route object.
129
- def delete(path, options = nil)
130
- add(path, options).delete
70
+
71
+ def reset!
72
+ @root = Node.new(self)
73
+ @default_app = Proc.new{ |env| Rack::Response.new("Your request couldn't be found", 404).finish }
74
+ @routes = []
75
+ @named_routes = {}
76
+ @known_methods = ['GET', "POST", "PUT", "DELETE"]
131
77
  end
132
78
 
133
- # Generate a URL for a specified route. This will accept a list of variable values plus any other variable names named as a hash.
134
- # This first value must be either the Route object or the name of the route.
135
- #
136
- # Example:
137
- # router = HttpRouter.new
138
- # router.add('/:foo.:format).name(:test).compile
139
- # router.url(:test, 123, 'html')
140
- # # ==> "/123.html"
141
- # router.url(:test, 123, :format => 'html')
142
- # # ==> "/123.html"
143
- # router.url(:test, :foo => 123, :format => 'html')
144
- # # ==> "/123.html"
145
- # router.url(:test, :foo => 123, :format => 'html', :fun => 'inthesun')
146
- # # ==> "/123.html?fun=inthesun"
147
79
  def url(route, *args)
148
80
  case route
149
81
  when Symbol then url(@named_routes[route], *args)
150
82
  when Route then route.url(*args)
151
- when nil then @handle_unavailable_route.call(:url, *args)
152
- else
153
- end
154
- end
155
-
156
- def url_with_params(route, *args)
157
- case route
158
- when Symbol then url_with_params(@named_routes[route], *args)
159
- when Route then route.url_with_params(*args)
160
- when nil then @handle_unavailable_route.call(:url_with_params, *args)
161
- else
162
- end
163
- end
164
-
165
- # Rack compatible #call. If matching route is found, and +dest+ value responds to #call, processing will pass to the matched route. Otherwise,
166
- # the default application will be called. The router will be available in the env under the key <tt>router</tt>. And parameters matched will
167
- # be available under the key <tt>router.params</tt>. The HttpRouter::Response object will be available under the key <tt>router.response</tt> if
168
- # a response is available.
169
- def call(env)
170
- request = ::Rack::Request.new(env)
171
- if redirect_trailing_slash? && (request.head? || request.get?) && request.path_info[-1] == ?/
172
- response = ::Rack::Response.new
173
- response.redirect(request.path_info[0, request.path_info.size - 1], 302)
174
- response.finish
175
- else
176
- @root.call(request) || @default_app.call(request.env)
83
+ else raise UngeneratableRouteException
177
84
  end
178
85
  end
179
86
 
180
- def recognize(env)
181
- @root.recognize(env)
182
- end
183
-
184
- # Returns a new node
185
- def node(*args)
186
- Node.new(self, *args)
187
- end
188
-
189
- # Returns a new request node
190
- def request_node(*args)
191
- RequestNode.new(self, *args)
192
- end
193
-
194
- def arbitrary_node(*args)
195
- ArbitraryNode.new(self, *args)
196
- end
197
-
198
- # Returns a new variable
199
- def variable(*args)
200
- Variable.new(self, *args)
201
- end
202
-
203
- # Returns a new glob
204
- def glob(*args)
205
- Glob.new(self, *args)
206
- end
207
-
208
- # Returns a new route
209
- def route(*args)
210
- Route.new(self, *args)
211
- end
212
-
213
- # Creates a deep-copy of the router.
214
- def clone(klass = self.class)
215
- cloned_router = klass.new(@default_app, @options)
216
- @routes.each do |route|
217
- new_route = route.clone(cloned_router)
218
- cloned_router.add_route(new_route).compile
219
- new_route.name(route.named) if route.named
220
- if route.dest
221
- begin
222
- new_route.to route.dest.clone
223
- rescue
224
- new_route.to route.dest
225
- end
226
- end
227
- end
228
- cloned_router
229
- end
230
-
231
- def split(path)
232
- Parts.new(path)
233
- end
234
-
235
- def self.uri_escape!(s)
236
- s.to_s.gsub!(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) { "%#{$1.unpack('H2'*$1.size).join('%').upcase}" }
237
- end
238
-
239
- def self.uri_unescape!(s)
240
- s.to_s.gsub!(/((?:%[0-9a-fA-F]{2})+)/n){ [$1.delete('%')].pack('H*') }
87
+ def ignore_trailing_slash?
88
+ @ignore_trailing_slash
241
89
  end
242
90
 
243
91
  def append_querystring(uri, params)
@@ -1,265 +1,163 @@
1
1
  class HttpRouter
2
2
  class Node
3
- class Response < Struct.new(:path, :param_values)
4
- attr_reader :params
5
- def initialize(path, param_values)
6
- super
7
- if path.splitting_indexes
8
- param_values = param_values.dup
9
- path.splitting_indexes.each{|i| param_values[i] = param_values[i].split(HttpRouter::Parts::SLASH_RX)}
10
- end
11
- @params = path.route.default_values ? path.route.default_values.merge(path.hashify_params(param_values)) : path.hashify_params(param_values)
12
- end
13
- end
3
+ autoload :Glob, 'http_router/node/glob'
4
+ autoload :Variable, 'http_router/node/variable'
5
+ autoload :Regex, 'http_router/node/regex'
6
+ autoload :SpanningRegex, 'http_router/node/spanning_regex'
7
+ autoload :GlobRegex, 'http_router/node/glob_regex'
8
+ autoload :FreeRegex, 'http_router/node/free_regex'
9
+ autoload :Arbitrary, 'http_router/node/arbitrary'
10
+ autoload :Request, 'http_router/node/request'
14
11
 
15
- attr_accessor :value, :variable, :catchall
16
- attr_reader :linear, :lookup, :request_node, :arbitrary_node
12
+ attr_reader :priority, :router
17
13
 
18
14
  def initialize(router)
19
15
  @router = router
20
- reset!
21
16
  end
22
17
 
23
- def reset!
24
- @linear, @lookup, @catchall = nil, nil, nil
18
+ def [](request)
19
+ destination(request, false)
20
+ unless request.path.empty?
21
+ linear(request)
22
+ lookup(request)
23
+ variable(request)
24
+ glob(request)
25
+ end
26
+ destination(request)
25
27
  end
26
28
 
27
- def add(val)
28
- if val.respond_to?(:matches?)
29
- if val.matches_with
30
- add_to_linear(val)
31
- else
32
- add_to_catchall(val)
33
- end
34
- elsif val.is_a?(Regexp)
35
- add_to_linear(val)
36
- else
37
- create_lookup
38
- @lookup[val] ||= router.node
39
- end
29
+ def linear(request)
30
+ @linear && @linear.each{|n| n[request]}
40
31
  end
41
-
42
- def add_to_linear(val)
43
- create_linear
44
- n = if @linear.assoc(val)
45
- @linear.assoc(val).last
46
- else
47
- new_node = router.node
48
- @linear << [val, new_node]
49
- new_node
32
+
33
+ def lookup(request)
34
+ if @lookup && @lookup[request.path.first]
35
+ request = request.clone
36
+ @lookup[request.path.shift][request]
50
37
  end
51
- @linear.sort!{|a, b| b.first.priority <=> a.first.priority }
52
- n
53
38
  end
54
-
55
- def add_arbitrary(procs)
56
- target = self
57
- if procs && !procs.empty?
58
- @arbitrary_node ||= router.arbitrary_node
59
- @arbitrary_node.create_linear
60
- target = router.node
61
- @arbitrary_node.linear << [procs, target]
62
- if @value
63
- @arbitrary_node.catchall = router.node
64
- @arbitrary_node.catchall.value = @value
65
- @value = nil
66
- end
67
- elsif @arbitrary_node
68
- target = @arbitrary_node.catchall = router.node
69
- end
70
- target
39
+
40
+ def variable(request)
41
+ @variable && @variable[request]
71
42
  end
72
-
73
- def add_to_catchall(val)
74
- (@catchall ||= router.node).variable = val
75
- @catchall
43
+
44
+ def glob(request)
45
+ @glob && @glob[request]
46
+ end
47
+
48
+ def request(request)
49
+ @request && @request[request]
76
50
  end
77
-
78
- def add_request_methods(request_options)
79
- raise UnsupportedRequestConditionError if request_options && (request_options.keys & RequestNode::RequestMethods).size != request_options.size
80
- current_nodes = [self]
81
- RequestNode::RequestMethods.each do |method|
82
- if request_options && request_options.key?(method) # so, the request method we care about it ..
83
- current_nodes = [@request_node ||= router.request_node] if current_nodes == [self]
84
- for current_node_index in (0...current_nodes.size)
85
- current_node = current_nodes.at(current_node_index)
86
- current_node.request_method = method unless current_node.request_method
87
- case RequestNode::RequestMethods.index(method) <=> RequestNode::RequestMethods.index(current_node.request_method)
88
- when 0 #use this node
89
- Array(request_options[method]).each_with_index do |request_value, index|
90
- if request_value.is_a?(Regexp)
91
- new_node = router.request_node
92
- current_nodes[index == 0 ? current_node_index : current_nodes.length] = new_node
93
- current_node.create_linear
94
- current_node.linear << [request_value, new_node]
95
- else
96
- router.request_methods_specified << request_value if method == :request_method
97
- current_node.create_lookup
98
- current_nodes[index == 0 ? current_node_index : current_nodes.length] = (current_node.lookup[request_value] ||= router.request_node)
99
- end
51
+
52
+ def arbitrary(request)
53
+ @arbitrary && @arbitrary.each{|n| n[request]}
54
+ end
55
+
56
+ def unescape(val)
57
+ val.to_s.gsub(/((?:%[0-9a-fA-F]{2})+)/n){ [$1.delete('%')].pack('H*') }
58
+ end
59
+
60
+ def destination(request_obj, match_partially = true)
61
+ request(request_obj)
62
+ arbitrary(request_obj)
63
+ if match_partially or request_obj.path.empty?
64
+ @destination && @destination.each do |d|
65
+ if d.route.match_partially? or request_obj.path.empty? or (@router.ignore_trailing_slash? and request_obj.path.size == 1 and request_obj.path.last == '')
66
+ if request_obj.perform_call
67
+ env = request_obj.rack_request.dup.env
68
+ env['router.params'] ||= {}
69
+ env['router.params'].merge!(d.hashify_params(request_obj.params))
70
+ matched = if d.route.match_partially?
71
+ env['PATH_INFO'] = "/#{request_obj.path.join('/')}"
72
+ env['SCRIPT_NAME'] += request_obj.rack_request.path_info[0, request_obj.rack_request.path_info.size - env['PATH_INFO'].size]
73
+ else
74
+ env["PATH_INFO"] = ''
75
+ env["SCRIPT_NAME"] += request_obj.rack_request.path_info
100
76
  end
101
- when 1 #this node is farther ahead
102
- current_nodes[current_node_index] = (current_node.catchall ||= router.request_node)
103
- when -1 #this method is more important than the current node
104
- next_node = current_node.dup
105
- current_node.reset!
106
- current_node.request_method = method
107
- current_node.catchall ||= next_node
108
- redo
77
+ throw :success, d.route.dest.call(env)
78
+ else
79
+ throw :success, Response.new(request_obj, d)
109
80
  end
110
81
  end
111
- current_nodes.flatten!
112
- else
113
- current_nodes.map!{|n| n.is_a?(RequestNode) && n.request_method == method ? (n.catchall ||= router.request_node) : n}
114
82
  end
115
83
  end
116
- transplant_value
117
- current_nodes
118
84
  end
119
85
 
120
- protected
121
-
122
- attr_reader :router
123
-
124
- def transplant_value
125
- if @value && @request_node
126
- target_node = @request_node
127
- while target_node.request_method
128
- target_node = (target_node.catchall ||= router.request_node)
129
- end
130
- target_node.value ||= @value
131
- @value = nil
132
- end
86
+ def add_variable
87
+ @variable ||= Variable.new(@router)
133
88
  end
134
-
135
- def escape_val(val)
136
- val.is_a?(Array) ? val.each{|v| HttpRouter.uri_unescape!(v)} : HttpRouter.uri_unescape!(val)
137
- val
89
+
90
+ def add_glob
91
+ @glob ||= Glob.new(@router)
138
92
  end
139
93
 
140
- def find_on_parts(request, parts, action = :call, params = [])
141
- if parts and !parts.empty?
142
- find_on_parts(request, nil, :"#{action}_with_trailing_slash", params) if parts.size == 1 and parts.first == ''
143
- if @linear
144
- dupped_parts, dupped_params = nil, nil
145
- response = @linear.find do |(tester, node)|
146
- if tester.respond_to?(:matches?) and match = tester.matches?(parts)
147
- dupped_parts, dupped_params = parts.dup, params.dup
148
- dupped_params << escape_val(tester.consume(match, dupped_parts))
149
- node.find_on_parts(request, dupped_parts, action, dupped_params)
150
- elsif tester.respond_to?(:match) and match = tester.match(parts.whole_path) and match.begin(0) == 0
151
- dupped_parts, dupped_params = router.split(parts.whole_path[match[0].size, parts.whole_path.size]), params.dup
152
- node.find_on_parts(request, dupped_parts, action, dupped_params)
153
- else
154
- nil
94
+ def add_request(opts)
95
+ @request ||= Request.new(@router)
96
+ next_requests = [@request]
97
+ Request.request_methods.each do |method|
98
+ next_requests.map! do |next_request|
99
+ next_request.request_method = method
100
+ (opts[method].nil? ? [nil] : Array(opts[method])).map do |request_matcher|
101
+ case request_matcher
102
+ when nil
103
+ next_request.add_catchall
104
+ when String
105
+ next_request.add_lookup(request_matcher)
106
+ when Regexp
107
+ next_request.add_linear(request_matcher)
155
108
  end
156
109
  end
157
110
  end
158
- if match = @lookup && @lookup[parts.first]
159
- match.find_on_parts(request, parts[1, parts.size - 1], action, params)
160
- end
161
- if catchall
162
- dupped_parts, dupped_params = parts.dup, params.dup
163
- dupped_params << escape_val(catchall.variable.consume(nil, dupped_parts))
164
- catchall.find_on_parts(request, dupped_parts, action, dupped_params)
165
- end
111
+ next_requests.flatten!
166
112
  end
167
- request_node.find_on_request_methods(request, parts, action, params) if request_node
168
- arbitrary_node.find_on_arbitrary(request, parts, action, params) if arbitrary_node
169
- response = process_match(self, parts, params, request, action) if @value
170
- nil
113
+ next_requests
171
114
  end
172
115
 
173
- def process_match(node, parts, params, request, action)
174
- env = request.env
175
- case action
176
- when :call, :call_with_trailing_slash
177
- node.value.each do |path|
178
- response_struct = Response.new(path, params)
179
- previous_params = env['router.params']
180
- env['router'] = router
181
- env['router.params'] ||= {}
182
- env['router.params'].merge!(response_struct.params)
183
- env['router.response'] = response_struct
184
- env['SCRIPT_NAME'] ||= ''
185
- matched = if path.route.partially_match?
186
- env['PATH_INFO'] = "#{HttpRouter::Parts::SLASH}#{parts && parts.join(HttpRouter::Parts::SLASH)}"
187
- env['SCRIPT_NAME'] += request.path_info[0, request.path_info.size - env['PATH_INFO'].size]
188
- true
189
- elsif (parts and (action == :call_with_trailing_slash) and (router.ignore_trailing_slash? or (parts.size == 1 and parts.first == ''))) or parts.nil? || parts.empty?
190
- env["PATH_INFO"] = ''
191
- env["SCRIPT_NAME"] += request.path_info
192
- true
193
- else
194
- false
195
- end
196
- if matched
197
- response = path.route.dest.call(env)
198
- env['router.last_repsonse'] = response
199
- if response.first != 404 and response.first != 410
200
- throw :response, response
201
- end
202
- end
203
- end if node.value
204
- when :nocall, :nocall_with_trailing_slash
205
- responses = node.value.select do |path|
206
- matched = if path.route.partially_match?
207
- true
208
- elsif (parts and (action == :nocall_with_trailing_slash) and (router.ignore_trailing_slash? or (parts.size == 1 and parts.first == ''))) or parts.nil? || parts.empty?
209
- true
210
- else
211
- false
116
+ def add_arbitrary(blk, param_names)
117
+ @arbitrary ||= []
118
+ @arbitrary << Arbitrary.new(@router, blk, param_names)
119
+ @arbitrary.last
120
+ end
121
+
122
+ def add_match(regexp, matching_indicies = [0], priority = 0)
123
+ @linear ||= []
124
+ if priority != 0
125
+ @linear.each_with_index { |n, i|
126
+ if priority > (n.priority || 0)
127
+ @linear[i, 0] = Regex.new(@router, regexp, matching_indicies, priority)
128
+ return @linear[i]
212
129
  end
213
- end
214
- throw :response, responses.map{|r| Response.new(r, params)} unless responses.empty?
215
- else
216
- raise
130
+ }
217
131
  end
132
+ @linear << Regex.new(@router, regexp, matching_indicies, priority)
133
+ @linear.last
218
134
  end
219
135
 
220
- protected
221
- def create_linear
222
- @linear ||= []
223
- end
224
-
225
- def create_lookup
226
- @lookup ||= {}
227
- end
228
- end
229
-
230
- class ArbitraryNode < Node
231
- def find_on_arbitrary(request, parts, action, params)
232
- next_node = @linear && !@linear.empty? && @linear.find { |(procs, node)|
233
- params_hash = node.value ? node.value.first.hashify_params(params) : {}
234
- procs.all?{|p| p.call(request, params_hash)}
235
- }
236
- if next_node
237
- process_match(next_node.last, parts, params, request, action)
238
- elsif @catchall
239
- process_match(@catchall, parts, params, request, action)
240
- elsif @value
241
- process_match(self, parts, params, request, action)
242
- end
136
+ def add_spanning_match(regexp, matching_indicies = [0])
137
+ @linear ||= []
138
+ @linear << SpanningRegex.new(@router, regexp, matching_indicies)
139
+ @linear.last
243
140
  end
244
- end
245
141
 
246
- class RequestNode < Node
247
- RequestMethods = [:request_method, :host, :port, :scheme, :user_agent, :ip, :fullpath, :query_string].freeze
248
- attr_accessor :request_method
249
- def find_on_request_methods(request, parts, action, params)
250
- if @request_method
251
- request_value = request.send(request_method)
252
- if @linear && !@linear.empty? && match = @linear.find { |(regexp, node)| regexp === request_value }
253
- match.last.find_on_request_methods(request, parts, action, params)
254
- end
255
- @lookup[request_value].find_on_request_methods(request, parts, action, params) if @lookup and @lookup[request_value]
256
- @catchall.find_on_request_methods(request, parts, action, params) if @catchall
257
- end
258
- if @value
259
- process_match(self, parts, params, request, action)
260
- elsif arbitrary_node
261
- arbitrary_node.find_on_arbitrary(request, parts, action, params)
262
- end
142
+ def add_free_match(regexp)
143
+ @linear ||= []
144
+ @linear << FreeRegex.new(@router, regexp)
145
+ @linear.last
146
+ end
147
+
148
+ def add_destination(route)
149
+ @destination ||= []
150
+ @destination << route
151
+ end
152
+
153
+ def add_lookup(part)
154
+ @lookup ||= {}
155
+ @lookup[part] ||= Node.new(@router)
263
156
  end
157
+
158
+ def join_whole_path(request)
159
+ request.path.join('/')
160
+ end
161
+
264
162
  end
265
- end
163
+ end