chimps 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/Gemfile +3 -9
  2. data/Gemfile.lock +14 -10
  3. data/README.rdoc +146 -240
  4. data/Rakefile +4 -33
  5. data/VERSION +1 -1
  6. data/lib/chimps/config.rb +35 -21
  7. data/lib/chimps/{utils/error.rb → error.rb} +1 -12
  8. data/lib/chimps/query_request.rb +67 -0
  9. data/lib/chimps/request.rb +82 -108
  10. data/lib/chimps/response.rb +62 -22
  11. data/lib/chimps/utils/typewriter.rb +90 -0
  12. data/lib/chimps/utils/uses_curl.rb +22 -12
  13. data/lib/chimps/utils.rb +50 -6
  14. data/lib/chimps/workflows/download.rb +72 -0
  15. data/lib/chimps/workflows/upload.rb +113 -0
  16. data/lib/chimps.rb +12 -12
  17. data/spec/chimps/query_request_spec.rb +44 -0
  18. data/spec/chimps/request_spec.rb +92 -0
  19. data/spec/chimps/response_spec.rb +0 -1
  20. data/spec/chimps/workflows/download_spec.rb +48 -0
  21. data/spec/spec_helper.rb +2 -19
  22. metadata +46 -91
  23. data/.document +0 -5
  24. data/.gitignore +0 -32
  25. data/CHANGELOG.textile +0 -4
  26. data/bin/chimps +0 -5
  27. data/lib/chimps/cli.rb +0 -28
  28. data/lib/chimps/commands/base.rb +0 -65
  29. data/lib/chimps/commands/batch.rb +0 -40
  30. data/lib/chimps/commands/create.rb +0 -31
  31. data/lib/chimps/commands/destroy.rb +0 -26
  32. data/lib/chimps/commands/download.rb +0 -46
  33. data/lib/chimps/commands/help.rb +0 -100
  34. data/lib/chimps/commands/list.rb +0 -41
  35. data/lib/chimps/commands/query.rb +0 -82
  36. data/lib/chimps/commands/search.rb +0 -48
  37. data/lib/chimps/commands/show.rb +0 -30
  38. data/lib/chimps/commands/test.rb +0 -39
  39. data/lib/chimps/commands/update.rb +0 -34
  40. data/lib/chimps/commands/upload.rb +0 -50
  41. data/lib/chimps/commands.rb +0 -125
  42. data/lib/chimps/typewriter.rb +0 -349
  43. data/lib/chimps/utils/log.rb +0 -48
  44. data/lib/chimps/utils/uses_model.rb +0 -34
  45. data/lib/chimps/utils/uses_yaml_data.rb +0 -93
  46. data/lib/chimps/workflows/batch.rb +0 -127
  47. data/lib/chimps/workflows/downloader.rb +0 -102
  48. data/lib/chimps/workflows/up.rb +0 -149
  49. data/lib/chimps/workflows/upload/bundler.rb +0 -249
  50. data/lib/chimps/workflows/upload/notifier.rb +0 -59
  51. data/lib/chimps/workflows/upload/token.rb +0 -77
  52. data/lib/chimps/workflows/upload/uploader.rb +0 -51
  53. data/lib/chimps/workflows.rb +0 -12
  54. data/spec/chimps/typewriter_spec.rb +0 -114
  55. data/spec/chimps/workflows/upload/bundler_spec.rb +0 -75
  56. data/spec/chimps/workflows/upload/token_spec.rb +0 -6
data/lib/chimps/config.rb CHANGED
@@ -4,48 +4,62 @@ module Chimps
4
4
 
5
5
  # Chimps configuration. Managed by
6
6
  # Configliere[http://github.com/infochimps/configliere]
