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 +58 -210
- data/lib/http_router/node.rb +124 -226
- data/lib/http_router/node/arbitrary.rb +16 -0
- data/lib/http_router/node/free_regex.rb +19 -0
- data/lib/http_router/node/glob.rb +16 -0
- data/lib/http_router/node/glob_regex.rb +9 -0
- data/lib/http_router/node/regex.rb +26 -0
- data/lib/http_router/node/request.rb +44 -0
- data/lib/http_router/node/spanning_regex.rb +16 -0
- data/lib/http_router/node/variable.rb +11 -0
- data/lib/http_router/optional_compiler.rb +8 -15
- data/lib/http_router/path.rb +36 -49
- data/lib/http_router/regex_route.rb +20 -0
- data/lib/http_router/request.rb +26 -0
- data/lib/http_router/response.rb +13 -0
- data/lib/http_router/route.rb +121 -299
- data/lib/http_router/version.rb +1 -1
- data/test/helper.rb +11 -9
- data/test/rack/test_urlmap.rb +9 -9
- data/test/test_arbitrary.rb +18 -10
- data/test/test_misc.rb +28 -28
- data/test/test_mounting.rb +81 -81
- data/test/test_request.rb +6 -0
- data/test/test_trailing_slash.rb +0 -4
- data/test/test_variable.rb +1 -9
- metadata +16 -16
- data/lib/http_router/glob.rb +0 -20
- data/lib/http_router/interface/sinatra.rb +0 -149
- data/lib/http_router/parts.rb +0 -24
- data/lib/http_router/rack.rb +0 -18
- data/lib/http_router/rack/builder.rb +0 -60
- data/lib/http_router/rack/url_map.rb +0 -10
- data/lib/http_router/root.rb +0 -36
- data/lib/http_router/static.rb +0 -5
- data/lib/http_router/variable.rb +0 -30
- data/test/sinatra/recognize_spec.rb +0 -168
- data/test/sinatra/test_recognize.rb +0 -150
@@ -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,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
|
@@ -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
|
-
@
|
7
|
-
@
|
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
|
-
|
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
|
-
|
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
|
data/lib/http_router/path.rb
CHANGED
@@ -1,74 +1,61 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Path
|
3
|
-
attr_reader :
|
4
|
-
def initialize(route, path,
|
5
|
-
@route, @path, @
|
6
|
-
|
7
|
-
raise AmbiguousVariableException, "You have duplicate variable name present: #{
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
44
|
+
uri_escape!(path)
|
59
45
|
[path, options]
|
60
46
|
end
|
61
47
|
|
62
|
-
def
|
63
|
-
|
48
|
+
def original_path
|
49
|
+
@path
|
64
50
|
end
|
65
51
|
|
66
|
-
|
67
|
-
|
52
|
+
private
|
53
|
+
def raw_url(args,options)
|
54
|
+
raise UngeneratableRouteException
|
68
55
|
end
|
69
56
|
|
70
|
-
def
|
71
|
-
|
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
|
data/lib/http_router/route.rb
CHANGED
@@ -1,238 +1,84 @@
|
|
1
|
-
require 'strscan'
|
2
|
-
|
3
1
|
class HttpRouter
|
4
2
|
class Route
|
5
|
-
attr_reader :
|
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
|
-
@
|
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
|
-
@
|
22
|
-
|
23
|
-
@
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
106
|
-
|
107
|
-
request_method('HEAD')
|
35
|
+
def dest
|
36
|
+
@app
|
108
37
|
end
|
109
38
|
|
110
|
-
|
111
|
-
|
112
|
-
request_method('PUT')
|
39
|
+
def regex?
|
40
|
+
false
|
113
41
|
end
|
114
42
|
|
115
|
-
|
116
|
-
|
117
|
-
|
43
|
+
def to(dest = nil, &dest2)
|
44
|
+
@app = dest || dest2
|
45
|
+
compile
|
46
|
+
self
|
118
47
|
end
|
119
48
|
|
120
|
-
|
121
|
-
|
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
|
-
|
139
|
-
|
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
|
-
|
156
|
-
|
157
|
-
@name
|
59
|
+
def request_method(m)
|
60
|
+
((@conditions ||= {})[:request_method] ||= []) << m; self
|
158
61
|
end
|
159
62
|
|
160
|
-
|
161
|
-
|
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
|
-
|
180
|
-
|
181
|
-
@partially_match = match
|
182
|
-
self
|
67
|
+
def scheme(scheme)
|
68
|
+
((@conditions ||= {})[:scheme] ||= []) << scheme; self
|
183
69
|
end
|
184
70
|
|
185
|
-
|
186
|
-
|
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
|
-
|
199
|
-
|
200
|
-
guard_compiled
|
201
|
-
@arbitrary << (proc || block)
|
75
|
+
def matching(matchers)
|
76
|
+
@opts.merge!(matchers)
|
202
77
|
self
|
203
78
|
end
|
204
79
|
|
205
|
-
|
206
|
-
|
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
|
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
|
-
|
262
|
-
def
|
263
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
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.
|
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.
|
310
|
-
elsif path.
|
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
|
-
|
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
|
332
|
-
"
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
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
|
-
|
360
|
-
router.glob(v_name, @matches_with[v_name])
|
203
|
+
node.add_lookup(parts[0])
|
361
204
|
end
|
362
205
|
else
|
363
|
-
|
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
|
-
|
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
|
375
|
-
|
376
|
-
|
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
|
-
[
|
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
|