mynyml-simple_router 0.1 → 0.8
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/Rakefile +2 -2
- data/examples/rack_app.ru +45 -0
- data/lib/simple_router/engines/simple_engine.rb +95 -23
- data/lib/simple_router/routes.rb +12 -8
- data/simple_router.gemspec +3 -1
- data/test/engines/test_simple_engine.rb +120 -6
- data/test/test_dsl.rb +1 -1
- data/test/test_routes.rb +2 -2
- metadata +3 -1
data/Rakefile
CHANGED
@@ -22,7 +22,7 @@ end
|
|
22
22
|
|
23
23
|
spec = Gem::Specification.new do |s|
|
24
24
|
s.name = 'simple_router'
|
25
|
-
s.version = '0.
|
25
|
+
s.version = '0.8'
|
26
26
|
s.summary = "Minimalistic, simple router meant to be used with pure rack applications."
|
27
27
|
s.description = "Minimalistic, simple router meant to be used with pure rack applications."
|
28
28
|
s.author = "Martin Aumont"
|
@@ -30,7 +30,7 @@ spec = Gem::Specification.new do |s|
|
|
30
30
|
s.homepage = ''
|
31
31
|
s.has_rdoc = true
|
32
32
|
s.require_path = "lib"
|
33
|
-
s.files = all_except(/doc
|
33
|
+
s.files = all_except([/doc\/.*/, /pkg\/*/])
|
34
34
|
end
|
35
35
|
|
36
36
|
Rake::GemPackageTask.new(spec) do |p|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
root = Pathname(__FILE__).dirname.parent.expand_path
|
3
|
+
$:.unshift(root.join('lib'))
|
4
|
+
|
5
|
+
# run me with:
|
6
|
+
# $rackup examples/rack_app.ru
|
7
|
+
# --------------------------------------------------
|
8
|
+
require 'rubygems'
|
9
|
+
require 'simple_router'
|
10
|
+
|
11
|
+
class App
|
12
|
+
include SimpleRouter::DSL
|
13
|
+
|
14
|
+
get '/' do |params|
|
15
|
+
<<-html
|
16
|
+
<pre>
|
17
|
+
params: #{params.inspect}
|
18
|
+
</pre>
|
19
|
+
html
|
20
|
+
end
|
21
|
+
|
22
|
+
get '/:foo.:type' do |foo, type, params|
|
23
|
+
<<-html
|
24
|
+
<pre>
|
25
|
+
foo: #{foo.inspect}
|
26
|
+
type: #{type.inspect}
|
27
|
+
params: #{params.inspect}
|
28
|
+
</pre>
|
29
|
+
html
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
request = Rack::Request.new(env)
|
34
|
+
|
35
|
+
verb = request.request_method.downcase.to_sym
|
36
|
+
path = Rack::Utils.unescape(request.path_info)
|
37
|
+
|
38
|
+
route, args = self.class.routes.match(verb, path)
|
39
|
+
route.nil? ?
|
40
|
+
[404, {'Content-Type' => 'text/html'}, '404 page not found'] :
|
41
|
+
[200, {'Content-Type' => 'text/html'}, [route.action.call(*args.push(request.params))]]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
run App.new
|
@@ -1,29 +1,101 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
1
3
|
module SimpleRouter
|
2
4
|
module Engines
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
5
|
+
module SimpleEngine #:nodoc:
|
6
|
+
|
7
|
+
def self.match(*args)
|
8
|
+
Base.match(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Base
|
12
|
+
# Finds a route definition that matches a path
|
13
|
+
#
|
14
|
+
# ===== Arguments
|
15
|
+
# * path: actual path to match (e.g. ENV['PATH_INFO'])
|
16
|
+
# * routes: array of 'routes', where each route is composed of [pattern, options]. If route isn't an array, an empty options hash is assumed
|
17
|
+
#
|
18
|
+
# Currently, this engine implementation ignores route options.
|
19
|
+
#
|
20
|
+
# ===== Returns
|
21
|
+
# Array of two elements:
|
22
|
+
#
|
23
|
+
# * index 0: first matching route
|
24
|
+
# * index 1: array of values for the matched route's variables (in the order they were specified in the route)
|
25
|
+
#
|
26
|
+
# ===== Examples
|
27
|
+
#
|
28
|
+
# SimpleEngine.match('/foo', ['/', '/foo', '/bar/baz']) #=> ['/foo', []]
|
29
|
+
# SimpleEngine.match('/80/07/01', ['/:year/:month/:day']) #=> ['/foo', ['80', '07', '01']]
|
30
|
+
#
|
31
|
+
def self.match(path, routes)
|
32
|
+
path = Path.new(path)
|
33
|
+
patterns = routes.map {|route| Pattern.new(Array(route).first) }
|
34
|
+
|
35
|
+
patterns.each do |pattern|
|
36
|
+
return [pattern.to_s, pattern.vars] if pattern == path
|
37
|
+
end
|
38
|
+
|
39
|
+
[nil, []]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Path #:nodoc:
|
44
|
+
attr_accessor :parts, :ext
|
45
|
+
|
46
|
+
def initialize(path)
|
47
|
+
self.parts, self.ext = split_path(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
'/' + self.parts.join('/') + self.ext
|
25
52
|
end
|
26
|
-
|
53
|
+
|
54
|
+
private
|
55
|
+
def split_path(path)
|
56
|
+
path = path.to_s
|
57
|
+
ext = Pathname(path).extname
|
58
|
+
path = path.sub(/#{ext}$/,'')
|
59
|
+
parts = path.split('/').reject {|part| part.empty? }
|
60
|
+
[parts, ext]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Pattern < Path #:nodoc:
|
65
|
+
|
66
|
+
def variables
|
67
|
+
return [] unless @match
|
68
|
+
|
69
|
+
a = []
|
70
|
+
self.parts.each_with_index do |part,i|
|
71
|
+
a << @match.parts[i] if part[0] == ?:
|
72
|
+
end
|
73
|
+
a << @match.ext[1..-1] if self.ext[1] == ?:
|
74
|
+
a
|
75
|
+
end
|
76
|
+
alias :vars :variables
|
77
|
+
|
78
|
+
def ==(path)
|
79
|
+
is_match = size_match?(path) && ext_match?(path) && static_match?(path)
|
80
|
+
@match = path if is_match
|
81
|
+
is_match
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def size_match?(path)
|
86
|
+
self.parts.size == path.parts.size
|
87
|
+
end
|
88
|
+
|
89
|
+
def ext_match?(path)
|
90
|
+
(self.ext == path.ext) || (self.ext[1] == ?: && !path.ext.empty?)
|
91
|
+
end
|
92
|
+
|
93
|
+
def static_match?(path)
|
94
|
+
self.parts.each_with_index do |part,i|
|
95
|
+
return false unless part[0] == ?: || path.parts[i] == part
|
96
|
+
end
|
97
|
+
true
|
98
|
+
end
|
27
99
|
end
|
28
100
|
end
|
29
101
|
end
|
data/lib/simple_router/routes.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module SimpleRouter
|
2
|
-
class Routes < Array
|
2
|
+
class Routes < Array
|
3
3
|
|
4
4
|
# routing engine
|
5
5
|
attr_accessor :engine
|
@@ -13,16 +13,20 @@ module SimpleRouter
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def match(verb, path)
|
16
|
-
|
17
|
-
|
18
|
-
paths = routes.map {|route| route.path }
|
16
|
+
none = [nil, nil]
|
17
|
+
return none if self.empty?
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
routes = self.select {|route| route.verb == verb }
|
20
|
+
paths = routes.map {|route| route.path }
|
21
|
+
|
22
|
+
path, vars = self.engine.match(path, paths)
|
23
|
+
return none if path.nil?
|
24
|
+
|
25
|
+
route = routes.detect {|route| route.path == path }
|
26
|
+
[route, vars]
|
23
27
|
end
|
24
28
|
|
25
|
-
class Route
|
29
|
+
class Route
|
26
30
|
attr_accessor :verb,:path,:options,:action
|
27
31
|
|
28
32
|
def initialize(verb, path, options, &action)
|
data/simple_router.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.8"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Aumont
|
@@ -30,6 +30,8 @@ files:
|
|
30
30
|
- test/test_dsl.rb
|
31
31
|
- test/test_routes.rb
|
32
32
|
- test/test_helper.rb
|
33
|
+
- examples
|
34
|
+
- examples/rack_app.ru
|
33
35
|
- simple_router.gemspec
|
34
36
|
- TODO
|
35
37
|
- lib
|
@@ -1,12 +1,126 @@
|
|
1
1
|
require 'test/test_helper'
|
2
2
|
|
3
|
-
Engine = SimpleRouter::Engines::SimpleEngine
|
4
|
-
|
5
3
|
class SimpleEngineTest < Test::Unit::TestCase
|
4
|
+
include SimpleRouter::Engines::SimpleEngine
|
5
|
+
|
6
|
+
test "matches static paths" do
|
7
|
+
Base.match('/', ['/', '/foo']).first.should be('/')
|
8
|
+
Base.match('/foo', ['/', '/foo']).first.should be('/foo')
|
9
|
+
Base.match('/bar', ['/', '/foo']).first.should be(nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
test "matches variable paths" do
|
13
|
+
path, vars = Base.match('/80/07', ['/foo', '/:year/:month'])
|
14
|
+
path.should be('/:year/:month')
|
15
|
+
vars.should be(['80','07'])
|
16
|
+
end
|
17
|
+
|
18
|
+
test "matches hybrid paths" do
|
19
|
+
path, vars = Base.match('/archives/80/07', ['/foo', '/archives/:year/:month'])
|
20
|
+
path.should be('/archives/:year/:month')
|
21
|
+
vars.should be(['80','07'])
|
22
|
+
end
|
23
|
+
|
24
|
+
test "ignores leading slash in path" do
|
25
|
+
path, vars = Base.match('archives/80/07', ['/foo', '/archives/:year/:month'])
|
26
|
+
path.should be('/archives/:year/:month')
|
27
|
+
vars.should be(['80','07'])
|
28
|
+
end
|
29
|
+
|
30
|
+
test "no matches" do
|
31
|
+
path, vars = Base.match('/80/07/01', ['/foo', '/:year/:month'])
|
32
|
+
path.should be(nil)
|
33
|
+
vars.should be([])
|
34
|
+
end
|
35
|
+
|
36
|
+
test "treats extention as pattern part" do
|
37
|
+
path, vars = Base.match('/a/b.xml', ['/:foo/:bar', '/:foo/:bar.:type'])
|
38
|
+
path.should be('/:foo/:bar.:type')
|
39
|
+
vars.should be(['a','b','xml'])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class PatternTest < Test::Unit::TestCase
|
44
|
+
include SimpleRouter::Engines::SimpleEngine
|
45
|
+
|
46
|
+
test "static pattern matches a path" do
|
47
|
+
path = Path.new('/foo/bar')
|
48
|
+
pattern = Pattern.new('/foo/bar')
|
49
|
+
|
50
|
+
assert pattern == path
|
51
|
+
end
|
52
|
+
|
53
|
+
test "variable pattern matches a path" do
|
54
|
+
path = Path.new('/foo/bar')
|
55
|
+
pattern = Pattern.new('/:foo/:bar')
|
56
|
+
|
57
|
+
assert pattern == path
|
58
|
+
end
|
59
|
+
|
60
|
+
test "pattern variables" do
|
61
|
+
path = Path.new('/foo/bar/baz')
|
62
|
+
pattern = Pattern.new('/:a/:b/:c')
|
63
|
+
|
64
|
+
assert pattern == path
|
65
|
+
pattern.vars.should be(%w( foo bar baz ))
|
66
|
+
end
|
67
|
+
|
68
|
+
test "pattern variables with extention" do
|
69
|
+
path = Path.new('/foo/bar/baz.xml')
|
70
|
+
pattern = Pattern.new('/:a/:b/:c.:type')
|
71
|
+
|
72
|
+
assert pattern == path
|
73
|
+
pattern.vars.should be(%w( foo bar baz xml ))
|
74
|
+
end
|
75
|
+
|
76
|
+
test "variable pattern matches a path with static extention" do
|
77
|
+
path = Path.new('/foo/bar.xml')
|
78
|
+
pattern = Pattern.new('/:foo/:bar.xml')
|
79
|
+
|
80
|
+
assert pattern == path
|
81
|
+
end
|
82
|
+
|
83
|
+
test "variable pattern matches a path with variable extention" do
|
84
|
+
path = Path.new('/foo/bar.xml')
|
85
|
+
pattern = Pattern.new('/:foo/:bar.:type')
|
86
|
+
|
87
|
+
assert pattern == path
|
88
|
+
end
|
89
|
+
|
90
|
+
test "pattern without extention doesn't match path with extention" do
|
91
|
+
path = Path.new('/foo/bar.xml')
|
92
|
+
pattern = Pattern.new('/:foo/:bar')
|
93
|
+
|
94
|
+
assert pattern != path
|
95
|
+
end
|
96
|
+
|
97
|
+
test "pattern with static extention doesn't match path without extention" do
|
98
|
+
path = Path.new('/foo/bar')
|
99
|
+
pattern = Pattern.new('/:foo/:bar.xml')
|
100
|
+
|
101
|
+
assert pattern != path
|
102
|
+
end
|
103
|
+
|
104
|
+
test "pattern with variable extention doesn't match path without extention" do
|
105
|
+
path = Path.new('/foo/bar')
|
106
|
+
pattern = Pattern.new('/:foo/:bar.:type')
|
107
|
+
|
108
|
+
assert pattern != path
|
109
|
+
end
|
110
|
+
|
111
|
+
test "doesn't ignore dots in path parts" do
|
112
|
+
path = Path.new('/foo/bar.baz/abc')
|
113
|
+
pattern = Pattern.new('/:a/:b/:c')
|
114
|
+
|
115
|
+
assert pattern == path
|
116
|
+
pattern.variables.should be(%w( foo bar.baz abc ))
|
117
|
+
end
|
118
|
+
|
119
|
+
test "doesn't get confused with extention when path contains other dots" do
|
120
|
+
path = Path.new('/foo/bar.baz/abc.xml')
|
121
|
+
pattern = Pattern.new('/:a/:b/:c.:type')
|
6
122
|
|
7
|
-
|
8
|
-
|
9
|
-
Engine.match('/foo', ['/', '/foo']).should be('/foo')
|
10
|
-
Engine.match('/bar', ['/', '/foo']).should be(nil)
|
123
|
+
assert pattern == path
|
124
|
+
pattern.variables.should be(%w( foo bar.baz abc xml ))
|
11
125
|
end
|
12
126
|
end
|
data/test/test_dsl.rb
CHANGED
@@ -33,6 +33,6 @@ class DslTest < Test::Unit::TestCase
|
|
33
33
|
App.get('/bar') { 'bar' }
|
34
34
|
|
35
35
|
App.routes.match(:get, '/foo').should_not be( nil )
|
36
|
-
App.routes.match(:get, '/foo').action.call.should be('foo')
|
36
|
+
App.routes.match(:get, '/foo').first.action.call.should be('foo')
|
37
37
|
end
|
38
38
|
end
|
data/test/test_routes.rb
CHANGED
@@ -21,14 +21,14 @@ class RoutesTest < Test::Unit::TestCase
|
|
21
21
|
@routes.add(:get, '/bar', {}, &@action)
|
22
22
|
|
23
23
|
@routes.match(:get, '/bar').should_not be(nil)
|
24
|
-
@routes.match(:get, '/bar').path.should be('/bar')
|
24
|
+
@routes.match(:get, '/bar').first.path.should be('/bar')
|
25
25
|
end
|
26
26
|
|
27
27
|
test "returns nil when no route matches" do
|
28
28
|
@routes.add(:get, '/foo', {}, &@action)
|
29
29
|
@routes.add(:get, '/bar', {}, &@action)
|
30
30
|
|
31
|
-
@routes.match('/baz', :get).should be(nil)
|
31
|
+
@routes.match('/baz', :get).should be([nil,nil])
|
32
32
|
end
|
33
33
|
|
34
34
|
## engine
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mynyml-simple_router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.8"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Aumont
|
@@ -30,6 +30,8 @@ files:
|
|
30
30
|
- test/test_dsl.rb
|
31
31
|
- test/test_routes.rb
|
32
32
|
- test/test_helper.rb
|
33
|
+
- examples
|
34
|
+
- examples/rack_app.ru
|
33
35
|
- simple_router.gemspec
|
34
36
|
- TODO
|
35
37
|
- lib
|