7
- Config = Configliere.new
8
-
9
- Config.define :site_config, :description => "Path to site-wide configuration file", :env_var => "CHIMPS_ETC", :default => "/etc/chimps/chimpsrc.yaml", :type => String, :no_help => true
10
- Config.define :config, :description => "Path to user configuration file", :env_var => "CHIMPS_RC", :default => (ENV["HOME"] && File.expand_path("~/.chimps")), :flag => :c, :type => String
11
- Config.define :log, :description => "Path to log file", :flag => :l, :type => String
12
- Config.define :timestamp_format, :description => "Format for timestamps", :type => String, :no_help => true, :default => "%Y%m%d-%H%M%S"
13
- Config.define :plugin_dirs, :description => "List of directories from which to load plugins", :type => Array, :no_help => true, :default => ['/usr/share/chimps', '/usr/local/share/chimps']
14
- Config.define :skip_plugins, :description => "Don't load any plugins", :flag => :q, :type => :boolean
15
- Config.define :verbose, :description => "Be verbose", :flag => :v, :type => :boolean
16
-
17
- Config.define 'query.host', :description => "Host to send Query API requests to", :type => String, :default => "http://api.infochimps.com", :no_help => true, :env_var => "CHIMPS_QUERY_HOST"
18
- Config.define 'query.key', :description => "API key for the Query API", :type => String, :no_help => true
7
+ #
8
+ # @return [Configliere::Param]
9
+ def self.config
10
+ @config ||= Configliere.new
11
+ end
12
+
13
+ # Backwards compatibility for version < 0.3.0.
14
+ Config = config
19
15
 
20
- Config.define 'site.username', :description => "Your Infochimps username", :type => String
21
- Config.define 'site.host', :description => "Host to send Dataset API requests to", :type => String, :default => "http://infochimps.org", :env_var => "CHIMPS_DATASET_HOST"
22
- Config.define 'site.key', :description => "API key for the Dataset API", :type => String
23
- Config.define 'site.secret', :description => "API secret for the Dataset API", :type => String
16
+ def self.define_config
17
+ config.define :site_config, :description => "Path to site-wide configuration file", :env_var => "CHIMPS_ETC", :default => "/etc/chimps/chimps.yaml", :type => String, :no_help => true
18
+ config.define :config, :description => "Path to user configuration file", :env_var => "CHIMPS_RC", :default => (ENV["HOME"] && File.expand_path("~/.chimps")), :flag => :c, :type => String
19
+ config.define :log, :description => "Path to log file", :flag => :l, :type => String
20
+ config.define :timestamp_format, :description => "Format for timestamps", :type => String, :no_help => true, :default => "%Y%m%d-%H%M%S"
21
+ config.define :plugin_dirs, :description => "List of directories from which to load plugins", :type => Array, :no_help => true, :default => ['/usr/share/chimps', '/usr/local/share/chimps']
22
+ config.define :skip_plugins, :description => "Don't load any plugins", :flag => :q, :type => :boolean
23
+ config.define :verbose, :description => "Be verbose", :flag => :v, :type => :boolean
24
+
25
+ config.define 'query.host', :description => "Host to send Query API requests to", :type => String, :default => "http://api.infochimps.com", :no_help => true, :env_var => "APEYEYE", :no_help => true
26
+ config.define 'query.key', :description => "API key for the Query API", :type => String, :no_help => true
27
+
28
+ config.define 'dataset.username', :description => "Your Infochimps username", :type => String
29
+ config.define 'dataset.host', :description => "Host to send Dataset API requests to", :type => String, :default => "http://www.infochimps.com", :env_var => "GEORGE", :no_help => true
30
+ config.define 'dataset.key', :description => "API key for the Dataset API", :type => String
31
+ config.define 'dataset.secret', :description => "API secret for the Dataset API", :type => String
32
+ end
33
+ define_config
24
34
 
25
35
  # Is Chimps in verbose mode?
26
36
  #
27
37
  # @return [true, false, nil]
28
38
  def self.verbose?
29
- Config[:verbose]
39
+ config[:verbose]
30
40
  end
31
41
 
32
42
  # The username Chimps will pass to Infochimps.
33
43
  #
34
44
  # @return [String]
35
45
  def self.username
36
- Config[:site][:username] or raise AuthenticationError.new("No Dataset API username set in #{Chimps::Config[:config]} or #{Chimps::Config[:site_config]}")
46
+ config[:dataset][:username] or raise AuthenticationError.new("No Dataset API username set in #{Chimps.config[:config]} or #{Chimps.config[:site_config]}")
37
47
  end
