recs4 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.
- data/LICENSE.txt +20 -0
- data/Manifest.txt +12 -0
- data/README.txt +47 -0
- data/Rakefile +57 -0
- data/lib/recs4.rb +2 -0
- data/lib/recs4/operation.rb +385 -0
- data/lib/recs4/version.rb +9 -0
- data/lib/recs4/xml/element.rb +80 -0
- data/lib/recs4/xml/elements.rb +45 -0
- data/setup.rb +1585 -0
- data/test/jp_recs4_test.rb +53 -0
- data/test/test_helper.rb +2 -0
- metadata +65 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 kinumi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files (the "Software"
|
5
|
+
), to deal in the Software without restriction, including without
|
6
|
+
limitation the rights to use, copy, modify, merge, publish, distribute,
|
7
|
+
sublicense, and/or sell copies of the Software, and to permit persons
|
8
|
+
to whom the Software is furnished to do so, subject to the following
|
9
|
+
conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included
|
12
|
+
in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
== RECS4 is
|
2
|
+
|
3
|
+
An interface library to use
|
4
|
+
the Amazon E-Commerce Service 4.0 (ECS4.0) API on Ruby.
|
5
|
+
|
6
|
+
|
7
|
+
== Example to use
|
8
|
+
|
9
|
+
o = RECS4::Operation.new("Your AWSAccessKeyId", :locale => "jp")
|
10
|
+
|
11
|
+
# ItemLookup Operation
|
12
|
+
r = o.item_lookup("some asin", :response_group => "Large")
|
13
|
+
# Get element value
|
14
|
+
r[:items][:item][:asin].text #=> "some asin"
|
15
|
+
# Get attribute value
|
16
|
+
r[:items][:item][:item_attributes][:creator].a("Role") #=> "Artist"
|
17
|
+
|
18
|
+
# ItemSearch Operation
|
19
|
+
r = o.item_search("keywords")
|
20
|
+
# Get total pages
|
21
|
+
r[:items][:total_pages].text.to_i
|
22
|
+
# Get All items
|
23
|
+
r[:items][:item].each do |item|
|
24
|
+
item[:item_attributes][:artist].text
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
== License
|
29
|
+
|
30
|
+
See LICENSE
|
31
|
+
|
32
|
+
|
33
|
+
== Report bugs
|
34
|
+
|
35
|
+
https://rubyforge.org/projects/recs4/
|
36
|
+
|
37
|
+
|
38
|
+
== SVN repository
|
39
|
+
|
40
|
+
http://www.looselife.org/svn/ruby/lib/recs4/trunk/
|
41
|
+
|
42
|
+
|
43
|
+
== Developper
|
44
|
+
|
45
|
+
* kinumi
|
46
|
+
* kinumi@looselife.org
|
47
|
+
* http://www.looselife.org/
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'recs4', 'version')
|
13
|
+
|
14
|
+
AUTHOR = "kinumi" # can also be an array of Authors
|
15
|
+
EMAIL = "kinumi@looselife.org"
|
16
|
+
DESCRIPTION = "An interface library to use the Amazon E-Commerce Service 4.0 (ECS4.0) API on Ruby."
|
17
|
+
GEM_NAME = "recs4" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "recs4" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
21
|
+
|
22
|
+
|
23
|
+
NAME = "recs4"
|
24
|
+
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
25
|
+
# VERS = ENV['VERSION'] || (RECS4::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
26
|
+
ENV['VERSION'] = (RECS4::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
27
|
+
VERS = ENV['VERSION']
|
28
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
29
|
+
RDOC_OPTS = [
|
30
|
+
"--quiet",
|
31
|
+
"--title",
|
32
|
+
"recs4 documentation",
|
33
|
+
"--opname",
|
34
|
+
"index.html",
|
35
|
+
"--line-numbers",
|
36
|
+
"--main",
|
37
|
+
"README",
|
38
|
+
"--inline-source"
|
39
|
+
]
|
40
|
+
|
41
|
+
# Generate all the Rake tasks
|
42
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
43
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
44
|
+
p.author = AUTHOR
|
45
|
+
p.description = DESCRIPTION
|
46
|
+
p.email = EMAIL
|
47
|
+
p.summary = DESCRIPTION
|
48
|
+
p.url = HOMEPATH
|
49
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
50
|
+
p.test_globs = ["test/**/*_test.rb"]
|
51
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
52
|
+
|
53
|
+
# == Optional
|
54
|
+
#p.changes - A description of the release's latest changes.
|
55
|
+
#p.extra_deps - An array of rubygem dependencies.
|
56
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
57
|
+
end
|
data/lib/recs4.rb
ADDED
@@ -0,0 +1,385 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "open-uri"
|
3
|
+
require "kconv"
|
4
|
+
require "recs4/xml/element"
|
5
|
+
|
6
|
+
module RECS4 #:nodoc:
|
7
|
+
LOCALES = [
|
8
|
+
"ca",
|
9
|
+
"de",
|
10
|
+
"fr",
|
11
|
+
"jp",
|
12
|
+
"uk",
|
13
|
+
"us",
|
14
|
+
]
|
15
|
+
DEFAULT_LOCALE = "us"
|
16
|
+
|
17
|
+
WS_URLS = {
|
18
|
+
"ca" => "http://webservices.amazon.ca/onca/xml?Service=AWSECommerceService",
|
19
|
+
"de" => "http://webservices.amazon.de/onca/xml?Service=AWSECommerceService",
|
20
|
+
"fr" => "http://webservices.amazon.fr/onca/xml?Service=AWSECommerceService",
|
21
|
+
"jp" => "http://webservices.amazon.co.jp/onca/xml?Service=AWSECommerceService",
|
22
|
+
"uk" => "http://webservices.amazon.co.uk/onca/xml?Service=AWSECommerceService",
|
23
|
+
"us" => "http://webservices.amazon.com/onca/xml?Service=AWSECommerceService",
|
24
|
+
}
|
25
|
+
|
26
|
+
# All operations of ECS4.0
|
27
|
+
OPERATIONS = {
|
28
|
+
:browse_node_lookup => "BrowseNodeLookup",
|
29
|
+
:cart_add => "CartAdd",
|
30
|
+
:cart_clear => "CartClear",
|
31
|
+
:cart_create => "CartCreate",
|
32
|
+
:cart_get => "CartGet",
|
33
|
+
:cart_modify => "CartModify",
|
34
|
+
:customer_content_lookup => "CustomerContentLookup",
|
35
|
+
:customer_content_search => "CustomerContentSearch",
|
36
|
+
:help => "Help",
|
37
|
+
:item_lookup => "ItemLookup",
|
38
|
+
:item_search => "ItemSearch",
|
39
|
+
:list_lookup => "ListLookup",
|
40
|
+
:list_search => "ListSearch",
|
41
|
+
:seller_listing_lookup => "SellerListingLookup",
|
42
|
+
:seller_listing_search => "SellerListingSearch",
|
43
|
+
:seller_lookup => "SellerLookup",
|
44
|
+
:similarity_lookup => "SimilarityLookup",
|
45
|
+
:transaction_lookup => "TransactionLookup",
|
46
|
+
}
|
47
|
+
|
48
|
+
# Common parameters of all operations
|
49
|
+
GLOBAL_PARAMS = {
|
50
|
+
:locale => "Locale",
|
51
|
+
:associate_tag => "AssociateTag",
|
52
|
+
:version => "Version",
|
53
|
+
}
|
54
|
+
|
55
|
+
# Parameters of ItemLookup
|
56
|
+
ITEM_LOOKUP_PARAMS = {
|
57
|
+
:item_id => "ItemId",
|
58
|
+
:id_type => "IdType",
|
59
|
+
:search_index => "SearchIndex",
|
60
|
+
:merchant_id => "MerchantId",
|
61
|
+
:condition => "Condition",
|
62
|
+
:delivery_method => "DeliveryMethod",
|
63
|
+
:ispu_postal_code => "ISPUPostalCode",
|
64
|
+
:offer_page => "OfferPage",
|
65
|
+
:review_page => "ReviewPage",
|
66
|
+
:variation_page => "VariationPage",
|
67
|
+
:response_group => "ResponseGroup",
|
68
|
+
}
|
69
|
+
|
70
|
+
# Parameters of ItemSearch
|
71
|
+
ITEM_SEARCH_PARAMS = {
|
72
|
+
:availability => "Availability",
|
73
|
+
:review_sort => "ReviewSort",
|
74
|
+
:search_index => "SearchIndex",
|
75
|
+
:keywords => "Keywords",
|
76
|
+
:title => "Title",
|
77
|
+
:power => "Power",
|
78
|
+
:browse_node => "BrowseNode",
|
79
|
+
:artist => "Artist",
|
80
|
+
:author => "Author",
|
81
|
+
:actor => "Actor",
|
82
|
+
:director => "Director",
|
83
|
+
:audience_rating => "AudienceRating",
|
84
|
+
:manufacturer => "Manufacturer",
|
85
|
+
:music_label => "MusicLabel",
|
86
|
+
:composer => "Composer",
|
87
|
+
:publisher => "Publisher",
|
88
|
+
:brand => "Brand",
|
89
|
+
:conductor => "Conductor",
|
90
|
+
:orchestra => "Orchestra",
|
91
|
+
:text_stream => "TextStream",
|
92
|
+
:item_page => "ItemPage",
|
93
|
+
:sort => "Sort",
|
94
|
+
:city => "City",
|
95
|
+
:cuisine => "Cuisine",
|
96
|
+
:neighborhood => "Neighborhood",
|
97
|
+
:minimum_price => "MinimumPrice",
|
98
|
+
:maximum_price => "MaximumPrice",
|
99
|
+
:merchantid => "MerchantId",
|
100
|
+
:condition => "Condition",
|
101
|
+
:delivery_method => "DeliveryMethod",
|
102
|
+
:response_group => "ResponseGroup",
|
103
|
+
}
|
104
|
+
|
105
|
+
# Parameter of BrowseNodeLookup
|
106
|
+
BROWSE_NODE_LOOKUP_PARAMS = {
|
107
|
+
:browse_node_id => "BrowseNodeId",
|
108
|
+
:response_group => "ResponseGroup",
|
109
|
+
}
|
110
|
+
|
111
|
+
# All parameters
|
112
|
+
ALL_PARAMS = {}
|
113
|
+
ALL_PARAMS.merge! GLOBAL_PARAMS
|
114
|
+
ALL_PARAMS.merge! ITEM_LOOKUP_PARAMS
|
115
|
+
ALL_PARAMS.merge! ITEM_SEARCH_PARAMS
|
116
|
+
ALL_PARAMS.merge! BROWSE_NODE_LOOKUP_PARAMS
|
117
|
+
|
118
|
+
#
|
119
|
+
# This class wraps the Amazon ECS4.0 API
|
120
|
+
#
|
121
|
+
# Using REST request internally.
|
122
|
+
#
|
123
|
+
# Example to use:
|
124
|
+
#
|
125
|
+
# o = RECS4::Operation.new("Your AWSAccessKeyId", :locale => "jp")
|
126
|
+
#
|
127
|
+
# # ItemLookup Operation
|
128
|
+
# r = o.item_lookup("some asin", :response_group => "Large")
|
129
|
+
# # Get element value
|
130
|
+
# r[:items][:item][:asin].text #=> "some asin"
|
131
|
+
# # Get attribute value
|
132
|
+
# r[:items][:item][:item_attributes][:creator].a("Role") #=> "Artist"
|
133
|
+
#
|
134
|
+
# # ItemSearch Operation
|
135
|
+
# r = o.item_search("keywords")
|
136
|
+
# # Get total pages
|
137
|
+
# r[:items][:total_pages].text.to_i
|
138
|
+
# # Get All items
|
139
|
+
# r[:items][:item].each do |item|
|
140
|
+
# item[:item_attributes][:artist].text
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
class Operation
|
144
|
+
#
|
145
|
+
# Available options: Look RECS4::GLOBAL_PARAMS
|
146
|
+
#
|
147
|
+
# "Locale" parameter is required in ECS4.0 API.
|
148
|
+
# But :locale option can be skipped in RECS4.
|
149
|
+
# DEFAULT_LOCALE is used.
|
150
|
+
#
|
151
|
+
def initialize(aws_access_key_id, options = {})
|
152
|
+
@aws_access_key_id = aws_access_key_id
|
153
|
+
@options = {}
|
154
|
+
GLOBAL_PARAMS.each_key do |key|
|
155
|
+
@options[key] = options[key]
|
156
|
+
end
|
157
|
+
unless @options.keys.include?(:locale)
|
158
|
+
@options[:locale] = DEFAULT_LOCALE
|
159
|
+
end
|
160
|
+
@element_names = {}
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Available options: Look RECS4::ITEM_LOOKUP_PARAMS
|
165
|
+
#
|
166
|
+
def item_lookup(item_id, options = {})
|
167
|
+
unless has_valid_item_lookup_query(item_id, options)
|
168
|
+
return nil
|
169
|
+
end
|
170
|
+
url = item_lookup_url(item_id, options)
|
171
|
+
response_xml = get_response(url)
|
172
|
+
parsed_xml = REXML::Document.new(response_xml)
|
173
|
+
return RECS4::XML::Element.new(parsed_xml.root)
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Available options: Look RECS4::ITEM_SEARCH_PARAMS
|
178
|
+
#
|
179
|
+
# "keywords" can be nil.
|
180
|
+
# If "keywords" is nil, other options for searching must not be nil.
|
181
|
+
# For example, :artist, :author, and so on.
|
182
|
+
#
|
183
|
+
def item_search(keywords, options = {})
|
184
|
+
unless options[:search_index]
|
185
|
+
options[:search_index] = "Blended"
|
186
|
+
end
|
187
|
+
unless has_valid_item_search_query(keywords, options)
|
188
|
+
return nil
|
189
|
+
end
|
190
|
+
url = item_search_url(keywords, options)
|
191
|
+
response_xml = get_response(url)
|
192
|
+
parsed_xml = REXML::Document.new(response_xml)
|
193
|
+
return RECS4::XML::Element.new(parsed_xml.root)
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# Available options: Look BROWSE_NODE_LOOKUP_PARAMS
|
198
|
+
#
|
199
|
+
def browse_node_lookup(browse_node_id, options = {})
|
200
|
+
unless has_valid_browse_node_lookup_query(browse_node_id, options)
|
201
|
+
return nil
|
202
|
+
end
|
203
|
+
url = browse_node_lookup_url(browse_node_id, options)
|
204
|
+
response_xml = get_response(url)
|
205
|
+
parsed_xml = REXML::Document.new(response_xml)
|
206
|
+
return RECS4::XML::Element.new(parsed_xml.root)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Not implemented yet
|
210
|
+
def cart_add
|
211
|
+
end
|
212
|
+
# Not implemented yet
|
213
|
+
def cart_clear
|
214
|
+
end
|
215
|
+
# Not implemented yet
|
216
|
+
def cart_get
|
217
|
+
end
|
218
|
+
# Not implemented yet
|
219
|
+
def cart_modify
|
220
|
+
end
|
221
|
+
# Not implemented yet
|
222
|
+
def customer_controller_lookup
|
223
|
+
end
|
224
|
+
# Not implemented yet
|
225
|
+
def customer_controller_search
|
226
|
+
end
|
227
|
+
# Not implemented yet
|
228
|
+
def help
|
229
|
+
end
|
230
|
+
# Not implemented yet
|
231
|
+
def list_lookup
|
232
|
+
end
|
233
|
+
# Not implemented yet
|
234
|
+
def list_search
|
235
|
+
end
|
236
|
+
# Not implemented yet
|
237
|
+
def seller_listing_lookup
|
238
|
+
end
|
239
|
+
# Not implemented yet
|
240
|
+
def seller_listing_search
|
241
|
+
end
|
242
|
+
# Not implemented yet
|
243
|
+
def seller_lookup
|
244
|
+
end
|
245
|
+
# Not implemented yet
|
246
|
+
def similarity_lookup
|
247
|
+
end
|
248
|
+
# Not implemented yet
|
249
|
+
def transaction_lookup
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
#
|
254
|
+
# Get HTTP response
|
255
|
+
#
|
256
|
+
def get_response(url)
|
257
|
+
return open(url).read
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# Generate a Operation URL
|
262
|
+
#
|
263
|
+
def operation_url(operation, params = {})
|
264
|
+
url = WS_URLS[@options[:locale]].dup
|
265
|
+
url << "&AWSAccessKeyId=#{@aws_access_key_id}"
|
266
|
+
url << "&Operation=#{OPERATIONS[operation]}"
|
267
|
+
ALL_PARAMS.each_key do |key|
|
268
|
+
if params[key]
|
269
|
+
url << "&#{ALL_PARAMS[key]}=#{params[key]}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
return URI.escape(url.toutf8)
|
273
|
+
end
|
274
|
+
|
275
|
+
#
|
276
|
+
# Generate a ItemLookup operation URL
|
277
|
+
#
|
278
|
+
def item_lookup_url(item_id, options = {})
|
279
|
+
params = {
|
280
|
+
:item_id => item_id
|
281
|
+
}
|
282
|
+
GLOBAL_PARAMS.each_key do |key|
|
283
|
+
if options[key]
|
284
|
+
params[key] = options[key]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
ITEM_LOOKUP_PARAMS.each_key do |key|
|
288
|
+
if options[key]
|
289
|
+
params[key] = options[key]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
return operation_url(:item_lookup, params)
|
293
|
+
end
|
294
|
+
|
295
|
+
#
|
296
|
+
# Generate a ItemSearch operation URL
|
297
|
+
#
|
298
|
+
def item_search_url(keywords, options = {})
|
299
|
+
params = {}
|
300
|
+
if keywords
|
301
|
+
params[:keywords] = keywords
|
302
|
+
end
|
303
|
+
GLOBAL_PARAMS.each_key do |key|
|
304
|
+
if options[key]
|
305
|
+
params[key] = options[key]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
ITEM_SEARCH_PARAMS.each_key do |key|
|
309
|
+
if options[key]
|
310
|
+
params[key] = options[key]
|
311
|
+
end
|
312
|
+
end
|
313
|
+
return operation_url(:item_search, params)
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# Generate a BrowseNodeLookup operation URL
|
318
|
+
#
|
319
|
+
def browse_node_lookup_url(browse_node_id, options = {})
|
320
|
+
params = {
|
321
|
+
:browse_node_id => browse_node_id
|
322
|
+
}
|
323
|
+
GLOBAL_PARAMS.each_key do |key|
|
324
|
+
if options[key]
|
325
|
+
params[key] = options[key]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
BROWSE_NODE_LOOKUP_PARAMS.each_key do |key|
|
329
|
+
if options[key]
|
330
|
+
params[key] = options[key]
|
331
|
+
end
|
332
|
+
end
|
333
|
+
return operation_url(:browse_node_lookup, params)
|
334
|
+
end
|
335
|
+
|
336
|
+
def has_valid_item_lookup_query(item_id, options ={})
|
337
|
+
# ItemId is required
|
338
|
+
unless item_id
|
339
|
+
return false
|
340
|
+
end
|
341
|
+
return true
|
342
|
+
end
|
343
|
+
|
344
|
+
def has_valid_item_search_query(keywords, options = {})
|
345
|
+
# SearchIndex is required
|
346
|
+
unless options[:search_index]
|
347
|
+
return false
|
348
|
+
end
|
349
|
+
# At least one of the parameter to search
|
350
|
+
search_word_enabled = nil
|
351
|
+
unless keywords
|
352
|
+
[
|
353
|
+
:title,
|
354
|
+
:browse_node,
|
355
|
+
:power,
|
356
|
+
:artist,
|
357
|
+
:author,
|
358
|
+
:actor,
|
359
|
+
:director,
|
360
|
+
:manufacturer,
|
361
|
+
:music_label,
|
362
|
+
:composer,
|
363
|
+
:publisher,
|
364
|
+
:brand,
|
365
|
+
:conductor,
|
366
|
+
:orchestra
|
367
|
+
].each do |key|
|
368
|
+
search_word_enabled ||= options[key]
|
369
|
+
end
|
370
|
+
unless search_word_enabled
|
371
|
+
return false
|
372
|
+
end
|
373
|
+
end
|
374
|
+
return true
|
375
|
+
end
|
376
|
+
|
377
|
+
def has_valid_browse_node_lookup_query(browse_node_id, options ={})
|
378
|
+
# BrowseNode is required
|
379
|
+
unless browse_node_id
|
380
|
+
return false
|
381
|
+
end
|
382
|
+
return true
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|