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

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,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