38
48
 
49
+ # The current Chimps library version.
50
+ #
51
+ # @return [String]
39
52
  def self.version
53
+ return @version if @version
40
54
  version_path = File.join(File.dirname(__FILE__), '../../VERSION')
41
55
  @version ||= File.exist?(version_path) && File.new(version_path).read
42
56
  end
43
57
 
44
58
  # Require all Ruby files in the directory
45
- # Chimps::Config[:plugin_dirs].
59
+ # Chimps.config[:plugin_dirs].
46
60
  def self.load_plugins
47
- return if Chimps::Config[:skip_plugins]
48
- plugin_dirs = Chimps::Config[:plugin_dirs]
61
+ return if Chimps.config[:skip_plugins]
62
+ plugin_dirs = Chimps.config[:plugin_dirs]
49
63
  plugin_dirs.each do |dir|
50
64
  Dir[File.expand_path(dir) + "/*.rb"].each { |plugin| require plugin }
51
65
  end
@@ -3,9 +3,6 @@ module Chimps
3
3
  # subclasses of Chimps::Error so they can be easily caught.
4
4
  Error = Class.new(StandardError)
5
5
 
6
- # Raised when the user provides bad input on the command line.
7
- CLIError = Class.new(Error)
8
-
9
6
  # Raised when the user hasn't specified any API credentials or the
10
7
  # server rejects the user's API credentials.
11
8
  #
@@ -18,9 +15,6 @@ module Chimps
18
15
  # Roughly corresponds to HTTP status code 5xx.
19
16
  ServerError = Class.new(Error)
20
17
 
21
- # Raised when IMW fails to properly package files to upload.
22
- PackagingError = Class.new(Error)
23
-
24
18
  # Raised when there is an error in uploading to S3 or in notifiying
25
19
  # Infochimps of the new package.
26
20
  UploadError = Class.new(Error)
@@ -29,12 +23,7 @@ module Chimps
29
23
  # methods.
30
24
  NotImplementedError = Class.new(Error)
31
25
 
32
- # Raised when the response from Infochimps isn't well-formed or is
33
- # unexpected.
26
+ # Raised when the response from Infochimps isn't well-formed.
34
27
  ParseError = Class.new(Error)
35
-
36
- # Raised when Chimps encounters response data it doesn't know how to
37
- # pretty print.
38
- PrintingError = Class.new(Error)
39
28
  end
40
29
 
@@ -0,0 +1,67 @@
1
+ module Chimps
2
+
3
+ # A class to encapsulate requests made against the Infochimps paid
4
+ # query API.
5
+ class QueryRequest < Request
6
+
7
+ # Is this request authentiable (has the Chimps user specified an
8
+ # API key and secret in their configuration file)?
9
+ #
10
+ # @return [true, false]
11
+ def authenticable?
12
+ !Chimps.config[:query][:key].blank?
13
+ end
14
+
15
+ def url_with_query_string
16
+ qs = (query_string && query_string.size > 0 ? query_string : nil)
17
+ case
18
+ when qs.nil?
19
+ base_url
20
+ when base_url.include?("?")
21
+ base_url + "&#{qs}"
22
+ else
23
+ base_url + "?#{qs}"
24
+ end
25
+ end
26
+
27
+ # The host to send requests to.
28
+ #
29
+ # @return [String]
30
+ def host
31
+ @host ||= Chimps.config[:query][:host]
32
+ end
33
+
34
+ # All Query API requests must be signed.
35
+ #
36
+ # @return [true]
37
+ def authenticate?
38
+ return true
39
+ end
40
+
41
+ # Authenticate this request by stuffing the <tt>:requested_at</tt>
42
+ # and <tt>:apikey</tt> properties into its <tt>:query_params</tt>
43
+ # hash.
44
+ #
45
+ # Will do nothing at all if Chimps::Request#authenticate? returns
46
+ # false.
47
+ def authenticate_if_necessary!
48
+ return unless authenticate? && should_encode?
49
+ raise Chimps::AuthenticationError.new("Query API key (Chimps.config[:query][:key]) from #{Chimps.config[:config]} or #{Chimps.config[:site_config]}") unless authenticable?
50
+ query_params[:requested_at] = Time.now.to_i.to_s
51
+ query_params[:apikey] = Chimps.config[:query][:key]
52
+ end
53
+
54
+ # Append the signature to the unsigned query string.
55
+ #
56
+ # The signature made from the Chimps user's API secret and either
57
+ # the query string text (stripped of <tt>&</tt> and <tt>=</tt>)
58
+ # for GET and DELETE requests or the request body for POST and PUT
59
+ # requests.
60
+ #
61
+ # @return [String]
62
+ def signed_query_string
63
+ unsigned_query_string
64
+ end
65
+
66
+ end
67
+ end
@@ -12,25 +12,28 @@ module Chimps
12
12
  # Default headers to pass with every request.
