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.
@@ -0,0 +1,16 @@
1
+ class HttpRouter
2
+ class Node
3
+ class Arbitrary < Node
4
+ def initialize(router, blk, param_names)
5
+ @router, @blk, @param_names = router, blk, param_names
6
+ end
7
+
8
+ def [](request)
9
+ request = request.clone
10
+ request.continue = proc { |state| destination(request) if state }
11
+ params = @param_names.nil? ? {} : Hash[@param_names.zip(request.params)]
12
+ @blk.call(request, params)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ class HttpRouter
2
+ class Node
3
+ class FreeRegex < Node
4
+ attr_reader :matcher
5
+ def initialize(router, matcher)
6
+ @router, @matcher = router, matcher
7
+ end
8
+
9
+ def [](request)
10
+ whole_path = "/#{join_whole_path(request)}"
11
+ if match = @matcher.match(whole_path)
12
+ request = request.clone
13
+ request.extra_env['router.regex_match'] = match
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ class HttpRouter
2
+ class Node
3
+ class Glob < Node
4
+ def [](request)
5
+ request = request.clone
6
+ request.params << []
7
+ remaining_parts = request.path.dup
8
+ until remaining_parts.empty?
9
+ request.params[-1] << unescape(remaining_parts.shift)
10
+ request.path = remaining_parts
11
+ super(request)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class HttpRouter
2
+ class Node
3
+ class GlobRegex < SpanningRegex
4
+ def add_params(request)
5
+ @capturing_indicies.each { |idx| request.params << unescape(match[idx].split('/')) }
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ class HttpRouter
2
+ class Node
3
+ class Regex < Node
4
+ alias_method :node_lookup, :[]
5
+
6
+ attr_reader :matcher
7
+
8
+ def initialize(router, matcher, capturing_indicies, priority = 0)
9
+ @router, @matcher, @capturing_indicies, @priority = router, matcher, capturing_indicies, priority
10
+ end
11
+
12
+ def [](request)
13
+ if match = @matcher.match(request.path.first) and match.begin(0).zero?
14
+ request = request.clone
15
+ request.path.shift
16
+ add_params(request, match)
17
+ super(request)
18
+ end
19
+ end
20
+
21
+ def add_params(request, match)
22
+ @capturing_indicies.each { |idx| request.params << unescape(match[idx]) }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ class HttpRouter
2
+ class Node
3
+ class Request < Node
4
+ def self.request_methods
5
+ [:host, :request_method, :scheme, :user_agent]
6
+ end
7
+
8
+ def initialize(router)
9
+ @router, @linear, @catchall, @lookup = router, [], nil, {}
10
+ end
11
+
12
+ def request_method=(meth)
13
+ @request_method = meth == :method ? :request_method : meth
14
+ end
15
+
16
+ def add_lookup(val)
17
+ @lookup[val] ||= Request.new(@router)
18
+ end
19
+
20
+ def add_catchall
21
+ @catchall ||= Request.new(@router)
22
+ end
23
+
24
+ def add_linear(matcher)
25
+ next_node = Request.new(@router)
26
+ @linear << [matcher, next_node]
27
+ next_node
28
+ end
29
+
30
+ def [](request)
31
+ matched = false
32
+ if @request_method
33
+ val = request.rack_request.send(@request_method.to_sym)
34
+ @linear.each { |(matcher, node)| node[request] if matcher === val }
35
+ @lookup[val][request] if @lookup.key?(val)
36
+ @catchall[request] if @catchall
37
+ matched = @lookup.key?(val) || !@catchall.nil?
38
+ else
39
+ super(request)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ class HttpRouter
2
+ class Node
3
+ class SpanningRegex < Regex
4
+ def [](request)
5
+ whole_path = join_whole_path(request)
6
+ if match = @matcher.match(whole_path) and match.begin(0).zero?
7
+ request = request.clone
8
+ add_params(request, match)
9
+ remaining_path = whole_path[match[0].size + (whole_path[match[0].size] == ?/ ? 1 : 0), whole_path.size]
10
+ request.path = remaining_path.split('/')
11
+ node_lookup(request)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ class HttpRouter
2
+ class Node
3
+ class Variable < Node
4
+ def [](request)
5
+ request = request.clone
6
+ request.params << unescape(request.path.shift)
7
+ super(request)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,22 +2,14 @@ class HttpRouter
2
2
  class OptionalCompiler
