jeff 0.3.2 → 0.4.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/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard 'rspec', :cli => '-fd' do
2
2
  notification :off
3
3
  watch(%r{^spec/.+_spec\.rb$})
4
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
5
5
  watch('spec/spec_helper.rb') { 'spec' }
6
6
  end
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Jeff
2
2
 
3
3
  **Jeff** is a light-weight module that mixes in client behaviour for
4
- [Amazon Web Services (AWS)][aws]. It wraps [Excon][excon], parses with
5
- [Nokogiri][nokogiri], and implements [Signature Version 2][sign].
4
+ [Amazon Web Services (AWS)][aws]. It wraps [Excon][excon] and implements
5
+ [Signature Version 2][sign].
6
6
 
7
7
  ![jeff][jeff]
8
8
 
9
9
  ## Usage
10
10
 
11
- Build a a hypothetical client.
11
+ Build a hypothetical client.
12
12
 
13
13
  ```ruby
14
14
  class Client
@@ -20,15 +20,14 @@ Customise default headers and parameters.
20
20
 
21
21
  ```ruby
22
22
  class Client
23
- headers 'User-Agent' => 'Client'
24
- params 'Service' => 'SomeService',
23
+ params 'Service' => 'Service',
25
24
  'Tag' => -> { tag }
26
25
 
27
26
  attr_accessor :tag
28
27
  end
29
28
  ```
30
29
 
31
- Set an AWS endpoint and credentials.
30
+ Set AWS endpoint and credentials.
32
31
 
33
32
  ```ruby
34
33
  client = Client.new.tap do |config|
@@ -41,33 +40,16 @@ end
41
40
  You should now be able to access the endpoint.
42
41
 
43
42
  ```ruby
44
- res = client.post query: {},
45
- body: 'data'
46
-
47
- puts res.status # => 200
48
- puts res.body.root # => { 'Foo' => 'Bar' }
49
- ```
50
-
51
- ### Chunked Requests
52
-
53
- You can upload large files performantly by passing a proc that delivers
54
- chunks.
55
-
56
- ```ruby
57
- file = File.open 'data'
58
- chunker = -> { file.read Excon::CHUNK_SIZE).to_s }
59
-
60
- client.post query: {},
61
- request_block: chunker
43
+ client.post query: { 'Foo' => 'Bar' },
44
+ body: 'data'
62
45
  ```
63
46
 
64
47
  ## Compatibility
65
48
 
66
- **Jeff** is compatible with [all Ruby 1.9 flavours][travis].
49
+ **Jeff** is compatible with [Ruby 1.9 flavours][travis].
67
50
 
