hart 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ rack-test -v 0.6.3
2
+ cutest -v 1.2.2
3
+ seg -v 0.0.1
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
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest -r ./test/helper.rb ./test/*.rb
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
@@ -0,0 +1,15 @@
1
+ require_relative "../lib/hart"
2
+ require "rack/test"
3
+
4
+ class Driver
5
+ include Rack::Test::Methods
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def app
12
+ @app
13
+ end
14
+ end
15
+
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: []