chimps 0.2.2 → 0.3.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.
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