resourceful 0.3.1 → 0.5.0
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/Manifest +24 -28
- data/Rakefile +44 -14
- data/lib/resourceful.rb +11 -21
- data/lib/resourceful/authentication_manager.rb +3 -2
- data/lib/resourceful/cache_manager.rb +58 -1
- data/lib/resourceful/exceptions.rb +34 -0
- data/lib/resourceful/header.rb +95 -0
- data/lib/resourceful/http_accessor.rb +0 -2
- data/lib/resourceful/memcache_cache_manager.rb +3 -13
- data/lib/resourceful/net_http_adapter.rb +15 -5
- data/lib/resourceful/request.rb +180 -18
- data/lib/resourceful/resource.rb +38 -141
- data/lib/resourceful/response.rb +142 -95
- data/resourceful.gemspec +9 -7
- data/spec/acceptance/authorization_spec.rb +16 -0
- data/spec/acceptance/caching_spec.rb +192 -0
- data/spec/acceptance/header_spec.rb +24 -0
- data/spec/acceptance/redirecting_spec.rb +12 -0
- data/spec/acceptance/resource_spec.rb +84 -0
- data/spec/acceptance_shared_specs.rb +12 -17
- data/spec/{acceptance_spec.rb → old_acceptance_specs.rb} +27 -57
- data/spec/simple_sinatra_server.rb +74 -0
- data/spec/simple_sinatra_server_spec.rb +98 -0
- data/spec/spec_helper.rb +21 -7
- metadata +50 -42
- data/spec/resourceful/authentication_manager_spec.rb +0 -249
- data/spec/resourceful/cache_manager_spec.rb +0 -223
- data/spec/resourceful/header_spec.rb +0 -38
- data/spec/resourceful/http_accessor_spec.rb +0 -164
- data/spec/resourceful/memcache_cache_manager_spec.rb +0 -111
- data/spec/resourceful/net_http_adapter_spec.rb +0 -96
- data/spec/resourceful/options_interpreter_spec.rb +0 -102
- data/spec/resourceful/request_spec.rb +0 -186
- data/spec/resourceful/resource_spec.rb +0 -600
- data/spec/resourceful/response_spec.rb +0 -238
- data/spec/resourceful/stubbed_resource_proxy_spec.rb +0 -58
- data/spec/simple_http_server_shared_spec.rb +0 -162
- data/spec/simple_http_server_shared_spec_spec.rb +0 -212
@@ -1,238 +0,0 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
require Pathname(__FILE__).dirname + '../spec_helper'
|
3
|
-
|
4
|
-
require 'resourceful/response'
|
5
|
-
|
6
|
-
describe Resourceful::Response do
|
7
|
-
before do
|
8
|
-
@net_http = mock('net_http')
|
9
|
-
Net::HTTP::Get.stub!(:new).and_return(@net_http)
|
10
|
-
@uri = 'http://www.example.com'
|
11
|
-
|
12
|
-
@response = Resourceful::Response.new(@uri, 0, {}, "")
|
13
|
-
end
|
14
|
-
|
15
|
-
describe 'init' do
|
16
|
-
|
17
|
-
it 'should be instantiatable' do
|
18
|
-
@response.should be_instance_of(Resourceful::Response)
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should take a [uri, code, header, body] array' do
|
22
|
-
r = Resourceful::Response.new(@uri, 200, {}, "")
|
23
|
-
r.code.should == 200
|
24
|
-
r.header.should == {}
|
25
|
-
r.body.should == ""
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'should have a code' do
|
31
|
-
@response.should respond_to(:code)
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'should have a header' do
|
35
|
-
@response.should respond_to(:header)
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'should have header aliased as headers' do
|
39
|
-
@response.should respond_to(:headers)
|
40
|
-
@response.headers.should == @response.header
|
41
|
-
end
|
42
|
-
|
43
|
-
describe "#is_success?" do
|
44
|
-
it 'should be true for 200' do
|
45
|
-
Resourceful::Response.new(@uri, 200, {}, "").is_success?.should == true
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'should be true for any 2xx' do
|
49
|
-
Resourceful::Response.new(@uri, 299, {}, "").is_success?.should == true
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'should not be true for 300' do
|
53
|
-
Resourceful::Response.new(@uri, 300, {}, "").is_success?.should == false
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'should not be true for 199' do
|
57
|
-
Resourceful::Response.new(@uri, 199, {}, "").is_success?.should == false
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
describe '#is_unsuccesful?' do
|
62
|
-
it 'should be true for a 4xx series response code' do
|
63
|
-
Resourceful::Response.new(@uri, 404, {}, "").is_unsuccesful?.should == true
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'should be true for a 5xx series response code' do
|
67
|
-
Resourceful::Response.new(@uri, 500, {}, "").is_unsuccesful?.should == true
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'should be not true for a 2xx series response code' do
|
71
|
-
Resourceful::Response.new(@uri, 200, {}, "").is_unsuccesful?.should == false
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'should be true for a 3xx series response code' do
|
75
|
-
Resourceful::Response.new(@uri, 302, {}, "").is_unsuccesful?.should == true
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe '#is_client_error?' do
|
80
|
-
it 'be true for a 4xx series response code' do
|
81
|
-
Resourceful::Response.new(@uri, 404, {}, "").is_client_error?.should == true
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'be false for anything else' do
|
85
|
-
Resourceful::Response.new(@uri, 200, {}, "").is_client_error?.should == false
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
describe '#is_server_error?' do
|
90
|
-
it 'be true for a 5xx series response code' do
|
91
|
-
Resourceful::Response.new(@uri, 500, {}, "").is_server_error?.should == true
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'be false for anything else' do
|
95
|
-
Resourceful::Response.new(@uri, 200, {}, "").is_server_error?.should == false
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
it 'should know if it is a redirect' do
|
101
|
-
Resourceful::Response.new(@uri, 301, {}, "").is_redirect?.should == true
|
102
|
-
Resourceful::Response.new(@uri, 302, {}, "").is_redirect?.should == true
|
103
|
-
Resourceful::Response.new(@uri, 303, {}, "").is_redirect?.should == true
|
104
|
-
Resourceful::Response.new(@uri, 307, {}, "").is_redirect?.should == true
|
105
|
-
|
106
|
-
#aliased as was_redirect?
|
107
|
-
Resourceful::Response.new(@uri, 301, {}, "").was_redirect?.should == true
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'should know if it is a permanent redirect' do
|
111
|
-
Resourceful::Response.new(@uri, 301, {}, "").is_permanent_redirect?.should == true
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'should know if it is a temporary redirect' do
|
115
|
-
Resourceful::Response.new(@uri, 303, {}, "").is_temporary_redirect?.should == true
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'should know if its authoritative' do
|
119
|
-
@response.should respond_to(:authoritative?)
|
120
|
-
end
|
121
|
-
|
122
|
-
it 'should allow authoritative to be set' do
|
123
|
-
@response.authoritative = true
|
124
|
-
@response.authoritative?.should be_true
|
125
|
-
end
|
126
|
-
|
127
|
-
it 'should know if it is authorized' do
|
128
|
-
@response.should respond_to(:is_not_authorized?)
|
129
|
-
Resourceful::Response.new(@uri, 200, {}, "").is_not_authorized?.should == false
|
130
|
-
Resourceful::Response.new(@uri, 401, {}, "").is_not_authorized?.should == true
|
131
|
-
end
|
132
|
-
|
133
|
-
describe 'caching and expiration' do
|
134
|
-
before do
|
135
|
-
Time.stub!(:now).and_return(Time.utc(2008,5,15,18,0,1), Time.utc(2008,5,15,20,0,0))
|
136
|
-
|
137
|
-
@response = Resourceful::Response.new(@uri, 0, {'Date' => ['Thu, 15 May 2008 18:00:00 GMT']}, "")
|
138
|
-
@response.request_time = Time.utc(2008,5,15,17,59,59)
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'should know if its #stale?' do
|
142
|
-
@response.should respond_to(:stale?)
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'should be stale if it is expired' do
|
146
|
-
@response.should_receive(:expired?).and_return(true)
|
147
|
-
@response.should be_stale
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'should know if its #expired?' do
|
151
|
-
@response.should respond_to(:expired?)
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'should be expired if Now is after the "Expire" header' do
|
155
|
-
Time.stub!(:now).and_return(Time.utc(2008,5,23,18,0))
|
156
|
-
@response.header['Expire'] = [(Time.now - 60*60).httpdate]
|
157
|
-
|
158
|
-
@response.should be_expired
|
159
|
-
end
|
160
|
-
|
161
|
-
it 'should have a #current_age' do
|
162
|
-
@response.should respond_to(:current_age)
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'should calculate the #current_age' do
|
166
|
-
@response.current_age.should == (2 * 60 * 60 + 2)
|
167
|
-
end
|
168
|
-
|
169
|
-
it 'should know if its #cachable?' do
|
170
|
-
@response.should respond_to(:cachable?)
|
171
|
-
end
|
172
|
-
|
173
|
-
it 'should normally be cachable' do
|
174
|
-
@response.cachable?.should be_true
|
175
|
-
end
|
176
|
-
|
177
|
-
def response_with_header(header = {})
|
178
|
-
Resourceful::Response.new(@uri, 200, header, "")
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'should not be cachable if the vary header has "*"' do
|
182
|
-
r = response_with_header('Vary' => ['*'])
|
183
|
-
r.cachable?.should be_false
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'should not be cachable if the Cache-Control header is set to no-store' do
|
187
|
-
r = response_with_header('Cache-Control' => ['no-store'])
|
188
|
-
r.cachable?.should be_false
|
189
|
-
end
|
190
|
-
|
191
|
-
it 'should be stale if the Cache-Control header is set to must-revalidate' do
|
192
|
-
r = response_with_header('Cache-Control' => ['must-revalidate'])
|
193
|
-
r.should be_stale
|
194
|
-
end
|
195
|
-
|
196
|
-
it 'should be stale if the Cache-Control header is set to no-cache' do
|
197
|
-
r = response_with_header('Cache-Control' => ['no-cache'])
|
198
|
-
r.should be_stale
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
|
203
|
-
describe '#body' do
|
204
|
-
it 'should have a body method' do
|
205
|
-
@response.should respond_to(:body)
|
206
|
-
end
|
207
|
-
|
208
|
-
require 'zlib'
|
209
|
-
['gzip', ' gzip', ' gzip ', 'GZIP', 'gzIP'].each do |gzip|
|
210
|
-
it "ungzip the body if content-encoding header field is #{gzip}" do
|
211
|
-
compressed_date = StringIO.new.tap do |out|
|
212
|
-
Zlib::GzipWriter.new(out).tap do |zout|
|
213
|
-
zout << "This is a test"
|
214
|
-
zout.close
|
215
|
-
end
|
216
|
-
end.string
|
217
|
-
|
218
|
-
@response = Resourceful::Response.new(@uri, 0, {'Content-Encoding' => [gzip]}, compressed_date)
|
219
|
-
|
220
|
-
@response.body.should == "This is a test"
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
it 'should leave body unmolested if Content-Encoding missing' do
|
225
|
-
@response = Resourceful::Response.new(@uri, 0, {}, "This is a test")
|
226
|
-
@response.body.should == "This is a test"
|
227
|
-
end
|
228
|
-
|
229
|
-
it 'should raise error if Content-Encoding is not supported' do
|
230
|
-
@response = Resourceful::Response.new(@uri, 0, {'Content-Encoding' => ['broken-identity']}, "This is a test")
|
231
|
-
lambda {
|
232
|
-
@response.body
|
233
|
-
}.should raise_error(Resourceful::UnsupportedContentCoding)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
end
|
238
|
-
|
@@ -1,58 +0,0 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
require Pathname(__FILE__).dirname + '../spec_helper'
|
3
|
-
|
4
|
-
require 'resourceful/stubbed_resource_proxy'
|
5
|
-
|
6
|
-
describe Resourceful::StubbedResourceProxy, "init" do
|
7
|
-
it 'should require real resource' do
|
8
|
-
lambda{
|
9
|
-
Resourceful::StubbedResourceProxy.new
|
10
|
-
}.should raise_error(ArgumentError)
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'should require canned responses hash' do
|
14
|
-
lambda{
|
15
|
-
Resourceful::StubbedResourceProxy.new(stub('resource'))
|
16
|
-
}.should raise_error(ArgumentError)
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'should be creatable with a Resource and canned responses' do
|
20
|
-
Resourceful::StubbedResourceProxy.new(stub('resource'), {})
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe Resourceful::StubbedResourceProxy do
|
25
|
-
before do
|
26
|
-
@resource = stub('resource', :effective_uri => "http://test.invalid/foo")
|
27
|
-
@stubbed_resource = Resourceful::StubbedResourceProxy.
|
28
|
-
new(@resource, [{:mime_type => 'application/xml',
|
29
|
-
:body => '<thing>1</thing>'}])
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'should return canned response body for matching requests' do
|
33
|
-
resp = @stubbed_resource.get
|
34
|
-
resp.body.should == '<thing>1</thing>'
|
35
|
-
resp['content-type'].should == 'application/xml'
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'should pass #get() through to base resource if no matching canned response is defined' do
|
39
|
-
@resource.should_receive(:get)
|
40
|
-
@stubbed_resource.get(:accept => 'application/unknown')
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'should pass #post() through to base resource if no canned response is defined' do
|
44
|
-
@resource.should_receive(:post)
|
45
|
-
@stubbed_resource.post
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'should pass #put() through to base resource if no canned response is defined' do
|
49
|
-
@resource.should_receive(:put)
|
50
|
-
@stubbed_resource.put
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'should pass #effective_uri() through to base resource if no canned response is defined' do
|
54
|
-
@resource.should_receive(:effective_uri)
|
55
|
-
@stubbed_resource.effective_uri
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
@@ -1,162 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
# this sets up a very simple http server using thin to be used in specs.
|
4
|
-
SimpleGet = lambda do |env|
|
5
|
-
body = ["Hello, world!"]
|
6
|
-
[ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
|
7
|
-
end unless defined? SimpleGet
|
8
|
-
|
9
|
-
SimplePost = lambda do |env|
|
10
|
-
body = [env['rack.input'].string]
|
11
|
-
[ 201, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
|
12
|
-
end unless defined? SimplePost
|
13
|
-
|
14
|
-
SimplePut = lambda do |env|
|
15
|
-
body = [env['rack.input'].string]
|
16
|
-
[ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
|
17
|
-
end unless defined? SimplePut
|
18
|
-
|
19
|
-
SimpleDel = lambda do |env|
|
20
|
-
body = ["KABOOM!"]
|
21
|
-
[ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
|
22
|
-
end unless defined? SimpleDel
|
23
|
-
|
24
|
-
# has the method used in the body of the response
|
25
|
-
MethodResponder = lambda do |env|
|
26
|
-
body = [env['REQUEST_METHOD']]
|
27
|
-
[ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
|
28
|
-
end unless defined? MethodResponder
|
29
|
-
|
30
|
-
# has a response code of whatever it was given in the url /code/{123}
|
31
|
-
CodeResponder = lambda do |env|
|
32
|
-
code = env['PATH_INFO'] =~ /([\d]+)/ ? Integer($1) : 404
|
33
|
-
body = [code.to_s]
|
34
|
-
|
35
|
-
[ code, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
|
36
|
-
end unless defined? CodeResponder
|
37
|
-
|
38
|
-
# YAML-parses the query string (expected hash) and sets the header to that
|
39
|
-
HeaderResponder = lambda do |env|
|
40
|
-
header = YAML.load(URI.unescape(env['QUERY_STRING']))
|
41
|
-
body = [header.inspect]
|
42
|
-
|
43
|
-
header.merge!({
|
44
|
-
'Content-Type' => 'text/plain',
|
45
|
-
'Content-Length' => body.join.size.to_s
|
46
|
-
})
|
47
|
-
|
48
|
-
[ 200, header, body ]
|
49
|
-
end unless defined? HeaderResponder
|
50
|
-
|
51
|
-
# Echos the request header in the response body
|
52
|
-
EchoHeaderResponder = lambda do |env|
|
53
|
-
body = [env.inspect]
|
54
|
-
|
55
|
-
header = {
|
56
|
-
'Content-Type' => 'text/plain',
|
57
|
-
'Content-Length' => body.join.size.to_s
|
58
|
-
}
|
59
|
-
|
60
|
-
[ 200, header, body ]
|
61
|
-
end unless defined? EchoHeaderResponder
|
62
|
-
|
63
|
-
# redirect. /redirect/{301|302}?{url}
|
64
|
-
Redirector = lambda do |env|
|
65
|
-
code = env['PATH_INFO'] =~ /([\d]+)/ ? Integer($1) : 404
|
66
|
-
location = env['QUERY_STRING']
|
67
|
-
body = [location]
|
68
|
-
|
69
|
-
[ code, {'Content-Type' => 'text/plain', 'Location' => location, 'Content-Length' => body.join.size.to_s}, body ]
|
70
|
-
end unless defined? Redirector
|
71
|
-
|
72
|
-
# Returns 304 if 'If-Modified-Since' is after given mod time
|
73
|
-
ModifiedResponder = lambda do |env|
|
74
|
-
modtime = Time.httpdate(URI.unescape(env['QUERY_STRING']))
|
75
|
-
|
76
|
-
code = 200
|
77
|
-
if env['HTTP_IF_MODIFIED_SINCE']
|
78
|
-
code = 304
|
79
|
-
end
|
80
|
-
body = [modtime.to_s]
|
81
|
-
|
82
|
-
header = {'Content-Type' => 'text/plain',
|
83
|
-
'Content-Length' => body.join.size.to_s,
|
84
|
-
'Last-Modified' => modtime.httpdate,
|
85
|
-
'Cache-Control' => 'must-revalidate'}
|
86
|
-
|
87
|
-
[ code, header, body ]
|
88
|
-
end unless defined? ModifiedResponder
|
89
|
-
|
90
|
-
require 'rubygems'
|
91
|
-
require 'httpauth'
|
92
|
-
AuthorizationResponder = lambda do |env|
|
93
|
-
authtype = env['QUERY_STRING']
|
94
|
-
header = {}
|
95
|
-
if auth_string = env['HTTP_AUTHORIZATION']
|
96
|
-
if authtype == "basic" &&
|
97
|
-
['admin', 'secret'] == HTTPAuth::Basic.unpack_authorization(auth_string)
|
98
|
-
code = 200
|
99
|
-
body = ["Authorized"]
|
100
|
-
elsif authtype == "digest" #&&
|
101
|
-
credentials = HTTPAuth::Digest::Credentials.from_header(auth_string) &&
|
102
|
-
credentials &&
|
103
|
-
credentials.validate(:password => 'secret', :method => 'GET')
|
104
|
-
code = 200
|
105
|
-
body = ["Authorized"]
|
106
|
-
else
|
107
|
-
code = 401
|
108
|
-
body = ["Not Authorized"]
|
109
|
-
end
|
110
|
-
else
|
111
|
-
code = 401
|
112
|
-
body = ["Not Authorized"]
|
113
|
-
if authtype == "basic"
|
114
|
-
header = {'WWW-Authenticate' => HTTPAuth::Basic.pack_challenge('Test Auth')}
|
115
|
-
elsif authtype == "digest"
|
116
|
-
chal = HTTPAuth::Digest::Credentials.new(:realm => 'Test Auth', :qop => 'auth')
|
117
|
-
header = {'WWW-Authenticate' => chal.to_header}
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
[ code, header.merge({'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}), body ]
|
122
|
-
end unless defined? AuthorizationResponder
|
123
|
-
|
124
|
-
describe 'simple http server', :shared => true do
|
125
|
-
before(:all) do
|
126
|
-
require 'rack'
|
127
|
-
require 'thin'
|
128
|
-
|
129
|
-
app = Rack::Builder.new do |env|
|
130
|
-
use Rack::ShowExceptions
|
131
|
-
|
132
|
-
map( '/get' ){ run SimpleGet }
|
133
|
-
map( '/post' ){ run SimplePost }
|
134
|
-
map( '/put' ){ run SimplePut }
|
135
|
-
map( '/delete' ){ run SimpleDel }
|
136
|
-
|
137
|
-
map( '/method' ){ run MethodResponder }
|
138
|
-
map( '/code' ){ run CodeResponder }
|
139
|
-
map( '/redirect' ){ run Redirector }
|
140
|
-
map( '/header' ){ run HeaderResponder }
|
141
|
-
map( '/echo_header' ){ run EchoHeaderResponder }
|
142
|
-
map( '/modified' ){ run ModifiedResponder }
|
143
|
-
map( '/auth' ){ run AuthorizationResponder }
|
144
|
-
end
|
145
|
-
|
146
|
-
#spawn the server in a separate thread
|
147
|
-
@httpd = Thread.new do
|
148
|
-
Thin::Logging.silent = true
|
149
|
-
# Thin::Logging.debug = true
|
150
|
-
Thin::Server.start(app)
|
151
|
-
end
|
152
|
-
#give the server a chance to initialize
|
153
|
-
sleep 0.05
|
154
|
-
end
|
155
|
-
|
156
|
-
after(:all) do
|
157
|
-
# kill the server thread
|
158
|
-
@httpd.exit
|
159
|
-
end
|
160
|
-
|
161
|
-
|
162
|
-
end
|