3
3
  attr_reader :paths
4
4
  def initialize(path)
5
- @start_index = 0
6
- @end_index = 1
7
- @paths = [""]
8
- @chars = path.split('')
9
- while !@chars.empty?
5
+ @start_index, @end_index = 0, 1
6
+ @paths, @chars = [""], path.split('')
7
+ until @chars.empty?
10
8
  case @chars.first[0]
11
- when ?(
12
- @chars.shift and double_paths
13
- when ?)
14
- @chars.shift and half_paths
9
+ when ?( then @chars.shift and double_paths
10
+ when ?) then @chars.shift and half_paths
15
11
  when ?\\
16
- if @chars[1] == ?( || @chars[1] == ?)
17
- @chars.shift
18
- else
19
- add_to_current_set(@chars.shift)
20
- end
12
+ @chars[1] == ?( || @chars[1] == ?) ? @chars.shift : add_to_current_set(@chars.shift)
21
13
  add_to_current_set(@chars.shift)
22
14
  else
23
15
  add_to_current_set(@chars.shift)
@@ -25,7 +17,8 @@ class HttpRouter
25
17
  end
26
18
  @paths
27
19
  end
28
-
20
+
21
+ private
29
22
  def add_to_current_set(c)
30
23
  (@start_index...@end_index).each { |path_index| @paths[path_index] << c }
31
24
  end
@@ -1,74 +1,61 @@
1
1
  class HttpRouter
2
2
  class Path
3
- attr_reader :parts, :route, :splitting_indexes, :path
4
- def initialize(route, path, parts, splitting_indexes)
5
- @route, @path, @parts, @splitting_indexes = route, path, parts, splitting_indexes
6
- duplicate_variable_names = variable_names.dup.uniq!
7
- raise AmbiguousVariableException, "You have duplicate variable name present: #{duplicate_variable_names.join(', ')}" if duplicate_variable_names
8
- regex_parts = path.split(/([:\*][a-zA-Z0-9_]+)/)
9
- @path_validation_regex, code = '', ''
10
- regex_parts.each_with_index{ |part, index|
11
- new_part = case part[0]
12
- when ?:, ?*
13
- if index != 0 && regex_parts[index - 1][-1] == ?\\
3
+ attr_reader :route, :param_names
4
+ def initialize(route, path, param_names = [])
5
+ @route, @path, @param_names, @static = route, path, param_names, param_names.empty?
6
+ duplicate_param_names = param_names.dup.uniq!
7
+ raise AmbiguousVariableException, "You have duplicate variable name present: #{duplicate_param_names.join(', ')}" if duplicate_param_names
8
+ if path.respond_to?(:split)
9
+ regex_parts = path.split(/([:\*][a-zA-Z0-9_]+)/)
10
+ @path_validation_regex, code = '', ''
11
+ regex_parts.each_with_index{ |part, index|
12
+ new_part = case part[0]
13
+ when ?:, ?*
14
+ if index != 0 && regex_parts[index - 1][-1] == ?\\
15
+ @path_validation_regex << Regexp.quote(part)
16
+ code << part
17
+ else
18
+ @path_validation_regex << (route.matches_with[part[1, part.size].to_sym] || '.*?').to_s
19
+ code << "\#{args.shift || (options && options.delete(:#{part[1, part.size]})) || raise(MissingParameterException, \"missing parameter :#{part[1, part.size]}\")}"
20
+ end
21
+ else
14
22
  @path_validation_regex << Regexp.quote(part)
15
23
  code << part
16
- else
17
- @path_validation_regex << (route.matches_with[part[1, part.size].to_sym] || '.*?').to_s
18
- code << "\#{args.shift || (options && options.delete(:#{part[1, part.size]})) || raise(MissingParameterException, \"missing parameter :#{part[1, part.size]}\")}"
19
24
  end
20
- else
21
- @path_validation_regex << Regexp.quote(part)
22
- code << part
25
+ new_part
26
+ }
27
+ @path_validation_regex = Regexp.new("^#{@path_validation_regex}$")
28
+ instance_eval "
29
+ def raw_url(args,options)
30
+ \"#{code}\"
23
31
  end
24
- new_part
25
- }
26
- @path_validation_regex = Regexp.new("^#{@path_validation_regex}$")
27
- instance_eval "
28
- def raw_url(args,options)
29
- \"#{code}\"
32
+ ", __FILE__, __LINE__
30
33
  end
