hobby 0.0.0 → 0.0.1
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.
- checksums.yaml +4 -4
- data/Gemfile +7 -0
- data/LICENSE +1 -0
- data/hobby.gemspec +5 -14
- data/lib/hobby/app.rb +36 -43
- data/lib/hobby/router.rb +26 -14
- data/lib/hobby/rspec/router.rb +35 -0
- data/lib/hobby.rb +3 -4
- data/spec/app_spec.rb +146 -0
- data/spec/apps/Env.rb +15 -0
- data/spec/apps/Main.rb +4 -0
- data/spec/apps/Map.rb +5 -0
- data/spec/apps/Nested.rb +17 -0
- data/spec/apps/OneRouteRouter.rb +8 -0
- data/spec/apps/Use.rb +15 -0
- data/spec/apps/WithoutPath.rb +3 -0
- data/spec/helper.rb +21 -0
- data/spec/router_group_spec.rb +46 -0
- data/spec/router_spec.rb +37 -0
- metadata +31 -114
- data/lib/hobby/router/route.rb +0 -20
- data/test/helper.rb +0 -5
- data/test/test_app.rb +0 -189
- data/test/test_router.rb +0 -65
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42e7f7a0e7818909ac5d95c50484655aab19fac1
|
|
4
|
+
data.tar.gz: 7b0f857a9f4ceb6dfdb0eff86d6d546d2b285a6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2f3740ca3b95e942af4bf6dc4fb5f8d5acfdd21bd6c48f0544b8f262de0f004856a31547f3739ad440a11195c80a0348e4c4237afb026d4fa566dc6cb138ad2
|
|
7
|
+
data.tar.gz: fe87a3f5ca1be2c5ee1a346cdeb9e26800c642e4de8251217f8461ba91c8dbb63c7b52ea1a208edb35f6851d27b47e385df4bfb153849065e826fc1b2ace1fed
|
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/hobby.gemspec
CHANGED
|
@@ -4,12 +4,11 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = 'hobby'
|
|
7
|
-
spec.version = '0.0.
|
|
8
|
-
spec.authors = ['
|
|
9
|
-
spec.email = ['
|
|
10
|
-
spec.
|
|
11
|
-
spec.
|
|
12
|
-
spec.homepage = 'https://github.com/patriciomacadden/hobbit'
|
|
7
|
+
spec.version = '0.0.1'
|
|
8
|
+
spec.authors = ['Anatoly Cherno']
|
|
9
|
+
spec.email = ['anatoly.cherno@gmail.com']
|
|
10
|
+
spec.summary = %q{A minimal DSL over rack}
|
|
11
|
+
spec.homepage = 'https://github.com/ch1c0t/hobby'
|
|
13
12
|
spec.license = 'MIT'
|
|
14
13
|
|
|
15
14
|
spec.files = `git ls-files`.split($/)
|
|
@@ -18,12 +17,4 @@ Gem::Specification.new do |spec|
|
|
|
18
17
|
spec.require_paths = ['lib']
|
|
19
18
|
|
|
20
19
|
spec.add_dependency 'rack'
|
|
21
|
-
spec.add_dependency 'include_constants'
|
|
22
|
-
|
|
23
|
-
spec.add_development_dependency 'bundler', '~> 1.3'
|
|
24
|
-
spec.add_development_dependency 'codeclimate-test-reporter'
|
|
25
|
-
spec.add_development_dependency 'rack-test'
|
|
26
|
-
spec.add_development_dependency 'rake'
|
|
27
|
-
spec.add_development_dependency 'minitest'
|
|
28
|
-
spec.add_development_dependency 'minitest-power_assert'
|
|
29
20
|
end
|
data/lib/hobby/app.rb
CHANGED
|
@@ -1,66 +1,59 @@
|
|
|
1
1
|
module Hobby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
module App
|
|
3
|
+
def self.included app
|
|
4
|
+
app.extend Singleton
|
|
5
|
+
app.builder, app.router = Rack::Builder.new, Router.new
|
|
6
|
+
app.request, app.response = Rack::Request, Rack::Response
|
|
7
|
+
end
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
end
|
|
15
|
-
end
|
|
9
|
+
module Singleton
|
|
10
|
+
attr_accessor :builder, :router, :request, :response
|
|
11
|
+
|
|
12
|
+
def new (*)
|
|
13
|
+
builder.run super
|
|
14
|
+
builder.to_app
|
|
16
15
|
end
|
|
17
16
|
|
|
17
|
+
extend Forwardable
|
|
18
|
+
delegate [:map, :use] => :builder
|
|
19
|
+
|
|
18
20
|
Verbs.each do |verb|
|
|
19
|
-
define_method verb.downcase do |path, &route|
|
|
21
|
+
define_method verb.downcase do |path = '/', &route|
|
|
20
22
|
router.add_route verb, path, &route
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
|
-
|
|
24
|
-
alias :_new :new
|
|
25
|
-
def new *args, &block
|
|
26
|
-
builder.run _new(*args, &block)
|
|
27
|
-
builder
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
extend Forwardable
|
|
31
|
-
delegate [:map, :use] => :builder
|
|
32
25
|
end
|
|
33
26
|
|
|
34
|
-
attr_reader :env, :request, :response
|
|
35
|
-
|
|
36
27
|
def call env
|
|
37
28
|
dup.handle env
|
|
38
29
|
end
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@response = Response.new
|
|
31
|
+
protected
|
|
32
|
+
def handle env
|
|
33
|
+
route = self.class.router.route_for (@env = env)
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
if route
|
|
36
|
+
response.write instance_exec &route
|
|
37
|
+
else
|
|
38
|
+
response.status = 404
|
|
39
|
+
end
|
|
47
40
|
|
|
48
|
-
|
|
41
|
+
response
|
|
42
|
+
end
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
44
|
+
private
|
|
45
|
+
attr_reader :env
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
def request
|
|
48
|
+
@request ||= self.class.request.new env
|
|
49
|
+
end
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
response
|
|
59
|
-
else
|
|
60
|
-
response.status = 404
|
|
51
|
+
def response
|
|
52
|
+
@response ||= self.class.response.new
|
|
61
53
|
end
|
|
62
54
|
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
def my
|
|
56
|
+
env.fetch :path_params, {}
|
|
57
|
+
end
|
|
65
58
|
end
|
|
66
59
|
end
|
data/lib/hobby/router.rb
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
module Hobby
|
|
2
2
|
class Router
|
|
3
|
-
require_relative 'router/route'
|
|
4
|
-
|
|
5
3
|
def initialize
|
|
6
|
-
@routes =
|
|
4
|
+
@routes = Routes.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def add_route verb, path, &route
|
|
8
|
+
path = nil if path.eql? '/'
|
|
9
|
+
@routes["#{verb}#{path}"] = route
|
|
7
10
|
end
|
|
8
11
|
|
|
9
|
-
def
|
|
10
|
-
@routes[
|
|
11
|
-
|
|
12
|
+
def route_for env
|
|
13
|
+
route, params = @routes["#{env['REQUEST_METHOD']}#{env['PATH_INFO']}"]
|
|
14
|
+
env[:path_params] = params if params
|
|
15
|
+
route
|
|
12
16
|
end
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
class Routes < Hash
|
|
19
|
+
def initialize
|
|
20
|
+
@patterns = {}
|
|
21
|
+
super { |hash, key| hash[key] = find key }
|
|
17
22
|
end
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
def []= key, route
|
|
25
|
+
if key.include? ?:
|
|
26
|
+
string = key.gsub(/(:\w+)/) { "(?<#{$1[1..-1]}>[^/?#]+)" }
|
|
27
|
+
@patterns[/^#{string}$/] = route
|
|
28
|
+
else
|
|
29
|
+
super and super "#{key}/", route
|
|
23
30
|
end
|
|
24
31
|
end
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def find key
|
|
36
|
+
_, route = @patterns.find { |pattern, _| pattern.match key }
|
|
37
|
+
[route, $~.names.map(&:to_sym).zip($~.captures).to_h] if route
|
|
38
|
+
end
|
|
27
39
|
end
|
|
28
40
|
end
|
|
29
41
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Hobby::RSpec
|
|
2
|
+
module Router
|
|
3
|
+
extend self
|
|
4
|
+
SOME_ROUTE = ->{}
|
|
5
|
+
|
|
6
|
+
def env_for path, verb = 'GET'
|
|
7
|
+
{'REQUEST_METHOD' => verb, 'PATH_INFO' => path }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_routes *routes
|
|
11
|
+
routes.each { |route| subject.add_route 'GET', route, &SOME_ROUTE }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
extend RSpec::Matchers::DSL
|
|
15
|
+
|
|
16
|
+
define :find_route do |path, verb = 'GET'|
|
|
17
|
+
match do |subject|
|
|
18
|
+
env = env_for path, verb
|
|
19
|
+
route = subject.route_for env
|
|
20
|
+
|
|
21
|
+
params_are_ok = (@params ? (@params.to_a - env[:path_params].to_a).empty? : true)
|
|
22
|
+
|
|
23
|
+
(route == SOME_ROUTE) && params_are_ok
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
chain :and_set_params do |**params|
|
|
27
|
+
@params = params
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.included example_group
|
|
32
|
+
example_group.extend self
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/hobby.rb
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
require 'rack'
|
|
2
2
|
require 'forwardable'
|
|
3
|
-
require 'include_constants'
|
|
4
3
|
|
|
5
4
|
module Hobby
|
|
6
5
|
Verbs = %w!DELETE GET HEAD OPTIONS PATCH POST PUT!
|
|
7
|
-
require 'hobby/app'
|
|
8
|
-
autoload :Router, 'hobby/router'
|
|
9
|
-
include_constants :Builder, :Request, :Response, from: Rack
|
|
10
6
|
end
|
|
7
|
+
|
|
8
|
+
require 'hobby/router'
|
|
9
|
+
require 'hobby/app'
|
data/spec/app_spec.rb
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
require 'rack/test'
|
|
3
|
+
|
|
4
|
+
$sources = {}
|
|
5
|
+
|
|
6
|
+
Dir['spec/apps/*.rb'].each do |file|
|
|
7
|
+
name = File.basename file, '.rb'
|
|
8
|
+
|
|
9
|
+
eval %!
|
|
10
|
+
class #{name}
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :app
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
!
|
|
16
|
+
|
|
17
|
+
$sources[Object.const_get name] = IO.read file
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_app described_class
|
|
21
|
+
body = $sources[described_class]
|
|
22
|
+
|
|
23
|
+
eval %!
|
|
24
|
+
Class.new do
|
|
25
|
+
include Hobby::App
|
|
26
|
+
#{body}
|
|
27
|
+
end
|
|
28
|
+
!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe Hobby::App do
|
|
32
|
+
include Rack::Test::Methods
|
|
33
|
+
|
|
34
|
+
describe '.new' do
|
|
35
|
+
context 'an app with nested app(s)' do
|
|
36
|
+
let(:subject) { build_app(Nested).new }
|
|
37
|
+
it { should be_a Rack::URLMap }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context 'an app without nested app(s)' do
|
|
41
|
+
let(:subject) { build_app(Main).new }
|
|
42
|
+
it { should be_a Hobby::App }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '#call' do
|
|
47
|
+
def app
|
|
48
|
+
build_app(Main).new
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'when a route was found' do
|
|
52
|
+
it 'evaluates the route' do
|
|
53
|
+
get '/existent'
|
|
54
|
+
|
|
55
|
+
assert { last_response.ok? }
|
|
56
|
+
assert { last_response.body == 'existent' }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context 'when a route was not found' do
|
|
61
|
+
it 'responds with 404 status code' do
|
|
62
|
+
get '/nonexistent/route'
|
|
63
|
+
|
|
64
|
+
assert { last_response.not_found? }
|
|
65
|
+
assert { last_response.body.empty? }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'gives a distinct object to each request' do
|
|
70
|
+
get '/first'
|
|
71
|
+
assert { last_response.body == 'first' }
|
|
72
|
+
|
|
73
|
+
get '/second'
|
|
74
|
+
assert { last_response.body != 'first' }
|
|
75
|
+
assert { last_response.body == 'second' }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe :integration do
|
|
80
|
+
before do
|
|
81
|
+
described_class.app = build_app described_class
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def app
|
|
85
|
+
described_class.app.new
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe Map do
|
|
89
|
+
it 'mounts an application to the rack stack' do
|
|
90
|
+
get '/map'
|
|
91
|
+
assert { last_response.body == 'from map' }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe Use do
|
|
96
|
+
it 'adds a middleware to the rack stack' do
|
|
97
|
+
get '/use'
|
|
98
|
+
assert { last_response.body == 'from use' }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe WithoutPath do
|
|
103
|
+
it 'is accessible as /' do
|
|
104
|
+
get '/'
|
|
105
|
+
assert { last_response.body == 'root' }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe OneRouteRouter do
|
|
110
|
+
it 'returns for any route' do
|
|
111
|
+
get '/'
|
|
112
|
+
assert { last_response.body == 'for any route' }
|
|
113
|
+
|
|
114
|
+
get '/some-other-route'
|
|
115
|
+
assert { last_response.body == 'for any route' }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe Env do
|
|
120
|
+
it do
|
|
121
|
+
get '/query_string?1=2&3=4'
|
|
122
|
+
assert { last_response.body == '1=2&3=4' }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it do
|
|
126
|
+
get '/access_params?key=value'
|
|
127
|
+
assert { last_response.body == 'value' }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it do
|
|
131
|
+
get '/access_path_params_via_my'
|
|
132
|
+
assert { last_response.body == 'true' }
|
|
133
|
+
|
|
134
|
+
get '/access_path_params_via_env'
|
|
135
|
+
assert { last_response.body == 'true' }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe Nested do
|
|
140
|
+
it do
|
|
141
|
+
get '/nested'
|
|
142
|
+
assert { last_response.body == 'a:b:c' }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/spec/apps/Env.rb
ADDED
data/spec/apps/Main.rb
ADDED
data/spec/apps/Map.rb
ADDED
data/spec/apps/Nested.rb
ADDED
data/spec/apps/Use.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
middleware = Class.new do
|
|
2
|
+
def initialize(app = nil)
|
|
3
|
+
@app = app
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def call(env)
|
|
7
|
+
request = Rack::Request.new(env)
|
|
8
|
+
@app.call(env) unless request.path_info == '/use'
|
|
9
|
+
[200, {}, 'from use']
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
use middleware
|
|
14
|
+
|
|
15
|
+
get('/') { 'hello world' }
|
data/spec/helper.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'devtools/spec_helper'
|
|
2
|
+
|
|
3
|
+
require 'hobby'
|
|
4
|
+
require 'hobby/rspec/router'
|
|
5
|
+
|
|
6
|
+
require 'minitest'
|
|
7
|
+
require 'minitest-power_assert'
|
|
8
|
+
Minitest::Assertions.prepend Minitest::PowerAssert::Assertions
|
|
9
|
+
|
|
10
|
+
if defined? Mutant
|
|
11
|
+
class Mutant::Selector::Expression
|
|
12
|
+
def call _subject
|
|
13
|
+
integration.all_tests
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
RSpec.configure do |config|
|
|
19
|
+
config.expect_with :rspec, :minitest
|
|
20
|
+
config.include Hobby::RSpec::Router, type: :router
|
|
21
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
|
|
3
|
+
describe Hobby::RSpec::Router do
|
|
4
|
+
before do
|
|
5
|
+
@Group = Class.new do
|
|
6
|
+
def subject
|
|
7
|
+
@router ||= Hobby::Router.new
|
|
8
|
+
end
|
|
9
|
+
end.include described_class
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'contains a few helpers in its singleton' do
|
|
13
|
+
[:env_for, :add_routes, :find_route].each do |name|
|
|
14
|
+
assert { @Group.respond_to? name }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe 'env_for' do
|
|
19
|
+
context 'two arguments passed' do
|
|
20
|
+
it 'returns an environment' do
|
|
21
|
+
env = { "REQUEST_METHOD" => 'POST', "PATH_INFO" => '/some_path' }
|
|
22
|
+
assert { env == (@Group.env_for '/some_path', 'POST') }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'one argument passed' do
|
|
27
|
+
it 'defaults to GET' do
|
|
28
|
+
env = { "REQUEST_METHOD" => 'GET', "PATH_INFO" => '/some_path' }
|
|
29
|
+
assert { env == (@Group.env_for '/some_path') }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe 'add_routes' do
|
|
35
|
+
it do
|
|
36
|
+
routes = %w! first second third!
|
|
37
|
+
group = @Group.new
|
|
38
|
+
|
|
39
|
+
group.add_routes *routes
|
|
40
|
+
|
|
41
|
+
routes.each do |route|
|
|
42
|
+
expect(group.subject.route_for group.env_for 'first').to be_truthy
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/spec/router_spec.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
|
|
3
|
+
describe Hobby::Router, type: :router do
|
|
4
|
+
routes = %w!
|
|
5
|
+
/with.dot
|
|
6
|
+
/with-hyphen
|
|
7
|
+
/hello/:name
|
|
8
|
+
/say/:something/to/:someone
|
|
9
|
+
/route/:id.json
|
|
10
|
+
/existent/only/for/GET
|
|
11
|
+
!
|
|
12
|
+
|
|
13
|
+
before { add_routes *routes }
|
|
14
|
+
|
|
15
|
+
it { should find_route '/with.dot' }
|
|
16
|
+
it { should find_route '/with-hyphen' }
|
|
17
|
+
it { should find_route '/with.dot/' }
|
|
18
|
+
it { should find_route '/with-hyphen/' }
|
|
19
|
+
|
|
20
|
+
it { should find_route '/existent/only/for/GET' }
|
|
21
|
+
it { should_not find_route '/nonexistent' }
|
|
22
|
+
it { should_not find_route '/existent/only/for/GET', 'POST' }
|
|
23
|
+
|
|
24
|
+
it { should find_route('/hello/ololo').and_set_params(name: 'ololo') }
|
|
25
|
+
it { should find_route('/route/66.json').and_set_params(id: '66') }
|
|
26
|
+
it do
|
|
27
|
+
should find_route('/say/nothing/to/no_one').
|
|
28
|
+
and_set_params(something: 'nothing', someone: 'no_one')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
it 'memoizes requests with params' do
|
|
33
|
+
subject.route_for env_for '/hello/ololo'
|
|
34
|
+
|
|
35
|
+
assert { subject.instance_variable_get(:@routes).include? 'GET/hello/ololo' }
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hobby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Anatoly Cherno
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2016-11-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -24,107 +24,9 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
-
|
|
28
|
-
name: include_constants
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :runtime
|
|
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: bundler
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '1.3'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '1.3'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: codeclimate-test-reporter
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: rack-test
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: rake
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: minitest
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - ">="
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
111
|
-
- !ruby/object:Gem::Dependency
|
|
112
|
-
name: minitest-power_assert
|
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0'
|
|
118
|
-
type: :development
|
|
119
|
-
prerelease: false
|
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - ">="
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0'
|
|
125
|
-
description: A minimalistic microframework built on top of rack
|
|
27
|
+
description:
|
|
126
28
|
email:
|
|
127
|
-
-
|
|
29
|
+
- anatoly.cherno@gmail.com
|
|
128
30
|
executables: []
|
|
129
31
|
extensions: []
|
|
130
32
|
extra_rdoc_files: []
|
|
@@ -140,11 +42,19 @@ files:
|
|
|
140
42
|
- lib/hobby.rb
|
|
141
43
|
- lib/hobby/app.rb
|
|
142
44
|
- lib/hobby/router.rb
|
|
143
|
-
- lib/hobby/router
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
|
|
45
|
+
- lib/hobby/rspec/router.rb
|
|
46
|
+
- spec/app_spec.rb
|
|
47
|
+
- spec/apps/Env.rb
|
|
48
|
+
- spec/apps/Main.rb
|
|
49
|
+
- spec/apps/Map.rb
|
|
50
|
+
- spec/apps/Nested.rb
|
|
51
|
+
- spec/apps/OneRouteRouter.rb
|
|
52
|
+
- spec/apps/Use.rb
|
|
53
|
+
- spec/apps/WithoutPath.rb
|
|
54
|
+
- spec/helper.rb
|
|
55
|
+
- spec/router_group_spec.rb
|
|
56
|
+
- spec/router_spec.rb
|
|
57
|
+
homepage: https://github.com/ch1c0t/hobby
|
|
148
58
|
licenses:
|
|
149
59
|
- MIT
|
|
150
60
|
metadata: {}
|
|
@@ -164,12 +74,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
164
74
|
version: '0'
|
|
165
75
|
requirements: []
|
|
166
76
|
rubyforge_project:
|
|
167
|
-
rubygems_version: 2.
|
|
77
|
+
rubygems_version: 2.5.1
|
|
168
78
|
signing_key:
|
|
169
79
|
specification_version: 4
|
|
170
|
-
summary: A
|
|
80
|
+
summary: A minimal DSL over rack
|
|
171
81
|
test_files:
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
|
|
82
|
+
- spec/app_spec.rb
|
|
83
|
+
- spec/apps/Env.rb
|
|
84
|
+
- spec/apps/Main.rb
|
|
85
|
+
- spec/apps/Map.rb
|
|
86
|
+
- spec/apps/Nested.rb
|
|
87
|
+
- spec/apps/OneRouteRouter.rb
|
|
88
|
+
- spec/apps/Use.rb
|
|
89
|
+
- spec/apps/WithoutPath.rb
|
|
90
|
+
- spec/helper.rb
|
|
91
|
+
- spec/router_group_spec.rb
|
|
92
|
+
- spec/router_spec.rb
|
data/lib/hobby/router/route.rb
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
class Hobby::Router
|
|
2
|
-
class Route
|
|
3
|
-
attr_reader :compiled_path, :extra_params, :path
|
|
4
|
-
def initialize(path, &block)
|
|
5
|
-
@path = path
|
|
6
|
-
@block = block
|
|
7
|
-
|
|
8
|
-
@extra_params = []
|
|
9
|
-
compiled_path = path.gsub(/:\w+/) do |match|
|
|
10
|
-
@extra_params << match.gsub(':', '').to_sym
|
|
11
|
-
'([^/?#]+)'
|
|
12
|
-
end
|
|
13
|
-
@compiled_path = /^#{compiled_path}$/
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def to_proc
|
|
17
|
-
@block
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
data/test/helper.rb
DELETED
data/test/test_app.rb
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
require_relative 'helper'
|
|
2
|
-
|
|
3
|
-
module Minitest
|
|
4
|
-
class Test
|
|
5
|
-
include Rack::Test::Methods
|
|
6
|
-
|
|
7
|
-
def mock_app(&block)
|
|
8
|
-
@app = Class.new(Hobby::App, &block).new
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def app
|
|
12
|
-
@app
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
describe Hobby::App do
|
|
18
|
-
describe :main_app do
|
|
19
|
-
before do
|
|
20
|
-
mock_app do
|
|
21
|
-
Hobby::Verbs.each do |verb|
|
|
22
|
-
class_eval "#{verb.downcase}('/') { '#{verb}' }"
|
|
23
|
-
class_eval "#{verb.downcase}('/route.json') { '#{verb} /route.json' }"
|
|
24
|
-
class_eval "#{verb.downcase}('/route/:id.json') { request.params[:id] }"
|
|
25
|
-
class_eval "#{verb.downcase}('/:name') { request.params[:name] }"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
Hobby::Verbs.each do |verb|
|
|
31
|
-
describe 'when the request matches a route' do
|
|
32
|
-
it "matches #{verb} ''" do
|
|
33
|
-
send verb.downcase, ''
|
|
34
|
-
assert last_response.ok?
|
|
35
|
-
assert_equal verb, last_response.body
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
it 'matches #{verb} /' do
|
|
39
|
-
send verb.downcase, '/'
|
|
40
|
-
assert last_response.ok?
|
|
41
|
-
assert_equal verb, last_response.body
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it 'matches #{verb} /route.json' do
|
|
45
|
-
send verb.downcase, '/route.json'
|
|
46
|
-
assert last_response.ok?
|
|
47
|
-
assert_equal "#{verb} /route.json", last_response.body
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it 'matches #{verb} /route/:id.json' do
|
|
51
|
-
send verb.downcase, '/route/1.json'
|
|
52
|
-
assert last_response.ok?
|
|
53
|
-
assert_equal '1', last_response.body
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
it 'matches #{verb} /:name' do
|
|
57
|
-
send verb.downcase, '/hobbit'
|
|
58
|
-
assert last_response.ok?
|
|
59
|
-
assert_equal 'hobbit', last_response.body
|
|
60
|
-
|
|
61
|
-
send verb.downcase, '/hello-hobbit'
|
|
62
|
-
assert last_response.ok?
|
|
63
|
-
assert_equal 'hello-hobbit', last_response.body
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
describe 'when the request not matches a route' do
|
|
68
|
-
it 'responds with 404 status code' do
|
|
69
|
-
send verb.downcase, '/not/found'
|
|
70
|
-
assert last_response.not_found?
|
|
71
|
-
assert_equal '', last_response.body
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
describe :map_app do
|
|
78
|
-
before do
|
|
79
|
-
mock_app do
|
|
80
|
-
map '/map' do
|
|
81
|
-
run Proc.new { |env| [200, {}, ['from map']] }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
get('/') { 'hello world' }
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
it 'mounts an application to the rack stack' do
|
|
89
|
-
get '/map'
|
|
90
|
-
assert_equal 'from map', last_response.body
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
describe :use_app do
|
|
95
|
-
before do
|
|
96
|
-
mock_app do
|
|
97
|
-
middleware = Class.new do
|
|
98
|
-
def initialize(app = nil)
|
|
99
|
-
@app = app
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def call(env)
|
|
103
|
-
request = Rack::Request.new(env)
|
|
104
|
-
@app.call(env) unless request.path_info == '/use'
|
|
105
|
-
[200, {}, 'from use']
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
use middleware
|
|
110
|
-
|
|
111
|
-
get('/') { 'hello world' }
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
it 'adds a middleware to the rack stack' do
|
|
116
|
-
get '/use'
|
|
117
|
-
assert_equal 'from use', last_response.body
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
describe :halt_app do
|
|
122
|
-
before do
|
|
123
|
-
mock_app do
|
|
124
|
-
get '/halt' do
|
|
125
|
-
response.status = 501
|
|
126
|
-
halt response.finish
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
get '/halt_finished' do
|
|
130
|
-
halt [404, {}, ['Not found']]
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
it 'halts the execution with a response' do
|
|
136
|
-
get '/halt'
|
|
137
|
-
assert { last_response.status == 501 }
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
it 'halts the execution with a finished response' do
|
|
141
|
-
get '/halt_finished'
|
|
142
|
-
assert { last_response.status == 404 }
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
describe :router_app do
|
|
147
|
-
before do
|
|
148
|
-
mock_app do
|
|
149
|
-
router do
|
|
150
|
-
Class.new do
|
|
151
|
-
def add_route(*)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def route_for _request
|
|
155
|
-
Proc.new { 'for any route' }
|
|
156
|
-
end
|
|
157
|
-
end.new
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
it 'returns for any route' do
|
|
163
|
-
get '/'
|
|
164
|
-
assert { last_response.body == 'for any route' }
|
|
165
|
-
|
|
166
|
-
get '/some-other-route'
|
|
167
|
-
assert { last_response.body == 'for any route' }
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
describe :custom_members do
|
|
172
|
-
before do
|
|
173
|
-
mock_app do
|
|
174
|
-
builder { Rack::Builder.new }
|
|
175
|
-
Request = Rack::Request
|
|
176
|
-
Response = Rack::Response
|
|
177
|
-
|
|
178
|
-
get '/' do
|
|
179
|
-
'it works'
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
it 'works' do
|
|
185
|
-
get '/'
|
|
186
|
-
assert { last_response.body == 'it works' }
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
data/test/test_router.rb
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
require_relative 'helper'
|
|
2
|
-
|
|
3
|
-
def request_to path, request_method: 'GET'
|
|
4
|
-
Hobby::Request.new Rack::MockRequest.env_for "http://example.com:8080/#{path}", method: request_method
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
describe Hobby::Router do
|
|
8
|
-
before do
|
|
9
|
-
@router = Hobby::Router.new
|
|
10
|
-
@route = -> { :wrapped }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it 'works' do
|
|
14
|
-
@router.add_route 'GET', '/ololo', &@route
|
|
15
|
-
|
|
16
|
-
route = @router.route_for request_to 'ololo'
|
|
17
|
-
assert { route.to_proc.call == :wrapped }
|
|
18
|
-
|
|
19
|
-
route = @router.route_for request_to 'ololo2'
|
|
20
|
-
assert { route.nil? }
|
|
21
|
-
|
|
22
|
-
route = @router.route_for request_to 'ololo', request_method: 'POST'
|
|
23
|
-
assert { route.nil? }
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'with .' do
|
|
27
|
-
@router.add_route 'GET', '/route.json', &@route
|
|
28
|
-
|
|
29
|
-
route = @router.route_for request_to 'route.json'
|
|
30
|
-
assert { route.to_proc.call == :wrapped }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it 'with -' do
|
|
34
|
-
@router.add_route 'GET', '/hello-world', &@route
|
|
35
|
-
|
|
36
|
-
route = @router.route_for request_to 'hello-world'
|
|
37
|
-
assert { route.to_proc.call == :wrapped }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'with params' do
|
|
41
|
-
@router
|
|
42
|
-
.add_route 'GET', '/hello/:name' do :first end
|
|
43
|
-
.add_route 'GET', '/say/:something/to/:someone' do :second end
|
|
44
|
-
|
|
45
|
-
request = request_to 'hello/ololo'
|
|
46
|
-
route = @router.route_for request
|
|
47
|
-
assert { route.to_proc.call == :first }
|
|
48
|
-
assert { request.params[:name] == 'ololo'}
|
|
49
|
-
|
|
50
|
-
request = request_to 'say/nothing/to/no_one'
|
|
51
|
-
route = @router.route_for request
|
|
52
|
-
assert { route.to_proc.call == :second }
|
|
53
|
-
assert { request.params[:something] == 'nothing'}
|
|
54
|
-
assert { request.params[:someone] == 'no_one'}
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
it 'with . and params' do
|
|
58
|
-
@router.add_route 'GET', '/route/:id.json', &@route
|
|
59
|
-
|
|
60
|
-
request = request_to 'route/42.json'
|
|
61
|
-
route = @router.route_for request
|
|
62
|
-
assert { route.to_proc.call == :wrapped }
|
|
63
|
-
assert { request.params[:id] == '42' }
|
|
64
|
-
end
|
|
65
|
-
end
|