http_router 0.6.4 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/http_router/node/free_regex.rb +1 -1
- data/lib/http_router/node.rb +15 -15
- data/lib/http_router/path.rb +3 -3
- data/lib/http_router/route.rb +17 -1
- data/lib/http_router/version.rb +1 -1
- data/lib/http_router.rb +107 -39
- data/test/test_variable.rb +7 -2
- metadata +14 -3
@@ -8,7 +8,7 @@ class HttpRouter
|
|
8
8
|
|
9
9
|
def [](request)
|
10
10
|
whole_path = "/#{join_whole_path(request)}"
|
11
|
-
if match = @matcher.match(whole_path)
|
11
|
+
if match = @matcher.match(whole_path) and match[0].size == whole_path.size
|
12
12
|
request = request.clone
|
13
13
|
request.extra_env['router.regex_match'] = match
|
14
14
|
match.names.size.times{|i| request.params << match[i + 1]} if match.respond_to?(:names) && match.names
|
data/lib/http_router/node.rb
CHANGED
@@ -141,20 +141,6 @@ class HttpRouter
|
|
141
141
|
add_prioritized_match(SpanningRegex.new(@router, regexp, matching_indicies, priority, splitting_indicies))
|
142
142
|
end
|
143
143
|
|
144
|
-
def add_prioritized_match(match)
|
145
|
-
@linear ||= []
|
146
|
-
if match.priority != 0
|
147
|
-
@linear.each_with_index { |n, i|
|
148
|
-
if match.priority > (n.priority || 0)
|
149
|
-
@linear[i, 0] = match
|
150
|
-
return @linear[i]
|
151
|
-
end
|
152
|
-
}
|
153
|
-
end
|
154
|
-
@linear << match
|
155
|
-
@linear.last
|
156
|
-
end
|
157
|
-
|
158
144
|
def add_free_match(regexp)
|
159
145
|
@linear ||= []
|
160
146
|
@linear << FreeRegex.new(@router, regexp)
|
@@ -172,8 +158,22 @@ class HttpRouter
|
|
172
158
|
end
|
173
159
|
|
174
160
|
def join_whole_path(request)
|
175
|
-
request.path
|
161
|
+
request.path * '/'
|
176
162
|
end
|
177
163
|
|
164
|
+
private
|
165
|
+
def add_prioritized_match(match)
|
166
|
+
@linear ||= []
|
167
|
+
if match.priority != 0
|
168
|
+
@linear.each_with_index { |n, i|
|
169
|
+
if match.priority > (n.priority || 0)
|
170
|
+
@linear[i, 0] = match
|
171
|
+
return @linear[i]
|
172
|
+
end
|
173
|
+
}
|
174
|
+
end
|
175
|
+
@linear << match
|
176
|
+
@linear.last
|
177
|
+
end
|
178
178
|
end
|
179
179
|
end
|
data/lib/http_router/path.rb
CHANGED
@@ -2,7 +2,7 @@ class HttpRouter
|
|
2
2
|
class Path
|
3
3
|
attr_reader :route, :param_names
|
4
4
|
def initialize(route, path, param_names = [])
|
5
|
-
@route, @path, @param_names, @
|
5
|
+
@route, @path, @param_names, @dynamic = route, path, param_names, !param_names.empty?
|
6
6
|
duplicate_param_names = param_names.dup.uniq!
|
7
7
|
raise AmbiguousVariableException, "You have duplicate variable name present: #{duplicate_param_names.join(', ')}" if duplicate_param_names
|
8
8
|
if path.respond_to?(:split)
|
@@ -34,7 +34,7 @@ class HttpRouter
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def hashify_params(params)
|
37
|
-
|
37
|
+
@dynamic && params ? param_names.zip(params).inject({}) { |h, (k,v)| h[k] = v; h } : {}
|
38
38
|
end
|
39
39
|
|
40
40
|
def url(args, options)
|
@@ -50,7 +50,7 @@ class HttpRouter
|
|
50
50
|
end
|
51
51
|
|
52
52
|
private
|
53
|
-
def raw_url(args,options)
|
53
|
+
def raw_url(args, options)
|
54
54
|
raise UngeneratableRouteException
|
55
55
|
end
|
56
56
|
|
data/lib/http_router/route.rb
CHANGED
@@ -125,7 +125,7 @@ class HttpRouter
|
|
125
125
|
|
126
126
|
def url(*args)
|
127
127
|
result, extra_params = url_with_params(*args)
|
128
|
-
|
128
|
+
append_querystring(result, extra_params)
|
129
129
|
end
|
130
130
|
|
131
131
|
def clone(new_router)
|
@@ -262,5 +262,21 @@ class HttpRouter
|
|
262
262
|
end
|
263
263
|
path_obj
|
264
264
|
end
|
265
|
+
|
266
|
+
def append_querystring(uri, params)
|
267
|
+
if params && !params.empty?
|
268
|
+
uri_size = uri.size
|
269
|
+
params.each do |k,v|
|
270
|
+
case v
|
271
|
+
when Array
|
272
|
+
v.each { |v_part| uri << '&' << ::Rack::Utils.escape(k.to_s) << '%5B%5D=' << ::Rack::Utils.escape(v_part.to_s) }
|
273
|
+
else
|
274
|
+
uri << '&' << ::Rack::Utils.escape(k.to_s) << '=' << ::Rack::Utils.escape(v.to_s)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
uri[uri_size] = ??
|
278
|
+
end
|
279
|
+
uri
|
280
|
+
end
|
265
281
|
end
|
266
282
|
end
|
data/lib/http_router/version.rb
CHANGED
data/lib/http_router.rb
CHANGED
@@ -13,18 +13,45 @@ class HttpRouter
|
|
13
13
|
attr_reader :root, :routes, :known_methods, :named_routes
|
14
14
|
attr_accessor :default_app, :url_mount
|
15
15
|
|
16
|
+
# Raised when a Route is not able to be generated.
|
16
17
|
UngeneratableRouteException = Class.new(RuntimeError)
|
18
|
+
# Raised when a Route is generated that isn't valid.
|
17
19
|
InvalidRouteException = Class.new(RuntimeError)
|
20
|
+
# Raised when a Route is not able to be generated due to a missing parameter.
|
18
21
|
MissingParameterException = Class.new(RuntimeError)
|
19
22
|
|
20
|
-
|
21
|
-
|
23
|
+
# Creates a new HttpRouter.
|
24
|
+
# Can be called with either <tt>HttpRouter.new(proc{|env| ... }, { .. options .. })</tt> or with the first argument omitted.
|
25
|
+
# If there is a proc first, then it's used as the default app in the case of a non-match.
|
26
|
+
# Supported options are
|
27
|
+
# * :default_app -- Default application used if there is a non-match on #call. Defaults to 404 generator.
|
28
|
+
# * :ignore_trailing_slash -- Ignore a trailing / when attempting to match. Defaults to +true+.
|
29
|
+
# * :redirect_trailing_slash -- On trailing /, redirect to the same path without the /. Defaults to +false+.
|
30
|
+
def initialize(*args, &blk)
|
31
|
+
default_app, options = args.first.is_a?(Hash) ? [nil, args.first] : [args.first, args[1]]
|
22
32
|
@options = options
|
23
33
|
@default_app = default_app || options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
|
24
34
|
@ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
|
35
|
+
@redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
|
36
|
+
reset!
|
25
37
|
instance_eval(&blk) if blk
|
26
38
|
end
|
27
39
|
|
40
|
+
# Adds a path to be recognized.
|
41
|
+
#
|
42
|
+
# To assign a part of the path to a specific variable, use :variable_name within the route.
|
43
|
+
# For example, <tt>add('/path/:id')</tt> would match <tt>/path/test</tt>, with the variable <tt>:id</tt> having the value <tt>"test"</tt>.
|
44
|
+
#
|
45
|
+
# You can receive mulitple parts into a single variable by using the glob syntax.
|
46
|
+
# For example, <tt>add('/path/*id')</tt> would match <tt>/path/123/456/789</tt>, with the variable <tt>:id</tt> having the value <tt>["123", "456", "789"]</tt>.
|
47
|
+
#
|
48
|
+
# As well, paths can end with two optional parts, <tt>*</tt> and <tt>/?</tt>. If it ends with a <tt>*</tt>, it will match partially, returning the part of the path unmatched in the PATH_INFO value of the env. The part matched to will be returned in the SCRIPT_NAME. If it ends with <tt>/?</tt>, then a trailing / on the path will be optionally matched for that specific route. As trailing /'s are ignored by default, you probably don't actually want to use this option that frequently.
|
49
|
+
#
|
50
|
+
# Routes can also contain optional parts. There are surrounded with <tt>( )</tt>'s. If you need to match on a bracket in the route itself, you can escape the parentheses with a backslash.
|
51
|
+
#
|
52
|
+
# The second argument, options, is an optional hash that can modify the route in further ways. See HttpRouter::Route#with_options for details. Typically, you want to add further options to the route by calling additional methods on it. See HttpRouter::Route for further details.
|
53
|
+
#
|
54
|
+
# Returns the route object.
|
28
55
|
def add(path, opts = {}, &app)
|
29
56
|
route = case path
|
30
57
|
when Regexp
|
@@ -41,40 +68,65 @@ class HttpRouter
|
|
41
68
|
@routes << route
|
42
69
|
end
|
43
70
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
71
|
+
# Adds a path that only responds to the request method +GET+.
|
72
|
+
#
|
73
|
+
# Returns the route object.
|
74
|
+
def get(path, opts = {}, &app); add_with_request_method(path, :get, opts, &app); end
|
49
75
|
|
50
|
-
|
51
|
-
|
52
|
-
|
76
|
+
# Adds a path that only responds to the request method +POST+.
|
77
|
+
#
|
78
|
+
# Returns the route object.
|
79
|
+
def post(path, opts = {}, &app); add_with_request_method(path, :post, opts, &app); end
|
80
|
+
|
81
|
+
# Adds a path that only responds to the request method +HEAD+.
|
82
|
+
#
|
83
|
+
# Returns the route object.
|
84
|
+
def head(path, opts = {}, &app); add_with_request_method(path, :head, opts, &app); end
|
85
|
+
|
86
|
+
# Adds a path that only responds to the request method +DELETE+.
|
87
|
+
#
|
88
|
+
# Returns the route object.
|
89
|
+
def delete(path, opts = {}, &app); add_with_request_method(path, :delete, opts, &app); end
|
90
|
+
|
91
|
+
# Adds a path that only responds to the request method +PUT+.
|
92
|
+
#
|
93
|
+
# Returns the route object.
|
94
|
+
def put(path, opts = {}, &app); add_with_request_method(path, :put, opts, &app); end
|
53
95
|
|
54
96
|
def recognize(env)
|
55
97
|
call(env, false)
|
56
98
|
end
|
57
99
|
|
100
|
+
# Rack compatible #call. If matching route is found, and +dest+ value responds to #call, processing will pass to the matched route. Otherwise,
|
101
|
+
# the default application will be called. The router will be available in the env under the key <tt>router</tt>. And parameters matched will
|
102
|
+
# be available under the key <tt>router.params</tt>.
|
58
103
|
def call(env, perform_call = true)
|
59
104
|
rack_request = Rack::Request.new(env)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
test_env = Rack::Request.new(rack_request.env.clone)
|
65
|
-
test_env.env['REQUEST_METHOD'] = m
|
66
|
-
test_env.env['HTTP_ROUTER_405_TESTING_ACCEPTANCE'] = true
|
67
|
-
test_request = Request.new(test_env.path_info, test_env, 405)
|
68
|
-
catch(:success) { @root[test_request] }
|
69
|
-
end
|
70
|
-
supported_methods.empty? ? @default_app.call(env) : [405, {'Allow' => supported_methods.sort.join(", ")}, []]
|
71
|
-
elsif response
|
72
|
-
response
|
105
|
+
if redirect_trailing_slash? && (rack_request.head? || rack_request.get?) && rack_request.path_info[-1] == ?/
|
106
|
+
response = ::Rack::Response.new
|
107
|
+
response.redirect(request.path_info[0, request.path_info.size - 1], 302)
|
108
|
+
response.finish
|
73
109
|
else
|
74
|
-
|
110
|
+
request = Request.new(rack_request.path_info, rack_request, perform_call)
|
111
|
+
response = catch(:success) { @root[request] }
|
112
|
+
if !response
|
113
|
+
supported_methods = (@known_methods - [env['REQUEST_METHOD']]).select do |m|
|
114
|
+
test_env = Rack::Request.new(rack_request.env.clone)
|
115
|
+
test_env.env['REQUEST_METHOD'] = m
|
116
|
+
test_env.env['HTTP_ROUTER_405_TESTING_ACCEPTANCE'] = true
|
117
|
+
test_request = Request.new(test_env.path_info, test_env, 405)
|
118
|
+
catch(:success) { @root[test_request] }
|
119
|
+
end
|
120
|
+
supported_methods.empty? ? @default_app.call(env) : [405, {'Allow' => supported_methods.sort.join(", ")}, []]
|
121
|
+
elsif response
|
122
|
+
response
|
123
|
+
else
|
124
|
+
@default_app.call(env)
|
125
|
+
end
|
75
126
|
end
|
76
127
|
end
|
77
128
|
|
129
|
+
# Resets the router to a clean state.
|
78
130
|
def reset!
|
79
131
|
@root = Node.new(self)
|
80
132
|
@default_app = Proc.new{ |env| Rack::Response.new("Your request couldn't be found", 404).finish }
|
@@ -83,32 +135,41 @@ class HttpRouter
|
|
83
135
|
@known_methods = ['GET', "POST", "PUT", "DELETE"]
|
84
136
|
end
|
85
137
|
|
138
|
+
# Assigns the default application.
|
139
|
+
def default(app)
|
140
|
+
@default_app = app
|
141
|
+
end
|
142
|
+
|
143
|
+
# Generate a URL for a specified route. This will accept a list of variable values plus any other variable names named as a hash.
|
144
|
+
# This first value must be either the Route object or the name of the route.
|
145
|
+
#
|
146
|
+
# Example:
|
147
|
+
# router = HttpRouter.new
|
148
|
+
# router.add('/:foo.:format).name(:test).compile
|
149
|
+
# router.url(:test, 123, 'html')
|
150
|
+
# # ==> "/123.html"
|
151
|
+
# router.url(:test, 123, :format => 'html')
|
152
|
+
# # ==> "/123.html"
|
153
|
+
# router.url(:test, :foo => 123, :format => 'html')
|
154
|
+
# # ==> "/123.html"
|
155
|
+
# router.url(:test, :foo => 123, :format => 'html', :fun => 'inthesun')
|
156
|
+
# # ==> "/123.html?fun=inthesun"
|
86
157
|
def url(route, *args)
|
87
158
|
case route
|
88
|
-
when Symbol then
|
159
|
+
when Symbol then @named_routes.key?(route) ? @named_routes[route].url(*args) : raise(UngeneratableRouteException)
|
89
160
|
when Route then route.url(*args)
|
90
161
|
else raise UngeneratableRouteException
|
91
162
|
end
|
92
163
|
end
|
93
164
|
|
165
|
+
# Ignore trailing slash feature enabled? See #initialize for details.
|
94
166
|
def ignore_trailing_slash?
|
95
167
|
@ignore_trailing_slash
|
96
168
|
end
|
97
169
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
params.each do |k,v|
|
102
|
-
case v
|
103
|
-
when Array
|
104
|
-
v.each { |v_part| uri << '&' << ::Rack::Utils.escape(k.to_s) << '%5B%5D=' << ::Rack::Utils.escape(v_part.to_s) }
|
105
|
-
else
|
106
|
-
uri << '&' << ::Rack::Utils.escape(k.to_s) << '=' << ::Rack::Utils.escape(v.to_s)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
uri[uri_size] = ??
|
110
|
-
end
|
111
|
-
uri
|
170
|
+
# Redirect trailing slash feature enabled? See #initialize for details.
|
171
|
+
def redirect_trailing_slash?
|
172
|
+
@redirect_trailing_slash
|
112
173
|
end
|
113
174
|
|
114
175
|
# Creates a deep-copy of the router.
|
@@ -126,4 +187,11 @@ class HttpRouter
|
|
126
187
|
end
|
127
188
|
cloned_router
|
128
189
|
end
|
190
|
+
|
191
|
+
private
|
192
|
+
def add_with_request_method(path, method, opts = {}, &app)
|
193
|
+
route = add(path, opts).send(method.to_sym)
|
194
|
+
route.to(app) if app
|
195
|
+
route
|
196
|
+
end
|
129
197
|
end
|
data/test/test_variable.rb
CHANGED
@@ -43,7 +43,7 @@ class TestVariable < MiniTest::Unit::TestCase
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def test_match_path
|
46
|
-
r = router { add
|
46
|
+
r = router { add %r{/(test123|\d+)} }
|
47
47
|
assert_route r, '/test123'
|
48
48
|
assert_route r, '/123'
|
49
49
|
assert_route nil, '/test123andmore'
|
@@ -92,6 +92,10 @@ class TestVariable < MiniTest::Unit::TestCase
|
|
92
92
|
assert_route '/test/*variable.:format', 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
|
93
93
|
end
|
94
94
|
|
95
|
+
def test_glob_with_optional_format
|
96
|
+
assert_route '/test/*variable(.:format)', 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
|
97
|
+
end
|
98
|
+
|
95
99
|
def test_glob_with_literal
|
96
100
|
assert_route '/test/*variable.html', 'test/one/two/three.html', {:variable => ['one', 'two', 'three']}
|
97
101
|
end
|
@@ -116,6 +120,7 @@ class TestVariable < MiniTest::Unit::TestCase
|
|
116
120
|
def test_match_path_with_groups
|
117
121
|
r = router { add(%r{/(?<year>\\d{4})/(?<month>\\d{2})/(?<day>\\d{2})/?}) }
|
118
122
|
assert_route r, '/1234/23/56', {:year => '1234', :month => '23', :day => '56'}
|
119
|
-
end
|
123
|
+
end
|
124
|
+
"
|
120
125
|
end
|
121
126
|
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 13
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 6
|
8
|
-
-
|
9
|
-
version: 0.6.
|
9
|
+
- 5
|
10
|
+
version: 0.6.5
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Joshua Hull
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2011-03-
|
18
|
+
date: 2011-03-23 00:00:00 -07:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
@@ -25,6 +26,7 @@ dependencies:
|
|
25
26
|
requirements:
|
26
27
|
- - ">="
|
27
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
28
30
|
segments:
|
29
31
|
- 1
|
30
32
|
- 0
|
@@ -40,6 +42,7 @@ dependencies:
|
|
40
42
|
requirements:
|
41
43
|
- - ~>
|
42
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 21
|
43
46
|
segments:
|
44
47
|
- 0
|
45
48
|
- 2
|
@@ -55,6 +58,7 @@ dependencies:
|
|
55
58
|
requirements:
|
56
59
|
- - ~>
|
57
60
|
- !ruby/object:Gem::Version
|
61
|
+
hash: 15
|
58
62
|
segments:
|
59
63
|
- 2
|
60
64
|
- 0
|
@@ -70,6 +74,7 @@ dependencies:
|
|
70
74
|
requirements:
|
71
75
|
- - ">="
|
72
76
|
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
73
78
|
segments:
|
74
79
|
- 0
|
75
80
|
version: "0"
|
@@ -83,6 +88,7 @@ dependencies:
|
|
83
88
|
requirements:
|
84
89
|
- - ">="
|
85
90
|
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
86
92
|
segments:
|
87
93
|
- 0
|
88
94
|
version: "0"
|
@@ -96,6 +102,7 @@ dependencies:
|
|
96
102
|
requirements:
|
97
103
|
- - ">="
|
98
104
|
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
99
106
|
segments:
|
100
107
|
- 0
|
101
108
|
version: "0"
|
@@ -109,6 +116,7 @@ dependencies:
|
|
109
116
|
requirements:
|
110
117
|
- - ">="
|
111
118
|
- !ruby/object:Gem::Version
|
119
|
+
hash: 3
|
112
120
|
segments:
|
113
121
|
- 0
|
114
122
|
version: "0"
|
@@ -122,6 +130,7 @@ dependencies:
|
|
122
130
|
requirements:
|
123
131
|
- - ~>
|
124
132
|
- !ruby/object:Gem::Version
|
133
|
+
hash: 23
|
125
134
|
segments:
|
126
135
|
- 1
|
127
136
|
- 0
|
@@ -208,6 +217,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
208
217
|
requirements:
|
209
218
|
- - ">="
|
210
219
|
- !ruby/object:Gem::Version
|
220
|
+
hash: 3
|
211
221
|
segments:
|
212
222
|
- 0
|
213
223
|
version: "0"
|
@@ -216,6 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
216
226
|
requirements:
|
217
227
|
- - ">="
|
218
228
|
- !ruby/object:Gem::Version
|
229
|
+
hash: 3
|
219
230
|
segments:
|
220
231
|
- 0
|
221
232
|
version: "0"
|