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