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.
@@ -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