13
13
  DEFAULT_HEADERS = { :content_type => 'application/json', :accept => 'application/json', :user_agent => "Chimps #{Chimps.version}" }
14
14
 
15
- # Path of the URL to submit to. Must be a String.
15
+ # Path of the URL to submit to. Must be a String. Can start with
16
+ # an initial '/' or not -- no big deal ;)
16
17
  attr_accessor :path
17
18
 
18
19
  # Parameters to include in the query string of the URL to submit
19
- # to. Must be a Hash.
20
+ # to. Can be a string or a Hash
20
21
  attr_accessor :query_params
21
22
 
22
- # Data to include in the body of the request. Must be a Hash.
23
- attr_accessor :data
23
+ # Data to include in the body of the request. Can be a Hash or a
24
+ # String.
25
+ attr_accessor :body
24
26
 
25
27
  # Initialize a Request to the given +path+.
26
28
  #
27
29
  # Query parameters and data can be passed in as hashes named
28
- # <tt>:params</tt> and <tt>:data</tt>, respectively.
30
+ # <tt>:params</tt> and <tt>:body</tt>, respectively.
29
31
  #
30
32
  # If <tt>:sign</tt> is passed in the +options+ then the URL of
31
33
  # this request will be signed with the Chimps user's Infochimps
32
- # API key and secret. Failure to properly sign will raise an
33
- # error.
34
+ # API key and secret. Signing a request which doesn't need to be
35
+ # signed is just fine. Forgetting to sign a request which needs
36
+ # to be signed will result in a 401 error from Infochimps.
34
37
  #
35
38
  # If <tt>:sign_if_possible</tt> is passed in the +options+ then an
36
39
  # attemp to sign the URL will be made though an error will _not_
@@ -38,21 +41,36 @@ module Chimps
38
41
  #
39
42
  # @param [String] path
40
43
  # @param [Hash] options
41
- # @option options [Hash] params Query parameters to include in the URL
42
- # @option options [Hash] data Data to include in the request body
44
+ # @option options [Hash] query Query parameters to include in the URL
45
+ # @option options [Hash] body Data to include in the request body
43
46
  # @option options [true, false] sign Sign this request, raising an error on failure
44
47
  # @option options [true, false] sign_if_possible Sign this request, no error on failure
48
+ # @option potions [true, false] raw If raw then encoding the query string and request body is up to you.
45
49
  # @return [Chimps::Request]
46
50
  def initialize path, options={}
47
- @path = path
48
- @query_params = options[:query_params] || options[:params] || {}
49
- @data = options[:data] || {}
50
- @authentication_required = [:authenticate, :authenticated, :authenticate_if_possible, :sign, :signed, :sign_if_possible].any? { |key| options.include?(key) }
51
+ self.path = path
52
+ self.query_params = options[:query_params] || options[:query] || options[:params] || {}
53
+ self.body = options[:body] || options[:data] || {}
54
+
55
+ @authentication_required = [:authenticate, :authenticated, :authenticate_if_possible, :sign, :signed, :sign_if_possible].any? { |key| options[key] }
51
56
  @forgive_authentication_error = options[:sign_if_possible] || options[:authenticate_if_possible]
57
+ @raw = options[:raw]
52
58
  authenticate_if_necessary!