68
- [aws]: http://aws.amazon.com/
69
- [excon]: https://github.com/geemus/excon
70
- [jeff]: http://f.cl.ly/items/0a3R3J0k1R2f423k1q2l/jeff.jpg
71
- [nokogiri]: http://nokogiri.org/
72
- [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
73
- [travis]: http://travis-ci.org/#!/hakanensari/jeff
51
+ [aws]: http://aws.amazon.com/
52
+ [excon]: https://github.com/geemus/excon
53
+ [jeff]: http://f.cl.ly/items/0a3R3J0k1R2f423k1q2l/jeff.jpg
54
+ [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
55
+ [travis]: http://travis-ci.org/#!/hakanensari/jeff
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.email = ['hakan.ensari@papercavalier.com']
8
8
  gem.description = %q{Minimum-viable Amazon Web Services (AWS) client}
9
9
  gem.summary = %q{AWS client}
10
- gem.homepage = 'https://github.com/hakanensari/jeff'
10
+ gem.homepage = 'https://github.com/papercavalier/jeff'
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
13
13
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -16,8 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = Jeff::VERSION
18
18
 
19
- gem.add_dependency 'excon', '~> 0.14.0'
20
- gem.add_dependency 'nokogiri', '~> 1.5'
19
+ gem.add_dependency 'excon', '~> 0.15.0'
21
20
  gem.add_development_dependency 'guard-rspec'
22
21
  gem.add_development_dependency 'rake'
23
22
  gem.add_development_dependency 'rspec'
@@ -1,12 +1,14 @@
1
- require 'time'
2
-
3
1
  require 'excon'
2
+ require 'time'
4
3
 
5
4
  require 'jeff/secret'
6
- require 'jeff/streamer'
7
5
  require 'jeff/version'
8
6
 
7
+ # Jeff is a light-weight module that mixes in client behaviour for Amazon Web
8
+ # Services (AWS).
9
9
  module Jeff
10
+ USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
11
+
10
12
  MissingEndpoint = Class.new ArgumentError
11
13
  MissingKey = Class.new ArgumentError
12
14
  MissingSecret = Class.new ArgumentError
@@ -15,6 +17,13 @@ module Jeff
15
17
 
16
18
  def self.included(base)
17
19
  base.extend ClassMethods
20
+
21
+ base.headers 'User-Agent' => USER_AGENT
22
+
23
+ base.params 'AWSAccessKeyId' => -> { key },
24
+ 'SignatureVersion' => '2',
25
+ 'SignatureMethod' => 'HmacSHA256',
26
+ 'Timestamp' => -> { Time.now.utc.iso8601 }
18
27
  end
19
28
 
20
29
  # Internal: Builds a sorted query.
@@ -23,7 +32,7 @@ module Jeff
23
32
  #
24
33
  # Returns a query String.
25
34
  def build_query(hsh)
26
- default_params
35
+ params
27
36
  .merge(hsh)
28
37
  .sort
29
38
  .map { |k, v| "#{k}=#{ escape v }" }
@@ -32,22 +41,10 @@ module Jeff
32
41
 
33
42
  # Internal: Returns an Excon::Connection.
34
43
  def connection
35
- @connection ||= Excon.new endpoint, headers: default_headers,
44
+ @connection ||= Excon.new endpoint, headers: headers,
36
45
  idempotent: true
37
46
  end
38
47
 
39
- # Internal: Returns the Hash default request parameters.
40
- def default_params
41
- self.class.params.reduce({}) do |a, (k, v)|
42
- a.update k => (v.is_a?(Proc) ? instance_exec(&v) : v)
43
- end
44
- end
45
-
46
- # Internal: Returns the Hash default headers.
47
- def default_headers
48
- self.class.headers
49
- end
50
-
51
48
  # Internal: Gets the String AWS endpoint.
52
49
  #
53
50
  # Raises a MissingEndpoint error if endpoint is missing.
@@ -58,6 +55,11 @@ module Jeff
58
55
  # Sets the String AWS endpoint.
59
56
  attr_writer :endpoint
60
57
 
58
+ # Internal: Returns the Hash default headers.
59
+ def headers
60
+ self.class.headers
61
+ end
62
+
61
63
  # Internal: Gets the String AWS access key id.
62
64
  #
63
65
  # Raises a MissingKey error if key is missing.
@@ -68,6 +70,13 @@ module Jeff
68
70
  # Sets the String AWS access key id.
69
71
  attr_writer :key
70
72
 
73
+ # Internal: Returns the Hash default request parameters.
74
+ def params
75
+ self.class.params.reduce({}) do |a, (k, v)|
76
+ a.update k => (v.is_a?(Proc) ? instance_exec(&v) : v)
77
+ end
78
+ end
79
+
71
80
  # Internal: Gets the Jeff::Secret.
72
81
  #
73
82
  # Raises a MissingSecret error if secret is missing.
@@ -84,18 +93,13 @@ module Jeff
84
93
  @secret = Secret.new key
85
94
  end
86
95
 
87
- # Generate HTTP request verb methods that sign queries and then delegate
88
- # request to Excon.
89
- Excon::HTTP_VERBS. each do |method|
96
+ # Generate HTTP request verb methods that sign queries and return response
97
+ # bodies as IO objects.
98
+ Excon::HTTP_VERBS.each do |method|
90
99
  eval <<-DEF
91
100
  def #{method}(opts = {})
92
- streamer = Streamer.new
93
- opts.update method: :#{method},
94
- response_block: streamer
95
- res = connection.request sign opts
96
- res.body = streamer
97
-
98
- res
101
+ opts.update method: :#{method}
102
+ connection.request sign opts
99
103
  end
100
104
  DEF
101
105
  end
@@ -134,16 +138,13 @@ module Jeff
134
138
  end
135
139
 
136
140
  module ClassMethods
137
- # Amazon recommends that libraries identify themselves via a User Agent.
138
- USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
139
-
140
141
  # Gets/Updates the default headers.
141
142
  #
142
143
  # hsh - A Hash of headers.
143
144
  #
144
145
  # Returns the Hash headers.
145
146
  def headers(hsh = nil)
146
- @headers ||= { 'User-Agent' => USER_AGENT }
147
+ @headers ||= {}
147
148
  @headers.update hsh if hsh
148
149
 
149
150
  @headers
@@ -155,12 +156,7 @@ module Jeff
155
156
  #
156
157
  # Returns the Hash parameters.
157
158
  def params(hsh = nil)
158
- @params ||= {
159
- 'AWSAccessKeyId' => -> { key },
160
- 'SignatureVersion' => '2',
161
- 'SignatureMethod' => 'HmacSHA256',
162
- 'Timestamp' => -> { Time.now.utc.iso8601 }
163
- }
159
+ @params ||= {}
164
160
  @params.update hsh if hsh
165
161
 
166
162
  @params
@@ -1,3 +1,3 @@
1
1
  module Jeff
2
- VERSION = '0.3.2'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -61,8 +61,8 @@ describe Jeff do
61
61
  client.key = 'key'
62
62
  end
63
63
 
64
- describe '#default_params' do
65
- subject { client.default_params }
64
+ describe '#params' do
65
+ subject { client.params }
66
66
 
67
67
  it 'should include the key' do
68
68
  subject['AWSAccessKeyId'].should eql client.key
@@ -116,11 +116,13 @@ describe Jeff do
116
116
 
117
117
  Excon::HTTP_VERBS.each do |method|
118
118
  describe "##{method}" do
119
- subject { client.send(method, mock: true).body.root['request'] }
119
+ subject do
120
+ client.send(method, mock: true).body
121
+ end
120
122
 
121
123
  before do
122
124
  Excon.stub({ method: method.to_sym }) do |params|
123
- { body: "<request>#{params[:method]}</request>" }
125
+ { body: method, status: 200 }
124
126
  end
125
127
  end
126
128
 
@@ -132,12 +134,12 @@ describe Jeff do
132
134
  end
133
135
  end
134
136
 
135
- context 'given a failed request' do
137
+ context 'given a temporary failure' do
136
138
  before do
137
139
  has_run = false
138
140
  Excon.stub({ method: :get }) do |params|
139
141
  if has_run
140
- { status: 200 }
142
+ { body: 'ok', status: 200 }
141
143
  else
142
144
  has_run = true
143
145
  raise Excon::Errors::SocketError.new Exception.new 'Mock Error'
@@ -148,21 +150,7 @@ describe Jeff do
148
150
  after { Excon.stubs.clear }
149
151
 
150
152
  it 'should retry' do
151
- client.get(mock: true).status.should be 200
152
- end
153
- end
154
-
155
- context 'given a malformed XML' do
156
- before do
157
- Excon.stub({ method: :get }) do |params|
158
- { body: "\n<?xml version=\"1.0\" ?><foo/>" }
159
- end
160
- end
161
-
162
- after { Excon.stubs.clear }
163
-
164
- it 'should not raise an error' do
165
- client.get mock: true
153
+ client.get(mock: true).body.should eq 'ok'
166
154
  end
167
155
  end
168
156
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jeff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-11 00:00:00.000000000 Z
12
+ date: 2012-07-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: excon
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.14.0
21
+ version: 0.15.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,23 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.14.0
30
- - !ruby/object:Gem::Dependency
31
- name: nokogiri
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: '1.5'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: '1.5'
29
+ version: 0.15.0
46
30
  - !ruby/object:Gem::Dependency
47
31
  name: guard-rspec
48
32
  requirement: !ruby/object:Gem::Requirement
@@ -105,20 +89,14 @@ files:
105
89
  - LICENSE
106
90
  - README.md
107
91
  - Rakefile
108
- - examples/amazon.yml.example
109
- - examples/debug.rb
110
92
  - jeff.gemspec
111
93
  - lib/jeff.rb
112
- - lib/jeff/document.rb
113
94
  - lib/jeff/secret.rb
114
- - lib/jeff/streamer.rb
115
95
  - lib/jeff/version.rb
116
- - spec/jeff/document_spec.rb
117
96
  - spec/jeff/secret_spec.rb
118
- - spec/jeff/streamer_spec.rb
119
97
  - spec/jeff_spec.rb
120
98
  - spec/spec_helper.rb
121
- homepage: https://github.com/hakanensari/jeff
99
+ homepage: https://github.com/papercavalier/jeff
122
100
  licenses: []
123
101
  post_install_message:
124
102
  rdoc_options: []
@@ -130,12 +108,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
130
108
  - - ! '>='
131
109
  - !ruby/object:Gem::Version
132
110
  version: '0'
111
+ segments:
112
+ - 0
113
+ hash: -177229951203254918
133
114
  required_rubygems_version: !ruby/object:Gem::Requirement
134
115
  none: false
135
116
  requirements:
136
117
  - - ! '>='
137
118
  - !ruby/object:Gem::Version
138
119
  version: '0'
120
+ segments:
121
+ - 0
122
+ hash: -177229951203254918
139
123
  requirements: []
140
124
  rubyforge_project:
141
125
  rubygems_version: 1.8.23
@@ -143,9 +127,6 @@ signing_key:
143
127
  specification_version: 3
144
128
  summary: AWS client
145
129
  test_files:
146
- - spec/jeff/document_spec.rb
147
130
  - spec/jeff/secret_spec.rb
148
- - spec/jeff/streamer_spec.rb
149
131
  - spec/jeff_spec.rb
150
132
  - spec/spec_helper.rb
151
- has_rdoc:
@@ -1,3 +0,0 @@
1
- key: foo
2
- secret: bar
3
- tag: baz
@@ -1,41 +0,0 @@
1
- $:.unshift File.expand_path '../../lib', __FILE__
2
-
3
- require 'yaml'
4
-
5
- require 'jeff'
6
- require 'pry'
7
- require 'pry-doc'
8
-
9
- class Client
10
- include Jeff
11
-
12
- params 'AssociateTag' => -> { tag },
13
- 'Service' => 'AWSECommerceService',
14
- 'Version' => '2011-08-01'
15
-
16
- attr_accessor :tag
17
-
18
- def initialize
19
- self.key = config['key']
20
- self.secret = config['secret']
21
- self.tag = config['tag']
22
- self.endpoint = 'http://ecs.amazonaws.com/onca/xml'
23
- end
24
-
25
- def find(asins)
26
- params = {
27
- 'Operation' => 'ItemLookup',
28
- 'ItemId' => Array(asins).join(',')
29
- }
30
-
31
- get query: params
32
- end
33
-
34
- private
35
-
36
- def config
37
- @config ||= YAML.load_file File.expand_path '../amazon.yml', __FILE__
38
- end
39
- end
40
-
41
- binding.pry
@@ -1,48 +0,0 @@
1
- require 'nokogiri'
2
-
3
- module Jeff
4
- class Document < Nokogiri::XML::SAX::Document
5
- def characters(val)
6
- val.strip!
7
- unless val.empty?
8
- (node['__content__'] ||= '') << val
9
- end
10
- end
11
-
12
- def end_element(key)
13
- child = @stack.pop
14
-
15
- if child.keys == ['__content__']
16
- child = child['__content__']
17
- end
18
-
19
- case node[key]
20
- when Array
21
- node[key] << child
22
- when Hash, String
23
- node[key] = [node[key], child]
24
- else
25
- node[key] = child
26
- end
27
- end
28
-
29
- def start_element(key, attrs = [])
30
- @stack << {}
31
- attrs.each { |attr| node.store *attr }
32
- end
33
-
34
- def start_document
35
- @stack = [{}]
36
- end
37
-
38
- def root
39
- @stack.first
40
- end
41
-
42
- private
43
-
44
- def node
45
- @stack.last
46
- end
47
- end
48
- end
@@ -1,22 +0,0 @@
1
- require 'jeff/document'
2
-
3
- module Jeff
4
- class Streamer
5
- def initialize
6
- @parser = Nokogiri::XML::SAX::PushParser.new Document.new
7
- end
8
-
9
- def call(chunk, remaining_bytes, total_bytes)
10
- @parser << chunk.sub(/^\n/, '')
11
- @parser.finish if remaining_bytes == 0
12
- end
13
-
14
- def document
15
- @parser.document
16
- end
17
-
18
- def root
19
- document.root
20
- end
21
- end
22
- end
@@ -1,49 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Jeff
4
- describe Document do
5
- let(:io) do
6
- StringIO.new %{
7
- <?xml version=\"1.0\" ?>
8
- <ItemAttributes>
9
- <Title>Anti-Oedipus</Title>
10
- <Author>Gilles Deleuze</Author>
11
- <Author>Felix Guattari</Author>
12
- <Creator Role="Translator">Robert Hurley</Creator>
13
- </ItemAttributes>
14
- }.strip
15
- end
16
-
17
- let(:doc) { described_class.new }
18
- let(:parser) { Nokogiri::XML::SAX::Parser.new doc }
19
-
20
- before do
21
- io.rewind
22
- parser.parse io
23
- end
24
-
25
- describe '#root' do
26
- subject { doc.root['ItemAttributes'] }
27
-
28
- it { should be_a Hash }
29
-
30
- it 'should handle only children' do
31
- subject['Title'].should eql 'Anti-Oedipus'
32
- end
33
-
34
- it 'should hande arrays' do
35
- subject['Author'].should be_an Array
36
- end
37
-
38
- it 'should handle attributes' do
39
- creator = subject['Creator']
40
- creator['Role'].should eql 'Translator'
41
- creator['__content__'].should eql 'Robert Hurley'
42
- end
43
-
44
- it 'should ignore space between tags' do
45
- should_not have_key '__content__'
46
- end
47
- end
48
- end
49
- end
@@ -1,28 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Jeff
4
- describe Streamer do
5
- let(:streamer) { Streamer.new }
6
-
7
- let(:xml) do
8
- %{
9
- <?xml version="1.0" ?>
10
- <foo>
11
- <bar>1</bar>
12
- </foo>
13
- }.strip.gsub />\s+</, '><'
14
- end
15
-
16
- it 'should parse a stream' do
17
- bytes_sent = 0
18
- total_bytes = xml.size
19
-
20
- xml.scan(/.{1,8}/m).each do |chunk|
21
- bytes_sent += chunk.size
22
- streamer.call chunk, total_bytes - bytes_sent, total_bytes
23
- end
24
-
25
- streamer.root.should have_key 'foo'
26
- end
27
- end
28
- end