google-api-client 0.3.0 → 0.4.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/CHANGELOG.md +43 -0
- data/README.md +4 -5
- data/bin/google-api +12 -7
- data/lib/google/api_client.rb +179 -161
- data/lib/google/api_client/discovery/api.rb +1 -0
- data/lib/google/api_client/discovery/method.rb +11 -8
- data/lib/google/api_client/discovery/resource.rb +1 -0
- data/lib/google/api_client/discovery/schema.rb +2 -1
- data/lib/google/api_client/environment.rb +15 -0
- data/lib/google/api_client/reference.rb +41 -31
- data/lib/google/api_client/result.rb +5 -10
- data/lib/google/api_client/version.rb +3 -1
- data/spec/google/api_client/discovery_spec.rb +44 -51
- data/spec/google/api_client_spec.rb +19 -10
- data/tasks/gem.rake +7 -6
- data/tasks/git.rake +1 -1
- data/tasks/rdoc.rake +5 -8
- data/tasks/spec.rake +3 -2
- data/tasks/wiki.rake +48 -7
- data/tasks/yard.rake +7 -4
- metadata +11 -17
- data/CHANGELOG +0 -36
- data/lib/google/api_client/parser.rb +0 -59
- data/lib/google/api_client/parsers/json/error_parser.rb +0 -34
- data/lib/google/api_client/parsers/json/pagination.rb +0 -40
- data/lib/google/api_client/parsers/json_parser.rb +0 -119
- data/spec/google/api_client/parsers/json_parser_spec.rb +0 -55
- data/tasks/clobber.rake +0 -2
@@ -18,6 +18,7 @@ require 'addressable/template'
|
|
18
18
|
|
19
19
|
require 'google/api_client/errors'
|
20
20
|
|
21
|
+
|
21
22
|
module Google
|
22
23
|
class APIClient
|
23
24
|
##
|
@@ -139,16 +140,14 @@ module Google
|
|
139
140
|
def normalize_parameters(parameters={})
|
140
141
|
# Convert keys to Strings when appropriate
|
141
142
|
if parameters.kind_of?(Hash) || parameters.kind_of?(Array)
|
142
|
-
#
|
143
|
-
|
144
|
-
# values?
|
145
|
-
parameters = parameters.inject({}) do |accu, (k, v)|
|
143
|
+
# Returning an array since parameters can be repeated (ie, Adsense Management API)
|
144
|
+
parameters = parameters.inject([]) do |accu, (k, v)|
|
146
145
|
k = k.to_s if k.kind_of?(Symbol)
|
147
146
|
k = k.to_str if k.respond_to?(:to_str)
|
148
147
|
unless k.kind_of?(String)
|
149
148
|
raise TypeError, "Expected String, got #{k.class}."
|
150
149
|
end
|
151
|
-
accu[k]
|
150
|
+
accu << [k,v]
|
152
151
|
accu
|
153
152
|
end
|
154
153
|
else
|
@@ -174,7 +173,7 @@ module Google
|
|
174
173
|
template_variables.include?(k)
|
175
174
|
end
|
176
175
|
if query_parameters.size > 0
|
177
|
-
uri.query_values = (uri.query_values ||
|
176
|
+
uri.query_values = (uri.query_values || []) + query_parameters
|
178
177
|
end
|
179
178
|
# Normalization is necessary because of undesirable percent-escaping
|
180
179
|
# during URI template expansion
|
@@ -204,7 +203,11 @@ module Google
|
|
204
203
|
method = self.http_method
|
205
204
|
uri = self.generate_uri(parameters)
|
206
205
|
headers = headers.to_a if headers.kind_of?(Hash)
|
207
|
-
return
|
206
|
+
return Faraday::Request.create(method.to_s.downcase.to_sym) do |req|
|
207
|
+
req.url(Addressable::URI.parse(uri))
|
208
|
+
req.headers = Faraday::Utils::Headers.new(headers)
|
209
|
+
req.body = body
|
210
|
+
end
|
208
211
|
end
|
209
212
|
|
210
213
|
##
|
@@ -271,7 +274,7 @@ module Google
|
|
271
274
|
required_variables = ((self.parameter_descriptions.select do |k, v|
|
272
275
|
v['required']
|
273
276
|
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
|
274
|
-
missing_variables = required_variables - parameters.
|
277
|
+
missing_variables = required_variables - parameters.map(&:first)
|
275
278
|
if missing_variables.size > 0
|
276
279
|
raise ArgumentError,
|
277
280
|
"Missing required parameters: #{missing_variables.join(', ')}."
|
@@ -14,7 +14,7 @@
|
|
14
14
|
|
15
15
|
|
16
16
|
require 'time'
|
17
|
-
require '
|
17
|
+
require 'multi_json'
|
18
18
|
require 'base64'
|
19
19
|
require 'autoparse'
|
20
20
|
require 'addressable/uri'
|
@@ -23,6 +23,7 @@ require 'addressable/template'
|
|
23
23
|
require 'google/inflection'
|
24
24
|
require 'google/api_client/errors'
|
25
25
|
|
26
|
+
|
26
27
|
module Google
|
27
28
|
class APIClient
|
28
29
|
module Schema
|
@@ -1,3 +1,18 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
1
16
|
module Google
|
2
17
|
class APIClient
|
3
18
|
module ENV
|
@@ -12,11 +12,16 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
|
16
|
+
gem 'faraday', '~> 0.7.0'
|
17
|
+
require 'faraday'
|
18
|
+
require 'faraday/utils'
|
19
|
+
require 'multi_json'
|
17
20
|
require 'addressable/uri'
|
21
|
+
require 'stringio'
|
18
22
|
require 'google/api_client/discovery'
|
19
23
|
|
24
|
+
|
20
25
|
module Google
|
21
26
|
class APIClient
|
22
27
|
class Reference
|
@@ -27,6 +32,7 @@ module Google
|
|
27
32
|
@client = options[:client]
|
28
33
|
@version = options[:version] || 'v1'
|
29
34
|
|
35
|
+
self.connection = options[:connection] || Faraday.default_connection
|
30
36
|
self.api_method = options[:api_method]
|
31
37
|
self.parameters = options[:parameters] || {}
|
32
38
|
# These parameters are handled differently because they're not
|
@@ -36,21 +42,19 @@ module Google
|
|
36
42
|
self.headers = options[:headers] || []
|
37
43
|
if options[:body]
|
38
44
|
self.body = options[:body]
|
39
|
-
elsif options[:merged_body]
|
40
|
-
self.merged_body = options[:merged_body]
|
41
45
|
elsif options[:body_object]
|
42
46
|
if options[:body_object].respond_to?(:to_json)
|
43
47
|
serialized_body = options[:body_object].to_json
|
44
48
|
elsif options[:body_object].respond_to?(:to_hash)
|
45
|
-
serialized_body =
|
49
|
+
serialized_body = MultiJson.encode(options[:body_object].to_hash)
|
46
50
|
else
|
47
51
|
raise TypeError,
|
48
52
|
'Could not convert body object to JSON.' +
|
49
53
|
'Must respond to :to_json or :to_hash.'
|
50
54
|
end
|
51
|
-
self.
|
55
|
+
self.body = serialized_body
|
52
56
|
else
|
53
|
-
self.
|
57
|
+
self.body = ''
|
54
58
|
end
|
55
59
|
unless self.api_method
|
56
60
|
self.http_method = options[:http_method] || 'GET'
|
@@ -62,6 +66,19 @@ module Google
|
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
69
|
+
def connection
|
70
|
+
return @connection
|
71
|
+
end
|
72
|
+
|
73
|
+
def connection=(new_connection)
|
74
|
+
if new_connection.kind_of?(Faraday::Connection)
|
75
|
+
@connection = new_connection
|
76
|
+
else
|
77
|
+
raise TypeError,
|
78
|
+
"Expected Faraday::Connection, got #{new_connection.class}."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
65
82
|
def api_method
|
66
83
|
return @api_method
|
67
84
|
end
|
@@ -113,32 +130,18 @@ module Google
|
|
113
130
|
end
|
114
131
|
|
115
132
|
def body=(new_body)
|
116
|
-
if new_body.respond_to?(:
|
117
|
-
@body = new_body
|
133
|
+
if new_body.respond_to?(:to_str)
|
134
|
+
@body = new_body.to_str
|
135
|
+
elsif new_body.respond_to?(:inject)
|
136
|
+
@body = (new_body.inject(StringIO.new) do |accu, chunk|
|
137
|
+
accu.write(chunk)
|
138
|
+
accu
|
139
|
+
end).string
|
118
140
|
else
|
119
|
-
raise TypeError, "Expected body to
|
141
|
+
raise TypeError, "Expected body to be String or Enumerable chunks."
|
120
142
|
end
|
121
143
|
end
|
122
144
|
|
123
|
-
def merged_body
|
124
|
-
return (self.body.inject(StringIO.new) do |accu, chunk|
|
125
|
-
accu.write(chunk)
|
126
|
-
accu
|
127
|
-
end).string
|
128
|
-
end
|
129
|
-
|
130
|
-
def merged_body=(new_merged_body)
|
131
|
-
if new_merged_body.respond_to?(:string)
|
132
|
-
new_merged_body = new_merged_body.string
|
133
|
-
elsif new_merged_body.respond_to?(:to_str)
|
134
|
-
new_merged_body = new_merged_body.to_str
|
135
|
-
else
|
136
|
-
raise TypeError,
|
137
|
-
"Expected String or StringIO, got #{new_merged_body.class}."
|
138
|
-
end
|
139
|
-
self.body = [new_merged_body]
|
140
|
-
end
|
141
|
-
|
142
145
|
def headers
|
143
146
|
return @headers ||= []
|
144
147
|
end
|
@@ -177,10 +180,16 @@ module Google
|
|
177
180
|
def to_request
|
178
181
|
if self.api_method
|
179
182
|
return self.api_method.generate_request(
|
180
|
-
self.parameters, self.
|
183
|
+
self.parameters, self.body, self.headers
|
181
184
|
)
|
182
185
|
else
|
183
|
-
return
|
186
|
+
return Faraday::Request.create(
|
187
|
+
self.http_method.to_s.downcase.to_sym
|
188
|
+
) do |req|
|
189
|
+
req.url(Addressable::URI.parse(self.uri))
|
190
|
+
req.headers = Faraday::Utils::Headers.new(self.headers)
|
191
|
+
req.body = self.body
|
192
|
+
end
|
184
193
|
end
|
185
194
|
end
|
186
195
|
|
@@ -195,6 +204,7 @@ module Google
|
|
195
204
|
end
|
196
205
|
options[:headers] = self.headers
|
197
206
|
options[:body] = self.body
|
207
|
+
options[:connection] = self.connection
|
198
208
|
return options
|
199
209
|
end
|
200
210
|
end
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
|
15
16
|
module Google
|
16
17
|
class APIClient
|
17
18
|
##
|
@@ -30,21 +31,15 @@ module Google
|
|
30
31
|
attr_reader :response
|
31
32
|
|
32
33
|
def status
|
33
|
-
return @response
|
34
|
+
return @response.status
|
34
35
|
end
|
35
36
|
|
36
37
|
def headers
|
37
|
-
return @response
|
38
|
+
return @response.headers
|
38
39
|
end
|
39
40
|
|
40
41
|
def body
|
41
|
-
return @body
|
42
|
-
response_body = @response[2]
|
43
|
-
merged_body = (response_body.inject(StringIO.new) do |accu, chunk|
|
44
|
-
accu.write(chunk)
|
45
|
-
accu
|
46
|
-
end).string
|
47
|
-
end)
|
42
|
+
return @response.body
|
48
43
|
end
|
49
44
|
|
50
45
|
def data
|
@@ -56,7 +51,7 @@ module Google
|
|
56
51
|
data = self.body
|
57
52
|
case media_type
|
58
53
|
when 'application/json'
|
59
|
-
data =
|
54
|
+
data = MultiJson.decode(data)
|
60
55
|
# Strip data wrapper, if present
|
61
56
|
data = data['data'] if data.has_key?('data')
|
62
57
|
else
|
@@ -15,11 +15,13 @@
|
|
15
15
|
|
16
16
|
# Used to prevent the class/module from being loaded more than once
|
17
17
|
if !defined?(::Google::APIClient::VERSION)
|
18
|
+
|
19
|
+
|
18
20
|
module Google
|
19
21
|
class APIClient
|
20
22
|
module VERSION
|
21
23
|
MAJOR = 0
|
22
|
-
MINOR =
|
24
|
+
MINOR = 4
|
23
25
|
TINY = 0
|
24
26
|
|
25
27
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
@@ -14,9 +14,13 @@
|
|
14
14
|
|
15
15
|
require 'spec_helper'
|
16
16
|
|
17
|
-
|
17
|
+
gem 'faraday', '~> 0.7.0'
|
18
|
+
require 'faraday'
|
19
|
+
require 'faraday/utils'
|
20
|
+
require 'multi_json'
|
21
|
+
|
22
|
+
gem 'signet', '~> 0.3.0'
|
18
23
|
require 'signet/oauth_1/client'
|
19
|
-
require 'httpadapter/adapters/net_http'
|
20
24
|
|
21
25
|
require 'google/api_client'
|
22
26
|
require 'google/api_client/version'
|
@@ -84,8 +88,7 @@ describe Google::APIClient do
|
|
84
88
|
:uri => @client.discovery_uri('prediction', 'v1.2'),
|
85
89
|
:authenticated => false
|
86
90
|
)
|
87
|
-
|
88
|
-
uri.should === (
|
91
|
+
request.to_env(Faraday.default_connection)[:url].should === (
|
89
92
|
'https://www.googleapis.com/discovery/v1/apis/prediction/v1.2/rest' +
|
90
93
|
'?userIp=127.0.0.1'
|
91
94
|
)
|
@@ -98,8 +101,7 @@ describe Google::APIClient do
|
|
98
101
|
:uri => @client.discovery_uri('prediction', 'v1.2'),
|
99
102
|
:authenticated => false
|
100
103
|
)
|
101
|
-
|
102
|
-
uri.should === (
|
104
|
+
request.to_env(Faraday.default_connection)[:url].should === (
|
103
105
|
'https://www.googleapis.com/discovery/v1/apis/prediction/v1.2/rest' +
|
104
106
|
'?key=qwerty'
|
105
107
|
)
|
@@ -113,11 +115,10 @@ describe Google::APIClient do
|
|
113
115
|
:uri => @client.discovery_uri('prediction', 'v1.2'),
|
114
116
|
:authenticated => false
|
115
117
|
)
|
116
|
-
|
117
|
-
|
118
|
-
'
|
119
|
-
|
120
|
-
)
|
118
|
+
request.to_env(Faraday.default_connection)[:url].query_values.should == {
|
119
|
+
'key' => 'qwerty',
|
120
|
+
'userIp' => '127.0.0.1'
|
121
|
+
}
|
121
122
|
end
|
122
123
|
|
123
124
|
it 'should correctly generate API objects' do
|
@@ -167,12 +168,21 @@ describe Google::APIClient do
|
|
167
168
|
:api_method => @prediction.training.insert,
|
168
169
|
:parameters => {'data' => '12345', }
|
169
170
|
)
|
170
|
-
method
|
171
|
-
|
172
|
-
uri.should ==
|
171
|
+
request.method.should == :post
|
172
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
173
173
|
'https://www.googleapis.com/prediction/v1.2/training?data=12345'
|
174
|
-
|
175
|
-
body.should
|
174
|
+
request.headers.should be_empty
|
175
|
+
request.body.should == ''
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should generate valid requests when repeated parameters are passed' do
|
179
|
+
request = @client.generate_request(
|
180
|
+
:api_method => @prediction.training.insert,
|
181
|
+
:parameters => [['data', '1'],['data','2']]
|
182
|
+
)
|
183
|
+
request.method.should == :post
|
184
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
185
|
+
'https://www.googleapis.com/prediction/v1.2/training?data=1&data=2'
|
176
186
|
end
|
177
187
|
|
178
188
|
it 'should generate requests against the correct URIs' do
|
@@ -180,8 +190,7 @@ describe Google::APIClient do
|
|
180
190
|
:api_method => @prediction.training.insert,
|
181
191
|
:parameters => {'data' => '12345'}
|
182
192
|
)
|
183
|
-
|
184
|
-
uri.should ==
|
193
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
185
194
|
'https://www.googleapis.com/prediction/v1.2/training?data=12345'
|
186
195
|
end
|
187
196
|
|
@@ -190,8 +199,7 @@ describe Google::APIClient do
|
|
190
199
|
:api_method => @prediction.training.insert,
|
191
200
|
:parameters => {'data' => '12345'}
|
192
201
|
)
|
193
|
-
|
194
|
-
uri.should ==
|
202
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
195
203
|
'https://www.googleapis.com/prediction/v1.2/training?data=12345'
|
196
204
|
end
|
197
205
|
|
@@ -203,8 +211,7 @@ describe Google::APIClient do
|
|
203
211
|
:api_method => prediction.training.insert,
|
204
212
|
:parameters => {'data' => '123'}
|
205
213
|
)
|
206
|
-
|
207
|
-
uri.should == (
|
214
|
+
request.to_env(Faraday.default_connection)[:url].should === (
|
208
215
|
'https://testing-domain.googleapis.com/' +
|
209
216
|
'prediction/v1.2/training?data=123'
|
210
217
|
)
|
@@ -218,10 +225,8 @@ describe Google::APIClient do
|
|
218
225
|
:api_method => @prediction.training.insert,
|
219
226
|
:parameters => {'data' => '12345'}
|
220
227
|
)
|
221
|
-
|
222
|
-
headers
|
223
|
-
headers.keys.should include('Authorization')
|
224
|
-
headers['Authorization'].should =~ /^OAuth/
|
228
|
+
request.headers.should have_key('Authorization')
|
229
|
+
request.headers['Authorization'].should =~ /^OAuth/
|
225
230
|
end
|
226
231
|
|
227
232
|
it 'should generate OAuth 2 requests' do
|
@@ -231,10 +236,8 @@ describe Google::APIClient do
|
|
231
236
|
:api_method => @prediction.training.insert,
|
232
237
|
:parameters => {'data' => '12345'}
|
233
238
|
)
|
234
|
-
|
235
|
-
headers
|
236
|
-
headers.keys.should include('Authorization')
|
237
|
-
headers['Authorization'].should =~ /^OAuth/
|
239
|
+
request.headers.should have_key('Authorization')
|
240
|
+
request.headers['Authorization'].should =~ /^Bearer/
|
238
241
|
end
|
239
242
|
|
240
243
|
it 'should not be able to execute improperly authorized requests' do
|
@@ -245,8 +248,7 @@ describe Google::APIClient do
|
|
245
248
|
@prediction.training.insert,
|
246
249
|
{'data' => '12345'}
|
247
250
|
)
|
248
|
-
status
|
249
|
-
status.should == 401
|
251
|
+
result.response.status.should == 401
|
250
252
|
end
|
251
253
|
|
252
254
|
it 'should not be able to execute improperly authorized requests' do
|
@@ -256,8 +258,7 @@ describe Google::APIClient do
|
|
256
258
|
@prediction.training.insert,
|
257
259
|
{'data' => '12345'}
|
258
260
|
)
|
259
|
-
status
|
260
|
-
status.should == 401
|
261
|
+
result.response.status.should == 401
|
261
262
|
end
|
262
263
|
|
263
264
|
it 'should not be able to execute improperly authorized requests' do
|
@@ -289,11 +290,10 @@ describe Google::APIClient do
|
|
289
290
|
result = @client.execute(
|
290
291
|
@prediction.training.insert,
|
291
292
|
{},
|
292
|
-
|
293
|
+
MultiJson.encode({"id" => "bucket/object"}),
|
293
294
|
{'Content-Type' => 'application/json'}
|
294
295
|
)
|
295
|
-
|
296
|
-
Hash[headers]['Content-Type'].should == 'application/json'
|
296
|
+
result.request.headers['Content-Type'].should == 'application/json'
|
297
297
|
end
|
298
298
|
end
|
299
299
|
|
@@ -334,8 +334,7 @@ describe Google::APIClient do
|
|
334
334
|
},
|
335
335
|
:authenticated => false
|
336
336
|
)
|
337
|
-
|
338
|
-
uri.should == (
|
337
|
+
request.to_env(Faraday.default_connection)[:url].should === (
|
339
338
|
'https://www.googleapis.com/plus/v1/' +
|
340
339
|
'people/107807692475771887386/activities/public'
|
341
340
|
)
|
@@ -395,8 +394,7 @@ describe Google::APIClient do
|
|
395
394
|
:api_method => 'latitude.currentLocation.get',
|
396
395
|
:authenticated => false
|
397
396
|
)
|
398
|
-
|
399
|
-
uri.should ==
|
397
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
400
398
|
'https://www.googleapis.com/latitude/v1/currentLocation'
|
401
399
|
end
|
402
400
|
|
@@ -405,8 +403,7 @@ describe Google::APIClient do
|
|
405
403
|
:api_method => @latitude.current_location.get,
|
406
404
|
:authenticated => false
|
407
405
|
)
|
408
|
-
|
409
|
-
uri.should ==
|
406
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
410
407
|
'https://www.googleapis.com/latitude/v1/currentLocation'
|
411
408
|
end
|
412
409
|
|
@@ -415,8 +412,7 @@ describe Google::APIClient do
|
|
415
412
|
:api_method => 'latitude.currentLocation.get',
|
416
413
|
:authenticated => false
|
417
414
|
)
|
418
|
-
status
|
419
|
-
status.should == 401
|
415
|
+
result.response.status.should == 401
|
420
416
|
end
|
421
417
|
end
|
422
418
|
|
@@ -451,8 +447,7 @@ describe Google::APIClient do
|
|
451
447
|
:api_method => 'moderator.profiles.get',
|
452
448
|
:authenticated => false
|
453
449
|
)
|
454
|
-
|
455
|
-
uri.should ==
|
450
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
456
451
|
'https://www.googleapis.com/moderator/v1/profiles/@me'
|
457
452
|
end
|
458
453
|
|
@@ -461,8 +456,7 @@ describe Google::APIClient do
|
|
461
456
|
:api_method => @moderator.profiles.get,
|
462
457
|
:authenticated => false
|
463
458
|
)
|
464
|
-
|
465
|
-
uri.should ==
|
459
|
+
request.to_env(Faraday.default_connection)[:url].should ===
|
466
460
|
'https://www.googleapis.com/moderator/v1/profiles/@me'
|
467
461
|
end
|
468
462
|
|
@@ -474,8 +468,7 @@ describe Google::APIClient do
|
|
474
468
|
[],
|
475
469
|
{:authenticated => false}
|
476
470
|
)
|
477
|
-
status
|
478
|
-
status.should == 401
|
471
|
+
result.response.status.should == 401
|
479
472
|
end
|
480
473
|
end
|
481
474
|
end
|