sucker 2.0.0.pre.4 → 2.0.0.pre.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,55 +1,91 @@
1
- Sucker
2
- ======
1
+ # Sucker
3
2
 
4
- Sucker is a Nokogiri-based, optionally-evented Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html).
3
+ Sucker is a Nokogiri-based, optionally-evented Ruby wrapper to the
4
+ [Amazon Product Advertising API](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html).
5
5
 
6
6
  ![Hoover](https://github.com/papercavalier/sucker/raw/master/hoover.jpg)
7
7
 
8
- Caption: Workers queuing to download data from Amazon.
8
+ > Workers queuing to download data from Amazon.
9
9
 
10
- Usage
11
- -----
10
+ ## Usage
12
11
 
13
- Read the [Amazon API](http://aws.amazon.com/archives/Product%20Advertising%20API).
14
- Check out [these examples](http://relishapp.com/papercavalier/sucker) if in a hurry.
12
+ _Note_: For the README of version 1.6.1 and earlier, [click here](https://github.com/papercavalier/sucker/tree/v1.6.1).
15
13
 
16
- Set up.
14
+ 1. Define your Amazon credentials.
17
15
 
18
- ```ruby
19
- request = Sucker.new(
20
- :locale => :us,
21
- :key => amazon_key,
22
- :secret => amazon_secret)
23
- ```
16
+ ```ruby
17
+ Sucker.configure do |c|
18
+ c.locale = :us
19
+ c.key = amazon_key
20
+ c.secret = amazon_secret
21
+ c.associate_tag = associate_tag
22
+ end
24
23
 
25
- Build a request.
24
+ 2. Set up a request.
26
25
 
27
- ```ruby
28
- request << {
29
- 'Operation' => 'ItemLookup',
30
- 'IdType' => 'ASIN',
31
- 'ItemId' => 10.asins,
32
- 'ResponseGroup' => 'ItemAttributes' }
33
- ```
26
+ ```ruby
27
+ request = Sucker.new
28
+ ```
34
29
 
35
- Get a response.
30
+ Alternatively, you may your credentials when initializing the request.
36
31
 
37
- ```ruby
38
- response = request.get
39
- ```
32
+ ```ruby
33
+ request = Sucker.new(
34
+ :locale => :us,
35
+ :key => amazon_key,
36
+ :secret => amazon_secret)
37
+ end
38
+ ```
39
+
40
+ 2. Build a request.
41
+
42
+ ```ruby
43
+ request << {
44
+ 'Operation' => 'ItemLookup',
45
+ 'IdType' => 'ASIN',
46
+ 'ItemId' => 10.asins,
47
+ 'ResponseGroup' => 'ItemAttributes' }
48
+ ```
49
+
50
+ Amazon provides countless configuration options to fine-tune your query. Read
51
+ [their API](http://aws.amazon.com/archives/Product%20Advertising%20API) or
52
+ check out [these common scenarios](http://relishapp.com/papercavalier/sucker)
53
+ if in a hurry.
54
+
55
+ 3. Get a response.
40
56
 
41
- Fulfill a business value.
57
+ ```ruby
58
+ response = request.get
59
+ ```
60
+
61
+ Fulfill a business value.
62
+
63
+ ```ruby
64
+ if response.valid?
65
+ response.find('Item').each do |item|
66
+ # consume
67
+ end
68
+ end
69
+ ```
70
+
71
+ 4. Repeat ad infinitum.
72
+
73
+ ```ruby
74
+ request << { 'ItemId' => 10.new.asins }
75
+ request.get["Item"].each do |item|
76
+ # consume
77
+ end
78
+ ```
79
+
80
+
81
+ ## Some tips
82
+
83
+ Inspect the response as a hash to find out nodes you are interested in.
42
84
 
43
85
  ```ruby
44
- if response.valid?
45
- response.each('Item') do |item|
46
- consume(item)
47
- end
48
- end
86
+ p response.to_hash
49
87
  ```
50
88
 
51
- Repeat ad infinitum.
52
-
53
89
  The following are all valid ways to query a response:
54
90
 
55
91
  ```ruby
@@ -67,7 +103,6 @@ p response.valid?,
67
103
  response.code,
68
104
  response.errors,
69
105
  response.has_errors?,
70
- response.to_hash,
71
106
  response.xml
72
107
  ```
73
108
 
@@ -83,7 +118,8 @@ adapter.socket_local.host = '10.0.0.2'
83
118
  Evented Requests
84
119
  ----------------
85
120
 
86
- I am including a EM:Synchrony patch in the 2.0 release.
121
+ Large responses can block! I am including an EM:Synchrony patch in the 2.0
122
+ release.
87
123
 
88
124
  ```ruby
89
125
  require 'sucker/synchrony'
@@ -95,8 +131,8 @@ EM.synchrony do
95
131
  EM.stop
96
132
  end
97
133
 
98
- For more meaningful examples, read [these examples](http://relishapp.com/papercavalier/sucker/evented-requests).
99
134
  ```
135
+ For more meaningful examples, read [here](http://relishapp.com/papercavalier/sucker/evented-requests).
100
136
 
101
137
  Stubbing Tests
102
138
  --------------
@@ -1,3 +1,4 @@
1
+ require 'sucker/config'
1
2
  require 'sucker/request'
2
3
  require 'sucker/response'
3
4
 
@@ -5,9 +6,7 @@ require 'sucker/response'
5
6
  #
6
7
  # Sucker is a Ruby wrapper to the Amazon Product Advertising API.
7
8
  module Sucker
8
-
9
9
  class << self
10
-
11
10
  # Initializes a request object.
12
11
  #
13
12
  # request = Sucker.new(
@@ -18,5 +17,18 @@ module Sucker
18
17
  def new(args={})
19
18
  Request.new(args)
20
19
  end
20
+
21
+ # Configures locale-specific details.
22
+ #
23
+ # Sucker.configure do |c|
24
+ # c.locale = :us
25
+ # c.key = api_key
26
+ # c.secret = api_secret
27
+ # c.associate_tag = associate_tag
28
+ # end
29
+ #
30
+ def configure(&block)
31
+ block.call(Config)
32
+ end
21
33
  end
22
34
  end
@@ -0,0 +1,18 @@
1
+ module Sucker
2
+ # Amazon credentials.
3
+ module Config
4
+ extend self
5
+
6
+ # The Amazon locale.
7
+ attr_accessor :locale
8
+
9
+ # The Amazon Web Services access key.
10
+ attr_accessor :key
11
+
12
+ # The Amazon Web Services secret.
13
+ attr_accessor :secret
14
+
15
+ # The Amazon associate tag.
16
+ attr_accessor :associate_tag
17
+ end
18
+ end
@@ -35,8 +35,8 @@ module Sucker
35
35
 
36
36
  private
37
37
 
38
- def timestamp
39
- Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
40
- end
38
+ def timestamp
39
+ Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
40
+ end
41
41
  end
42
42
  end
@@ -5,6 +5,8 @@ require 'sucker/parameters'
5
5
  module Sucker
6
6
  # A wrapper around the API request.
7
7
  class Request
8
+ extend Forwardable
9
+
8
10
  HOSTS = {
9
11
  :us => 'ecs.amazonaws.com',
10
12
  :uk => 'ecs.amazonaws.co.uk',
@@ -13,27 +15,14 @@ module Sucker
13
15
  :fr => 'ecs.amazonaws.fr',
14
16
  :jp => 'ecs.amazonaws.jp' }
15
17
 
16
- class << self
17
-
18
- # Available Amazon locales.
19
- def locales
20
- @locales ||= HOSTS.keys
21
- end
22
- end
23
-
24
- # The Amazon associate tag.
25
- attr_accessor :associate_tag
26
-
27
- # The Amazon Web Services access key.
28
- attr_accessor :key
29
-
30
- # The Amazon locale.
31
- attr_accessor :locale
18
+ LOCALES = HOSTS.keys
32
19
 
33
- # The Amazon Web Services secret.
34
- attr_accessor :secret
20
+ def_delegators :@config, :associate_tag, :associate_tag=,
21
+ :key, :key=,
22
+ :locale, :locale=,
23
+ :secret, :secret=
35
24
 
36
- # Initializes a request object.
25
+ # Creates a new a request.
37
26
  #
38
27
  # Takes an optional hash of attribute and value pairs.
39
28
  #
@@ -43,13 +32,9 @@ module Sucker
43
32
  # :secret => amazon_secret)
44
33
  # :associate_tag => amazon_tag)
45
34
  #
46
- def initialize(args={})
47
- args.each { |k, v| send("#{k}=", v) }
48
- end
49
-
50
- # The HTTP adapter.
51
- def adapter
52
- @adapter ||= HTTPClient.new
35
+ def initialize(args = {})
36
+ @config = Config
37
+ args.each { |k, v| self.send("#{k}=", v) }
53
38
  end
54
39
 
55
40
  # Merges a hash into the existing parameters.
@@ -63,6 +48,11 @@ module Sucker
63
48
  parameters.merge!(hash)
64
49
  end
65
50
 
51
+ # The HTTP adapter.
52
+ def adapter
53
+ @adapter ||= HTTPClient.new
54
+ end
55
+
66
56
  # Performs a request and returns a response.
67
57
  #
68
58
  # response = request.get
@@ -80,7 +70,6 @@ module Sucker
80
70
  # Resets parameters and returns self.
81
71
  def reset
82
72
  parameters.reset
83
-
84
73
  self
85
74
  end
86
75
 
@@ -100,33 +89,33 @@ module Sucker
100
89
 
101
90
  private
102
91
 
103
- def build_query_string
104
- parameters.
105
- normalize.
106
- merge({ 'AWSAccessKeyId' => key,
107
- 'AssociateTag' => associate_tag.to_s }).
108
- sort.
109
- map { |k, v| "#{k}=" + escape(v) }.
110
- join('&')
111
- end
112
-
113
- def sign(query_string)
114
- digest = OpenSSL::Digest::Digest.new('sha256')
115
- url_string = ['GET', host, '/onca/xml', query_string].join("\n")
116
- hmac = OpenSSL::HMAC.digest(digest, secret, url_string)
117
- signature = escape([hmac].pack('m').chomp)
92
+ def build_query_string
93
+ parameters.
94
+ normalize.
95
+ merge({ 'AWSAccessKeyId' => key,
96
+ 'AssociateTag' => associate_tag.to_s }).
97
+ sort.
98
+ map { |k, v| "#{k}=" + escape(v) }.
99
+ join('&')
100
+ end
118
101
 
119
- query_string + '&Signature=' + signature
102
+ def escape(value)
103
+ value.gsub(/([^a-zA-Z0-9_.~-]+)/) do
104
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
120
105
  end
106
+ end
121
107
 
122
- def escape(value)
123
- value.gsub(/([^a-zA-Z0-9_.~-]+)/) do
124
- '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
125
- end
126
- end
108
+ def host
109
+ HOSTS[locale.to_sym]
110
+ end
127
111
 
128
- def host
129
- HOSTS[locale.to_sym]
130
- end
112
+ def sign(query_string)
113
+ digest = OpenSSL::Digest::Digest.new('sha256')
114
+ url_string = ['GET', host, '/onca/xml', query_string].join("\n")
115
+ hmac = OpenSSL::HMAC.digest(digest, secret, url_string)
116
+ signature = escape([hmac].pack('m').chomp)
117
+
118
+ query_string + '&Signature=' + signature
119
+ end
131
120
  end
132
121
  end
@@ -3,13 +3,13 @@ require 'em-synchrony'
3
3
  require 'em-synchrony/em-http'
4
4
 
5
5
  module Sucker
6
- # Below, we minimally patch Request and Response to make them fiber-aware.
6
+ # We minimally patch Request and Response to make them fiber-aware.
7
7
  class Request
8
8
  def adapter
9
9
  @adapter ||= EM::HttpRequest
10
10
  end
11
11
 
12
- # Performs an evented request and yields a response to provided block.
12
+ # Performs an evented request and yields a response to given block.
13
13
  def aget(&block)
14
14
  http = EM::HttpRequest.new(url).aget
15
15
  http.callback { block.call(Response.new(http)) }
@@ -1,3 +1,3 @@
1
1
  module Sucker
2
- VERSION = '2.0.0.pre.4'
2
+ VERSION = '2.0.0.pre.5'
3
3
  end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ module Sucker
4
+ describe Config
5
+ # nothing to spec here.
6
+ end
@@ -5,16 +5,10 @@ module Sucker
5
5
  use_vcr_cassette 'spec/sucker/request', :record => :new_episodes
6
6
 
7
7
  let(:request) do
8
- Request.new(
9
- :locale => :us,
10
- :key => 'key',
11
- :secret => 'secret')
12
- end
13
-
14
- describe ".locales" do
15
- it "returns available locales" do
16
- Request.locales.should =~ [:us, :uk, :de, :ca, :fr, :jp]
17
- end
8
+ Request.new(:locale => :us,
9
+ :key => 'key',
10
+ :secret => 'secret',
11
+ :associate_tag => 'tag')
18
12
  end
19
13
 
20
14
  describe '#<<' do
@@ -39,8 +33,8 @@ module Sucker
39
33
  request.parameters['foo'].should be_nil
40
34
  end
41
35
 
42
- it "returns the request object" do
43
- request.reset.should be_a Request
36
+ it "returns itself" do
37
+ request.reset.should eql request
44
38
  end
45
39
  end
46
40
 
@@ -63,7 +57,7 @@ module Sucker
63
57
  end
64
58
 
65
59
  it 'canonicalizes query' do
66
- query.should match /AWSAccessKeyId=key&AssociateTag=&Service=([^&]+)&Timestamp=([^&]+)&Version=([^&]+)/
60
+ query.should match /AWSAccessKeyId=key&AssociateTag=tag&Service=([^&]+)&Timestamp=([^&]+)&Version=([^&]+)/
67
61
  end
68
62
 
69
63
  it 'includes a timestamp' do
@@ -90,10 +84,10 @@ module Sucker
90
84
  end
91
85
  end
92
86
 
93
- describe '#host' do
94
- it 'returns a host' do
95
- request.locale = :fr
96
- request.send(:host).should eql 'ecs.amazonaws.fr'
87
+ describe "#host" do
88
+ it "returns the host for specified locale" do
89
+ request.locale = :uk
90
+ request.send(:host).should eql Request::HOSTS[request.locale]
97
91
  end
98
92
  end
99
93
  end
@@ -9,10 +9,10 @@ module Sucker
9
9
  let(:asins) { ['0816614024', '0143105825'] }
10
10
 
11
11
  let(:response) do
12
- request = Sucker.new(
13
- :locale => :us,
14
- :key => amazon_key,
15
- :secret => amazon_secret)
12
+ request = Request.new(:locale => :us,
13
+ :key => 'key',
14
+ :secret => 'secret',
15
+ :associate_tag => 'tag')
16
16
  request << {
17
17
  'Operation' => 'ItemLookup',
18
18
  'IdType' => 'ASIN',
@@ -2,8 +2,15 @@ require "spec_helper"
2
2
 
3
3
  describe Sucker do
4
4
  describe ".new" do
5
- it "returns a Request object" do
6
- Sucker.new.should be_an_instance_of Sucker::Request
5
+ it "returns a request" do
6
+ subject.new.should be_a Sucker::Request
7
+ end
8
+ end
9
+
10
+ describe ".configure" do
11
+ it "yields a configuration object" do
12
+ subject.should_receive(:configure).and_yield(Sucker::Config)
13
+ subject.configure { |c| }
7
14
  end
8
15
  end
9
16
  end
metadata CHANGED
@@ -7,8 +7,8 @@ version: !ruby/object:Gem::Version
7
7
  - 0
8
8
  - 0
9
9
  - pre
10
- - 4
11
- version: 2.0.0.pre.4
10
+ - 5
11
+ version: 2.0.0.pre.5
12
12
  platform: ruby
13
13
  authors:
14
14
  - Paper Cavalier
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-06-21 00:00:00 +01:00
19
+ date: 2011-07-04 00:00:00 +01:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -43,28 +43,14 @@ dependencies:
43
43
  - !ruby/object:Gem::Version
44
44
  segments:
45
45
  - 1
46
- - 4
47
- version: "1.4"
46
+ - 5
47
+ version: "1.5"
48
48
  type: :runtime
49
49
  version_requirements: *id002
50
- - !ruby/object:Gem::Dependency
51
- name: bundler
52
- prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
54
- none: false
55
- requirements:
56
- - - ~>
57
- - !ruby/object:Gem::Version
58
- segments:
59
- - 1
60
- - 0
61
- version: "1.0"
62
- type: :development
63
- version_requirements: *id003
64
50
  - !ruby/object:Gem::Dependency
65
51
  name: cucumber
66
52
  prerelease: false
67
- requirement: &id004 !ruby/object:Gem::Requirement
53
+ requirement: &id003 !ruby/object:Gem::Requirement
68
54
  none: false
69
55
  requirements:
70
56
  - - ~>
@@ -74,11 +60,11 @@ dependencies:
74
60
  - 10
75
61
  version: "0.10"
76
62
  type: :development
77
- version_requirements: *id004
63
+ version_requirements: *id003
78
64
  - !ruby/object:Gem::Dependency
79
65
  name: em-http-request
80
66
  prerelease: false
81
- requirement: &id005 !ruby/object:Gem::Requirement
67
+ requirement: &id004 !ruby/object:Gem::Requirement
82
68
  none: false
83
69
  requirements:
84
70
  - - ~>
@@ -91,11 +77,11 @@ dependencies:
91
77
  - 4
92
78
  version: 1.0.0.beta.4
93
79
  type: :development
94
- version_requirements: *id005
80
+ version_requirements: *id004
95
81
  - !ruby/object:Gem::Dependency
96
82
  name: em-synchrony
97
83
  prerelease: false
98
- requirement: &id006 !ruby/object:Gem::Requirement
84
+ requirement: &id005 !ruby/object:Gem::Requirement
99
85
  none: false
100
86
  requirements:
101
87
  - - ~>
@@ -108,25 +94,11 @@ dependencies:
108
94
  - 1
109
95
  version: 0.3.0.beta.1
110
96
  type: :development
111
- version_requirements: *id006
112
- - !ruby/object:Gem::Dependency
113
- name: relish
114
- prerelease: false
115
- requirement: &id007 !ruby/object:Gem::Requirement
116
- none: false
117
- requirements:
118
- - - ~>
119
- - !ruby/object:Gem::Version
120
- segments:
121
- - 0
122
- - 4
123
- version: "0.4"
124
- type: :development
125
- version_requirements: *id007
97
+ version_requirements: *id005
126
98
  - !ruby/object:Gem::Dependency
127
99
  name: rspec
128
100
  prerelease: false
129
- requirement: &id008 !ruby/object:Gem::Requirement
101
+ requirement: &id006 !ruby/object:Gem::Requirement
130
102
  none: false
131
103
  requirements:
132
104
  - - ~>
@@ -136,11 +108,11 @@ dependencies:
136
108
  - 6
137
109
  version: "2.6"
138
110
  type: :development
139
- version_requirements: *id008
111
+ version_requirements: *id006
140
112
  - !ruby/object:Gem::Dependency
141
113
  name: vcr
142
114
  prerelease: false
143
- requirement: &id009 !ruby/object:Gem::Requirement
115
+ requirement: &id007 !ruby/object:Gem::Requirement
144
116
  none: false
145
117
  requirements:
146
118
  - - ~>
@@ -150,11 +122,11 @@ dependencies:
150
122
  - 10
151
123
  version: "1.10"
152
124
  type: :development
153
- version_requirements: *id009
125
+ version_requirements: *id007
154
126
  - !ruby/object:Gem::Dependency
155
127
  name: webmock
156
128
  prerelease: false
157
- requirement: &id010 !ruby/object:Gem::Requirement
129
+ requirement: &id008 !ruby/object:Gem::Requirement
158
130
  none: false
159
131
  requirements:
160
132
  - - ~>
@@ -164,7 +136,7 @@ dependencies:
164
136
  - 6
165
137
  version: "1.6"
166
138
  type: :development
167
- version_requirements: *id010
139
+ version_requirements: *id008
168
140
  description: A Nokogiri-based, optionally-evented Ruby wrapper to the Amazon Product Advertising API
169
141
  email:
170
142
  - code@papercavalier.com
@@ -175,6 +147,7 @@ extensions: []
175
147
  extra_rdoc_files: []
176
148
 
177
149
  files:
150
+ - lib/sucker/config.rb
178
151
  - lib/sucker/hash_builder.rb
179
152
  - lib/sucker/parameters.rb
180
153
  - lib/sucker/request.rb
@@ -203,6 +176,7 @@ files:
203
176
  - spec/fixtures/cassette_library/spec/sucker/request.yml
204
177
  - spec/fixtures/cassette_library/spec/sucker/response.yml
205
178
  - spec/spec_helper.rb
179
+ - spec/sucker/config_spec.rb
206
180
  - spec/sucker/hash_builder_spec.rb
207
181
  - spec/sucker/parameters_spec.rb
208
182
  - spec/sucker/request_spec.rb
@@ -266,6 +240,7 @@ test_files:
266
240
  - spec/fixtures/cassette_library/spec/sucker/request.yml
267
241
  - spec/fixtures/cassette_library/spec/sucker/response.yml
268
242
  - spec/spec_helper.rb
243
+ - spec/sucker/config_spec.rb
269
244
  - spec/sucker/hash_builder_spec.rb
270
245
  - spec/sucker/parameters_spec.rb
271
246
  - spec/sucker/request_spec.rb