vacuum 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +31 -49
- data/lib/vacuum/em.rb +23 -0
- data/lib/vacuum/{builder.rb → hash_builder.rb} +6 -12
- data/lib/vacuum/request.rb +79 -91
- data/lib/vacuum/response.rb +24 -46
- data/lib/vacuum/version.rb +1 -1
- data/lib/vacuum.rb +13 -12
- data/spec/vacuum/{builder_spec.rb → hash_builder_spec.rb} +5 -5
- data/spec/vacuum/request_spec.rb +54 -107
- data/spec/vacuum/response_spec.rb +30 -46
- metadata +11 -24
- data/lib/vacuum/cart.rb +0 -90
- data/lib/vacuum/cart_operations.rb +0 -12
- data/lib/vacuum/error.rb +0 -13
- data/lib/vacuum/locale.rb +0 -39
- data/lib/vacuum/lookup_operations.rb +0 -62
- data/lib/vacuum/search_operations.rb +0 -43
- data/spec/vacuum/cart_operations_spec.rb +0 -0
- data/spec/vacuum/cart_spec.rb +0 -0
- data/spec/vacuum/lookup_operations_spec.rb +0 -53
- data/spec/vacuum/search_operations_spec.rb +0 -59
data/README.md
CHANGED
@@ -1,61 +1,43 @@
|
|
1
1
|
# Vacuum
|
2
2
|
|
3
|
-
|
4
|
-
Advertising API] [2].
|
3
|
+
[![travis] [2]] [3]
|
5
4
|
|
6
|
-
|
5
|
+
![vacuum] [1]
|
7
6
|
|
8
|
-
|
7
|
+
Vacuum is a minimal Ruby wrapper to the [Amazon Product Advertising API] [4].
|
9
8
|
|
10
|
-
|
9
|
+
# Usage
|
11
10
|
|
12
|
-
|
11
|
+
```ruby
|
12
|
+
require 'vacuum'
|
13
13
|
|
14
|
-
|
14
|
+
# Create a request.
|
15
|
+
req = Vacuum.new key: 'key',
|
16
|
+
secret: 'secret',
|
17
|
+
tag: 'tag',
|
18
|
+
locale: 'us'
|
15
19
|
|
16
|
-
|
20
|
+
# Build query.
|
21
|
+
req.build 'Operation' => 'ItemSearch',
|
22
|
+
'SearchIndex' => 'All',
|
23
|
+
'Keywords' => 'Gilles Deleuze'
|
17
24
|
|
18
|
-
|
25
|
+
# Execute.
|
26
|
+
res = request.get
|
19
27
|
|
20
|
-
|
28
|
+
# Consume response.
|
29
|
+
if res.valid?
|
30
|
+
# res.to_hash
|
31
|
+
res.find('Item') do |item|
|
32
|
+
p item['ASIN']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
21
36
|
|
22
|
-
|
23
|
-
c.key = AMAZON_KEY
|
24
|
-
c.secret = AMAZON_SECRET
|
25
|
-
c.tag = AMAZON_ASSOCIATE_TAG
|
26
|
-
end
|
37
|
+
Read further [here] [5].
|
27
38
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
res = request.get
|
34
|
-
|
35
|
-
Or use a shorthand.
|
36
|
-
|
37
|
-
res = req.search('George Orwell')
|
38
|
-
|
39
|
-
Customise your request.
|
40
|
-
|
41
|
-
res = req.search('Books', :response_group => 'ItemAttributes',
|
42
|
-
:power => 'George Orwell'
|
43
|
-
|
44
|
-
For a reference of available methods and syntax, [read here] [3].
|
45
|
-
|
46
|
-
Consume the entire response.
|
47
|
-
|
48
|
-
res.to_hash
|
49
|
-
|
50
|
-
Quickly drop down to a particular node.
|
51
|
-
|
52
|
-
res.each('Item') do |item|
|
53
|
-
puts item['ASIN']
|
54
|
-
end
|
55
|
-
|
56
|
-
[Please see the project page] [4] for further detail.
|
57
|
-
|
58
|
-
[1]: http://nokogiri.org/
|
59
|
-
[2]: https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html
|
60
|
-
[3]: https://github.com/hakanensari/vacuum/blob/master/lib/vacuum/operations.rb
|
61
|
-
[4]: http://code.papercavalier.com/vacuum/
|
39
|
+
[1]: https://github.com/hakanensari/vacuum/blob/master/vacuum.png?raw=true
|
40
|
+
[2]: https://secure.travis-ci.org/hakanensari/vacuum.png
|
41
|
+
[3]: http://travis-ci.org/hakanensari/vacuum
|
42
|
+
[4]: https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html
|
43
|
+
[5]: https://github.com/hakanensari/vacuum/blob/master/examples/
|
data/lib/vacuum/em.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'vacuum'
|
2
|
+
require 'em-http-request'
|
3
|
+
|
4
|
+
module Vacuum
|
5
|
+
class Request
|
6
|
+
# Performs an async request.
|
7
|
+
#
|
8
|
+
# @yield Passes response to given block
|
9
|
+
def aget(&blk)
|
10
|
+
http = EM::HttpRequest.new(url).get
|
11
|
+
http.callback { blk.call _response(http) }
|
12
|
+
http.errback { blk.call _response(http) }
|
13
|
+
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def _response(http)
|
20
|
+
Response.new(http.response, http.response_header.status)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,15 +1,9 @@
|
|
1
1
|
module Vacuum
|
2
|
-
module
|
3
|
-
# Builds a hash from a Nokogiri XML document
|
2
|
+
module HashBuilder
|
3
|
+
# Builds a hash from a Nokogiri XML document.
|
4
4
|
#
|
5
|
-
# @
|
6
|
-
#
|
7
|
-
# essentially accomplishes the same.
|
8
|
-
#
|
9
|
-
# @see Based on https://gist.github.com/335286
|
10
|
-
#
|
11
|
-
# @param [Nokogiri::XML::Document] xml an XML document
|
12
|
-
# @return [Hash] a hashified version of the XML document
|
5
|
+
# @param [Nokogiri::XML::Document] xml An XML document
|
6
|
+
# @return [Hash]
|
13
7
|
def self.from_xml(xml)
|
14
8
|
case xml
|
15
9
|
when Nokogiri::XML::Document
|
@@ -17,8 +11,8 @@ module Vacuum
|
|
17
11
|
when Nokogiri::XML::Element
|
18
12
|
hsh = {}
|
19
13
|
|
20
|
-
xml.attributes.each_pair do |key,
|
21
|
-
hsh[key] =
|
14
|
+
xml.attributes.each_pair do |key, attribute|
|
15
|
+
hsh[key] = attribute.value
|
22
16
|
end
|
23
17
|
|
24
18
|
xml.children.each do |child|
|
data/lib/vacuum/request.rb
CHANGED
@@ -1,131 +1,119 @@
|
|
1
1
|
module Vacuum
|
2
|
-
# A wrapper around the request to the Amazon Product Advertising API
|
2
|
+
# A wrapper around the request to the Amazon Product Advertising API.
|
3
3
|
class Request
|
4
|
-
|
5
|
-
include LookupOperations
|
6
|
-
include SearchOperations
|
7
|
-
|
8
|
-
# The latest Amazon API version
|
9
|
-
#
|
10
|
-
# @see http://aws.amazon.com/archives/Product%20Advertising%20API
|
11
|
-
#
|
12
|
-
# @note If you have a whitelisted access key, override this in your
|
13
|
-
# parameters with an earlier version.
|
4
|
+
# The latest Amazon API version.
|
14
5
|
CURRENT_API_VERSION = '2011-08-01'
|
15
6
|
|
16
|
-
#
|
7
|
+
# A list of Amazon endpoints.
|
8
|
+
HOSTS = {
|
9
|
+
:ca => 'ecs.amazonaws.ca',
|
10
|
+
:cn => 'webservices.amazon.cn',
|
11
|
+
:de => 'ecs.amazonaws.de',
|
12
|
+
:es => 'webservices.amazon.es',
|
13
|
+
:fr => 'ecs.amazonaws.fr',
|
14
|
+
:it => 'webservices.amazon.it',
|
15
|
+
:jp => 'ecs.amazonaws.jp',
|
16
|
+
:uk => 'ecs.amazonaws.co.uk',
|
17
|
+
:us => 'ecs.amazonaws.com'
|
18
|
+
}
|
19
|
+
|
20
|
+
# Creates a new request for given locale and credentials.
|
17
21
|
#
|
18
|
-
# @param [
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
# @param [Hash] options
|
23
|
+
# @option opts [#to_sym] :locale An Amazon locale
|
24
|
+
# @option opts [String] :key An Amazon AWS access key ID
|
25
|
+
# @option opts [String] :secret An Amazon AWS access secret key
|
26
|
+
# @option opts [String] :tag An Amazon Associate tag
|
27
|
+
# @raise [MissingKey] An Amazon AWS access key ID was not given
|
28
|
+
# @raise [MissingSecret] An Amazon AWS secret key was not given
|
29
|
+
# @raise [MissingTag] An Amazon Associate tag was not given
|
30
|
+
# @return [self]
|
31
|
+
def initialize(options)
|
32
|
+
_reset!
|
33
|
+
|
34
|
+
locale = (options[:locale] || :us).to_sym
|
35
|
+
@host = HOSTS[locale] or raise BadLocale
|
36
|
+
@key = options[:key] or raise MissingKey
|
37
|
+
@secret = options[:secret] or raise MissingSecret
|
38
|
+
@tag = options[:tag] or raise MissingTag
|
22
39
|
end
|
23
40
|
|
24
|
-
# Merges
|
25
|
-
#
|
26
|
-
# @param [Hash] hsh pairs of parameter keys and values
|
41
|
+
# Merges given parameters into the request query.
|
27
42
|
#
|
28
|
-
# @
|
29
|
-
#
|
30
|
-
|
31
|
-
def <<(hsh)
|
43
|
+
# @param [Hash] hsh Pairs of keys and values
|
44
|
+
# @return [self]
|
45
|
+
def build(hsh)
|
32
46
|
hsh.each do |k, v|
|
33
|
-
|
34
|
-
v = v.is_a?(Array) ? v.join(',') : v.to_s
|
35
|
-
|
36
|
-
# Remove whitespace after commas.
|
37
|
-
v.gsub!(/,\s+/, ',')
|
38
|
-
|
39
|
-
# Camelize key.
|
40
|
-
k = k.to_s.
|
41
|
-
split('_').
|
42
|
-
map { |w| w[0, 1] = w[0, 1].upcase; w }.
|
43
|
-
join
|
44
|
-
|
45
|
-
@params[k] = v
|
47
|
+
@params[k] = v.is_a?(Array) ? v.join(',') : v.to_s
|
46
48
|
end
|
49
|
+
|
50
|
+
self
|
47
51
|
end
|
48
52
|
|
49
|
-
#
|
50
|
-
#
|
51
|
-
# @yield passes locale to block for configuration
|
53
|
+
# Replaces the request query with given parameters.
|
52
54
|
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# c.tag = YOUR_ASSOCIATE_TAG
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
def configure(&block)
|
61
|
-
block.call(@locale)
|
55
|
+
# see(#build)
|
56
|
+
def build!(hsh = {})
|
57
|
+
_reset!
|
58
|
+
build hsh
|
62
59
|
end
|
63
60
|
|
64
|
-
# Performs a request
|
61
|
+
# Performs a request.
|
65
62
|
#
|
66
|
-
# @return [Vacuum::Response]
|
63
|
+
# @return [Vacuum::Response] A response
|
67
64
|
def get
|
68
|
-
|
69
|
-
|
70
|
-
Response.new(resp.body, resp.code)
|
65
|
+
res = Net::HTTP.get_response(url)
|
66
|
+
Response.new(res.body, res.code)
|
71
67
|
end
|
72
68
|
|
73
|
-
# @return [Hash]
|
74
|
-
# @raise [Vacuum::MissingKey] Amazon key is missing
|
75
|
-
# @raise [Vacuum::MissingTag] Amazon associate tag is
|
76
|
-
# missing
|
69
|
+
# @return [Hash] The parameters that make up the request query.
|
77
70
|
def params
|
78
|
-
|
79
|
-
raise MissingTag unless @locale.tag
|
80
|
-
|
81
|
-
{ 'AWSAccessKeyId' => @locale.key,
|
82
|
-
'AssociateTag' => @locale.tag,
|
83
|
-
'Service' => 'AWSECommerceService',
|
84
|
-
'Timestamp' => timestamp,
|
85
|
-
'Version' => CURRENT_API_VERSION }.merge(@params)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Resets the request parameters
|
89
|
-
def reset!
|
90
|
-
@params = {}
|
71
|
+
_default_params.merge(@params)
|
91
72
|
end
|
92
73
|
|
93
|
-
# @
|
94
|
-
# @return [URI::HTTP] the Amazon URL
|
74
|
+
# @return [URI::HTTP] The URL for the API request
|
95
75
|
def url
|
96
|
-
|
97
|
-
|
98
|
-
URI::HTTP.build(:host => @locale.host,
|
76
|
+
URI::HTTP.build :host => @host,
|
99
77
|
:path => '/onca/xml',
|
100
|
-
:query =>
|
78
|
+
:query => _query_string
|
101
79
|
end
|
102
80
|
|
103
81
|
private
|
104
82
|
|
105
|
-
def
|
83
|
+
def _default_params
|
84
|
+
default = {
|
85
|
+
'AWSAccessKeyId' => @key,
|
86
|
+
'AssociateTag' => @tag,
|
87
|
+
'Service' => 'AWSECommerceService',
|
88
|
+
'Timestamp' => _timestamp,
|
89
|
+
'Version' => CURRENT_API_VERSION
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def _escape(value)
|
106
94
|
value.gsub(/([^a-zA-Z0-9_.~-]+)/) do
|
107
95
|
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
108
96
|
end
|
109
97
|
end
|
110
98
|
|
111
|
-
def
|
112
|
-
params.sort.map { |k, v| "#{k}=" +
|
113
|
-
end
|
99
|
+
def _query_string
|
100
|
+
qs = params.sort.map { |k, v| "#{k}=" + _escape(v) }.join('&')
|
114
101
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
unsigned_query].join("\n")
|
121
|
-
hmac = OpenSSL::HMAC.digest(digest, @locale.secret, url_string)
|
122
|
-
signature = escape([hmac].pack('m').chomp)
|
102
|
+
# Sign query string.
|
103
|
+
dig = OpenSSL::Digest::Digest.new 'sha256'
|
104
|
+
req = ['GET', @host, '/onca/xml', qs]
|
105
|
+
hmac = OpenSSL::HMAC.digest dig, @secret, req.join("\n")
|
106
|
+
sig = _escape [hmac].pack('m').chomp
|
123
107
|
|
124
|
-
"#{
|
108
|
+
"#{qs}&Signature=#{sig}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def _reset!
|
112
|
+
@params = {}
|
125
113
|
end
|
126
114
|
|
127
|
-
def
|
128
|
-
Time.now.utc.
|
115
|
+
def _timestamp
|
116
|
+
Time.now.utc.iso8601
|
129
117
|
end
|
130
118
|
end
|
131
119
|
end
|
data/lib/vacuum/response.rb
CHANGED
@@ -1,85 +1,63 @@
|
|
1
1
|
module Vacuum
|
2
|
-
# A wrapper around the API response
|
2
|
+
# A wrapper around the API response.
|
3
3
|
class Response
|
4
4
|
|
5
|
-
# @return [String] body
|
5
|
+
# @return [String] body The response body
|
6
6
|
attr_accessor :body
|
7
7
|
|
8
|
-
# @return [Integer] code
|
8
|
+
# @return [Integer] code The HTTP status code of the response
|
9
9
|
attr_accessor :code
|
10
10
|
|
11
|
-
# Creates a new response
|
11
|
+
# Creates a new response.
|
12
12
|
#
|
13
|
-
# @param [String] body
|
14
|
-
# @param [#to_i] code
|
13
|
+
# @param [String] body The response body
|
14
|
+
# @param [#to_i] code The HTTP status code of the response
|
15
15
|
def initialize(body, code)
|
16
16
|
@body = body
|
17
17
|
@code = code.to_i
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
21
|
-
# each matching document
|
22
|
-
#
|
23
|
-
# @param [String] query attribute to be queried
|
24
|
-
# @yield passes matching nodes to given block
|
25
|
-
#
|
26
|
-
# @example
|
27
|
-
# resp.each('Item') { |item| p item }
|
28
|
-
#
|
29
|
-
def each(query, &block)
|
30
|
-
find(query).each { |match| block.call(match) }
|
31
|
-
end
|
32
|
-
|
33
|
-
# @return [Array] errors in the response
|
20
|
+
# @return [Array] Errors in the response
|
34
21
|
def errors
|
35
|
-
find
|
22
|
+
find 'Error'
|
36
23
|
end
|
37
24
|
|
38
|
-
# Queries for a
|
25
|
+
# Queries for a given attribute, yielding matching nodes to a block if
|
26
|
+
# given one.
|
39
27
|
#
|
40
|
-
# @param [String]
|
41
|
-
# @
|
28
|
+
# @param [String] Query attribute to be queried
|
29
|
+
# @yield Optionally passes matching nodes to a given block
|
30
|
+
# @return [Array] Matches
|
42
31
|
#
|
43
32
|
# @example
|
44
|
-
# items =
|
33
|
+
# items = res.find('Item')
|
34
|
+
# asins = res.find('Item') { |item| item['ASIN'] }
|
45
35
|
#
|
46
36
|
def find(query)
|
47
|
-
xml.xpath("//xmlns:#{query}").map
|
37
|
+
xml.xpath("//xmlns:#{query}").map do |node|
|
38
|
+
hsh = HashBuilder.from_xml node
|
39
|
+
block_given? ? yield(hsh) : hsh
|
40
|
+
end
|
48
41
|
end
|
49
|
-
alias [] find
|
50
42
|
|
51
|
-
# @return [true, false]
|
43
|
+
# @return [true, false] Whether the response has errors
|
52
44
|
def has_errors?
|
53
45
|
errors.count > 0
|
54
46
|
end
|
55
47
|
|
56
|
-
#
|
57
|
-
# matching nodes, and collects final values.
|
58
|
-
#
|
59
|
-
# @param [String] query attribute to be queried
|
60
|
-
# @yield passes matching nodes to given block
|
61
|
-
# @return [Array] processed results
|
62
|
-
#
|
63
|
-
# @example
|
64
|
-
# asins = resp.map('Item') { |item| item['ASIN'] }
|
65
|
-
#
|
66
|
-
def map(path, &block)
|
67
|
-
find(path).map { |match| block.call(match) }
|
68
|
-
end
|
69
|
-
|
70
|
-
# @return [Hash] a hashified version of the response body
|
48
|
+
# @return [Hash] A hash representation of the entire response.
|
71
49
|
def to_hash
|
72
|
-
|
50
|
+
HashBuilder.from_xml xml
|
73
51
|
end
|
74
52
|
|
75
|
-
# @return [true, false]
|
53
|
+
# @return [true, false] Whether the HTTP response is OK
|
76
54
|
def valid?
|
77
55
|
code == 200
|
78
56
|
end
|
79
57
|
|
80
58
|
# @return [Nokogiri::XML] the XML document
|
81
59
|
def xml
|
82
|
-
@xml ||= Nokogiri::XML
|
60
|
+
@xml ||= Nokogiri::XML @body
|
83
61
|
end
|
84
62
|
end
|
85
63
|
end
|
data/lib/vacuum/version.rb
CHANGED
data/lib/vacuum.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require 'net/http'
|
2
3
|
require 'nokogiri'
|
3
4
|
require 'openssl'
|
5
|
+
require 'time'
|
4
6
|
|
5
|
-
%w{
|
6
|
-
error locale request response}.each do |f|
|
7
|
+
%w{hash_builder request response}.each do |f|
|
7
8
|
require "vacuum/#{f}"
|
8
9
|
end
|
9
10
|
|
10
|
-
|
11
|
-
# API.
|
11
|
+
|
12
|
+
# Vacuum is a Ruby wrapper to the Amazon Product Advertising API.
|
12
13
|
module Vacuum
|
13
|
-
|
14
|
+
class BadLocale < ArgumentError; end
|
15
|
+
class MissingKey < ArgumentError; end
|
16
|
+
class MissingSecret < ArgumentError; end
|
17
|
+
class MissingTag < ArgumentError; end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
extend Forwardable
|
14
21
|
|
15
|
-
|
16
|
-
# @return [Vacuum::Request] a request
|
17
|
-
#
|
18
|
-
# @note The locale key may be any of the following: +ca+, +cn+, +de+,
|
19
|
-
# +es+, +fr+, +it+, +jp+, +uk+, or +us+.
|
20
|
-
def self.[](locale)
|
21
|
-
@requests[locale] ||= Request.new(locale)
|
22
|
+
def_delegators Request, :new
|
22
23
|
end
|
23
24
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
module Vacuum
|
4
|
-
describe
|
4
|
+
describe HashBuilder do
|
5
5
|
let(:xml) do
|
6
6
|
xml = <<-XML.gsub!(/>\s+</, '><').strip!
|
7
7
|
<?xml version=\"1.0\" ?>
|
@@ -17,19 +17,19 @@ module Vacuum
|
|
17
17
|
|
18
18
|
describe '.from_xml' do
|
19
19
|
it 'returns a hash' do
|
20
|
-
|
20
|
+
HashBuilder.from_xml(xml).should be_an_instance_of Hash
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'handles only childs' do
|
24
|
-
|
24
|
+
HashBuilder.from_xml(xml)['Title'].should eql 'Anti-Oedipus'
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'handles arrays' do
|
28
|
-
|
28
|
+
HashBuilder.from_xml(xml)['Author'].should be_a Array
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'handles attributes' do
|
32
|
-
node =
|
32
|
+
node = HashBuilder.from_xml(xml)['Creator']
|
33
33
|
node['Role'].should eql 'Translator'
|
34
34
|
node['__content__'].should eql 'Robert Hurley'
|
35
35
|
end
|