peddler 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .DS_Store
data/History.txt ADDED
@@ -0,0 +1,8 @@
1
+ == 0.1.0 / 2009-08-14
2
+ * First public release.
3
+ == 0.1.1 / 2009-08-15
4
+ * Bumping gem to tag correctly on Github.
5
+ == 0.1.2 / 2009-08-17
6
+ * Minor stuff.
7
+ == 0.1.3 / 2009-11-06
8
+ * Nothing major, Tom.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2009 Hakan Şenol Ensari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,118 @@
1
+ = Peddler
2
+
3
+ Peddler is a Ruby wrapper to the Amazon Inventory management API.
4
+
5
+ == Example usage
6
+
7
+ Fire off a client:
8
+
9
+ client = Peddler::Client.new(
10
+ :username => "foo@bar.com",
11
+ :password => "secret",
12
+ :region => "us")
13
+
14
+ Create an inventory file:
15
+
16
+ batch = client.new_inventory_batch
17
+ item = client.new_inventory_item(
18
+ :product_id => "1234567890",
19
+ :price => 100.00,
20
+ :sku => "SKU-123",
21
+ :quantity => 10)
22
+ batch << item
23
+ ...
24
+
25
+ Repeat ad infinitum and upload:
26
+
27
+ batch.upload
28
+
29
+ The batch now should have an upload ID. Go ahead and check the error log:
30
+
31
+ upload_log = client.new_report(
32
+ :upload,
33
+ :id => batch.id)
34
+ upload_log.body
35
+ => "Feed Processing Summary:\n\tNumber of records processed\t\t1\n\tNumber of records successful\t\t1\n\n"
36
+
37
+ You're done listing and are wondering if you have any new orders:
38
+
39
+ orders_report = client.new_report :order
40
+ orders = client.detab(orders_report.body)
41
+ p orders.size
42
+ => 1500
43
+ p orders[0].item_name
44
+ => "A Thousand Plateaus: Capitalism and Schizophrenia (Paperback) by Gilles Deleuze"
45
+
46
+ Now that you have diligently processed the orders, post back the results to Amazon:
47
+
48
+ feed = client.new_order_fulfillment_feed
49
+ fulfilled_order = client.new_fulfilled_order(
50
+ :order_id => "123-1234567-1234567",
51
+ :order_date => "2009-08-01",
52
+ :carrier_code => "USPS",
53
+ :tracking_number => "0308 0330 0000 0000 0000")
54
+ feed << fulfilled_order
55
+
56
+ Again, repeat until done and upload:
57
+
58
+ feed.upload
59
+
60
+ Curious to see the processing report?
61
+
62
+ p feed.status
63
+ => "_SUBMITTED_"
64
+
65
+ Refresh until you get:
66
+
67
+ p feed.status!
68
+ => "_DONE_"
69
+
70
+ Finally, check the report:
71
+
72
+ p feed.download.to_s
73
+ => ...
74
+
75
+ Sadly, you also have an order you can't fulfill. No problem. The workflow is similar:
76
+
77
+ feed = client.new_order_cancellation_feed
78
+ cancelled_order = client.new_cancelled_order(
79
+ :order_id => "123-1234567-1234567",
80
+ :cancellation_reason_code => "NoInventory",
81
+ :amazon_order_item_code => "12341234567890")
82
+ feed << cancelled_order
83
+ feed.upload
84
+ sleep(60)
85
+ feed.status!
86
+ => "_DONE_"
87
+ p feed.download.to_s
88
+ => ...
89
+
90
+ Need to post a partial refund? You'll have to revert to the older batch refund API method:
91
+
92
+ refunds = client.new_refund_batch
93
+ refund = client.new_refund(
94
+ :order_id => "123-1234567-1234567",
95
+ :payments_transaction_id => "12341234567890",
96
+ :refund_amount => 1.00,
97
+ :reason => "GeneralAdjustment",
98
+ :message => "With our apologies.")
99
+ refunds << refund
100
+ refunds.upload
101
+
102
+ Great sales. For a change, let's download something different from Amazon. Here's a preorder report:
103
+
104
+ preorder_report = client.new_report(
105
+ :preorder,
106
+ :product_line => "Books",
107
+ :frequency => 2)
108
+ preorders = client.detab(preorder_report.body)
109
+ p preorders.size
110
+ => 2000
111
+ p preorders[0].asin
112
+ => "1234567890"
113
+ p preorders[0].average_asking_price
114
+ => "100"
115
+
116
+ Run rdoc and check the source for more detailed info.
117
+
118
+ Copyright © 2009 Hakan Senol Ensari, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ begin
4
+ require "jeweler"
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "peddler"
7
+ s.summary = "A Ruby wrapper to the Amazon Inventory Management API"
8
+ s.email = "hakan.ensari@papercavalier.com"
9
+ s.homepage = "http://snl.github.com/peddler"
10
+ s.description = "Peddler is a Ruby wrapper to the Amazon Inventory Management API."
11
+ s.authors = ["Hakan Senol Ensari"]
12
+ s.add_dependency "xml-simple"
13
+ s.add_development_dependency "rspec"
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 3
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'peddler'
@@ -0,0 +1,215 @@
1
+ # = Peddler
2
+ # Peddler is a Ruby wrapper to the Amazon Inventory management API.
3
+ #
4
+ # Peddler::Client has some detailed explanation and examples of usage.
5
+ module Peddler
6
+ # This is the public interface of the Peddler library.
7
+ class Client
8
+ # Creates a client instance.
9
+ #
10
+ # client = Peddler::Client.new :username => "foo@bar.com",
11
+ # :password => "secret",
12
+ # :region => "us"
13
+ #
14
+ def initialize(params={})
15
+ params.each_pair { |key, value| self.send("#{key}=", value) }
16
+ end
17
+
18
+ def username=(username)
19
+ self.transport.username = username
20
+ end
21
+
22
+ def password=(password)
23
+ self.transport.password = password
24
+ end
25
+
26
+ # Sets Amazon region.
27
+ #
28
+ # Possible regions: ["us", "uk", "de", "ca", "fr", "jp"]
29
+ def region=(region)
30
+ self.transport.region = region
31
+ end
32
+
33
+ # Creates an inventory batch.
34
+ #
35
+ # batch = client.new_inventory_batch
36
+ # book = new_inventory_item(
37
+ #  :product_id => "1234567890",
38
+ # :sku => "SKU-001",
39
+ # :price => 10.00,
40
+ # :quantity => 1)
41
+ # batch << book
42
+ # batch.upload
43
+ # report = client.new_report :upload, :id => batch.id
44
+ # p report.body
45
+ # => "Feed Processing Summary:\n\tNumber of records processed\t\t1\n\tNumber of records successful\t\t1\n\n"
46
+ #
47
+ def new_inventory_batch
48
+ Peddler::Inventory::Batch.new(self.transport.dup)
49
+ end
50
+
51
+ # Creates an inventory item. Parameter keys are lowercased and underscored but otherwise the same as
52
+ # Amazon's colum titles in their tab-delimited templates.
53
+ def new_inventory_item(params={})
54
+ Peddler::Inventory::Item.new(params)
55
+ end
56
+
57
+ # Returns count of pending inventory uploads queued at Amazon.
58
+ def inventory_queue
59
+ Peddler::Inventory::Queue.count(self.transport)
60
+ end
61
+
62
+ # Creates an order fulfillment batch.
63
+ #
64
+ # feed = client.new_order_fulfillment_feed
65
+ # fulfilled_order = client.new_fulfilled_order(
66
+ # :order_id => "123-1234567-1234567",
67
+ # :order_date => "2009-08-01")
68
+ # feed << fulfilled_order
69
+ # feed.upload
70
+ # feed.status
71
+ # => "_SUBMITTED_"
72
+ # sleep(60)
73
+ # feed.status!
74
+ # => "_DONE_"
75
+ # p feed.download.to_s
76
+ #
77
+ def new_order_fulfillment_feed
78
+ Peddler::Feeds::OrderFulfillment::Batch.new(self.transport.dup)
79
+ end
80
+
81
+ # Creates an item that can then be added to an order fulfillment feed. Keys are lowercased and underscored but
82
+ # otherwise the same as Amazon's headers. See section 7.1 in the API docs.
83
+ def new_fulfilled_order(params={})
84
+ Peddler::Feeds::OrderFulfillment::Item.new(params)
85
+ end
86
+
87
+ # Creates an order cancellation batch.
88
+ #
89
+ # feed = client.new_order_cancellation_feed
90
+ # cancelled_order = client.new_cancelled_order(
91
+ # :order_id => "123-1234567-1234567",
92
+ # :cancellation_reason_code => "NoInventory",
93
+ # :amazon_order_item_code => "12341234567890")
94
+ # feed << cancelled_order
95
+ # feed.upload
96
+ # feed.status
97
+ # => "_SUBMITTED_"
98
+ # sleep(60)
99
+ # feed.status!
100
+ # => "_DONE_"
101
+ # p feed.download.to_s
102
+ #
103
+ def new_order_cancellation_feed
104
+ Peddler::Feeds::OrderCancellation::Batch.new(self.transport.dup)
105
+ end
106
+
107
+ # Creates an item that can then be added to an order cancellation feed. Keys are lowercased and underscored but
108
+ # otherwise the same as Amazon's headers. See section 7.4 in the API docs.
109
+ def new_cancelled_order(params={})
110
+ Peddler::Feeds::OrderCancellation::Item.new(params)
111
+ end
112
+
113
+ # Creates a refund batch.
114
+ #
115
+ # batch = client.new_refund_batch
116
+ # refund = client.new_refund(
117
+ # :order_id => "123-1234567-1234567",
118
+ # :payments_transaction_id => "12341234567890",
119
+ # :refund_amount => 10.00,
120
+ # :reason => "CouldNotShip",
121
+ # :message => "With our apologies.")
122
+ # batch << refund
123
+ # batch.upload
124
+ # sleep(60)
125
+ # status = client.latest_reports :batch_refund, :count => 1
126
+ # report = client.new_report(
127
+ # :batch_refund,
128
+ # :id => status[0].id)
129
+ # p report.body
130
+ # => "123-1234567-1234567order-item-id: 12341234567890\tSUCCESS 10.00 is Refunded.\r\n"
131
+ #
132
+ def new_refund_batch
133
+ Peddler::Refunds::Batch.new(self.transport.dup)
134
+ end
135
+
136
+ # Creates a refund item that can then be added to a refund batch.
137
+ #
138
+ # Possible reasons: ["GeneralAdjustment", "CouldNotShip", "DifferentItem", "MerchandiseNotReceived", "MerchandiseNotAsDescribed"]
139
+ def new_refund(params={})
140
+ Peddler::Refunds::Item.new(params)
141
+ end
142
+
143
+ # Creates an instance for an already-generated report. Works only with what I call legacy reports, that is,
144
+ # anything that comes before section 7 in the API docs.
145
+ #
146
+ # Possible report names: [:upload, :order, :preorder, :batch_refund, :open_listings, :open_listings_lite, :open_listings_liter]
147
+ #
148
+ # You can download a specific report by using its ID. Otherwise, the instance will fetch the latest available report. One
149
+ # oddball exception: upload reports do require an ID and will return nil if you don't provide one.
150
+ #
151
+ # orders_report = client.new_report :order
152
+ # orders = client.detab(orders_report.body)
153
+ # orders[0].buyer_name
154
+ # => "John Doe"
155
+ #
156
+ # preorders_report = client.new_report(
157
+ #  :preorder,
158
+ # :product_line => "Books",
159
+ # :frequency => 2)
160
+ # preorders = client.detab(preorders_report.body)
161
+ # preorders[0].average_asking_price
162
+ # => "100"
163
+ #
164
+ def new_report(name,params={})
165
+ Peddler::LegacyReports::Report.new(self.transport.dup, name, params)
166
+ end
167
+
168
+ # Requests a report. Returns true when successful.
169
+ #
170
+ # Possible report names: [:order, :open_listings, :open_listings_lite, :open_listings_liter]
171
+ #
172
+ # client.generate_report :order, :number_of_days => 15
173
+ #
174
+ # A word of caution. Open listings may crap up with larger inventories. I will have to migrate to a cURL-based
175
+ # HTTP client to get that working again.
176
+ def generate_report(name,params={})
177
+ Peddler::LegacyReports.generate(self.transport, name, params)
178
+ end
179
+
180
+ # Creates an unshipped order report. Takes on some optional parameters, such as :id, :starts_at, :ends_at. By default,
181
+ # it will request a new unshipped order report for the past seven days.
182
+ #
183
+ # report = client.new_unshipped_orders_report
184
+ # report.status
185
+ # => "_SUBMITTED_"
186
+ # sleep(60)
187
+ # report.status!
188
+ # => "_DONE_"
189
+ # p report.unshipped_orders
190
+ #
191
+ def new_unshipped_orders_report(params={})
192
+ Peddler::Reports::UnshippedOrdersReport.new(self.transport.dup, params)
193
+ end
194
+
195
+ # Returns status of most recent reports. Optional "count" defaults to 10. Name can be [ :upload, :order, :batch_refund, :open_listings, :open_listings_lite, :open_listings_liter ].
196
+ #
197
+ # reports = client.latest_reports :order, :count => 1
198
+ # reports[0]
199
+ # => #<Peddler::LegacyReports::ReportStatus starts_at="07-29-2009:10-00-06" ...
200
+ #
201
+ def latest_reports(name,params={})
202
+ Peddler::LegacyReports.latest(self.transport, name, params)
203
+ end
204
+
205
+ # Decodes tab-delimited content into an array of OpenStruct objects.
206
+ def detab(msg)
207
+ Peddler::Handlers::TabDelimitedHandler.decode_response(msg)
208
+ end
209
+
210
+ protected
211
+ def transport #:nodoc:all
212
+ @transport ||= Peddler::Transport.new
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,184 @@
1
+ module Peddler
2
+ # This module includes functionality to handle the charge-when-ship-related feeds Amazon added to the API
3
+ # in its latest incarnation in 2009.
4
+ module Feeds
5
+ # Downloadable file. The processing report in case of feeds. Outputs Amazon's response verbatim.
6
+ # Will add functionality to parse the response some time down the road.
7
+ class Download
8
+ attr_accessor :id, :type, :related_reference_id, :available_at, :acknowledged
9
+
10
+ def initialize(transport, params={})
11
+ @mapped_params = {
12
+ "DownloadId" => "id",
13
+ "DownloadType" => "type",
14
+ "RelatedReferenceId" => "related_reference_id",
15
+ "AvailableDate" => "available_at",
16
+ "Acknowledged" => "acknowledged"}
17
+ @transport = transport
18
+ params.each_pair{ |key, value| self.send "#{@mapped_params[key]}=", value }
19
+ end
20
+
21
+ # Retrieves and returns report
22
+ def to_s
23
+ @body ||= download_report
24
+ end
25
+ private
26
+ def download_report
27
+ return nil if @id.nil?
28
+ @transport.modernize_request
29
+ @transport.query_params.merge!({
30
+ "Action" => "download",
31
+ "downloadId" => @id})
32
+ @transport.execute_request
33
+ end
34
+ end
35
+
36
+ # This is the base class.
37
+ class Feed
38
+ attr_writer :file_content
39
+ attr_accessor :batch, :download, :status, :type, :id, :submitted_at, :started_processing_at, :completed_processing_at, :messages_processed, :messages_successful, :messages_with_errors, :messages_with_warnings
40
+
41
+ def initialize(transport)
42
+ @transport = transport
43
+ @batch = []
44
+ @mapped_params = {
45
+ "UploadStatus" => "status",
46
+ "UploadType" => "type",
47
+ "UploadId" => "id",
48
+ "SubmittedDate" => "submitted_at",
49
+ "StartedProcessingDate" => "started_processing_at",
50
+ "CompletedProcessingDate" => "completed_processing_at",
51
+ "CompletedProcesssingDate" => "completed_processing_at",
52
+ "MessagesProcessed" => "messages_processed",
53
+ "MessagesSuccessful" => "messages_successful",
54
+ "MessagesWithErrors" => "messages_with_errors",
55
+ "MessagesWithWarnings" => "messages_with_warnings"}
56
+ end
57
+
58
+ # Returns content of the upload file.
59
+ def file_content
60
+ return @file_content if @file_content
61
+ out = @file_header
62
+ @batch.each{ |item| out << item.to_s }
63
+ @file_content = out
64
+ end
65
+
66
+ # Returns status and will also refresh if not already "done."
67
+ def status!
68
+ return @status if @status.nil? || @status =~ /_DONE_/
69
+ refresh_status
70
+ @status
71
+ end
72
+
73
+ # Uploads batch.
74
+ def upload
75
+ raise PeddlerError.new("Batch already uploaded") unless @id.nil?
76
+ @transport.modernize_request
77
+ @transport.query_params.merge!({
78
+ "Action" => "upload",
79
+ "uploadType" => @type})
80
+ @transport.body = file_content
81
+ res = @transport.execute_request
82
+ process_response(res)
83
+ @status
84
+ end
85
+
86
+ # Adds an item to the batch.
87
+ def <<(item)
88
+ @batch << item
89
+ end
90
+ private
91
+ def refresh_status
92
+ @transport.modernize_request
93
+ @transport.query_params.merge!({
94
+ "Action" => "uploadStatus",
95
+ "uploadId" => @id})
96
+ res = @transport.execute_request
97
+ process_response(res)
98
+ end
99
+
100
+ def process_response(res)
101
+ xml = Peddler::Handlers::XMLHandler.decode_response(res)
102
+ params = Peddler::Handlers::XMLHandler.parse(:upload, xml)
103
+ if params[0]
104
+ params[0].each_pair do |key, value|
105
+ if key == "RelatedDownloadsList"
106
+ params = Peddler::Handlers::XMLHandler.parse(:download, value)
107
+ @download = Peddler::Feeds::Download.new(@transport, params[0])
108
+ else
109
+ self.send "#{@mapped_params[key]}=", value
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ module OrderFulfillment
117
+ # This class contains methods to upload order fulfillment info to Amazon.
118
+ # See sections 7.1 through 7.3 in the API documentation for more detail.
119
+ class Batch < Peddler::Feeds::Feed
120
+ def initialize(transport)
121
+ @file_header = "order-id\torder-item-id\tquantity\tship-date\tcarrier-code\tcarrier-name\ttracking-number\tship-method\r\n"
122
+ @type = "_POST_FLAT_FILE_FULFILLMENT_DATA_"
123
+ super(transport)
124
+ end
125
+ end
126
+
127
+ # This is an order fulfillment item.
128
+ class Item
129
+ attr_accessor :order_id, :order_item_id, :quantity, :ship_date, :carrier_name, :tracking_number, :ship_method
130
+ attr_reader :carrier_code
131
+
132
+ def initialize(params={})
133
+ params.each_pair{ |key, value| send("#{key}=", value) }
134
+ end
135
+
136
+ # Validates when setting carrier code.
137
+ def carrier_code=(carrier_code)
138
+ @carrier_code = carrier_code if %w{USPS UPS FedEx other}.include?(carrier_code)
139
+ end
140
+
141
+ # Outputs a formatted line for the tab-delimited upload file.
142
+ def to_s
143
+ "#{@order_id}\t#{@order_item_id}\t#{@quantity}\t#{@ship_date}\t#{@carrier_code}\t#{@carrier_name}\t#{@tracking_number}\t#{@ship_method}\r\n"
144
+ end
145
+ end
146
+ end
147
+
148
+ # This module contains methods to upload cancelled orders to Amazon.
149
+ # See section 7.4 in the API documentation for more detail.
150
+ module OrderCancellation
151
+ class Batch < Peddler::Feeds::Feed
152
+ def initialize(transport)
153
+ @file_header = "TemplateType=OrderCancellation Version=1.0/1.0.3 This row for Amazon.com use only. Do not modify or delete.\r\n" +
154
+ "order-id\tcancellation-reason-code\tamazon-order-item-code\r\n"
155
+ @type = "_POST_FLAT_FILE_ORDER_ACKNOWLEDGEMENT_DATA_"
156
+ super(transport)
157
+ end
158
+ end
159
+
160
+ # This is a cancelled order item.
161
+ class Item
162
+ attr_accessor :order_id, :amazon_order_item_code
163
+ attr_reader :cancellation_reason_code
164
+
165
+ def initialize(params={})
166
+ params.each_pair{ |key, value| send("#{key}=", value) }
167
+ end
168
+
169
+ # Validates when setting cancellation reason code.
170
+ def cancellation_reason_code=(cancellation_reason_code)
171
+ @cancellation_reason_code = cancellation_reason_code if %w{ BuyerCanceled CustomerExchange CustomerReturn GeneralAdjustment MerchandiseNotReceived NoInventory ShippingAddressUndeliverable }.include?(cancellation_reason_code)
172
+ end
173
+
174
+ # Outputs a formatted line for the tab-delimited upload file.
175
+ def to_s
176
+ if @cancellation_reason_code.nil? != @amazon_order_item_code.nil?
177
+ raise PeddlerError.new("Provide codes for both cancellation reason and Amazon order item (or omit both).")
178
+ end
179
+ "#{@order_id}\t#{@cancellation_reason_code}\t#{@amazon_order_item_code}\r\n"
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,58 @@
1
+ module Peddler
2
+ module Handlers
3
+ class XMLHandler
4
+ # Decodes an XML response.
5
+ def self.decode_response(res)
6
+ XmlSimple.xml_in(res)
7
+ end
8
+ # Parses responses to uploads and status queries for feeds in Section 7 of the docs. Walks
9
+ # through lists and returns an array of hashes.
10
+ def self.parse(name, xml)
11
+ name = name.to_s.capitalize
12
+ list = xml["#{name}sStatusList"] || xml["#{name}sList"]
13
+ if list
14
+ list.collect { |s| parse_status(name, s) }
15
+ else
16
+ [ parse_status(name, xml) ]
17
+ end
18
+ end
19
+
20
+ # Parses legacy responses to queries on statuses of generated reports and inventory uploads.
21
+ def self.parse_legacy(xml)
22
+ if xml["Batch"]
23
+ xml["Batch"].collect { |input| Peddler::LegacyReports::UploadStatus.new(input) }
24
+ elsif xml["Report"]
25
+ xml["Report"].collect { |input| Peddler::LegacyReports::ReportStatus.new(input) }
26
+ end
27
+ end
28
+ protected
29
+ def self.parse_status(name, xml)
30
+ if xml[name]
31
+ xml[name][0].inject({}) do |memo, pair|
32
+ key, value = pair
33
+ value[0] = Time.parse(value[0]) if key =~ /Date$/
34
+ memo.merge!({ key => value[0] })
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ class TabDelimitedHandler
41
+ # Decodes tab-delimited content into an array of OpenStruct objects.
42
+ def self.decode_response(res)
43
+ lines = res.split("\n")
44
+ if lines.size > 1
45
+ params = lines[0].split("\t").collect{ |value| value.gsub(/-/, "_") }
46
+ params_size = params.size
47
+ (1..(lines.size - 1)).collect do |line_key|
48
+ values = lines[line_key].split("\t")
49
+ data = (0..(params_size - 1)).inject({}) { |memo, key| memo.merge( { params[key] => values[key] } ) }
50
+ OpenStruct.new(data)
51
+ end
52
+ else
53
+ res
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end