53
- super url_with_query_string
59
+ super(url_with_query_string, {:headers => DEFAULT_HEADERS.merge(options[:headers] || {})})
54
60
  end
55
61
 
62
+ # Return the URL for this request with the (signed, if necessary)
63
+ # query string appended.
64
+ #
65
+ # @return [String]
66
+ def url_with_query_string
67
+ if query_string && query_string.size > 0
68
+ base_url + "?#{query_string}"
69
+ else
70
+ base_url
71
+ end
72
+ end
73
+
56
74
  # Should the request be authenticated?
57
75
  #
58
76
  # @return [true, false]
@@ -61,12 +79,30 @@ module Chimps
61
79
  end
62
80
  alias_method :sign?, :authenticate?
63
81
 
82
+ # Should the query string and request body be encoded?
83
+ #
84
+ # Control this by passing in the <tt>:raw</tt> keyword when
85
+ # initializing this Request.
86
+ #
87
+ # @return [true, false]
88
+ def should_encode?
89
+ !@raw
90
+ end
91
+
92
+ # Should this be considered a raw request in which neither the
93
+ # query string nor the body should be encoded or escaped?
94
+ #
95
+ # @return [true, false]
96
+ def raw?
97
+ !!@raw
98
+ end
99
+
64
100
  # Is this request authentiable (has the Chimps user specified an
65
101
  # API key and secret in their configuration file)?
66
102
  #
67
103
  # @return [true, false]
68
104
  def authenticable?
69
- !Chimps::Config[:site][:key].blank? && !Chimps::Config[:site][:secret].blank?
105
+ !Chimps.config[:dataset][:key].blank? && !Chimps.config[:dataset][:secret].blank?
70
106
  end
71
107
  alias_method :signable?, :authenticable?
72
108
 
@@ -74,17 +110,15 @@ module Chimps
74
110
  #
75
111
  # @return [String]
76
112
  def host
77
- @host ||= Chimps::Config[:site][:host]
113
+ @host ||= Chimps.config[:dataset][:host]
78
114
  end
79
115
 
80
- # Return the URL for this request with the (signed, if necessary)
81
- # query string appended.
116
+ # Return the base URL for this request, consisting of the host and
117
+ # path but *not* the query string.
82
118
  #
83
119
  # @return [String]
84
- def url_with_query_string
85
- base_url = File.join(host, path)
86
- base_url += "?#{query_string}" unless query_string.blank?
87
- base_url
120
+ def base_url
121
+ File.join(host, path)
88
122
  end
89
123
 
90
124
  # Return the query string for this request, signed if necessary.
@@ -93,7 +127,7 @@ module Chimps
93
127
  def query_string
94
128
  (authenticate? && authenticable?) ? signed_query_string : unsigned_query_string
95
129
  end
96
-
130
+
97
131
  # Perform a GET request to this URL, returning a parsed response.
98
132
  #
99
133
  # Any headers in +options+ will passed to
@@ -101,10 +135,10 @@ module Chimps
101
135
  #
102
136
  # @param [Hash] options
103
137
  # @return [Chimps::Response]
104
- def get options={}
138
+ def get options={}, &block
105
139
  handle_exceptions do
106
140
  Chimps.log.info("GET #{url}")
107
- Response.new(super(DEFAULT_HEADERS.merge(options)))
141
+ Response.new(super(options, &block))
108
142
  end
109
143
  end
110
144
 
@@ -115,10 +149,10 @@ module Chimps
115
149
  #
116
150
  # @param [Hash] options
117
151
  # @return [Chimps::Response]
118
- def post options={}
152
+ def post options={}, &block
119
153
  handle_exceptions do
120
154
  Chimps.log.info("POST #{url}")
121
- Response.new(super(data_text, DEFAULT_HEADERS.merge(options)))
155
+ Response.new(super(encoded_body, options, &block))
122
156
  end
123
157
  end
124
158
 
@@ -129,10 +163,10 @@ module Chimps
129
163
  #
130
164
  # @param [Hash] options
131
165
  # @return [Chimps::Response]
