http_router 0.5.4 → 0.6.0

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/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