http_router 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/http_router.rb +6 -11
- data/lib/http_router/node.rb +21 -16
- data/lib/http_router/node/arbitrary.rb +3 -2
- data/lib/http_router/node/destination.rb +3 -2
- data/lib/http_router/node/free_regex.rb +4 -4
- data/lib/http_router/node/glob.rb +3 -3
- data/lib/http_router/node/glob_regex.rb +19 -12
- data/lib/http_router/node/lookup.rb +12 -8
- data/lib/http_router/node/regex.rb +2 -2
- data/lib/http_router/node/request.rb +11 -3
- data/lib/http_router/node/root.rb +22 -0
- data/lib/http_router/node/spanning_regex.rb +12 -12
- data/lib/http_router/node/variable.rb +2 -2
- data/lib/http_router/request.rb +3 -2
- data/lib/http_router/route.rb +5 -4
- data/lib/http_router/version.rb +1 -1
- data/test/test_recognize.rb +10 -0
- data/test/test_variable.rb +20 -0
- metadata +3 -2
data/lib/http_router.rb
CHANGED
@@ -39,7 +39,7 @@ class HttpRouter
|
|
39
39
|
@ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
|
40
40
|
@redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
|
41
41
|
@known_methods = Set.new(options && options[:known_methods] || [])
|
42
|
-
@
|
42
|
+
@counter = 0
|
43
43
|
reset!
|
44
44
|
instance_eval(&blk) if blk
|
45
45
|
end
|
@@ -138,7 +138,7 @@ class HttpRouter
|
|
138
138
|
|
139
139
|
# Resets the router to a clean state.
|
140
140
|
def reset!
|
141
|
-
@routes, @named_routes, @root = [], {}, Node.new(self
|
141
|
+
@routes, @named_routes, @root = [], {}, Node::Root.new(self)
|
142
142
|
@default_app = Proc.new{ |env| ::Rack::Response.new("Your request couldn't be found", 404).finish }
|
143
143
|
end
|
144
144
|
|
@@ -147,15 +147,6 @@ class HttpRouter
|
|
147
147
|
@default_app = app
|
148
148
|
end
|
149
149
|
|
150
|
-
def register_node(n)
|
151
|
-
@nodes << n
|
152
|
-
@nodes.size - 1
|
153
|
-
end
|
154
|
-
|
155
|
-
def [](pos)
|
156
|
-
@nodes.at(pos)
|
157
|
-
end
|
158
|
-
|
159
150
|
# Generate a URL for a specified route. This will accept a list of variable values plus any other variable names named as a hash.
|
160
151
|
# This first value must be either the Route object or the name of the route.
|
161
152
|
#
|
@@ -208,6 +199,10 @@ class HttpRouter
|
|
208
199
|
@root.compile
|
209
200
|
end
|
210
201
|
|
202
|
+
def next_counter
|
203
|
+
@counter += 1
|
204
|
+
end
|
205
|
+
|
211
206
|
private
|
212
207
|
def no_response(env, perform_call = true)
|
213
208
|
supported_methods = (@known_methods - [env['REQUEST_METHOD']]).select do |m|
|
data/lib/http_router/node.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
|
+
autoload :Root, 'http_router/node/root'
|
3
4
|
autoload :Glob, 'http_router/node/glob'
|
5
|
+
autoload :GlobRegex, 'http_router/node/glob_regex'
|
4
6
|
autoload :Variable, 'http_router/node/variable'
|
5
7
|
autoload :Regex, 'http_router/node/regex'
|
6
8
|
autoload :SpanningRegex, 'http_router/node/spanning_regex'
|
@@ -25,6 +27,10 @@ class HttpRouter
|
|
25
27
|
add(Glob.new(@router, self))
|
26
28
|
end
|
27
29
|
|
30
|
+
def add_glob_regexp(matcher)
|
31
|
+
add(GlobRegex.new(@router, self, matcher))
|
32
|
+
end
|
33
|
+
|
28
34
|
def add_request(opts)
|
29
35
|
add(Request.new(@router, self, opts))
|
30
36
|
end
|
@@ -57,20 +63,15 @@ class HttpRouter
|
|
57
63
|
false
|
58
64
|
end
|
59
65
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
private
|
67
|
+
def inject_root_methods(code = nil, &blk)
|
68
|
+
if code
|
69
|
+
root.methods_module.module_eval(code, __FILE__, __LINE__)
|
64
70
|
else
|
65
|
-
|
71
|
+
root.methods_module.module_eval(&blk)
|
66
72
|
end
|
67
73
|
end
|
68
74
|
|
69
|
-
def compile
|
70
|
-
instance_eval "def [](request)\n#{to_code}\nnil\nend", __FILE__, __LINE__
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
75
|
def add(matcher)
|
75
76
|
@matchers << matcher unless matcher.usable?(@matchers.last)
|
76
77
|
@matchers.last
|
@@ -80,14 +81,18 @@ class HttpRouter
|
|
80
81
|
@matchers.map{ |m| "# #{m.class}\n" << m.to_code }.join("\n") << "\n"
|
81
82
|
end
|
82
83
|
|
84
|
+
def root
|
85
|
+
@router.root
|
86
|
+
end
|
87
|
+
|
83
88
|
def depth
|
84
|
-
p = @parent
|
85
|
-
d =
|
86
|
-
until p.nil?
|
87
|
-
d += 1
|
88
|
-
p = p.parent
|
89
|
-
end
|
89
|
+
d, p = 0, @parent
|
90
|
+
d, p = d + 1, p.parent until p.nil?
|
90
91
|
d
|
91
92
|
end
|
93
|
+
|
94
|
+
def use_named_captures?
|
95
|
+
//.respond_to?(:names)
|
96
|
+
end
|
92
97
|
end
|
93
98
|
end
|
@@ -5,7 +5,6 @@ class HttpRouter
|
|
5
5
|
|
6
6
|
def initialize(router, parent, sallow_partial, blk, param_names)
|
7
7
|
@allow_partial, @blk, @param_names = allow_partial, blk, param_names
|
8
|
-
@node_position = router.register_node(blk)
|
9
8
|
super(router, parent)
|
10
9
|
end
|
11
10
|
|
@@ -14,13 +13,15 @@ class HttpRouter
|
|
14
13
|
end
|
15
14
|
|
16
15
|
def to_code
|
16
|
+
b, method_name = @blk, :"blk_#{router.next_counter}"
|
17
|
+
inject_root_methods { define_method(method_name) { b } }
|
17
18
|
"#{"if request.path_finished?" unless @allow_partial}
|
18
19
|
request.continue = proc { |state|
|
19
20
|
if state
|
20
21
|
#{super}
|
21
22
|
end
|
22
23
|
}
|
23
|
-
|
24
|
+
#{method_name}[request, #{@param_names.nil? || @param_names.empty? ? '{}' : "Hash[#{@param_names.inspect}.zip(request.params)]"}]
|
24
25
|
request.continue = nil
|
25
26
|
#{"end" unless @allow_partial}"
|
26
27
|
end
|
@@ -5,7 +5,6 @@ class HttpRouter
|
|
5
5
|
|
6
6
|
def initialize(router, parent, blk, allow_partial)
|
7
7
|
@blk, @allow_partial = blk, allow_partial
|
8
|
-
@node_position = router.register_node(blk)
|
9
8
|
super(router, parent)
|
10
9
|
end
|
11
10
|
|
@@ -14,9 +13,11 @@ class HttpRouter
|
|
14
13
|
end
|
15
14
|
|
16
15
|
def to_code
|
16
|
+
b, method_name = @blk, :"blk_#{router.next_counter}"
|
17
|
+
inject_root_methods { define_method(method_name) { b } }
|
17
18
|
"#{"if request.path_finished?" unless @allow_partial}
|
18
19
|
request.passed_with = catch(:pass) do
|
19
|
-
|
20
|
+
#{method_name}[request, #{@param_names.nil? || @param_names.empty? ? 'nil' : "Hash[#{@param_names.inspect}.zip(request.params)]"}]
|
20
21
|
end
|
21
22
|
#{"end" unless @allow_partial}"
|
22
23
|
end
|
@@ -8,17 +8,17 @@ class HttpRouter
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def to_code
|
11
|
-
"whole_path = \"/\#{request.joined_path}\"
|
12
|
-
if match = #{matcher.inspect}.match(whole_path) and match[0].size == whole_path.size
|
11
|
+
"whole_path#{depth} = \"/\#{request.joined_path}\"
|
12
|
+
if match = #{matcher.inspect}.match(whole_path#{depth}) and match[0].size == whole_path#{depth}.size
|
13
13
|
request.extra_env['router.regex_match'] = match
|
14
14
|
old_path = request.path
|
15
15
|
request.path = ['']
|
16
|
-
" << (
|
16
|
+
" << (use_named_captures? ?
|
17
17
|
"match.names.size.times{|i| request.params << match[i + 1]} if match.respond_to?(:names) && match.names" : "") << "
|
18
18
|
#{super}
|
19
19
|
request.path = old_path
|
20
20
|
request.extra_env.delete('router.regex_match')
|
21
|
-
" << (
|
21
|
+
" << (use_named_captures? ?
|
22
22
|
"params.slice!(-match.names.size, match.names.size)" : ""
|
23
23
|
) << "
|
24
24
|
end"
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
3
|
class Glob < Node
|
4
|
+
alias_method :node_to_code, :to_code
|
4
5
|
def usable?(other)
|
5
6
|
other.class == self.class
|
6
7
|
end
|
@@ -9,12 +10,11 @@ class HttpRouter
|
|
9
10
|
"request.params << (globbed_params#{depth} = [])
|
10
11
|
remaining_parts = request.path.dup
|
11
12
|
until remaining_parts.empty?
|
12
|
-
globbed_params#{depth} <<
|
13
|
+
globbed_params#{depth} << remaining_parts.shift
|
13
14
|
request.path = remaining_parts
|
14
15
|
#{super}
|
15
16
|
end
|
16
|
-
request.path[0,0] = request.params.pop
|
17
|
-
"
|
17
|
+
request.path[0,0] = request.params.pop"
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,18 +1,25 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
|
-
class GlobRegex <
|
3
|
+
class GlobRegex < Glob
|
4
|
+
attr_reader :matcher
|
5
|
+
def initialize(router, parent, matcher)
|
6
|
+
@matcher = matcher
|
7
|
+
super router, parent
|
8
|
+
end
|
9
|
+
|
10
|
+
def usable?(other)
|
11
|
+
other.class == self.class && other.matcher == matcher
|
12
|
+
end
|
13
|
+
|
4
14
|
def to_code
|
5
|
-
"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
request.path =
|
13
|
-
#{@capturing_indicies.size == 1 ? "request.params.pop" : "request.params.slice!(#{-@capturing_indicies.size}, #{@capturing_indicies.size})"}
|
14
|
-
end
|
15
|
-
"
|
15
|
+
"request.params << (globbed_params#{depth} = [])
|
16
|
+
remaining_parts = request.path.dup
|
17
|
+
while !remaining_parts.empty? and match = remaining_parts.first.match(#{@matcher.inspect}) and match[0] == remaining_parts.first
|
18
|
+
globbed_params#{depth} << remaining_parts.shift
|
19
|
+
request.path = remaining_parts
|
20
|
+
#{node_to_code}
|
21
|
+
end
|
22
|
+
request.path[0,0] = request.params.pop"
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|
@@ -15,15 +15,19 @@ class HttpRouter
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_code
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
inject_root_methods @map.keys.map {|k|
|
19
|
+
method = :"lookup_#{object_id}_#{k.hash}"
|
20
|
+
"define_method(#{method.inspect}) do |request|
|
21
|
+
part = request.path.shift
|
22
|
+
#{@map[k].map{|n| n.to_code} * "\n"}
|
23
|
+
request.path.unshift part
|
24
|
+
end"}.join("\n")
|
25
|
+
code = "
|
26
|
+
unless request.path_finished?
|
27
|
+
m = \"lookup_#{object_id}_\#{request.path.first.hash}\"
|
28
|
+
send(m, request) if respond_to?(m)
|
25
29
|
end
|
26
|
-
|
30
|
+
"
|
27
31
|
end
|
28
32
|
end
|
29
33
|
end
|
@@ -17,8 +17,8 @@ class HttpRouter
|
|
17
17
|
params_size = @splitting_indicies.size + @capturing_indicies.size
|
18
18
|
"if match = #{@matcher.inspect}.match(request.path.first) and match.begin(0).zero?
|
19
19
|
part = request.path.shift\n" <<
|
20
|
-
@splitting_indicies.map { |s| "request.params <<
|
21
|
-
@capturing_indicies.map { |c| "request.params <<
|
20
|
+
@splitting_indicies.map { |s| "request.params << match[#{s}].split(/\\//)\n" }.join <<
|
21
|
+
@capturing_indicies.map { |c| "request.params << match[#{c}]\n" }.join << "
|
22
22
|
#{super}
|
23
23
|
request.path.unshift part
|
24
24
|
#{params_size == 1 ? "request.params.pop" : "request.params.slice!(#{-params_size}, #{params_size})"}
|
@@ -16,13 +16,21 @@ class HttpRouter
|
|
16
16
|
def to_code
|
17
17
|
code = "if "
|
18
18
|
code << @opts.map do |k,v|
|
19
|
-
case v
|
20
|
-
when
|
21
|
-
else
|
19
|
+
case v.size
|
20
|
+
when 1 then to_code_condition(k, v.first)
|
21
|
+
else "(#{v.map{|k, vv| to_code_condition(k, vv)}.join(' or ')})"
|
22
22
|
end
|
23
23
|
end * ' and '
|
24
24
|
code << "\n #{super}\nend"
|
25
25
|
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def to_code_condition(k, v)
|
29
|
+
case v
|
30
|
+
when String then "#{v.inspect} == request.rack_request.#{k}"
|
31
|
+
else "#{v.inspect} === request.rack_request.#{k}"
|
32
|
+
end
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
28
36
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class HttpRouter
|
2
|
+
class Node
|
3
|
+
class Root < Node
|
4
|
+
attr_reader :methods_module
|
5
|
+
def initialize(router)
|
6
|
+
super(router, nil)
|
7
|
+
@methods_module = Module.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](request)
|
11
|
+
compile
|
12
|
+
self[request]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def compile
|
17
|
+
root.extend(root.methods_module)
|
18
|
+
instance_eval "def [](request)\n#{to_code}\nnil\nend", __FILE__, __LINE__
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -3,18 +3,18 @@ class HttpRouter
|
|
3
3
|
class SpanningRegex < Regex
|
4
4
|
def to_code
|
5
5
|
params_count = (@splitting_indicies || []).size + @capturing_indicies.size
|
6
|
-
"whole_path = request.joined_path
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
6
|
+
"whole_path#{depth} = request.joined_path
|
7
|
+
if match = #{@matcher.inspect}.match(whole_path#{depth}) and match.begin(0).zero?
|
8
|
+
original_path#{depth} = request.path.dup
|
9
|
+
" <<
|
10
|
+
(@splitting_indicies || []).map { |s| "request.params << match[#{s}].split(/\\//)\n" }.join <<
|
11
|
+
@capturing_indicies.map { |c| "request.params << match[#{c}]\n" }.join << "
|
12
|
+
remaining_path = whole_path#{depth}[match[0].size + (whole_path#{depth}[match[0].size] == ?/ ? 1 : 0), whole_path#{depth}.size]
|
13
|
+
request.path = remaining_path.split('/')
|
14
|
+
#{node_to_code}
|
15
|
+
request.path = original_path#{depth}
|
16
|
+
request.params.slice!(#{-params_count.size}, #{params_count})
|
17
|
+
end
|
18
18
|
"
|
19
19
|
end
|
20
20
|
end
|
@@ -7,9 +7,9 @@ class HttpRouter
|
|
7
7
|
|
8
8
|
def to_code
|
9
9
|
"unless request.path_finished?
|
10
|
-
request.params <<
|
10
|
+
request.params << request.path.shift
|
11
11
|
#{super}
|
12
|
-
request.path.unshift
|
12
|
+
request.path.unshift request.params.pop
|
13
13
|
end"
|
14
14
|
end
|
15
15
|
end
|
data/lib/http_router/request.rb
CHANGED
@@ -5,8 +5,9 @@ class HttpRouter
|
|
5
5
|
alias_method :rack, :rack_request
|
6
6
|
def initialize(path, rack_request, perform_call, &acceptance_test)
|
7
7
|
@rack_request, @perform_call, @acceptance_test = rack_request, perform_call, acceptance_test
|
8
|
-
@path = (path
|
9
|
-
@path
|
8
|
+
@path = URI.unescape(path).split(/\//)
|
9
|
+
@path.shift if @path.first == ''
|
10
|
+
@path.push('') if path[-1] == ?/
|
10
11
|
@extra_env = {}
|
11
12
|
@params = []
|
12
13
|
end
|
data/lib/http_router/route.rb
CHANGED
@@ -210,9 +210,9 @@ class HttpRouter
|
|
210
210
|
when ?*
|
211
211
|
param_names << name.to_sym
|
212
212
|
matches_with[name.to_sym] = @opts[name.to_sym]
|
213
|
-
@opts[name.to_sym] ? node.
|
213
|
+
@opts[name.to_sym] ? node.add_glob_regexp(@opts.delete(name.to_sym)) : node.add_glob
|
214
214
|
else
|
215
|
-
node.add_lookup(
|
215
|
+
node.add_lookup(parts[0])
|
216
216
|
end
|
217
217
|
else
|
218
218
|
capturing_indicies = []
|
@@ -237,9 +237,10 @@ class HttpRouter
|
|
237
237
|
name = part[1, part.size].to_sym
|
238
238
|
param_names << name
|
239
239
|
matches_with[name] = @opts[name]
|
240
|
-
|
240
|
+
@opts[name] ?
|
241
|
+
"((?:#{@opts[name]}\\/?)+)" : '(.*?)'
|
241
242
|
else
|
242
|
-
Regexp.quote(
|
243
|
+
Regexp.quote(part)
|
243
244
|
end
|
244
245
|
end
|
245
246
|
node = spans ? node.add_spanning_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies) :
|
data/lib/http_router/version.rb
CHANGED
data/test/test_recognize.rb
CHANGED
@@ -92,6 +92,16 @@ class TestRecognition < MiniTest::Unit::TestCase
|
|
92
92
|
assert_body '/', router.call(Rack::MockRequest.env_for('/'))
|
93
93
|
end
|
94
94
|
|
95
|
+
def test_request_mutation
|
96
|
+
got_this_far = false
|
97
|
+
non_matching, matching = router {
|
98
|
+
add("/test/:var/:var2/*glob").matching(:var2 => /123/, :glob => /[a-z]+/).get.arbitrary{|env, params| got_this_far = true; false}
|
99
|
+
add("/test/:var/:var2/*glob").matching(:var2 => /123/, :glob => /[a-z]+/).get
|
100
|
+
}
|
101
|
+
assert_route matching, '/test/123/123/asd/aasd/zxcqwe/asdzxc', {:var => '123', :var2 => '123', :glob => %w{asd aasd zxcqwe asdzxc}}
|
102
|
+
assert got_this_far, "matching should have gotten this far"
|
103
|
+
end
|
104
|
+
|
95
105
|
def test_multiple_partial
|
96
106
|
test, root = router {
|
97
107
|
add("/test").partial.to{|env| [200, {}, ['/test',env['PATH_INFO']]]}
|
data/test/test_variable.rb
CHANGED
@@ -90,6 +90,19 @@ class TestVariable < MiniTest::Unit::TestCase
|
|
90
90
|
assert_route '/test/*variable/test', '/test/one/two/three/test', {:variable => ['one', 'two', 'three']}
|
91
91
|
end
|
92
92
|
|
93
|
+
def test_glob_with_regexp
|
94
|
+
r = router { add('/test/*variable', :variable => /[a-z]+/) }
|
95
|
+
assert_route nil, '/test/asd/123'
|
96
|
+
assert_route nil, '/test/asd/asd123'
|
97
|
+
assert_route r, '/test/asd/qwe', :variable => ['asd', 'qwe']
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_glob_with_regexp_and_static
|
101
|
+
r = router { add('/test/*variable/test', :variable => /[a-z]+/) }
|
102
|
+
assert_route nil, '/test/asd/123/test'
|
103
|
+
assert_route r, '/test/asd/qwe/test', :variable => ['asd', 'qwe']
|
104
|
+
end
|
105
|
+
|
93
106
|
def test_glob_with_variable
|
94
107
|
assert_route '/test/*variable/:test', '/test/one/two/three', {:variable => ['one', 'two'], :test => 'three'}
|
95
108
|
end
|
@@ -98,6 +111,13 @@ class TestVariable < MiniTest::Unit::TestCase
|
|
98
111
|
assert_route '/test/*variable.:format', 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
|
99
112
|
end
|
100
113
|
|
114
|
+
def test_glob_regexp_with_format
|
115
|
+
r = router { add('/test/*variable.:format', :variable => /[a-z]+/, :format => 'html') }
|
116
|
+
assert_route nil, 'test/one/2/three.html'
|
117
|
+
assert_route nil, 'test/one/two/three.xml'
|
118
|
+
assert_route r, 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
|
119
|
+
end
|
120
|
+
|
101
121
|
def test_glob_with_optional_format
|
102
122
|
assert_route '/test/*variable(.:format)', 'test/one/two/three.html', {:variable => ['one', 'two', 'three'], :format => 'html'}
|
103
123
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: http_router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.8.
|
5
|
+
version: 0.8.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Joshua Hull
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-01 00:00:00 -
|
13
|
+
date: 2011-06-01 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -155,6 +155,7 @@ files:
|
|
155
155
|
- lib/http_router/node/lookup.rb
|
156
156
|
- lib/http_router/node/regex.rb
|
157
157
|
- lib/http_router/node/request.rb
|
158
|
+
- lib/http_router/node/root.rb
|
158
159
|
- lib/http_router/node/spanning_regex.rb
|
159
160
|
- lib/http_router/node/variable.rb
|
160
161
|
- lib/http_router/optional_compiler.rb
|