http_router 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +11 -5
- data/VERSION +1 -1
- data/lib/http_router/node.rb +101 -55
- data/lib/http_router/path.rb +19 -2
- data/lib/http_router/response.rb +5 -0
- data/lib/http_router/root.rb +44 -35
- data/lib/http_router/route.rb +194 -26
- data/lib/http_router/sinatra.rb +5 -4
- data/lib/http_router.rb +32 -150
- data/spec/generate_spec.rb +17 -7
- data/spec/rack/dispatch_spec.rb +4 -4
- data/spec/rack/generate_spec.rb +3 -3
- data/spec/rack/route_spec.rb +2 -2
- data/spec/recognize_spec.rb +31 -30
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
== Introduction
|
4
4
|
|
5
|
-
When I wrote Usher, I made a few
|
5
|
+
When I wrote Usher, I made a few compromises in design that I wasn't totally happy with. More and more features got added to it, and eventually, it became harder to maintain. I took a few moments to work in Node.js, and wrote a router there called Sherpa, which I was happier with. But I felt that by losing more abstraction, and tackling just the problem of HTTP routing, I could come up with something even better.
|
6
|
+
|
7
|
+
== Warning
|
8
|
+
|
9
|
+
This is very new code. Lots of stuff probably doesn't work right. I will likely never support all the features I had in Usher. Documentation is super-sparse.
|
6
10
|
|
7
11
|
== Features
|
8
12
|
|
@@ -38,14 +42,16 @@ e.g.
|
|
38
42
|
|
39
43
|
r = HttpRouter.new
|
40
44
|
r.add('/test/:variable(.:format)').name(:my_test_path).to {|env| [200, {}, "Hey dude #{env['router.params'][:variable]}"]}
|
41
|
-
r.add('/test').
|
42
|
-
r.add('/static').
|
45
|
+
r.add('/test').redirect("http://www.google.com/")
|
46
|
+
r.add('/static').static('/my_file_system')
|
43
47
|
|
44
|
-
As well, you can support regex matching and request conditions. To add a regex match, use <tt
|
45
|
-
To match on a request condition you can use <tt
|
48
|
+
As well, you can support regex matching and request conditions. To add a regex match, use <tt>matching(:id => /\d+/)</tt>.
|
49
|
+
To match on a request condition you can use <tt>condition(:request_method => %w(POST HEAD))</tt> or more succinctly <tt>request_method('POST', 'HEAD')</tt>.
|
46
50
|
|
47
51
|
There are convenience methods HttpRouter#get, HttpRouter#post, etc for each request method.
|
48
52
|
|
53
|
+
Routes will not be recognized unless <tt>#to</tt> has been called on it.
|
54
|
+
|
49
55
|
=== <tt>#url(name or route, *args)</tt>
|
50
56
|
|
51
57
|
Generates a route. The args can either be a hash, a list, or a mix of both.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/lib/http_router/node.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
|
-
attr_accessor :value, :variable, :catchall
|
4
|
-
attr_reader :linear, :lookup
|
3
|
+
attr_accessor :value, :variable, :catchall
|
4
|
+
attr_reader :linear, :lookup, :request_node, :extension_node
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
reset!
|
@@ -32,71 +32,117 @@ class HttpRouter
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
def add_extension(ext)
|
36
|
+
@extension_node ||= Node.new
|
37
|
+
@extension_node.add(ext)
|
38
|
+
end
|
39
|
+
|
35
40
|
def add_request_methods(options)
|
36
|
-
if options
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
41
|
+
if !options.empty?
|
42
|
+
generate_request_method_tree(options)
|
43
|
+
elsif @request_node
|
44
|
+
current_node = @request_node
|
45
|
+
while current_node.request_method
|
46
|
+
current_node = (current_node.catchall ||= RequestNode.new)
|
47
|
+
end
|
48
|
+
[current_node]
|
49
|
+
else
|
50
|
+
[self]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def transplant_value
|
57
|
+
if @value
|
58
|
+
target_node = @request_node
|
59
|
+
while target_node.request_method
|
60
|
+
target_node = (target_node.catchall ||= RequestNode.new)
|
61
|
+
end
|
62
|
+
target_node.value = @value
|
63
|
+
@value = nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_request_method_tree(request_options)
|
68
|
+
raise if (request_options.keys & RequestNode::RequestMethods).size != request_options.size
|
69
|
+
|
70
|
+
|
71
|
+
current_nodes = [@request_node ||= RequestNode.new]
|
72
|
+
RequestNode::RequestMethods.each do |method|
|
73
|
+
current_nodes.each_with_index do |current_node, current_node_index|
|
74
|
+
if request_options[method] #we care about the method
|
75
|
+
if current_node # and we have to pay attention to what currently is there.
|
76
|
+
unless current_node.request_method
|
77
|
+
current_node.request_method = method
|
78
|
+
end
|
79
|
+
|
80
|
+
case RequestNode::RequestMethods.index(method) <=> RequestNode::RequestMethods.index(current_node.request_method)
|
81
|
+
when 0 #use this node
|
82
|
+
if request_options[method].is_a?(Regexp)
|
83
|
+
current_node = RequestNode.new
|
84
|
+
current_node.linear << [request_options[method], current_node]
|
85
|
+
elsif request_options[method].is_a?(Array)
|
86
|
+
current_nodes[current_node_index] = request_options[method].map{|val| current_node.lookup[val] ||= RequestNode.new}
|
87
|
+
else
|
88
|
+
current_nodes[current_node_index] = (current_node.lookup[request_options[method]] ||= RequestNode.new)
|
66
89
|
end
|
67
|
-
|
68
|
-
current_nodes[current_node_index] = RequestNode.new
|
90
|
+
when 1 #this node is farther ahead
|
91
|
+
current_nodes[current_node_index] = (current_node.catchall ||= RequestNode.new)
|
92
|
+
redo
|
93
|
+
when -1 #this method is more important than the current node
|
94
|
+
new_node = RequestNode.new
|
95
|
+
new_node.request_method = method
|
96
|
+
new_node.catchall = current_node
|
97
|
+
current_nodes[current_node_index] = new_node
|
69
98
|
redo
|
70
99
|
end
|
71
|
-
elsif !current_node
|
72
|
-
@request_node = RequestNode.new
|
73
|
-
current_nodes[current_node_index] = @request_node
|
74
|
-
redo
|
75
100
|
else
|
76
|
-
|
101
|
+
current_nodes[current_node_index] = RequestNode.new
|
102
|
+
redo
|
77
103
|
end
|
104
|
+
elsif !current_node
|
105
|
+
@request_node = RequestNode.new
|
106
|
+
current_nodes[current_node_index] = @request_node
|
107
|
+
redo
|
108
|
+
else
|
109
|
+
current_node.catchall ||= RequestNode.new
|
78
110
|
end
|
79
|
-
current_nodes.flatten!
|
80
111
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
112
|
+
current_nodes.flatten!
|
113
|
+
end
|
114
|
+
transplant_value
|
115
|
+
current_nodes
|
116
|
+
end
|
117
|
+
|
118
|
+
def find_on_request_methods(request)
|
119
|
+
current_node = self
|
120
|
+
if current_node && current_node.request_node
|
121
|
+
current_node = current_node.request_node
|
122
|
+
while current_node
|
123
|
+
previous_node = current_node
|
124
|
+
break if current_node.nil? || current_node.is_a?(RoutingError) || current_node.value
|
125
|
+
request_value = request.send(current_node.request_method)
|
126
|
+
unless current_node.linear.empty?
|
127
|
+
next_node = current_node.linear.find do |(regexp, node)|
|
128
|
+
regexp === request_value
|
129
|
+
end
|
130
|
+
if next_node
|
131
|
+
current_node = next_node.last
|
132
|
+
next
|
133
|
+
end
|
134
|
+
end
|
135
|
+
current_node = current_node.lookup[request_value] || current_node.catchall
|
136
|
+
if current_node.nil?
|
137
|
+
current_node = previous_node.request_method == :request_method ? RoutingError.new(405, {"Allow" => previous_node.lookup.keys.join(", ")}) : nil
|
138
|
+
else
|
139
|
+
current_node
|
85
140
|
end
|
86
|
-
target_node.value = @value
|
87
|
-
@value = nil
|
88
|
-
end
|
89
|
-
current_nodes
|
90
|
-
elsif @request_node
|
91
|
-
current_node = @request_node
|
92
|
-
while current_node.request_method
|
93
|
-
current_node = (current_node.catchall ||= RequestNode.new)
|
94
141
|
end
|
95
|
-
[current_node]
|
96
|
-
else
|
97
|
-
[self]
|
98
142
|
end
|
143
|
+
current_node
|
99
144
|
end
|
145
|
+
|
100
146
|
end
|
101
147
|
|
102
148
|
class RequestNode < Node
|
data/lib/http_router/path.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'cgi'
|
1
2
|
class HttpRouter
|
2
3
|
class Path
|
3
4
|
attr_reader :parts, :extension
|
4
5
|
attr_accessor :route
|
5
6
|
def initialize(path, parts, extension)
|
6
7
|
@path, @parts, @extension = path, parts, extension
|
7
|
-
@eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options
|
8
|
+
@eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options.delete(:#{$1})) || raise(MissingParameterException.new(\"missing parameter #{$1}\"))}" }
|
8
9
|
instance_eval "
|
9
10
|
def raw_url(args,options)
|
10
11
|
\"#{@eval_path}\"
|
@@ -16,9 +17,25 @@ class HttpRouter
|
|
16
17
|
path = raw_url(args, options)
|
17
18
|
raise TooManyParametersException.new unless args.empty?
|
18
19
|
Rack::Utils.uri_escape!(path)
|
20
|
+
generate_querystring(path, options)
|
19
21
|
path
|
20
22
|
end
|
21
23
|
|
24
|
+
def generate_querystring(uri, params)
|
25
|
+
if params && !params.empty?
|
26
|
+
uri_size = uri.size
|
27
|
+
params.each do |k,v|
|
28
|
+
case v
|
29
|
+
when Array
|
30
|
+
v.each { |v_part| uri << '&' << CGI.escape(k.to_s) << '%5B%5D=' << CGI.escape(v_part.to_s) }
|
31
|
+
else
|
32
|
+
uri << '&' << CGI.escape(k.to_s) << '=' << CGI.escape(v.to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
uri[uri_size] = ??
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
22
39
|
def variables
|
23
40
|
unless @variables
|
24
41
|
@variables = @parts.select{|p| p.is_a?(Variable)}
|
@@ -28,7 +45,7 @@ class HttpRouter
|
|
28
45
|
end
|
29
46
|
|
30
47
|
def variable_names
|
31
|
-
variables.map{|v| v.name}
|
48
|
+
@variable_names ||= variables.map{|v| v.name}
|
32
49
|
end
|
33
50
|
|
34
51
|
def matches_extension?(extension)
|
data/lib/http_router/response.rb
CHANGED
data/lib/http_router/root.rb
CHANGED
@@ -5,17 +5,38 @@ class HttpRouter
|
|
5
5
|
reset!
|
6
6
|
end
|
7
7
|
|
8
|
+
def add_path(path)
|
9
|
+
node = path.parts.inject(self) { |node, part| node.add(part) }
|
10
|
+
if path.extension
|
11
|
+
node = node.add_extension(path.extension)
|
12
|
+
end
|
13
|
+
node
|
14
|
+
end
|
15
|
+
|
8
16
|
def find(request)
|
9
17
|
path = request.path_info.dup
|
10
18
|
path.slice!(-1) if @base.ignore_trailing_slash? && path[-1] == ?/
|
11
|
-
path
|
12
|
-
extension = $1
|
19
|
+
extension = extract_extension(path)
|
13
20
|
parts = @base.split(path)
|
14
21
|
parts << '' if path[path.size - 1] == ?/
|
15
22
|
|
16
|
-
current_node = self
|
17
23
|
params = []
|
18
|
-
|
24
|
+
if current_node = process_parts(parts, extension, params)
|
25
|
+
current_node = current_node.find_on_request_methods(request)
|
26
|
+
end
|
27
|
+
|
28
|
+
process_response(current_node, parts, extension, params, request)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def process_parts(parts, extension, params)
|
34
|
+
current_node = self
|
35
|
+
loop do
|
36
|
+
if parts.empty? && current_node.extension_node && extension
|
37
|
+
parts << extension
|
38
|
+
current_node = current_node.extension_node
|
39
|
+
end
|
19
40
|
break if current_node.nil? || (current_node.value && current_node.value.route.partially_match?) || parts.empty?
|
20
41
|
unless current_node.linear.empty?
|
21
42
|
whole_path = parts.join('/')
|
@@ -49,41 +70,21 @@ class HttpRouter
|
|
49
70
|
break
|
50
71
|
else
|
51
72
|
current_node = nil
|
73
|
+
break
|
52
74
|
end
|
53
75
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
unless current_node.linear.empty?
|
62
|
-
next_node = current_node.linear.find do |(regexp, node)|
|
63
|
-
regexp === request_value
|
64
|
-
end
|
65
|
-
if next_node
|
66
|
-
current_node = next_node.last
|
67
|
-
next
|
68
|
-
end
|
69
|
-
end
|
70
|
-
current_node = current_node.lookup[request_value] || current_node.catchall
|
71
|
-
if current_node.nil?
|
72
|
-
current_node = previous_node.request_method == :request_method ? RoutingError.new(405, {"Allow" => previous_node.lookup.keys.join(", ")}) : nil
|
73
|
-
else
|
74
|
-
current_node
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
if current_node.is_a?(RoutingError)
|
79
|
-
current_node
|
80
|
-
elsif current_node && current_node.value
|
76
|
+
current_node
|
77
|
+
end
|
78
|
+
|
79
|
+
def process_response(node, parts, extension, params, request)
|
80
|
+
if node.is_a?(RoutingError)
|
81
|
+
node
|
82
|
+
elsif node && node.value
|
81
83
|
if parts.empty?
|
82
|
-
post_match(
|
83
|
-
elsif
|
84
|
+
post_match(node.value, params, extension, request.path_info)
|
85
|
+
elsif node.value.route.partially_match?
|
84
86
|
rest = '/' << parts.join('/') << (extension ? ".#{extension}" : '')
|
85
|
-
|
86
|
-
post_match(current_node.value, params, nil, request.path_info[0, request.path_info.size - rest.size], rest)
|
87
|
+
post_match(node.value, params, nil, request.path_info[0, request.path_info.size - rest.size], rest)
|
87
88
|
else
|
88
89
|
nil
|
89
90
|
end
|
@@ -92,6 +93,14 @@ class HttpRouter
|
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
96
|
+
def extract_extension(path)
|
97
|
+
if path.gsub!(/\.([^\/\.]+)$/, '')
|
98
|
+
extension = $1
|
99
|
+
else
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
95
104
|
def post_match(path, params, extension, matched_path, remaining_path = nil)
|
96
105
|
if path.route.partially_match? || path.matches_extension?(extension)
|
97
106
|
Response.new(path, params, extension, matched_path, remaining_path)
|
data/lib/http_router/route.rb
CHANGED
@@ -3,14 +3,72 @@ class HttpRouter
|
|
3
3
|
attr_reader :dest, :paths
|
4
4
|
attr_accessor :trailing_slash_ignore, :partially_match, :default_values
|
5
5
|
|
6
|
-
def initialize(base,
|
7
|
-
@base
|
8
|
-
@
|
6
|
+
def initialize(base, path)
|
7
|
+
@base = base
|
8
|
+
@path = path
|
9
|
+
@original_path = path.dup
|
10
|
+
@partially_match = extract_partial_match(path)
|
11
|
+
@trailing_slash_ignore = extract_trailing_slash(path)
|
12
|
+
@variable_store = {}
|
13
|
+
@matches_with = {}
|
14
|
+
@conditions = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args, &block)
|
18
|
+
if RequestNode::RequestMethods.include?(method)
|
19
|
+
condition(method => args)
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
9
23
|
end
|
10
24
|
|
11
25
|
def name(name)
|
12
26
|
@name = name
|
13
|
-
@base.
|
27
|
+
@base.named_routes[name] = self
|
28
|
+
end
|
29
|
+
|
30
|
+
def get
|
31
|
+
request_method('GET', 'HEAD')
|
32
|
+
end
|
33
|
+
|
34
|
+
def post
|
35
|
+
request_method('POST')
|
36
|
+
end
|
37
|
+
|
38
|
+
def head
|
39
|
+
request_method('head')
|
40
|
+
end
|
41
|
+
|
42
|
+
def put
|
43
|
+
request_method('PUT')
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete
|
47
|
+
request_method('DELETE')
|
48
|
+
end
|
49
|
+
|
50
|
+
def only_get
|
51
|
+
request_method('DELETE')
|
52
|
+
end
|
53
|
+
|
54
|
+
def condition(conditions)
|
55
|
+
guard_compiled
|
56
|
+
conditions.each do |k,v|
|
57
|
+
@conditions.key?(k) ?
|
58
|
+
@conditions[k] << v :
|
59
|
+
@conditions[k] = Array(v)
|
60
|
+
end
|
61
|
+
self
|
62
|
+
end
|
63
|
+
alias_method :conditions, :condition
|
64
|
+
|
65
|
+
def matching(*match)
|
66
|
+
guard_compiled
|
67
|
+
@matches_with.merge!(match.pop) if match.last.is_a?(Hash)
|
68
|
+
match.each_slice(2) do |(k,v)|
|
69
|
+
@matches_with[k] = v
|
70
|
+
end
|
71
|
+
self
|
14
72
|
end
|
15
73
|
|
16
74
|
def named
|
@@ -18,19 +76,38 @@ class HttpRouter
|
|
18
76
|
end
|
19
77
|
|
20
78
|
def to(dest = nil, &block)
|
79
|
+
compile
|
21
80
|
@dest = dest || block
|
22
81
|
self
|
23
82
|
end
|
24
83
|
|
25
|
-
def
|
84
|
+
def partial(match = true)
|
26
85
|
@partially_match = match
|
27
86
|
self
|
28
87
|
end
|
29
88
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
89
|
+
def compiled?
|
90
|
+
!@paths.nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def compile
|
94
|
+
unless @paths
|
95
|
+
@paths = compile_paths
|
96
|
+
@paths.each do |path|
|
97
|
+
path.route = self
|
98
|
+
current_node = @base.root.add_path(path)
|
99
|
+
working_set = current_node.add_request_methods(@conditions)
|
100
|
+
working_set.each do |current_node|
|
101
|
+
current_node.value = path
|
102
|
+
end
|
103
|
+
end
|
33
104
|
end
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def redirect(path, status = 302)
|
109
|
+
guard_compiled
|
110
|
+
raise(ArgumentError, "Status has to be an integer between 300 and 399") unless (300..399).include?(status)
|
34
111
|
to { |env|
|
35
112
|
params = env['router.params']
|
36
113
|
response = ::Rack::Response.new
|
@@ -40,10 +117,11 @@ class HttpRouter
|
|
40
117
|
self
|
41
118
|
end
|
42
119
|
|
43
|
-
def
|
120
|
+
def static(root)
|
121
|
+
guard_compiled
|
122
|
+
raise AlreadyCompiledException.new if compiled?
|
44
123
|
if File.directory?(root)
|
45
|
-
|
46
|
-
to ::Rack::File.new(root)
|
124
|
+
partial.to ::Rack::File.new(root)
|
47
125
|
else
|
48
126
|
to proc{|env| env['PATH_INFO'] = File.basename(root); ::Rack::File.new(File.dirname(root)).call(env)}
|
49
127
|
end
|
@@ -60,11 +138,14 @@ class HttpRouter
|
|
60
138
|
|
61
139
|
def url(*args)
|
62
140
|
options = args.last.is_a?(Hash) ? args.pop : nil
|
141
|
+
options = default_values.merge(options) if default_values && options
|
63
142
|
path = matching_path(args.empty? ? options : args)
|
64
143
|
raise UngeneratableRouteException.new unless path
|
65
144
|
path.url(args, options)
|
66
145
|
end
|
67
146
|
|
147
|
+
private
|
148
|
+
|
68
149
|
def matching_path(params)
|
69
150
|
if @paths.size == 1
|
70
151
|
@paths.first
|
@@ -77,26 +158,113 @@ class HttpRouter
|
|
77
158
|
end
|
78
159
|
nil
|
79
160
|
else
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
161
|
+
@paths.reverse_each do |path|
|
162
|
+
if params && !params.empty?
|
163
|
+
return path if (path.variable_names & params.keys).size == path.variable_names.size
|
164
|
+
elsif path.variable_names.empty?
|
165
|
+
return path
|
166
|
+
end
|
167
|
+
end
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def extract_partial_match(path)
|
174
|
+
path[-1] == ?* && path.slice!(-1)
|
175
|
+
end
|
176
|
+
|
177
|
+
def extract_trailing_slash(path)
|
178
|
+
path[-2, 2] == '/?' && path.slice!(-2, 2)
|
179
|
+
end
|
180
|
+
|
181
|
+
def extract_extension(path)
|
182
|
+
if match = path.match(/^(.*)(\.:([a-zA-Z_]+))$/)
|
183
|
+
path.replace(match[1])
|
184
|
+
Variable.new(@base, match[3].to_sym)
|
185
|
+
elsif match = path.match(/^(.*)(\.([a-zA-Z_]+))$/)
|
186
|
+
path.replace(match[1])
|
187
|
+
match[3]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def compile_optionals(path)
|
193
|
+
start_index = 0
|
194
|
+
end_index = 1
|
195
|
+
|
196
|
+
paths = [""]
|
197
|
+
chars = path.split('')
|
198
|
+
|
199
|
+
chars.each do |c|
|
200
|
+
case c
|
201
|
+
when '('
|
202
|
+
# over current working set, double paths
|
203
|
+
(start_index...end_index).each do |path_index|
|
204
|
+
paths << paths[path_index].dup
|
205
|
+
end
|
206
|
+
start_index = end_index
|
207
|
+
end_index = paths.size
|
208
|
+
when ')'
|
209
|
+
start_index -= end_index - start_index
|
210
|
+
else
|
211
|
+
(start_index...end_index).each do |path_index|
|
212
|
+
paths[path_index] << c
|
91
213
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
214
|
+
end
|
215
|
+
end
|
216
|
+
paths
|
217
|
+
end
|
218
|
+
|
219
|
+
def compile_paths
|
220
|
+
paths = compile_optionals(@path)
|
221
|
+
paths.map do |path|
|
222
|
+
original_path = path.dup
|
223
|
+
extension = extract_extension(path)
|
224
|
+
new_path = @base.split(path).map do |part|
|
225
|
+
case part[0]
|
226
|
+
when ?:
|
227
|
+
v_name = part[1, part.size].to_sym
|
228
|
+
@variable_store[v_name] ||= Variable.new(@base, v_name, @matches_with[v_name])
|
229
|
+
when ?*
|
230
|
+
v_name = part[1, part.size].to_sym
|
231
|
+
@variable_store[v_name] ||= Glob.new(@base, v_name, @matches_with[v_name])
|
232
|
+
else
|
233
|
+
generate_interstitial_parts(part)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
new_path.flatten!
|
237
|
+
Path.new(original_path, new_path, extension)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def generate_interstitial_parts(part)
|
242
|
+
part_segments = part.split(/(:[a-zA-Z_]+)/)
|
243
|
+
if part_segments.size > 1
|
244
|
+
index = 0
|
245
|
+
part_segments.map do |seg|
|
246
|
+
new_seg = if seg[0] == ?:
|
247
|
+
next_index = index + 1
|
248
|
+
scan_regex = if next_index == part_segments.size
|
249
|
+
/^[^\/]+/
|
250
|
+
else
|
251
|
+
/^.*?(?=#{Regexp.quote(part_segments[next_index])})/
|
95
252
|
end
|
253
|
+
v_name = seg[1, seg.size].to_sym
|
254
|
+
@variable_store[v_name] ||= Variable.new(@base, v_name, scan_regex)
|
255
|
+
else
|
256
|
+
/^#{Regexp.quote(seg)}/
|
96
257
|
end
|
97
|
-
|
258
|
+
index += 1
|
259
|
+
new_seg
|
98
260
|
end
|
261
|
+
else
|
262
|
+
part
|
99
263
|
end
|
100
264
|
end
|
265
|
+
|
266
|
+
def guard_compiled
|
267
|
+
raise AlreadyCompiledException.new if compiled?
|
268
|
+
end
|
101
269
|
end
|
102
270
|
end
|
data/lib/http_router/sinatra.rb
CHANGED
@@ -60,9 +60,6 @@ class HttpRouter
|
|
60
60
|
|
61
61
|
def route(verb, path, options={}, &block)
|
62
62
|
name = options.delete(:name)
|
63
|
-
options[:conditions] ||= {}
|
64
|
-
options[:conditions][:request_method] = verb
|
65
|
-
options[:conditions][:host] = options.delete(:host) if options.key?(:host)
|
66
63
|
|
67
64
|
define_method "#{verb} #{path}", &block
|
68
65
|
unbound_method = instance_method("#{verb} #{path}")
|
@@ -75,7 +72,11 @@ class HttpRouter
|
|
75
72
|
|
76
73
|
invoke_hook(:route_added, verb, path, block)
|
77
74
|
|
78
|
-
route = router.add(path
|
75
|
+
route = router.add(path)
|
76
|
+
route.request_method(verb)
|
77
|
+
route.host(options.delete(:host)) if options.key?(:host)
|
78
|
+
|
79
|
+
route.to(block)
|
79
80
|
route.name(name) if name
|
80
81
|
route
|
81
82
|
end
|
data/lib/http_router.rb
CHANGED
@@ -14,15 +14,18 @@ class HttpRouter
|
|
14
14
|
UngeneratableRouteException = Class.new(RuntimeError)
|
15
15
|
MissingParameterException = Class.new(RuntimeError)
|
16
16
|
TooManyParametersException = Class.new(RuntimeError)
|
17
|
+
AlreadyCompiledException = Class.new(RuntimeError)
|
17
18
|
RoutingError = Struct.new(:status, :headers)
|
18
19
|
|
19
|
-
attr_reader :routes
|
20
|
+
attr_reader :named_routes, :routes, :root
|
20
21
|
|
21
22
|
def initialize(options = nil)
|
22
|
-
reset!
|
23
23
|
@default_app = options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
|
24
24
|
@ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
|
25
25
|
@redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
|
26
|
+
@routes = []
|
27
|
+
@named_routes = {}
|
28
|
+
reset!
|
26
29
|
end
|
27
30
|
|
28
31
|
def ignore_trailing_slash?
|
@@ -35,7 +38,8 @@ class HttpRouter
|
|
35
38
|
|
36
39
|
def reset!
|
37
40
|
@root = Root.new(self)
|
38
|
-
@routes
|
41
|
+
@routes.clear
|
42
|
+
@named_routes.clear
|
39
43
|
end
|
40
44
|
|
41
45
|
def default(app)
|
@@ -47,150 +51,44 @@ class HttpRouter
|
|
47
51
|
with_delimiter ? path.split('(/)') : path.split('/')
|
48
52
|
end
|
49
53
|
|
50
|
-
def add(path
|
51
|
-
|
52
|
-
|
53
|
-
trailing_slash_ignore = extract_trailing_slash(path)
|
54
|
-
paths = compile(path, options)
|
55
|
-
|
56
|
-
route = Route.new(self, options && options[:default_values])
|
57
|
-
route.trailing_slash_ignore = trailing_slash_ignore
|
58
|
-
route.partially_match = partially_match
|
59
|
-
paths.each_with_index do |path, i|
|
60
|
-
current_node = @root
|
61
|
-
path.parts.each { |part| current_node = current_node.add(part) }
|
62
|
-
working_set = current_node.add_request_methods(options)
|
63
|
-
working_set.each do |current_node|
|
64
|
-
current_node.value = path
|
65
|
-
path.route = route
|
66
|
-
route.paths << current_node.value
|
67
|
-
end
|
68
|
-
end
|
54
|
+
def add(path)
|
55
|
+
route = Route.new(self, path.dup)
|
56
|
+
@routes << route
|
69
57
|
route
|
70
58
|
end
|
71
59
|
|
72
|
-
def get(path
|
73
|
-
|
74
|
-
options[:conditions][:request_method] = ['HEAD', 'GET'] #TODO, this should be able to take an array
|
75
|
-
add(path, options)
|
76
|
-
end
|
77
|
-
|
78
|
-
def post(path, options = {})
|
79
|
-
options[:conditions] ||= {}
|
80
|
-
options[:conditions][:request_method] = 'POST'
|
81
|
-
add(path, options)
|
82
|
-
end
|
83
|
-
|
84
|
-
def put(path, options = {})
|
85
|
-
options[:conditions] ||= {}
|
86
|
-
options[:conditions][:request_method] = 'PUT'
|
87
|
-
add(path, options)
|
60
|
+
def get(path)
|
61
|
+
add(path).get
|
88
62
|
end
|
89
63
|
|
90
|
-
def
|
91
|
-
|
92
|
-
options[:conditions][:request_method] = 'DELETE'
|
93
|
-
add(path, options)
|
64
|
+
def post(path)
|
65
|
+
add(path).post
|
94
66
|
end
|
95
67
|
|
96
|
-
def
|
97
|
-
|
98
|
-
options[:conditions][:request_method] = "GET"
|
99
|
-
add(path, options)
|
68
|
+
def put(path)
|
69
|
+
add(path).put
|
100
70
|
end
|
101
71
|
|
102
|
-
def
|
103
|
-
|
104
|
-
path.slice!(-1)
|
105
|
-
true
|
106
|
-
else
|
107
|
-
false
|
108
|
-
end
|
72
|
+
def delete(path)
|
73
|
+
add(path).delete
|
109
74
|
end
|
110
75
|
|
111
|
-
def
|
112
|
-
|
113
|
-
path.slice!(-2, 2)
|
114
|
-
true
|
115
|
-
else
|
116
|
-
false
|
117
|
-
end
|
76
|
+
def only_get(path)
|
77
|
+
add(path).only_get
|
118
78
|
end
|
119
79
|
|
120
|
-
def
|
121
|
-
|
122
|
-
path.replace(match[1])
|
123
|
-
Variable.new(self, match[3].to_sym)
|
124
|
-
elsif match = path.match(/^(.*)(\.([a-zA-Z_]+))$/)
|
125
|
-
path.replace(match[1])
|
126
|
-
match[3]
|
127
|
-
end
|
80
|
+
def recognize(env)
|
81
|
+
response = @root.find(env.is_a?(Hash) ? Rack::Request.new(env) : env)
|
128
82
|
end
|
129
83
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
case c
|
139
|
-
when '('
|
140
|
-
# over current working set, double paths
|
141
|
-
(start_index...end_index).each do |path_index|
|
142
|
-
paths << paths[path_index].dup
|
143
|
-
end
|
144
|
-
start_index = end_index
|
145
|
-
end_index = paths.size
|
146
|
-
when ')'
|
147
|
-
start_index -= end_index - start_index
|
148
|
-
else
|
149
|
-
(start_index...end_index).each do |path_index|
|
150
|
-
paths[path_index] << c
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
variables = {}
|
156
|
-
paths.map do |path|
|
157
|
-
original_path = path.dup
|
158
|
-
extension = extract_extension(path)
|
159
|
-
new_path = split(path).map do |part|
|
160
|
-
case part[0]
|
161
|
-
when ?:
|
162
|
-
v_name = part[1, part.size].to_sym
|
163
|
-
variables[v_name] ||= Variable.new(self, v_name, options && options[:matches_with] && options && options[:matches_with][v_name])
|
164
|
-
when ?*
|
165
|
-
v_name = part[1, part.size].to_sym
|
166
|
-
variables[v_name] ||= Glob.new(self, v_name, options && options[:matches_with] && options && options[:matches_with][v_name])
|
167
|
-
else
|
168
|
-
part_segments = part.split(/(:[a-zA-Z_]+)/)
|
169
|
-
if part_segments.size > 1
|
170
|
-
index = 0
|
171
|
-
part_segments.map do |seg|
|
172
|
-
new_seg = if seg[0] == ?:
|
173
|
-
next_index = index + 1
|
174
|
-
scan_regex = if next_index == part_segments.size
|
175
|
-
/^[^\/]+/
|
176
|
-
else
|
177
|
-
/^.*?(?=#{Regexp.quote(part_segments[next_index])})/
|
178
|
-
end
|
179
|
-
v_name = seg[1, seg.size].to_sym
|
180
|
-
variables[v_name] ||= Variable.new(self, v_name, scan_regex)
|
181
|
-
else
|
182
|
-
/^#{Regexp.quote(seg)}/
|
183
|
-
end
|
184
|
-
index += 1
|
185
|
-
new_seg
|
186
|
-
end
|
187
|
-
else
|
188
|
-
part
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
new_path.flatten!
|
193
|
-
Path.new(original_path, new_path, extension)
|
84
|
+
def url(route, *args)
|
85
|
+
case route
|
86
|
+
when Symbol
|
87
|
+
url(@named_routes[route], *args)
|
88
|
+
when nil
|
89
|
+
raise UngeneratableRouteException.new
|
90
|
+
else
|
91
|
+
route.url(*args)
|
194
92
|
end
|
195
93
|
end
|
196
94
|
|
@@ -208,10 +106,6 @@ class HttpRouter
|
|
208
106
|
elsif response && response.route.dest && response.route.dest.respond_to?(:call)
|
209
107
|
process_params(env, response)
|
210
108
|
consume_path!(request, response) if response.partial_match?
|
211
|
-
#if response.rest
|
212
|
-
# request.env["SCRIPT_NAME"] += request.env["PATH_INFO"][0, -response.rest.size]
|
213
|
-
# request.env["PATH_INFO"] = response.rest || ''
|
214
|
-
#end
|
215
109
|
response.route.dest.call(env)
|
216
110
|
else
|
217
111
|
@default_app.call(env)
|
@@ -219,6 +113,8 @@ class HttpRouter
|
|
219
113
|
end
|
220
114
|
end
|
221
115
|
|
116
|
+
private
|
117
|
+
|
222
118
|
def consume_path!(request, response)
|
223
119
|
request.env["SCRIPT_NAME"] = (request.env["SCRIPT_NAME"] + response.matched_path)
|
224
120
|
request.env["PATH_INFO"] = response.remaining_path || ""
|
@@ -233,18 +129,4 @@ class HttpRouter
|
|
233
129
|
end
|
234
130
|
end
|
235
131
|
|
236
|
-
def recognize(env)
|
237
|
-
response = @root.find(env.is_a?(Hash) ? Rack::Request.new(env) : env)
|
238
|
-
end
|
239
|
-
|
240
|
-
def url(route, *args)
|
241
|
-
case route
|
242
|
-
when Symbol
|
243
|
-
url(@routes[route], *args)
|
244
|
-
when nil
|
245
|
-
raise UngeneratableRouteException.new
|
246
|
-
else
|
247
|
-
route.url(*args)
|
248
|
-
end
|
249
|
-
end
|
250
132
|
end
|
data/spec/generate_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe "HttpRouter#generate" do
|
|
6
6
|
context("static paths") do
|
7
7
|
['/', '/test', '/test/time', '/one/more/what', '/test.html'].each do |path|
|
8
8
|
it "should generate #{path.inspect}" do
|
9
|
-
route = @router.add(path)
|
9
|
+
route = @router.add(path).compile
|
10
10
|
@router.url(route).should == path
|
11
11
|
end
|
12
12
|
end
|
@@ -14,34 +14,44 @@ describe "HttpRouter#generate" do
|
|
14
14
|
|
15
15
|
context("dynamic paths") do
|
16
16
|
it "should generate from a hash" do
|
17
|
-
@router.add("/:var").name(:test)
|
17
|
+
@router.add("/:var").name(:test).compile
|
18
18
|
@router.url(:test, :var => 'test').should == '/test'
|
19
19
|
end
|
20
20
|
|
21
|
+
it "should generate from a hash with extra parts going to the query string" do
|
22
|
+
@router.add("/:var").name(:test).compile
|
23
|
+
@router.url(:test, :var => 'test', :query => 'string').should == '/test?query=string'
|
24
|
+
end
|
25
|
+
|
21
26
|
it "should generate from an array" do
|
22
|
-
@router.add("/:var").name(:test)
|
27
|
+
@router.add("/:var").name(:test).compile
|
23
28
|
@router.url(:test, 'test').should == '/test'
|
24
29
|
end
|
25
30
|
|
31
|
+
it "should generate from an array with extra parts going to the query string" do
|
32
|
+
@router.add("/:var").name(:test).compile
|
33
|
+
@router.url(:test, 'test', :query => 'string').should == '/test?query=string'
|
34
|
+
end
|
35
|
+
|
26
36
|
it "should generate with a format" do
|
27
|
-
@router.add("/test.:format").name(:test)
|
37
|
+
@router.add("/test.:format").name(:test).compile
|
28
38
|
@router.url(:test, 'html').should == '/test.html'
|
29
39
|
end
|
30
40
|
|
31
41
|
it "should generate with a format as a hash" do
|
32
|
-
@router.add("/test.:format").name(:test)
|
42
|
+
@router.add("/test.:format").name(:test).compile
|
33
43
|
@router.url(:test, :format => 'html').should == '/test.html'
|
34
44
|
end
|
35
45
|
|
36
46
|
it "should generate with an optional format" do
|
37
|
-
@router.add("/test(.:format)").name(:test)
|
47
|
+
@router.add("/test(.:format)").name(:test).compile
|
38
48
|
@router.url(:test, 'html').should == '/test.html'
|
39
49
|
@router.url(:test).should == '/test'
|
40
50
|
end
|
41
51
|
|
42
52
|
context "with optional parts" do
|
43
53
|
it "should generate both" do
|
44
|
-
@router.add("/:var1(/:var2)").name(:test)
|
54
|
+
@router.add("/:var1(/:var2)").name(:test).compile
|
45
55
|
@router.url(:test, 'var').should == '/var'
|
46
56
|
@router.url(:test, 'var', 'fooz').should == '/var/fooz'
|
47
57
|
@router.url(:test, :var1 => 'var').should == '/var'
|
data/spec/rack/dispatch_spec.rb
CHANGED
@@ -25,7 +25,7 @@ describe "Usher (for rack) route dispatching" do
|
|
25
25
|
describe "HTTP GET" do
|
26
26
|
before(:each) do
|
27
27
|
route_set.reset!
|
28
|
-
route_set.add('/sample'
|
28
|
+
route_set.add('/sample').request_method('GET').to(@app)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "should dispatch a request" do
|
@@ -42,7 +42,7 @@ describe "Usher (for rack) route dispatching" do
|
|
42
42
|
describe "HTTP POST" do
|
43
43
|
before(:each) do
|
44
44
|
route_set.reset!
|
45
|
-
route_set.add('/sample'
|
45
|
+
route_set.add('/sample').post.to(@app)
|
46
46
|
route_set.add('/sample').to(MockApp.new("You shouldn't get here if you are using POST"))
|
47
47
|
end
|
48
48
|
|
@@ -64,8 +64,8 @@ describe "Usher (for rack) route dispatching" do
|
|
64
64
|
|
65
65
|
it "should returns HTTP 405 if the method mis-matches" do
|
66
66
|
route_set.reset!
|
67
|
-
route_set.
|
68
|
-
route_set.
|
67
|
+
route_set.post('/sample').to(@app)
|
68
|
+
route_set.put('/sample').to(@app)
|
69
69
|
response = route_set.call_with_mock_request('/sample', 'GET')
|
70
70
|
response.status.should eql(405)
|
71
71
|
response['Allow'].should == 'POST, PUT'
|
data/spec/rack/generate_spec.rb
CHANGED
@@ -5,9 +5,9 @@ describe "Usher (for rack) route generation" do
|
|
5
5
|
before(:each) do
|
6
6
|
route_set.reset!
|
7
7
|
@app = MockApp.new("Hello World!")
|
8
|
-
route_set.add("/fixed").name(:fixed)
|
9
|
-
route_set.add("/named/simple/:named_simple_var").name(:simple)
|
10
|
-
route_set.add("/named/optional(/:named_optional_var)").name(:optional)
|
8
|
+
route_set.add("/fixed").name(:fixed).compile
|
9
|
+
route_set.add("/named/simple/:named_simple_var").name(:simple).compile
|
10
|
+
route_set.add("/named/optional(/:named_optional_var)").name(:optional).compile
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "named routes" do
|
data/spec/rack/route_spec.rb
CHANGED
@@ -29,14 +29,14 @@ describe "Rack interface extensions for Usher::Route" do
|
|
29
29
|
|
30
30
|
describe "static file serving" do
|
31
31
|
it "should serve from a static directory" do
|
32
|
-
@route_set.get("/static").
|
32
|
+
@route_set.get("/static").static(File.dirname(__FILE__))
|
33
33
|
@env = Rack::MockRequest.env_for("/static/#{File.basename(__FILE__)}")
|
34
34
|
status, headers, body = @route_set.call(@env)
|
35
35
|
body.path.should == File.join(File.dirname(__FILE__), File.basename(__FILE__))
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should serve a specific file" do
|
39
|
-
@route_set.get("/static-file").
|
39
|
+
@route_set.get("/static-file").static(__FILE__)
|
40
40
|
@env = Rack::MockRequest.env_for("/static-file")
|
41
41
|
status, headers, body = @route_set.call(@env)
|
42
42
|
body.path.should == __FILE__
|
data/spec/recognize_spec.rb
CHANGED
@@ -6,14 +6,14 @@ describe "HttpRouter#recognize" do
|
|
6
6
|
context("static paths") do
|
7
7
|
['/', '/test', '/test/time', '/one/more/what', '/test.html'].each do |path|
|
8
8
|
it "should recognize #{path.inspect}" do
|
9
|
-
route = @router.add(path)
|
9
|
+
route = @router.add(path).to(path)
|
10
10
|
@router.recognize(Rack::MockRequest.env_for(path)).route.should == route
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
context("with optional parts") do
|
15
15
|
it "work either way" do
|
16
|
-
route = @router.add("/test(/optional)")
|
16
|
+
route = @router.add("/test(/optional)").to(:test)
|
17
17
|
@router.recognize(Rack::MockRequest.env_for('/test')).route.should == route
|
18
18
|
@router.recognize(Rack::MockRequest.env_for('/test/optional')).route.should == route
|
19
19
|
end
|
@@ -21,7 +21,7 @@ describe "HttpRouter#recognize" do
|
|
21
21
|
|
22
22
|
context("partial matching") do
|
23
23
|
it "should match partially or completely" do
|
24
|
-
route = @router.add("/test*")
|
24
|
+
route = @router.add("/test*").to(:test)
|
25
25
|
@router.recognize(Rack::MockRequest.env_for('/test')).route.should == route
|
26
26
|
response = @router.recognize(Rack::MockRequest.env_for('/test/optional'))
|
27
27
|
response.route.should == route
|
@@ -31,19 +31,19 @@ describe "HttpRouter#recognize" do
|
|
31
31
|
|
32
32
|
context("trailing slashes") do
|
33
33
|
it "should ignore a trailing slash" do
|
34
|
-
route = @router.add("/test")
|
34
|
+
route = @router.add("/test").to(:test)
|
35
35
|
@router.recognize(Rack::MockRequest.env_for('/test/')).route.should == route
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should not recognize a trailing slash when used with the /? syntax and ignore_trailing_slash disabled" do
|
39
39
|
@router = HttpRouter.new(:ignore_trailing_slash => false)
|
40
|
-
route = @router.add("/test/?")
|
40
|
+
route = @router.add("/test/?").to(:test)
|
41
41
|
@router.recognize(Rack::MockRequest.env_for('/test/')).route.should == route
|
42
42
|
end
|
43
43
|
|
44
44
|
it "should recognize a trailing slash when used with the /? syntax and ignore_trailing_slash enabled" do
|
45
45
|
@router = HttpRouter.new(:ignore_trailing_slash => false)
|
46
|
-
route = @router.add("/test")
|
46
|
+
route = @router.add("/test").to(:test)
|
47
47
|
@router.recognize(Rack::MockRequest.env_for('/test/')).should be_nil
|
48
48
|
end
|
49
49
|
end
|
@@ -52,30 +52,30 @@ describe "HttpRouter#recognize" do
|
|
52
52
|
|
53
53
|
context "request methods" do
|
54
54
|
it "should pick a specific request_method" do
|
55
|
-
route = @router.
|
55
|
+
route = @router.post("/test").to(:test)
|
56
56
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'POST')).route.should == route
|
57
57
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).status.should == 405
|
58
58
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).headers['Allow'].should == "POST"
|
59
59
|
end
|
60
60
|
|
61
61
|
it "should pick a specific request_method with other paths all through it" do
|
62
|
-
@router.
|
63
|
-
@router.
|
64
|
-
@router.
|
65
|
-
@router.
|
66
|
-
@router.add("/test/post").
|
67
|
-
@router.add("/test").
|
68
|
-
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'POST')).
|
69
|
-
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).
|
70
|
-
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'PUT')).
|
71
|
-
@router.recognize(Rack::MockRequest.env_for('/test/post', :method => 'POST')).
|
72
|
-
@router.recognize(Rack::MockRequest.env_for('/test/post', :method => 'GET')).
|
73
|
-
@router.recognize(Rack::MockRequest.env_for('/test/post', :method => 'PUT')).
|
62
|
+
@router.post("/test").to(:test_post)
|
63
|
+
@router.post("/test/post").to(:test_post_post)
|
64
|
+
@router.get("/test").to(:test_get)
|
65
|
+
@router.get("/test/post").to(:test_post_get)
|
66
|
+
@router.add("/test/post").to(:test_post_catchall)
|
67
|
+
@router.add("/test").to(:test_catchall)
|
68
|
+
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'POST')).dest.should == :test_post
|
69
|
+
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'GET')).dest.should == :test_get
|
70
|
+
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'PUT')).dest.should == :test_catchall
|
71
|
+
@router.recognize(Rack::MockRequest.env_for('/test/post', :method => 'POST')).dest.should == :test_post_post
|
72
|
+
@router.recognize(Rack::MockRequest.env_for('/test/post', :method => 'GET')).dest.should == :test_post_get
|
73
|
+
@router.recognize(Rack::MockRequest.env_for('/test/post', :method => 'PUT')).dest.should == :test_post_catchall
|
74
74
|
end
|
75
75
|
|
76
76
|
it "should move an endpoint to the non-specific request method when a more specific route gets added" do
|
77
|
-
@router.add("/test").name(:test_catchall)
|
78
|
-
@router.
|
77
|
+
@router.add("/test").name(:test_catchall).to(:test1)
|
78
|
+
@router.post("/test").request_method('POST').name(:test_post).to(:test2)
|
79
79
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'POST')).route.named.should == :test_post
|
80
80
|
@router.recognize(Rack::MockRequest.env_for('/test', :method => 'PUT')).route.named.should == :test_catchall
|
81
81
|
end
|
@@ -84,7 +84,7 @@ describe "HttpRouter#recognize" do
|
|
84
84
|
|
85
85
|
context("dynamic paths") do
|
86
86
|
it "should recognize '/:variable'" do
|
87
|
-
route = @router.add('/:variable')
|
87
|
+
route = @router.add('/:variable').to(:test)
|
88
88
|
response = @router.recognize(Rack::MockRequest.env_for('/value'))
|
89
89
|
response.route.should == route
|
90
90
|
response.params.should == ['value']
|
@@ -92,15 +92,16 @@ describe "HttpRouter#recognize" do
|
|
92
92
|
end
|
93
93
|
|
94
94
|
it "should recognize '/test.:format'" do
|
95
|
-
route = @router.add('/test.:format')
|
95
|
+
route = @router.add('/test.:format').to(:test)
|
96
96
|
response = @router.recognize(Rack::MockRequest.env_for('/test.html'))
|
97
97
|
response.route.should == route
|
98
98
|
response.extension.should == 'html'
|
99
99
|
response.params_as_hash[:format].should == 'html'
|
100
|
+
@router.recognize(Rack::MockRequest.env_for('/test')).should be_nil
|
100
101
|
end
|
101
102
|
|
102
103
|
it "should recognize '/test(.:format)'" do
|
103
|
-
route = @router.add('/test(.:format)')
|
104
|
+
route = @router.add('/test(.:format)').to(:test)
|
104
105
|
response = @router.recognize(Rack::MockRequest.env_for('/test.html'))
|
105
106
|
response.route.should == route
|
106
107
|
response.extension.should == 'html'
|
@@ -112,7 +113,7 @@ describe "HttpRouter#recognize" do
|
|
112
113
|
end
|
113
114
|
|
114
115
|
it "should recognize '/:test.:format'" do
|
115
|
-
route = @router.add('/:test.:format')
|
116
|
+
route = @router.add('/:test.:format').to(:test)
|
116
117
|
response = @router.recognize(Rack::MockRequest.env_for('/hey.html'))
|
117
118
|
response.route.should == route
|
118
119
|
response.extension.should == 'html'
|
@@ -121,7 +122,7 @@ describe "HttpRouter#recognize" do
|
|
121
122
|
end
|
122
123
|
|
123
124
|
it "should recognize '/:test(.:format)'" do
|
124
|
-
route = @router.add('/:test(.:format)')
|
125
|
+
route = @router.add('/:test(.:format)').to(:test)
|
125
126
|
response = @router.recognize(Rack::MockRequest.env_for('/hey.html'))
|
126
127
|
response.route.should == route
|
127
128
|
response.extension.should == 'html'
|
@@ -136,13 +137,13 @@ describe "HttpRouter#recognize" do
|
|
136
137
|
|
137
138
|
context "globs" do
|
138
139
|
it "should recognize a glob" do
|
139
|
-
route = @router.add('/test/*variable')
|
140
|
+
route = @router.add('/test/*variable').to(:test)
|
140
141
|
response = @router.recognize(Rack::MockRequest.env_for('/test/one/two/three'))
|
141
142
|
response.route.should == route
|
142
143
|
response.params.should == [['one', 'two', 'three']]
|
143
144
|
end
|
144
145
|
it "should recognize a glob with a regexp" do
|
145
|
-
route = @router.add('/test/*variable/anymore'
|
146
|
+
route = @router.add('/test/*variable/anymore').matching(:variable => /^\d+$/).to(:test)
|
146
147
|
response = @router.recognize(Rack::MockRequest.env_for('/test/123/345/567/anymore'))
|
147
148
|
response.route.should == route
|
148
149
|
response.params.should == [['123', '345', '567']]
|
@@ -155,7 +156,7 @@ describe "HttpRouter#recognize" do
|
|
155
156
|
|
156
157
|
context("interstitial variables") do
|
157
158
|
it "should recognize interstitial variables" do
|
158
|
-
route = @router.add('/one-:variable-time')
|
159
|
+
route = @router.add('/one-:variable-time').to(:test)
|
159
160
|
response = @router.recognize(Rack::MockRequest.env_for('/one-value-time'))
|
160
161
|
response.route.should == route
|
161
162
|
response.params_as_hash[:variable].should == 'value'
|
@@ -164,7 +165,7 @@ describe "HttpRouter#recognize" do
|
|
164
165
|
|
165
166
|
context("dynamic greedy paths") do
|
166
167
|
it "should recognize greedy variables" do
|
167
|
-
route = @router.add('/:variable'
|
168
|
+
route = @router.add('/:variable').matching(:variable, /\d+/).to(:test)
|
168
169
|
response = @router.recognize(Rack::MockRequest.env_for('/123'))
|
169
170
|
response.route.should == route
|
170
171
|
response.params.should == ['123']
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 0
|
8
7
|
- 1
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Joshua Hull
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-27 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|