31
- "
32
34
  end
33
35
 
34
36
  def hashify_params(params)
35
- !static? && params ? variable_names.zip(params).inject({}) { |h, (k,v)| h[k] = v; h } : {}
36
- end
37
-
38
- def ===(other_path)
39
- return false if @parts.size != other_path.parts.size
40
- @parts.each_with_index {|p,i|
41
- return unless compare_parts(p, other_path.parts[i])
42
- }
43
- true
44
- end
45
-
46
- def compare_parts(p1, p2)
47
- case p1
48
- when Glob then p2.is_a?(Glob)
49
- when Variable then p2.is_a?(Variable)
50
- else p1 == p2
51
- end
37
+ !@static && params ? param_names.zip(params).inject({}) { |h, (k,v)| h[k] = v; h } : {}
52
38
  end
53
39
 
54
40
  def url(args, options)
55
41
  path = raw_url(args, options)
56
42
  raise InvalidRouteException if path !~ @path_validation_regex
57
43
  raise TooManyParametersException unless args.empty?
58
- HttpRouter.uri_escape!(path)
44
+ uri_escape!(path)
59
45
  [path, options]
60
46
  end
61
47
 
62
- def static?
63
- variables.empty?
48
+ def original_path
49
+ @path
64
50
  end
65
51
 
66
- def variables
67
- @variables ||= @parts.select{|p| p.is_a?(Variable)}
52
+ private
53
+ def raw_url(args,options)
54
+ raise UngeneratableRouteException
68
55
  end
69
56
 
70
- def variable_names
71
- @variable_names ||= variables.map{|v| v.name}
57
+ def uri_escape!(s)
58
+ s.to_s.gsub!(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) { "%#{$1.unpack('H2'*$1.size).join('%').upcase}" }
72
59
  end
73
60
  end