132
- def put options={}
166
+ def put options={}, &block
133
167
  handle_exceptions do
134
168
  Chimps.log.info("PUT #{url}")
135
- Response.new(super(data_text, DEFAULT_HEADERS.merge(options)))
169
+ Response.new(super(encoded_body, options, &block))
136
170
  end
137
171
  end
138
172
 
@@ -144,21 +178,20 @@ module Chimps
144
178
  #
145
179
  # @param [Hash] options
146
180
  # @return [Chimps::Response]
147
- def delete options={}
181
+ def delete options={}, &block
148
182
  handle_exceptions do
149
183
  Chimps.log.info("DELETE #{url}")
150
- Response.new(super(DEFAULT_HEADERS.merge(options)))
184
+ Response.new(super(options, &block))
151
185
  end
152
186
  end
153
187
 
154
- protected
155
188
  # Yield to +block+ but rescue any RestClient errors by wrapping
156
189
  # them in a Chimps::Response.
157
190
  def handle_exceptions &block
158
191
  begin
159
192
  yield
160
193
  rescue RestClient::Exception => e
161
- return Response.new(e.response, :error => e.message)
194
+ Response.new(e.response, :error => e.message)
162
195
  end
163
196
  end
164
197
 
@@ -169,17 +202,17 @@ module Chimps
169
202
  # Will do nothing at all if Chimps::Request#authenticate? returns
170
203
  # false.
171
204
  def authenticate_if_necessary!
172
- return unless authenticate?
173
- raise Chimps::AuthenticationError.new("API key or secret missing from #{Config[:config]} or #{Config[:site_config]}") unless (authenticable? || @forgive_authentication_error)
205
+ return unless authenticate? && should_encode?
206
+ raise Chimps::AuthenticationError.new("Dataset API key (Chimps.config[:dataset][:key]) or secret (Chimps.config[:dataset][:secret]) missing from #{Chimps.config[:config]} or #{Chimps.config[:site_config]}") unless (authenticable? || @forgive_authentication_error)
174
207
  query_params[:requested_at] = Time.now.to_i.to_s
175
- query_params[:apikey] = Chimps::Config[:site][:key]
208
+ query_params[:apikey] = Chimps.config[:dataset][:key]
176
209
  end
177
210
 
178
211
  # Return an unsigned query string for this request.
179
212
  #
180
213
  # @return [String]
181
214
  def unsigned_query_string
182
- RestClient::Payload.generate(query_params)
215
+ (should_encode? ? RestClient::Payload.generate(query_params) : query_params).to_s
183
216
  end
184
217
 
185
218
  # Return an unsigned query string for this request without the
@@ -193,14 +226,18 @@ module Chimps
193
226
  @query_params_text ||= obj_to_stripped_string(query_params)
194
227
  end
195
228
 
196
- # Return the data of this request as a string.
229
+ # Return this Requests's body as a suitably encoded string.
197
230
  #
198
231
  # This is the text that will be signed for POST and PUT requests.
199
232
  #
200
233
  # @return [String]
201
- def data_text
234
+ def encoded_body
235
+ @encoded_body ||= should_encode? ? encode(body) : body.to_s
236
+ end
237
+
238
+ def encode obj
202
239
  require 'json'
203
- @data_text ||= JSON.generate(data, {:max_nesting => false})
240
+ JSON.generate((obj == true ? {} : obj), {:max_nesting => false})
204
241
  end
205
242
 
206
243
  # Sign +string+ by concatenting it with the secret and computing
@@ -209,9 +246,9 @@ module Chimps
209
246
  # @param [String]
210
247
  # @return [String]
211
248
  def sign string
212
- raise Chimps::AuthenticationError.new("No API secret stored in #{Config[:config]} or #{Config[:site_config]}.") unless (authenticable? || @forgive_authentication_error)
249
+ raise Chimps::AuthenticationError.new("No API secret stored in #{Chimps.config[:config]} or #{Chimps.config[:site_config]}.") unless (authenticable? || @forgive_authentication_error)
213
250
  require 'digest/md5'
