vacuum 0.0.1 → 0.1.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/README.md CHANGED
@@ -1,61 +1,43 @@
1
1
  # Vacuum
2
2
 
3
- Vacuum is a [Nokogiri][1]-backed Ruby wrapper to the [Amazon Product
4
- Advertising API] [2].
3
+ [![travis] [2]] [3]
5
4
 
6
- [![travis](https://secure.travis-ci.org/hakanensari/vacuum.png)](http://travis-ci.org/hakanensari/vacuum)
5
+ ![vacuum] [1]
7
6
 
8
- ## Installation
7
+ Vacuum is a minimal Ruby wrapper to the [Amazon Product Advertising API] [4].
9
8
 
10
- Add to your Gemfile.
9
+ # Usage
11
10
 
12
- gem 'vacuum'
11
+ ```ruby
12
+ require 'vacuum'
13
13
 
14
- ## Usage
14
+ # Create a request.
15
+ req = Vacuum.new key: 'key',
16
+ secret: 'secret',
17
+ tag: 'tag',
18
+ locale: 'us'
15
19
 
16
- Set up a request.
20
+ # Build query.
21
+ req.build 'Operation' => 'ItemSearch',
22
+ 'SearchIndex' => 'All',
23
+ 'Keywords' => 'Gilles Deleuze'
17
24
 
18
- require "vacuum"
25
+ # Execute.
26
+ res = request.get
19
27
 
20
- req = Vacuum["us"]
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
- req.configure do |c|
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
- Search for something.
29
-
30
- req << { :operation => 'ItemSearch',
31
- :search_index => 'All',
32
- :keywords => 'George Orwell' }
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 Builder
3
- # Builds a hash from a Nokogiri XML document
2
+ module HashBuilder
3
+ # Builds a hash from a Nokogiri XML document.
4
4
  #
5
- # @note In earlier versions of the library, I was relying on the
6
- # XML Mini Nokogiri module in Active Support. This method
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, attr|
21
- hsh[key] = attr.value
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|
@@ -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
- include CartOperations
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
- # Creates a new request for specified locale
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 [#to_sym] locale two-letter abbreviation for locale
19
- def initialize(locale)
20
- @locale = Locale.new(locale.to_sym)
21
- @params = Hash.new
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 a hash of request parameters into the query
25
- #
26
- # @param [Hash] hsh pairs of parameter keys and values
41
+ # Merges given parameters into the request query.
27
42
  #
28
- # @example
29
- # request << { :key => 'value' }
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
- # Cast value to string.
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
- # Configures the Amazon locale
50
- #
51
- # @yield passes locale to block for configuration
53
+ # Replaces the request query with given parameters.
52
54
  #
53
- # @example
54
- # request.configure do |c|
55
- # c.key = YOUR_KEY
56
- # c.secret = YOUR_SECRET
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] a response
63
+ # @return [Vacuum::Response] A response
67
64
  def get
68
- resp = Net::HTTP.get_response(url)
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] params The request parameters
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
- raise MissingKey unless @locale.key
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
- # @raise [Vacuum::MissingSecret] Amazon secret is missing
94
- # @return [URI::HTTP] the Amazon URL
74
+ # @return [URI::HTTP] The URL for the API request
95
75
  def url
96
- raise MissingSecret unless @locale.secret
97
-
98
- URI::HTTP.build(:host => @locale.host,
76
+ URI::HTTP.build :host => @host,
99
77
  :path => '/onca/xml',
100
- :query => sign(query))
78
+ :query => _query_string
101
79
  end
102
80
 
103
81
  private
104
82
 
105
- def escape(value)
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 query
112
- params.sort.map { |k, v| "#{k}=" + escape(v) }.join('&')
113
- end
99
+ def _query_string
100
+ qs = params.sort.map { |k, v| "#{k}=" + _escape(v) }.join('&')
114
101
 
115
- def sign(unsigned_query)
116
- digest = OpenSSL::Digest::Digest.new('sha256')
117
- url_string = ['GET',
118
- @locale.host,
119
- '/onca/xml',
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
- "#{unsigned_query}&Signature=#{signature}"
108
+ "#{qs}&Signature=#{sig}"
109
+ end
110
+
111
+ def _reset!
112
+ @params = {}
125
113
  end
126
114
 
127
- def timestamp
128
- Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
115
+ def _timestamp
116
+ Time.now.utc.iso8601
129
117
  end
130
118
  end
131
119
  end
@@ -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 the response body
5
+ # @return [String] body The response body
6
6
  attr_accessor :body
7
7
 
8
- # @return [Integer] code the HTTP status code of the response
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 the response body
14
- # @param [#to_i] code the HTTP status code of the response
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
- # Queries for a specified attribute and yields to a given block
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('Error')
22
+ find 'Error'
36
23
  end
37
24
 
38
- # Queries for a specified attribute and returns matching nodes
25
+ # Queries for a given attribute, yielding matching nodes to a block if
26
+ # given one.
39
27
  #
40
- # @param [String] query attribute to be queried
41
- # @return [Array] matching nodes
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 = resp.find('Item')
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 { |e| Builder.from_xml(e) }
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] checks if the response has errors
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
- # Queries for a specifed attribute, yields to a given block
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
- Builder.from_xml(xml)
50
+ HashBuilder.from_xml xml
73
51
  end
74
52
 
75
- # @return [true, false] checks if the HTTP response is OK
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(@body)
60
+ @xml ||= Nokogiri::XML @body
83
61
  end
84
62
  end
85
63
  end
@@ -1,3 +1,3 @@
1
1
  module Vacuum
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
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{cart_operations lookup_operations search_operations builder cart
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
- # Vacuum is a Ruby wrapper to the Amazon Product Advertising
11
- # API.
11
+
12
+ # Vacuum is a Ruby wrapper to the Amazon Product Advertising API.
12
13
  module Vacuum
13
- @requests = Hash.new
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
- # @param [#to_sym] locale a locale key
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 Builder do
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
- Builder.from_xml(xml).should be_an_instance_of Hash
20
+ HashBuilder.from_xml(xml).should be_an_instance_of Hash
21
21
  end
22
22
 
23
23
  it 'handles only childs' do
24
- Builder.from_xml(xml)['Title'].should eql 'Anti-Oedipus'
24
+ HashBuilder.from_xml(xml)['Title'].should eql 'Anti-Oedipus'
25
25
  end
26
26
 
27
27
  it 'handles arrays' do
28
- Builder.from_xml(xml)['Author'].should be_a Array
28
+ HashBuilder.from_xml(xml)['Author'].should be_a Array
29
29
  end
30
30
 
31
31
  it 'handles attributes' do
32
- node = Builder.from_xml(xml)['Creator']
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