74
- end
61
+ end
@@ -0,0 +1,20 @@
1
+ class HttpRouter
2
+ class RegexRoute < Route
3
+ def initialize(router, path, opts = {})
4
+ @router, @path, @opts = router, path, opts
5
+ end
6
+
7
+ def compile
8
+ add_non_path_to_tree(@router.root.add_free_match(path), path, [])
9
+ @compiled = true
10
+ end
11
+
12
+ def match_partially?
13
+ true
14
+ end
15
+
16
+ def regex?
17
+ true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ class HttpRouter
2
+ class Request
3
+ attr_reader :perform_call
4
+ attr_accessor :path, :params, :rack_request, :extra_env, :continue
5
+ alias_method :rack, :rack_request
6
+ def initialize(path, rack_request, perform_call)
7
+ @rack_request, @perform_call = rack_request, perform_call
8
+ @path = (path[0] == ?/ ? path[1, path.size] : path).split(/\//)
9
+ @path << '' if path.size > 1 && path[-1] == ?/
10
+ @extra_env = {}
11
+ @params = []
12
+ end
13
+
14
+ def to_s
15
+ "request path, #{path.inspect}"
16
+ end
17
+
18
+ def clone
19
+ dup_obj = super
20
+ dup_obj.path = path.dup
21
+ dup_obj.params = params.dup
22
+ dup_obj.extra_env = extra_env.dup
23
+ dup_obj
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ class HttpRouter
2
+ class Response < Struct.new(:request, :path)
3
+ attr_reader :params
4
+ def initialize(request, path)
5
+ super(request, path)
6
+ @params = path.hashify_params(request.params)
7
+ end
8
+
9
+ def param_values
10
+ request.params
11
+ end
12
+ end
13
+ end
@@ -1,238 +1,84 @@
1
- require 'strscan'
2
-
3
1
  class HttpRouter
4
2
  class Route
5
- attr_reader :dest, :paths, :path, :matches_with, :original_path, :regex
6
- attr_accessor :trailing_slash_ignore, :partially_match, :default_values
3
+ attr_reader :default_values, :matches_with, :router, :path, :conditions
7
4
 
8
- def initialize(router, path)
5
+ def initialize(router, path, opts = {})
9
6
  @router = router
10
- @matches_with = {}
11
- @arbitrary = []
12
- @conditions = {}
13
- @default_values = {}
14
- case path
15
- when Regexp
16
- @regex = path
17
- path = '/*'
18
- match_path(@regex)
19
- end
7
+ @original_path = path
20
8
  @path = path
21
- @original_path = path.dup
22
- path[0,0] = '/' unless path[0] == ?/
23
- @partially_match = extract_partial_match(path)
24
- @trailing_slash_ignore = extract_trailing_slash(path)
25
- end
26
-
27
- def method_missing(method, *args, &block)
28
- if RequestNode::RequestMethods.include?(method)
29
- condition(method => args)
30
- else
31
- super
9
+ @opts = opts
10
+ @arbitrary = opts[:arbitrary] || opts[:__arbitrary__]
11
+ @conditions = opts[:conditions] || opts[:__conditions__] || {}
12
+ name(opts.delete(:name)) if opts.key?(:name)
13
+ @matches_with = {}
14
+ @default_values = opts[:default_values] || {}
15
+ if @original_path[-1] == ?*
16
+ @match_partially = true
17
+ path.slice!(-1)
32
18
  end
19
+ @paths = OptionalCompiler.new(path).paths
33
20
  end
34
21
 
35
- def regex?
36
- !@regex.nil?
37
- end
38
-
39
- def to_s
40
- "#{@original_path} conditions: #{@conditions.inspect} default_values: #{@default_values.inspect} name: #{named.inspect}"
41
- end
42
-
43
- # Returns the options used to create this route.
44
22
  def as_options
45
23
  {:matching => @matches_with, :conditions => @conditions, :default_values => @default_values, :name => @name, :partial => @partially_match, :arbitrary => @arbitrary}
46
24
  end
47
25
 
48
- # Creates a deep uncompiled copy of this route.
49
- def clone(new_router)
50
- Route.new(new_router, @original_path.dup).with_options(as_options)
51
- end
52
-
53
- # Uses an option hash to apply conditions to a Route.
54
- # The following keys are supported.
55
- # *name -- Maps to #name method.
56
- # *matching -- Maps to #matching method.
57
- # *conditions -- Maps to #conditions method.
58
- # *default_value -- Maps to #default_value method.
59
- def with_options(options)
60
- if options
61
- name(options.delete(:name)) if options[:name]
62
- matching(options.delete(:matching)) if options[:matching]
63
- condition(options.delete(:conditions)) if options[:conditions]
64
- default(options.delete(:default_values)) if options[:default_values]
65
- partial(options.delete(:partial)) if options[:partial]
66
- Array(options.delete(:arbitrary)).each{|a| arbitrary(&a)} if options[:arbitrary]
67
- matching(options)
68
- end
69
- self
70
- end
71
-
72
- # Sets the name of the route
73
- # Returns +self+.
74
- def name(name)
75
- @name = name
76
- router.named_routes[@name] = self if @name && compiled?
26
+ def partial(match_partially = true)
27
+ @match_partially = match_partially
77
28
  self
78
29
  end
79
30
 
80
- # Sets a default value for the route
81
- # Returns +self+.
82
- #
83
- # Example
84
- # router = HttpRouter.new
85
- # router.add("/:test").default(:test => 'foo').name(:test).compile
86
- # router.url(:test)
87
- # # ==> "/foo"
88
- # router.url(:test, 'override')
89
- # # ==> "/override"
90
- def default(v)
91
- @default_values.merge!(v)
92
- self
93
- end
94
-
95
- # Causes this route to recognize the GET request method. Returns +self+.
96
- def get
97
- request_method('GET')
98
- end
99
-
100
- # Causes this route to recognize the POST request method. Returns +self+.
101
- def post
102
- request_method('POST')
31
+ def match_partially?
32
+ @match_partially
103
33
  end
104
34
 
105
- # Causes this route to recognize the HEAD request method. Returns +self+.
106
- def head
107
- request_method('HEAD')
35
+ def dest
36
+ @app
108
37
  end
109
38
 
110
- # Causes this route to recognize the PUT request method. Returns +self+.
111
- def put
112
- request_method('PUT')
39
+ def regex?
40
+ false
113
41
  end
114
42
 
115
- # Causes this route to recognize the DELETE request method. Returns +self+.
116
- def delete
117
- request_method('DELETE')
43
+ def to(dest = nil, &dest2)
44
+ @app = dest || dest2
45
+ compile
46
+ self
118
47
  end
119
48
 
120
- # Sets a request condition for the route
121
- # Returns +self+.
122
- #
123
- # Example
124
- # router = HttpRouter.new
125
- # router.add("/:test").condition(:host => 'www.example.org').name(:test).compile
126
- def condition(conditions)
127
- guard_compiled
128
- conditions.each do |k,v|
129
- @conditions.key?(k) ?
130
- @conditions[k] << v :
131
- @conditions[k] = Array(v)
132
- @conditions[k].flatten!
133
- end
134
- self
49
+ def compiled?
50
+ @compiled
135
51
  end
136
- alias_method :conditions, :condition
137
52
 
138
- # Sets a regex matcher for a variable
139
- # Returns +self+.
140
- #
141
- # Example
142
- # router = HttpRouter.new
143
- # router.add("/:test").matching(:test => /\d+/).name(:test).compile
144
- def matching(match)
145
- guard_compiled
146
- match.each do |var_name, matchers|
147
- matchers = Array(matchers)
148
- matchers.each do |m|
149
- @matches_with.key?(var_name) ? raise : @matches_with[var_name] = m
150
- end
151
- end
53
+ def name(n)
54
+ @name = n
55
+ @router.named_routes[n] = self
152
56
  self
153
57
  end
154
58
 
155
- # Returns the current route's name.
156
- def named
157
- @name
59
+ def request_method(m)
60
+ ((@conditions ||= {})[:request_method] ||= []) << m; self
158
61
  end
159
62
 
160
- # Sets the destination of the route. Receives either a block, or a proc.
161
- # Returns +self+.
162
- #
163
- # Example
164
- # router = HttpRouter.new
165
- # router.add("/:test").matching(:test => /\d+/).name(:test).to(proc{ |env| Rack::Response.new("hi there").finish })
166
- # Or
167
- # router.add("/:test").matching(:test => /\d+/).name(:test).to { |env| Rack::Response.new("hi there").finish }
168
- def to(dest = nil, &block)
169
- compile
170
- @dest = dest || block
171
- if @dest.respond_to?(:url_mount=)
172
- urlmount = UrlMount.new(@original_path, @default_values)
173
- urlmount.url_mount = router.url_mount if router.url_mount
174
- @dest.url_mount = urlmount
175
- end
176
- self
63
+ def host(host)
64
+ ((@conditions ||= {})[:host] ||= []) << host; self
177
65
  end
178
66
 
179
- # Sets partial matching on this route. Defaults to +true+. Returns +self+.
180
- def partial(match = true)
181
- @partially_match = match
182
- self
67
+ def scheme(scheme)
68
+ ((@conditions ||= {})[:scheme] ||= []) << scheme; self
183
69
  end
184
70
 
185
- # Convenient regexp matching on an entire path. Returns +self+
186
- def match_path(matcher)
187
- arbitrary{|env, params, dest|
188
- match = matcher.match(env.path_info)
189
- if !match.nil? and match.begin(0) == 0 and match[0].size == env.path_info.size
190
- env['router.regex_match'] = match
191
- true
192
- else
193
- false
194
- end
195
- }
71
+ def user_agent(user_agent)
72
+ ((@conditions ||= {})[:user_agent] ||= []) << user_agent; self
196
73
  end
197
74
 
198
- # Adds an arbitrary proc matcher to a Route. Receives either a block, or a proc. The proc will receive a Rack::Request object, a Hash of the params, and the destination for this route. The proc must return true if the Route is matched. Returns +self+.
199
- def arbitrary(proc = nil, &block)
200
- guard_compiled
201
- @arbitrary << (proc || block)
75
+ def matching(matchers)
76
+ @opts.merge!(matchers)
202
77
  self
203
78
  end
204
79
 
205
- # Compile state for route. Returns +true+ or +false+.
206
- def compiled?
207
- !@paths.nil?
208
- end
209
-
210
- # Compiles the route and inserts it into the tree. This is called automatically when you add a destination via #to to the route. Until a route
211
- # is compiled, it will not be recognized.
212
- def compile
213
- if @paths.nil?
214
- router.named_routes[@name] = self if @name
215
- @paths = compile_paths
216
- @paths.each_with_index do |p1, i|
217
- @paths[i+1, @paths.size].each do |p2|
218
- raise AmbiguousRouteException if p1 === p2
219
- end
220
- end
221
- @paths.each do |path|
222
- current_node = router.root.add_path(path)
223
- working_set = current_node.add_request_methods(@conditions)
224
- working_set.map!{|node| node.add_arbitrary(@arbitrary)}
225
- working_set.each do |current_node|
226
- case current_node.value
227
- when nil
228
- current_node.value = [path]
229
- else
230
- current_node.value << path
231
- end
232
- end
233
- path.variable_names.each{|vn| router.variable_names << vn} unless path.static?
234
- end
235
- end
80
+ def default(defaults)
81
+ (@default_values ||= {}).merge!(defaults)
236
82
  self
237
83
  end
238
84
 
@@ -253,25 +99,31 @@ class HttpRouter
253
99
  if File.directory?(root)
254
100
  partial.to ::Rack::File.new(root)
255
101
  else
256
- to proc{|env| env['PATH_INFO'] = File.basename(root); ::Rack::File.new(File.dirname(root)).call(env)}
102
+ to {|env| env['PATH_INFO'] = File.basename(root); ::Rack::File.new(File.dirname(root)).call(env) }
257
103
  end
258
104
  self
259
105
  end
260
106
 
261
- # The current state of trailing / ignoring on this route. Returns +true+ or +false+.
262
- def trailing_slash_ignore?
263
- @trailing_slash_ignore
107
+ def post; request_method('POST'); end
108
+ def get; request_method('GET'); end
109
+ def put; request_method('PUT'); end
110
+ def delete; request_method('DELETE'); end
111
+ def head; request_method('HEAD'); end
112
+
113
+ def arbitrary(blk = nil, &blk2)
114
+ arbitrary_with_continue { |req, params|
115
+ req.continue[(blk || blk2)[req, params]]
116
+ }
264
117
  end
265
118
 
266
- # The current state of partial matching on this route. Returns +true+ or +false+.
267
- def partially_match?
268
- @partially_match
119
+ def arbitrary_with_continue(blk = nil, &blk2)
120
+ (@arbitrary ||= []) << (blk || blk2)
121
+ self
269
122
  end
270
123
 
271
- # Generates a URL for this route. See HttpRouter#url for how the arguments for this are structured.
272
124
  def url(*args)
273
125
  result, extra_params = url_with_params(*args)
274
- router.append_querystring(result, extra_params)
126
+ @router.append_querystring(result, extra_params)
275
127
  end
276
128
 
277
129
  def url_with_params(*args)
@@ -285,8 +137,9 @@ class HttpRouter
285
137
  end
286
138
  raise UngeneratableRouteException unless path
287
139
  result, params = path.url(args, options)
288
- mount_point = router.url_mount && router.url_mount.url(options)
289
- mount_point ? [File.join(mount_point, result), params] : [result, params]
140
+ #mount_point = router.url_mount && router.url_mount.url(options)
141
+ #mount_point ? [File.join(mount_point, result), params] : [result, params]
142
+ [result, params]
290
143
  end
291
144
 
292
145
  def significant_variable_names
@@ -301,13 +154,13 @@ class HttpRouter
301
154
  significant_keys = other_hash && significant_variable_names & other_hash.keys
302
155
  @paths.find { |path|
303
156
  var_count = significant_keys ? params.size + significant_keys.size : params.size
304
- path.variables.size == var_count
157
+ path.param_names.size == var_count
305
158
  }
306
159
  else
307
160
  @paths.reverse_each do |path|
308
161
  if params && !params.empty?
309
- return path if (path.variable_names & params.keys).size == path.variable_names.size
310
- elsif path.variable_names.empty?
162
+ return path if (path.param_names & params.keys).size == path.param_names.size
163
+ elsif path.param_names.empty?
311
164
  return path
312
165
  end
313
166
  end
@@ -316,108 +169,77 @@ class HttpRouter
316
169
  end
317
170
  end
318
171
 
319
- private
320
-
321
- attr_reader :router
322
-
323
- def extract_partial_match(path)
324
- path[-1] == ?* && path.slice!(-1)
325
- end
326
-
327
- def extract_trailing_slash(path)
328
- path[-2, 2] == '/?' && path.slice!(-2, 2)
172
+ def named
173
+ @name
329
174
  end
330
175
 
331
- def anonymous_variable(pos)
332
- "$#{pos}".to_sym
176
+ def to_s
177
+ "#<HttpRouter:Route #{object_id} @original_path=#{@original_path.inspect} @conditions=#{@conditions.inspect} @arbitrary=#{@arbitrary.inspect}>"
333
178
  end
334
179
 
335
- def compile_paths
336
- paths = HttpRouter::OptionalCompiler.new(@path).paths
337
- paths.map do |path|
338
- splitting_indexes = []
339
- variable_position = 0
340
- counter = 0
341
- original_path = path.dup
342
- split_path = router.split(path)
343
- position = 0
344
- new_path = split_path.map do |part|
345
- processed_parts = case part
346
- when /^:([a-zA-Z_0-9]*)$/
347
- v_name = $1.empty? ? anonymous_variable(counter += 1) : $1.to_sym
348
- router.variable(v_name, @matches_with[v_name])
349
- when /^\*([a-zA-Z_0-9]*)$/
350
- if position != split_path.size - 1
351
- v_name = $1.empty? ? anonymous_variable(counter += 1) : $1.to_sym
352
- splitting_indexes << variable_position
353
- remaining_path_parts = split_path[position + 1, split_path.size]
354
- look_ahead_variable = remaining_path_parts.find{|p| p[0] == ?: || p[0] == ?*}
355
- remaining_matcher = split_path[position + 1, look_ahead_variable ? remaining_path_parts.index(look_ahead_variable) : split_path.size].join('/')
356
- remaining_path_parts.index(look_ahead_variable) if look_ahead_variable
357
- router.variable(v_name, /^(#{@matches_with[v_name] || '[^\/]*?'}\/)+(?=#{Regexp.quote(remaining_matcher)})/)
180
+ private
181
+ def compile
182
+ return if @compiled
183
+ @paths.map! do |path|
184
+ param_names = []
185
+ node = @router.root
186
+ path.split(/\//).each do |part|
187
+ next if part == ''
188
+ parts = part.scan(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)
189
+ if parts.size == 1
190
+ name = part[1, part.size]
191
+ node = case parts[0][0]
192
+ when ?\\
193
+ node.add_lookup(parts[0][1].chr)
194
+ when ?:
195
+ param_names << name.to_sym
196
+ matches_with[name.to_sym] = @opts[name.to_sym]
197
+ @opts[name.to_sym] ? node.add_spanning_match(@opts.delete(name.to_sym)) : node.add_variable
198
+ when ?*
199
+ param_names << name.to_sym
200
+ matches_with[name.to_sym] = @opts[name.to_sym]
201
+ @opts[name.to_sym] ? node.add_spanning_match(@opts.delete(name.to_sym)) : node.add_glob
358
202
  else
359
- v_name = $1.empty? ? anonymous_variable(counter += 1) : $1.to_sym
360
- router.glob(v_name, @matches_with[v_name])
203
+ node.add_lookup(parts[0])
361
204
  end
362
205
  else
363
- generate_interstitial_parts(part)
206
+ captures = 0
207
+ priority = 0
208
+ regex = parts.inject('') do |reg, part|
209
+ reg << case part[0]
210
+ when ?\\
211
+ Regexp.quote(part[1].chr)
212
+ when ?:
213
+ captures += 1
214
+ name = part[1, part.size].to_sym
215
+ param_names << name
216
+ matches_with[name] = @opts[name]
217
+ "(#{(@opts[name] || '.*?')})"
218
+ else
219
+ priority += part.size
220
+ Regexp.quote(part)
221
+ end
222
+ end
223
+ capturing_indicies = []
224
+ captures.times {|i| capturing_indicies << i + 1}
225
+ node = node.add_match(Regexp.new("#{regex}$"), capturing_indicies, priority)
364
226
  end
365
- variable_position += Array(processed_parts).select{|part| part.is_a?(Variable)}.size
366
- position += 1
367
- processed_parts
368
227
  end
369
- new_path.flatten!
370
- Path.new(self, original_path, new_path, splitting_indexes.empty? ? nil : splitting_indexes)
228
+ add_non_path_to_tree(node, path, param_names)
371
229
  end
230
+ @compiled = true
372
231
  end
373
232
 
374
- def generate_interstitial_parts(part)
375
- part_segments = []
376
- scanner = StringScanner.new(part)
377
- while !scanner.eos?
378
- if scanner.scan(/\\[:\*]/)
379
- part_segments << [:static, ''] if part_segments.last.nil? or part_segments.last.first == :variable
380
- part_segments.last.last << scanner.matched[-1].chr
381
- elsif scanner.scan(/\\/)
382
- # do nothing
383
- elsif scanner.scan(/:[a-zA_Z0-9_]+/)
384
- part_segments << [:variable, scanner.matched]
385
- elsif scanner.scan(/./)
386
- part_segments << [:static, ''] if part_segments.last.nil? or part_segments.last.first == :variable
387
- part_segments.last.last << scanner.matched
388
- end
389
- end
390
- priority = 0
391
- if part_segments.size > 1
392
- index = 0
393
- segs = part_segments.map do |(type, seg)|
394
- new_seg = if type == :variable
395
- next_index = index + 1
396
- v_name = seg[1, seg.size].to_sym
397
- matcher = @matches_with[v_name]
398
- scan_regex = if next_index == part_segments.size
399
- matcher || /^[^\/]+/
400
- else
401
- /^#{matcher || '[^\/]+?'}(?=#{Regexp.quote(part_segments[next_index].last)})/
402
- end
403
- router.variable(v_name, scan_regex)
404
- else
405
- priority += seg.size
406
- Static.new("^#{Regexp.quote(seg)}")
407
- end
408
- index += 1
409
- new_seg
410
- end
411
- segs.each {|seg| seg.priority = priority}
412
- segs
233
+ def add_non_path_to_tree(node, path, names)
234
+ nodes = if @conditions && !@conditions.empty?
235
+ node.add_request(@conditions)
413
236
  else
414
- [part_segments.last.last]
237
+ [node]
415
238
  end
239
+ @arbitrary.each{|a| nodes.map!{|n| n.add_arbitrary(a, names)} } if @arbitrary
240
+ path_obj = Path.new(self, path, names)
241
+ nodes.each{|n| n.add_destination(path_obj)}
242
+ path_obj
416
243
  end
417
-
418
- def guard_compiled
419
- raise AlreadyCompiledException if compiled?
420
- end
421
-
422
244
  end
423
- end
245
+ end