214
- Digest::MD5.hexdigest(string + Config[:site][:secret])
251
+ Digest::MD5.hexdigest(string + Chimps.config[:dataset][:secret])
215
252
  end
216
253
 
217
254
  # Append the signature to the unsigned query string.
@@ -223,7 +260,9 @@ module Chimps
223
260
  #
224
261
  # @return [String]
225
262
  def signed_query_string
226
- signature = sign(data.blank? ? unsigned_query_string_stripped : data_text)
263
+ return unsigned_query_string unless should_encode?
264
+ text_to_sign = ((body == true || (! body.blank?)) ? encoded_body : unsigned_query_string_stripped)
265
+ signature = sign(text_to_sign)
227
266
  "#{unsigned_query_string}&signature=#{signature}"
228
267
  end
229
268
 
@@ -240,69 +279,4 @@ module Chimps
240
279
  end
241
280
 
242
281
  end
243
-
244
- # A class to encapsulate requests made against the Infochimps paid
245
- # query API.
246
- class QueryRequest < Request
247
-
248
- # Is this request authentiable (has the Chimps user specified an
249
- # API key and secret in their configuration file)?
250
- #
251
- # @return [true, false]
252
- def authenticable?
253
- !Chimps::Config[:query][:key].blank?
254
- end
255
-
256
- # The host to send requests to.
257
- #
258
- # @return [String]
259
- def host
260
- @host ||= Chimps::Config[:query][:host]
261
- end
262
-
263
- # All Query API requests must be signed.
264
- #
265
- # @return [true]
266
- def authenticate?
267
- return true
268
- end
269
-
270
- # Authenticate this request by stuffing the <tt>:requested_at</tt>
271
- # and <tt>:apikey</tt> properties into its <tt>:query_params</tt>
272
- # hash.
273
- #
274
- # Will do nothing at all if Chimps::Request#authenticate? returns
275
- # false.
276
- def authenticate_if_necessary!
277
- return unless authenticate?
278
- raise Chimps::AuthenticationError.new("API key or secret missing from #{Config[:config]} or #{Config[:site_config]}") unless (authenticable? || @forgive_authentication_error)
279
- query_params[:requested_at] = Time.now.to_i.to_s
280
- query_params[:apikey] = Chimps::Config[:query][:key]
281
- end
282
-
283
- # Sign +string+ by concatenting it with the secret and computing
284
- # the MD5 digest of the whole thing.
285
- #
286
- # @param [String]
287
- # @return [String]
288
- def sign string
289
- raise Chimps::AuthenticationError.new("No API secret stored in #{Config[:config]} or #{Config[:site_config]}.") unless (authenticable? || @forgive_authentication_error)
290
- require 'digest/md5'
291
- Digest::MD5.hexdigest(string + Config[:query][:secret])
292
- end
293
-
294
- # Append the signature to the unsigned query string.
295
- #
296
- # The signature made from the Chimps user's API secret and either
297
- # the query string text (stripped of <tt>&</tt> and <tt>=</tt>)
298
- # for GET and DELETE requests or the request body for POST and PUT
299
- # requests.
300
- #
301
- # @return [String]
302
- def signed_query_string
303
- unsigned_query_string
304
- end
305
-
306
- end
307
-
308
282
  end
@@ -1,3 +1,6 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
1
4
  module Chimps
2
5
 
3
6
  # A class to wrap responses from the Infochimps API.
@@ -27,7 +30,6 @@ module Chimps
27
30
  super()
28
31
  @body = body
29
32
  @error = options[:error]
30
- parse!
31
33
  end
32
34
 
33
35
  # The HTTP status code of the response.
@@ -59,15 +61,27 @@ module Chimps
59
61
  end
60
62
 
61
63
  # Parse the response from Infochimps.
64
+ #
65
+ # @return [Chimps::Response]
62
66
  def parse!
63
67
  data = parse_response_body
64
68
  case data
65
69
  # hack...sometimes we get back an array instead of a
66
70
  # hash...should change the API at Chimps end
67
71
  when Hash then merge!(data)
68
- when Array then self[:array] = data # see Chimps::Typewriter#accumulate
72
+ when Array then self[:array] = data
69
73
  when String then self[:string] = data
