hart 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gems +3 -0
- data/CHANGELOG +0 -0
- data/CONTRIBUTING +19 -0
- data/LICENSE +19 -0
- data/README.md +164 -0
- data/hart.gemspec +16 -0
- data/lib/hart.rb +50 -0
- data/makefile +4 -0
- data/test/all.rb +139 -0
- data/test/helper.rb +15 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0e2f4a971dd9e107927c8c5773c11fe9ddad051f
|
4
|
+
data.tar.gz: a0c803538d828027597f92fb858e8fd6ec045196
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a3edcbd23fc68ef7e909f9519867de831d4d91e9faff7e87f71f393977b107922e350f028c15e100a01a89ec8aaea52ecfcfbe312776c9480a944e7d919a4ac
|
7
|
+
data.tar.gz: e9b7d0b13c984f6b683893326eccd32b400d71dbbd243ec0c6e388d6630e09f409745f653ab155a1d64a5a661bfff28e29f3ad3fd707e5c1a9eb815a1292f130
|
data/.gems
ADDED
data/CHANGELOG
ADDED
File without changes
|
data/CONTRIBUTING
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
This code tries to solve a particular problem with a very simple
|
2
|
+
implementation. We try to keep the code to a minimum while making
|
3
|
+
it as clear as possible. The design is very likely finished, and
|
4
|
+
if some feature is missing it is possible that it was left out on
|
5
|
+
purpose. That said, new usage patterns may arise, and when that
|
6
|
+
happens we are ready to adapt if necessary.
|
7
|
+
|
8
|
+
A good first step for contributing is to meet us on IRC and discuss
|
9
|
+
ideas. We spend a lot of time on #lesscode at freenode, always ready
|
10
|
+
to talk about code and simplicity. If connecting to IRC is not an
|
11
|
+
option, you can create an issue explaining the proposed change and
|
12
|
+
a use case. We pay a lot of attention to use cases, because our
|
13
|
+
goal is to keep the code base simple. Usually the result of a
|
14
|
+
conversation is the creation of a different tool.
|
15
|
+
|
16
|
+
Please don't start the conversation with a pull request. The code
|
17
|
+
should come at last, and even though it may help to convey an idea,
|
18
|
+
more often than not it draws the attention to a particular
|
19
|
+
implementation.
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Michel Martens
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
Hart
|
2
|
+
====
|
3
|
+
|
4
|
+
Hash router
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Hart is a generic routing library that walks a hash and detects
|
10
|
+
matches according to a given pair of verb and path arguments.
|
11
|
+
|
12
|
+
Usage
|
13
|
+
-----
|
14
|
+
|
15
|
+
A basic example would look like this:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
words = {
|
19
|
+
"c" => {
|
20
|
+
"o" => {
|
21
|
+
"d" => {
|
22
|
+
define: "A husk; a pod; as, a peascod.",
|
23
|
+
|
24
|
+
"a" => {
|
25
|
+
define: "Concluding passage of a piece or movement."
|
26
|
+
},
|
27
|
+
|
28
|
+
"e" => {
|
29
|
+
define: "System of rules relating to one subject.",
|
30
|
+
|
31
|
+
"c" => {
|
32
|
+
define: "Device that compresses and decompresses data."
|
33
|
+
},
|
34
|
+
|
35
|
+
"x" => {
|
36
|
+
define: "A book; a manuscript."
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
h = Hart.new(routes)
|
45
|
+
h.call(:define, "/c/o/d") #=> "A husk; a pod; as, a peascod."
|
46
|
+
h.call(:define, "/c/o/d/e") #=> "System of rules relating to one subject."
|
47
|
+
h.call(:define, "/c/o/d/e/r") #=> nil
|
48
|
+
```
|
49
|
+
|
50
|
+
The `call` method accepts two arguments: a verb, which must be a
|
51
|
+
symbol, and a path, which must be a string in the form `"/a/b/c"`.
|
52
|
+
|
53
|
+
It is possible to define more than one verb per element:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
countries = {
|
57
|
+
"argentina" => {
|
58
|
+
population: 43_417_000,
|
59
|
+
area: 2_780_400,
|
60
|
+
},
|
61
|
+
|
62
|
+
"france" => {
|
63
|
+
population: 66_200_000,
|
64
|
+
area: 640_679,
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
h = Hart.new(countries)
|
69
|
+
h.call(:population, "/argentina") #=> 43417000
|
70
|
+
h.call(:population, "/france") #=> 66200000
|
71
|
+
h.call(:area, "/argentina") #=> 2780400
|
72
|
+
h.call(:area, "/france") #=> 640679
|
73
|
+
```
|
74
|
+
|
75
|
+
The default value for a miss can be configured as follows:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
countries[:default] = "Country not found."
|
79
|
+
```
|
80
|
+
|
81
|
+
After adding that key to the previous example, any miss will return
|
82
|
+
the string `"Country not found."`.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
h.call(:population, "/foobar") #=> "Country not found."
|
86
|
+
```
|
87
|
+
|
88
|
+
The value of each entry can be anything that responds to `[](key)`.
|
89
|
+
For instance, a `proc` could be used:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
countries["peru"] = proc { |key|
|
93
|
+
case key
|
94
|
+
when :population
|
95
|
+
30_400_000
|
96
|
+
when :area
|
97
|
+
1_285_216
|
98
|
+
else
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
h.call(:population, "/peru") #=> 30400000
|
104
|
+
```
|
105
|
+
|
106
|
+
If the special symbol `:id` is present as an element, it will match
|
107
|
+
any path segment in that position, given that no other matches
|
108
|
+
occurred.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
countries[:id] = proc { |key|
|
112
|
+
"No information about #{key}"
|
113
|
+
}
|
114
|
+
|
115
|
+
h.call(:area, "/foobar") #=> "No information about foobar"
|
116
|
+
```
|
117
|
+
|
118
|
+
The next example combines all the concepts explained so far, and
|
119
|
+
shows how to match HTTP requests based on `REQUEST_METHOD` and
|
120
|
+
`PATH_INFO`.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
routes = {
|
124
|
+
default: [404, {}, ["Not Found"]],
|
125
|
+
|
126
|
+
GET: [200, {}, ["GET /"]],
|
127
|
+
|
128
|
+
"foo" => {
|
129
|
+
"bar" => {
|
130
|
+
GET: [200, {}, ["GET /foo/bar"]],
|
131
|
+
PUT: [200, {}, ["PUT /foo/bar"]],
|
132
|
+
}
|
133
|
+
},
|
134
|
+
|
135
|
+
"users" => {
|
136
|
+
GET: [200, {}, ["GET /posts"]],
|
137
|
+
|
138
|
+
id: proc { |id|
|
139
|
+
{
|
140
|
+
GET: [200, {}, ["GET /users/#{id}"]],
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
h = Hart.new(routes)
|
147
|
+
h.call(:GET, "/foo/bar") #=> [200, {}, ["GET /foo/bar"]]
|
148
|
+
h.call(:PUT, "/foo/bar") #=> [200, {}, ["PUT /foo/bar"]]
|
149
|
+
h.call(:GET, "/users/42") #=> [200, {}, ["GET /users/42"]]
|
150
|
+
h.call(:GET, "/baz") #=> [404, {}, ["Not Found"]]
|
151
|
+
```
|
152
|
+
|
153
|
+
API
|
154
|
+
---
|
155
|
+
|
156
|
+
`call`: Accepts two arguments: a verb, which must be a symbol, and
|
157
|
+
a path, which must be a string in the form `"/a/b/c"`.
|
158
|
+
|
159
|
+
Installation
|
160
|
+
------------
|
161
|
+
|
162
|
+
```
|
163
|
+
$ gem install hart
|
164
|
+
```
|
data/hart.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "hart"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = "Hash routing"
|
5
|
+
s.description = "Hash routing"
|
6
|
+
s.authors = ["Michel Martens"]
|
7
|
+
s.email = ["michel@soveran.com"]
|
8
|
+
s.homepage = "https://github.com/soveran/hart"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_dependency "seg"
|
14
|
+
s.add_development_dependency "cutest"
|
15
|
+
s.add_development_dependency "rack-test"
|
16
|
+
end
|
data/lib/hart.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright (c) 2015 Michel Martens
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require "seg"
|
24
|
+
|
25
|
+
class Hart
|
26
|
+
attr_reader :routes
|
27
|
+
|
28
|
+
def initialize(routes)
|
29
|
+
@routes = routes
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(verb, path)
|
33
|
+
seg = Seg.new(path)
|
34
|
+
|
35
|
+
inbox = {}
|
36
|
+
route = @routes
|
37
|
+
|
38
|
+
while seg.capture(:segment, inbox)
|
39
|
+
route = route[inbox[:segment]] ||
|
40
|
+
route[:id] && route[:id][inbox[:segment]]
|
41
|
+
break if route.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
if route
|
45
|
+
route[verb.intern]
|
46
|
+
else
|
47
|
+
routes[:default]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/makefile
ADDED
data/test/all.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
scope "generic routing" do
|
2
|
+
peru = proc { |key|
|
3
|
+
case key
|
4
|
+
when :population
|
5
|
+
30_400_000
|
6
|
+
when :area
|
7
|
+
1_285_216
|
8
|
+
else
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
}
|
12
|
+
|
13
|
+
@words = {
|
14
|
+
"peru" => peru,
|
15
|
+
"c" => {
|
16
|
+
"o" => {
|
17
|
+
"d" => {
|
18
|
+
define: "A husk; a pod; as, a peascod.",
|
19
|
+
|
20
|
+
"a" => {
|
21
|
+
define: "Concluding passage of a piece or movement."
|
22
|
+
},
|
23
|
+
|
24
|
+
"e" => {
|
25
|
+
define: "System of rules relating to one subject.",
|
26
|
+
|
27
|
+
"c" => {
|
28
|
+
define: "Device that compresses and decompresses data."
|
29
|
+
},
|
30
|
+
|
31
|
+
"x" => {
|
32
|
+
define: "A book; a manuscript."
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
setup do
|
41
|
+
Hart.new(@words)
|
42
|
+
end
|
43
|
+
|
44
|
+
test do |h|
|
45
|
+
definition = h.call(:population, "/peru")
|
46
|
+
assert_equal definition, 30400000
|
47
|
+
|
48
|
+
definition = h.call(:define, "/c/o/d")
|
49
|
+
assert_equal definition, "A husk; a pod; as, a peascod."
|
50
|
+
|
51
|
+
definition = h.call(:define, "/c/o/d/a")
|
52
|
+
assert_equal definition, "Concluding passage of a piece or movement."
|
53
|
+
|
54
|
+
definition = h.call(:define, "/c/o/d/e")
|
55
|
+
assert_equal definition, "System of rules relating to one subject."
|
56
|
+
|
57
|
+
definition = h.call(:define, "/c/o/d/e/c")
|
58
|
+
assert_equal definition, "Device that compresses and decompresses data."
|
59
|
+
|
60
|
+
definition = h.call(:define, "/c/o/d/e/x")
|
61
|
+
assert_equal definition, "A book; a manuscript."
|
62
|
+
|
63
|
+
definition = h.call(:define, "/c/o/d/e/r")
|
64
|
+
assert_equal definition, nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
scope "routing HTTP requests" do
|
69
|
+
|
70
|
+
class HartWrapper
|
71
|
+
PATH_INFO = "PATH_INFO".freeze
|
72
|
+
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
73
|
+
|
74
|
+
def initialize(hart)
|
75
|
+
@hart = hart
|
76
|
+
end
|
77
|
+
|
78
|
+
def call(env)
|
79
|
+
@hart.call(env.fetch(REQUEST_METHOD), env.fetch(PATH_INFO))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@routes = {
|
84
|
+
default: [404, {}, [""]],
|
85
|
+
|
86
|
+
GET: [200, {}, ["GET /"]],
|
87
|
+
|
88
|
+
"foo" => {
|
89
|
+
"bar" => {
|
90
|
+
GET: [200, {}, ["GET /foo/bar"]],
|
91
|
+
PUT: [200, {}, ["PUT /foo/bar"]],
|
92
|
+
}
|
93
|
+
},
|
94
|
+
|
95
|
+
"users" => {
|
96
|
+
GET: [200, {}, ["GET /posts"]],
|
97
|
+
|
98
|
+
id: proc { |id|
|
99
|
+
{
|
100
|
+
GET: [200, {}, ["GET /users/#{id}"]],
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
app = HartWrapper.new(Hart.new(@routes))
|
107
|
+
|
108
|
+
setup do
|
109
|
+
Driver.new(app)
|
110
|
+
end
|
111
|
+
|
112
|
+
test "path + verb" do |f|
|
113
|
+
f.get("/foo/bar")
|
114
|
+
assert_equal 200, f.last_response.status
|
115
|
+
assert_equal "GET /foo/bar", f.last_response.body
|
116
|
+
|
117
|
+
f.put("/foo/bar")
|
118
|
+
assert_equal 200, f.last_response.status
|
119
|
+
assert_equal "PUT /foo/bar", f.last_response.body
|
120
|
+
end
|
121
|
+
|
122
|
+
test "verbs match only on root" do |f|
|
123
|
+
f.get("/bar/baz/foo")
|
124
|
+
assert_equal "", f.last_response.body
|
125
|
+
assert_equal 404, f.last_response.status
|
126
|
+
end
|
127
|
+
|
128
|
+
test "root" do |f|
|
129
|
+
f.get("/")
|
130
|
+
assert_equal "GET /", f.last_response.body
|
131
|
+
assert_equal 200, f.last_response.status
|
132
|
+
end
|
133
|
+
|
134
|
+
test "captures" do |f|
|
135
|
+
f.get("/users/42")
|
136
|
+
assert_equal "GET /users/42", f.last_response.body
|
137
|
+
assert_equal 200, f.last_response.status
|
138
|
+
end
|
139
|
+
end
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hart
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michel Martens
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: seg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cutest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack-test
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Hash routing
|
56
|
+
email:
|
57
|
+
- michel@soveran.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gems"
|
63
|
+
- CHANGELOG
|
64
|
+
- CONTRIBUTING
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- hart.gemspec
|
68
|
+
- lib/hart.rb
|
69
|
+
- makefile
|
70
|
+
- test/all.rb
|
71
|
+
- test/helper.rb
|
72
|
+
homepage: https://github.com/soveran/hart
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.4.5.1
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Hash routing
|
96
|
+
test_files: []
|