clientura 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +29 -0
- data/.rspec +2 -0
- data/.rubocop.yml +10 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +166 -0
- data/LICENSE.txt +20 -0
- data/README.md +71 -0
- data/README.rdoc +19 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/clientura.gemspec +114 -0
- data/lib/clientura/client/endpoint.rb +5 -0
- data/lib/clientura/client/middleware_function_context.rb +21 -0
- data/lib/clientura/client/request.rb +34 -0
- data/lib/clientura/client.rb +143 -0
- data/lib/clientura/version.rb +3 -0
- data/lib/clientura.rb +11 -0
- data/spec/clientura/adequate_spec.rb +204 -0
- data/spec/clientura/client_spec.rb +175 -0
- data/spec/clientura_spec.rb +9 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/support/test_server.rb +123 -0
- metadata +309 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'clientura/client/request'
|
2
|
+
require 'clientura/client/endpoint'
|
3
|
+
require 'clientura/client/middleware_function_context'
|
4
|
+
|
5
|
+
module Clientura
|
6
|
+
module Client
|
7
|
+
def self.included(klass)
|
8
|
+
klass.extend ClassMethods
|
9
|
+
klass.include InstanceMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def registered_endpoints
|
14
|
+
@registered_endpoints ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def registered_pipes
|
18
|
+
@registered_pipes ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def registered_middleware
|
22
|
+
@registered_middleware ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
[:get, :post, :put, :patch, :delete].each do |verb|
|
26
|
+
define_method verb do |name, path: nil|
|
27
|
+
register_endpoint(name, verb: verb, path: path || name.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def register_endpoint(name, verb:, path:)
|
32
|
+
registered_endpoints[name] = Endpoint.new verb,
|
33
|
+
path,
|
34
|
+
[*@middleware_context],
|
35
|
+
[*@pipes_context]
|
36
|
+
|
37
|
+
define_method name do |args = {}|
|
38
|
+
call_endpoint name, args
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method "#{name}_promise" do |args = {}|
|
42
|
+
Concurrent::Promise.execute { send(name, args) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def pipe(name, callable)
|
47
|
+
registered_pipes[name] = callable
|
48
|
+
end
|
49
|
+
|
50
|
+
def middleware(name, callable)
|
51
|
+
registered_middleware[name] = callable
|
52
|
+
end
|
53
|
+
|
54
|
+
def pipe_through(*pipes)
|
55
|
+
pipes = pipes.map { |o| normalize_mapper o }
|
56
|
+
|
57
|
+
@pipes_context ||= []
|
58
|
+
@pipes_context.push(*pipes)
|
59
|
+
yield
|
60
|
+
@pipes_context.pop pipes.count
|
61
|
+
end
|
62
|
+
|
63
|
+
def use_middleware(*middleware)
|
64
|
+
middleware = middleware.map { |o| normalize_mapper o }
|
65
|
+
|
66
|
+
@middleware_context ||= []
|
67
|
+
@middleware_context.push(*middleware)
|
68
|
+
yield
|
69
|
+
@middleware_context.pop middleware.count
|
70
|
+
end
|
71
|
+
|
72
|
+
def normalize_mapper(mapper)
|
73
|
+
case mapper
|
74
|
+
when Array
|
75
|
+
name, *config = mapper
|
76
|
+
{ name: name, config: config }
|
77
|
+
else
|
78
|
+
{ name: mapper, config: [] }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module InstanceMethods
|
84
|
+
def self.included(klass)
|
85
|
+
define_method :registered_endpoints do
|
86
|
+
klass.registered_endpoints
|
87
|
+
end
|
88
|
+
|
89
|
+
define_method :registered_pipes do
|
90
|
+
klass.registered_pipes
|
91
|
+
end
|
92
|
+
|
93
|
+
define_method :registered_middleware do
|
94
|
+
klass.registered_middleware
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_writer :config
|
99
|
+
|
100
|
+
def config
|
101
|
+
@config ||= {}
|
102
|
+
end
|
103
|
+
|
104
|
+
def save_config(args)
|
105
|
+
self.config = config.merge args
|
106
|
+
end
|
107
|
+
|
108
|
+
def call_endpoint(name_, args)
|
109
|
+
endpoint = registered_endpoints.fetch(name_)
|
110
|
+
|
111
|
+
path = if endpoint.path.respond_to?(:call)
|
112
|
+
endpoint.path.call(args)
|
113
|
+
else
|
114
|
+
endpoint.path
|
115
|
+
end
|
116
|
+
|
117
|
+
middlewares = endpoint.middleware.map do |name:, config:|
|
118
|
+
{ callable: registered_middleware.fetch(name), config: config }
|
119
|
+
end
|
120
|
+
|
121
|
+
request = middlewares
|
122
|
+
.reduce Request.new do |request_, callable:, config:|
|
123
|
+
middleware = MiddlewareFunctionContext.new(request: request_,
|
124
|
+
client: self,
|
125
|
+
args: args,
|
126
|
+
callable: callable,
|
127
|
+
config: config)
|
128
|
+
middleware.call
|
129
|
+
end
|
130
|
+
|
131
|
+
response = request.send(endpoint.verb, path)
|
132
|
+
|
133
|
+
endpoints = endpoint.pipes.map do |name:, config:|
|
134
|
+
-> (res) { registered_pipes.fetch(name).call(res, *config) }
|
135
|
+
end
|
136
|
+
|
137
|
+
endpoints.reduce response do |response_, pipe|
|
138
|
+
pipe.call response_
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/clientura.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
describe 'Ability to use this for solving real world problems' do
|
2
|
+
# Purpose of this spec is to define some real world use cases in a clean and
|
3
|
+
# readable manner to serve as an example of usage.
|
4
|
+
|
5
|
+
subject { instance }
|
6
|
+
|
7
|
+
let(:instance) { client_class.new(client_config) }
|
8
|
+
let(:client_config) { { uri: 'http://localhost:3001/real_world/' } }
|
9
|
+
let(:client_class) do
|
10
|
+
Class.new do
|
11
|
+
include Clientura::Client
|
12
|
+
|
13
|
+
middleware :configurable_uri, -> { update(:uri) { client.config[:uri] } }
|
14
|
+
middleware :header_with_token, -> { headers AuthToken: client.config[:token] }
|
15
|
+
middleware :pass_all_as_query_string, -> { update(:params) { args } }
|
16
|
+
middleware :namespace, -> (namespace) { update(:uri) { |uri| URI.join uri, namespace } }
|
17
|
+
middleware :bad_middleware, -> { raise 'Some Exception' }
|
18
|
+
middleware :slow_middleware, -> { sleep 0.1; self }
|
19
|
+
|
20
|
+
pipe :body_retriever, -> (res) { res.body.to_s }
|
21
|
+
pipe :parser, -> (res, parser) { parser.parse res }
|
22
|
+
pipe :answer_header, -> (res) { res.headers['Answer'] }
|
23
|
+
pipe :data_retriever, -> (res) { res['data'] }
|
24
|
+
|
25
|
+
use_middleware :configurable_uri do
|
26
|
+
get :root, path: '/'
|
27
|
+
|
28
|
+
use_middleware :header_with_token do
|
29
|
+
get :pass_token
|
30
|
+
end
|
31
|
+
|
32
|
+
pipe_through :body_retriever do
|
33
|
+
get :get_some, path: 'some'
|
34
|
+
put :put_some, path: 'some'
|
35
|
+
patch :patch_some, path: 'some'
|
36
|
+
post :post_some, path: 'some'
|
37
|
+
delete :delete_some, path: 'some'
|
38
|
+
|
39
|
+
pipe_through [:parser, JSON] do
|
40
|
+
get :parse_response
|
41
|
+
|
42
|
+
pipe_through :data_retriever do
|
43
|
+
get :comments, path: -> (params) { "comments/#{params[:id]}" }
|
44
|
+
get :users, path: -> (params) { "users/#{params[:id]}" }
|
45
|
+
get :attachments, path: -> (params) { "attachments/#{params[:id]}" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
use_middleware :pass_all_as_query_string do
|
50
|
+
get :pass_query_string
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
use_middleware [:namespace, 'namespace/'] do
|
55
|
+
get :namespaced
|
56
|
+
end
|
57
|
+
|
58
|
+
pipe_through :answer_header do
|
59
|
+
get :get_answer_header
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
use_middleware :bad_middleware do
|
64
|
+
get :raise_some_exception
|
65
|
+
end
|
66
|
+
|
67
|
+
use_middleware :slow_middleware, :bad_middleware do
|
68
|
+
get :slowly_raise_some_exception
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(uri:, token: nil)
|
72
|
+
save_config uri: uri, token: token
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'My wish to see this at least working' do
|
78
|
+
subject { super().root.status }
|
79
|
+
|
80
|
+
it { should eq 200 }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'My desire to use different verbs' do
|
84
|
+
describe 'GET' do
|
85
|
+
subject { super().get_some }
|
86
|
+
|
87
|
+
it { should eq 'get' }
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'POST' do
|
91
|
+
subject { super().post_some }
|
92
|
+
|
93
|
+
it { should eq 'post' }
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'PUT' do
|
97
|
+
subject { super().put_some }
|
98
|
+
|
99
|
+
it { should eq 'put' }
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'PATCH' do
|
103
|
+
subject { super().patch_some }
|
104
|
+
|
105
|
+
it { should eq 'patch' }
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'DELETE' do
|
109
|
+
subject { super().delete_some }
|
110
|
+
|
111
|
+
it { should eq 'delete' }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'My desire to see some exceptions' do
|
116
|
+
subject { -> { instance.raise_some_exception } }
|
117
|
+
|
118
|
+
it { should raise_error 'Some Exception' }
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'My desire to work with promise' do
|
122
|
+
context 'when no errors raised' do
|
123
|
+
subject { super().root_promise }
|
124
|
+
|
125
|
+
it { should be_pending }
|
126
|
+
|
127
|
+
it 'has correct value' do
|
128
|
+
expect(subject.value.status).to eq 200
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when awaited' do
|
132
|
+
before { subject.value }
|
133
|
+
|
134
|
+
it 'fulfills' do
|
135
|
+
expect(subject).to be_fulfilled
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'when error raised' do
|
141
|
+
subject { super().slowly_raise_some_exception_promise }
|
142
|
+
|
143
|
+
it { should be_pending }
|
144
|
+
|
145
|
+
it 'has nil value' do
|
146
|
+
expect(subject.value).to be nil
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when awaited' do
|
150
|
+
before { subject.value }
|
151
|
+
|
152
|
+
it 'rejects' do
|
153
|
+
expect(subject).to be_rejected
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'has correct message' do
|
157
|
+
expect(subject.reason.message).to eq 'Some Exception'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe 'My desire to pass some token in header' do
|
164
|
+
subject { super().pass_token.status }
|
165
|
+
|
166
|
+
let(:client_config) { super().merge token: token }
|
167
|
+
|
168
|
+
context 'when token is bad' do
|
169
|
+
let(:token) { 'Bad token!' }
|
170
|
+
|
171
|
+
it { should eq 403 }
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'when token is good' do
|
175
|
+
let(:token) { 'Secret' }
|
176
|
+
|
177
|
+
it { should eq 200 }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'My desire to parse response' do
|
182
|
+
subject { super().parse_response }
|
183
|
+
|
184
|
+
it { should eq 'data' => 'Awesome!' }
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'My desire to pass query string' do
|
188
|
+
subject { super().pass_query_string echo: 'Awesome!' }
|
189
|
+
|
190
|
+
it { should eq 'Awesome!' }
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'My desire to get response header' do
|
194
|
+
subject { super().get_answer_header }
|
195
|
+
|
196
|
+
it { should eq 'Awesome!' }
|
197
|
+
end
|
198
|
+
|
199
|
+
describe 'My desire to namespace routes' do
|
200
|
+
subject { super().namespaced.status }
|
201
|
+
|
202
|
+
it { should eq 200 }
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
describe Clientura::Client do
|
2
|
+
subject { client }
|
3
|
+
|
4
|
+
let(:uri) { 'http://localhost:3001' }
|
5
|
+
let(:client) { klass.new(uri: uri, token: 'InitializationToken') }
|
6
|
+
let(:klass) do
|
7
|
+
Class.new do
|
8
|
+
include Clientura::Client
|
9
|
+
|
10
|
+
middleware :static_token, -> (token) { headers(Token: token) }
|
11
|
+
middleware :init_token, -> { headers(token: client.config[:token]) }
|
12
|
+
middleware :token_passer, -> { headers(token: args[:token]) }
|
13
|
+
middleware :send_as_json, -> { update(:json) { args } }
|
14
|
+
middleware :pass_as_query, -> { update(:params) { |p| p.merge args } }
|
15
|
+
middleware :configurable_uri, lambda {
|
16
|
+
update(:uri) { |uri| URI.join client.config[:uri], uri }
|
17
|
+
}
|
18
|
+
|
19
|
+
pipe :body_retriever, -> (res) { res.body.to_s }
|
20
|
+
pipe :parser, -> (res, parser) { parser.parse res }
|
21
|
+
pipe :data_retriever, -> (res) { res['data'] }
|
22
|
+
|
23
|
+
use_middleware :configurable_uri do
|
24
|
+
pipe_through :body_retriever do
|
25
|
+
get :resource_raw, path: 'res'
|
26
|
+
|
27
|
+
pipe_through [:parser, JSON] do
|
28
|
+
get :root, path: '/'
|
29
|
+
get :resource, path: 'res'
|
30
|
+
get :name
|
31
|
+
get :echo_argument, path: -> (params) { "echo_argument/#{params[:argument]}" }
|
32
|
+
|
33
|
+
use_middleware :pass_as_query do
|
34
|
+
get :echo_param
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
pipe_through :body_retriever, [:parser, JSON] do
|
40
|
+
use_middleware [:static_token, 'StaticToken'] do
|
41
|
+
get :try_static_token, path: 'echo_header'
|
42
|
+
end
|
43
|
+
|
44
|
+
use_middleware [:static_token, 'Token'] do
|
45
|
+
get :echo_header_const, path: 'echo_header'
|
46
|
+
end
|
47
|
+
|
48
|
+
use_middleware :token_passer do
|
49
|
+
get :try_token_passer, path: 'echo_header'
|
50
|
+
get :echo_header
|
51
|
+
end
|
52
|
+
|
53
|
+
use_middleware :init_token do
|
54
|
+
get :try_init_token, path: 'echo_header'
|
55
|
+
end
|
56
|
+
|
57
|
+
get :configurable_uri_resource, path: 'res'
|
58
|
+
|
59
|
+
use_middleware :send_as_json do
|
60
|
+
post :send_json, path: 'data'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
use_middleware :configurable_uri do
|
66
|
+
pipe_through :body_retriever, [:parser, JSON], :data_retriever do
|
67
|
+
use_middleware :send_as_json do
|
68
|
+
post :sum
|
69
|
+
end
|
70
|
+
|
71
|
+
get :comments, path: -> (params) { "comments/#{params[:id]}" }
|
72
|
+
get :users, path: -> (params) { "users/#{params[:id]}" }
|
73
|
+
get :tags, path: -> (params) { "tags/#{params[:id]}" }
|
74
|
+
|
75
|
+
use_middleware :pass_as_query do
|
76
|
+
get :left_operand
|
77
|
+
get :right_operand
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(uri:, token:)
|
83
|
+
save_config uri: URI.parse(uri), token: token
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#root' do
|
89
|
+
subject { client.root }
|
90
|
+
|
91
|
+
it { should eq 'data' => 'root' }
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#resource' do
|
95
|
+
subject { client.resource }
|
96
|
+
|
97
|
+
it { should eq 'data' => 'resource' }
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#name' do
|
101
|
+
subject { client.name }
|
102
|
+
|
103
|
+
it { should eq 'data' => 'name' }
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#echo_argument' do
|
107
|
+
subject { client.echo_argument argument: 'Argument' }
|
108
|
+
|
109
|
+
it { should eq 'data' => 'Argument' }
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#echo_param' do
|
113
|
+
subject { client.echo_param param: 'Param' }
|
114
|
+
|
115
|
+
it { should eq 'data' => 'Param' }
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#echo_header' do
|
119
|
+
subject { client.echo_header token: 'Token' }
|
120
|
+
|
121
|
+
it { should eq 'data' => 'Token' }
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#echo_header_const' do
|
125
|
+
subject { client.echo_header_const }
|
126
|
+
|
127
|
+
it { should eq 'data' => 'Token' }
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#resource_raw' do
|
131
|
+
subject { client.resource_raw }
|
132
|
+
|
133
|
+
it { should eq JSON.dump data: 'resource' }
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'try_static_token' do
|
137
|
+
subject { client.try_static_token }
|
138
|
+
|
139
|
+
it { should eq 'data' => 'StaticToken' }
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'try_token_passer' do
|
143
|
+
subject { client.try_token_passer token: 'PassedToken' }
|
144
|
+
|
145
|
+
it { should eq 'data' => 'PassedToken' }
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'try_init_token' do
|
149
|
+
subject { client.try_init_token }
|
150
|
+
|
151
|
+
it { should eq 'data' => 'InitializationToken' }
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'configurable_uri_resource' do
|
155
|
+
subject { client.configurable_uri_resource }
|
156
|
+
|
157
|
+
it { should eq 'data' => 'resource' }
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#send_json' do
|
161
|
+
subject { client.send_json json_data: { foo: :bar } }
|
162
|
+
|
163
|
+
it { should eq 'data' => { 'foo' => 'bar' } }
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#sum' do
|
167
|
+
subject { client.sum sum: sum }
|
168
|
+
|
169
|
+
let(:sum) { client.left_operand(key: 'Secret') + client.right_operand(key: 'Secret') }
|
170
|
+
|
171
|
+
it { should eq true }
|
172
|
+
end
|
173
|
+
|
174
|
+
# it('__run_server__', :focus) { binding.pry }
|
175
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'codeclimate-test-reporter'
|
3
|
+
|
4
|
+
module SimpleCov::Configuration
|
5
|
+
def clean_filters
|
6
|
+
@filters = []
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
SimpleCov.configure do
|
11
|
+
clean_filters
|
12
|
+
load_profile 'test_frameworks'
|
13
|
+
end
|
14
|
+
|
15
|
+
ENV['COVERAGE'] && SimpleCov.start do
|
16
|
+
add_filter '/.rvm/'
|
17
|
+
end
|
18
|
+
|
19
|
+
CodeClimate::TestReporter.start
|
20
|
+
|
21
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
22
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
23
|
+
|
24
|
+
require 'bundler'
|
25
|
+
Bundler.require(:test)
|
26
|
+
require 'clientura'
|
27
|
+
|
28
|
+
# Requires supporting files with custom matchers and macros, etc,
|
29
|
+
# in ./support/ and its subdirectories.
|
30
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
31
|
+
|
32
|
+
RSpec.configure do |config|
|
33
|
+
# config.expose_current_running_example_as :example
|
34
|
+
config.raise_errors_for_deprecations!
|
35
|
+
config.run_all_when_everything_filtered = true
|
36
|
+
config.filter_run focus: true
|
37
|
+
# config.mock_with :rspec
|
38
|
+
# config.use_transactional_fixtures = false
|
39
|
+
|
40
|
+
config.order = :rand
|
41
|
+
|
42
|
+
config.before(:suite) do |_example|
|
43
|
+
pid = Process.fork do
|
44
|
+
Rack::Handler::Thin.run TestServer, Port: 3001
|
45
|
+
# trap(:INT) { Rack::Handler::WEBrick.shutdown }
|
46
|
+
# Rack::Handler::WEBrick.run TestServer.new,
|
47
|
+
# Port: 3001,
|
48
|
+
# Logger: WEBrick::Log.new('/dev/null'),
|
49
|
+
# AccessLog: []
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
at_exit do
|
54
|
+
Process.kill('INT', pid)
|
55
|
+
begin
|
56
|
+
Process.wait(pid)
|
57
|
+
rescue Errno::ECHILD
|
58
|
+
# ignore this error...I think it means the child process has already exited.
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
sleep 1
|
63
|
+
end
|
64
|
+
end
|