74
+ else nil
70
75
  end
76
+ @parsed = true
77
+ self
78
+ end
79
+
80
+ # Parse the response from Infochimps -- will do nothing if the
81
+ # response has already been parsed.
82
+ def parse
83
+ return if @parsed
84
+ parse!
71
85
  end
72
86
 
73
87
  # Was this response a success?
@@ -91,7 +105,7 @@ module Chimps
91
105
  #
92
106
  # @return [Hash]
93
107
  def data
94
- returning({}) do |d|
108
+ {}.tap do |d|
95
109
  each_pair do |key, value|
96
110
  d[key] = value
97
111
  end
@@ -100,16 +114,43 @@ module Chimps
100
114
 
101
115
  # Print this response.
102
116
  #
103
- # Options are also passed to Chimps::Typewriter.new; consult for
104
- # details.
105
- #
106
117
  # @param [Hash] options
107
- # @option options
118
+ # @option options [true, false] pretty whether to pretty print the response
108
119
  def print options={}
109
- out = options[:to] || options[:out] || $stdout
110
- err = options[:err] || $stderr
111
- err.puts(diagnostic_line) if error? || Chimps.verbose?
112
- Typewriter.new(self, options).print(out)
120
+ $stderr.puts(diagnostic_line) if error? || Chimps.verbose?
121
+ output = (options[:to] || $stdout)
122
+ if error?
123
+ parse!
124
+ output.puts self['errors'] if self['errors']
125
+ output.puts self['message'] if self['message']
126
+ else
127
+ case
128
+ when options[:yaml]
129
+ parse!
130
+ output.puts self.to_yaml
131
+ when options[:json] && options[:pretty]
132
+ parse!
133
+ if options[:pretty]
134
+ output.puts JSON.pretty_generate(self)
135
+ else
136
+ output.puts self.to_json
137
+ end
138
+ when headers[:content_type] =~ /json/i && options[:pretty]
139
+ parse!
140
+ output.puts JSON.pretty_generate(self)
141
+ when headers[:content_type] =~ /tab/i && options[:pretty]
142
+ Utils::Typewriter.new(self).print
143
+ else
144
+ output.puts body unless body.chomp.strip.size == 0
145
+ end
146
+ end
147
+ end
148
+
149
+ def print_headers options={}
150
+ output = (options[:output] || $stdout)
151
+ self.body.raw_headers.each_pair do |name, value|
152
+ output.puts "#{name}: #{value}"
153
+ end
113
154
  end
114
155
 
115
156
  protected
@@ -126,9 +167,11 @@ module Chimps
126
167
 
127
168
  # Raise a Chimps::ParseError, optionally including the response
128
169
  # body in the error message if Chimps is verbose.
129
- def parse_error!
130
- message = Chimps.verbose? ? "#{diagnostic_line}\n\n#{body}" : diagnostic_line
131
- raise ParseError.new(message)
170
+ def parse_error! error
171
+ if Chimps.verbose?
172
+ puts "\n\n#{body}"
173
+ end
174
+ nil
132
175
  end
133
176
 
134
177
  # Parse the body of this response using the YAML or JSON libraries
@@ -138,21 +181,18 @@ module Chimps
138
181
  def parse_response_body
139
182
  return {} if body.blank? || body == 'null'
140
183
  if content_type == :yaml
141
- require 'yaml'
142
184
  begin
143
- YAML.parse(body)
185
+ YAML.load(StringIO.new(body))
144
186
  rescue YAML::ParseError => e
145
- parse_error!
146
- rescue ArgumentError => e # WHY does YAML return an ArgumentError on malformed input...?
147
- @error = "Response was received but was malformed"
148
- parse_error!
187
+ parse_error! e
188
+ rescue ArgumentError => e
189
+ parse_error! e
149
190
  end
150
191
  else
151
- require 'json'
152
192
  begin
153
193
  JSON.parse(body)
154
194
  rescue JSON::ParserError => e
155
- parse_error!
195
+ parse_error! e
156
196
  end
157
197
  end
158
198
  end