patron 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/Rakefile CHANGED
@@ -26,6 +26,7 @@ require 'rake/clean'
26
26
  require 'rake/rdoctask'
27
27
  require 'spec/rake/spectask'
28
28
  require 'jeweler'
29
+ require 'yard'
29
30
 
30
31
  require 'rbconfig'
31
32
  include Config
@@ -101,6 +102,11 @@ Rake::RDocTask.new do |rdoc|
101
102
  rdoc.rdoc_files.include('lib/**/*.rb')
102
103
  end
103
104
 
105
+ YARD::Rake::YardocTask.new do |t|
106
+ t.files = ['lib/**/*.rb']
107
+ t.options = ['--readme', 'README.txt']
108
+ end
109
+
104
110
  desc "Run specs"
105
111
  Spec::Rake::SpecTask.new(:spec) do |t|
106
112
  t.spec_opts = ['--options', "spec/spec.opts"]
@@ -137,7 +143,7 @@ begin
137
143
 
138
144
  host = "#{config['username']}@rubyforge.org"
139
145
  remote_dir = "/var/www/gforge-projects/patron/"
140
- local_dir = 'rdoc'
146
+ local_dir = 'doc'
141
147
 
142
148
  Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
143
149
  end
@@ -40,6 +40,8 @@ static VALUE eTooManyRedirects = Qnil;
40
40
  struct curl_state {
41
41
  CURL* handle;
42
42
  char* upload_buf;
43
+ FILE* download_file;
44
+ FILE* upload_file;
43
45
  char error_buf[CURL_ERROR_SIZE];
44
46
  struct curl_slist* headers;
45
47
  };
@@ -156,43 +158,86 @@ static VALUE each_http_header(VALUE header, VALUE self) {
156
158
  return Qnil;
157
159
  }
158
160
 
161
+ static void set_chunked_encoding(struct curl_state *state) {
162
+ state->headers = curl_slist_append(state->headers, "Transfer-Encoding: chunked");
163
+ }
164
+
165
+ static FILE* open_file(VALUE filename, char* perms) {
166
+ FILE* handle = fopen(StringValuePtr(filename), perms);
167
+ if (!handle) {
168
+ rb_raise(rb_eArgError, "Unable to open specified file.");
169
+ }
170
+
171
+ return handle;
172
+ }
173
+
159
174
  // Set the options on the Curl handle from a Request object. Takes each field
160
175
  // in the Request object and uses it to set the appropriate option on the Curl
161
176
  // handle.
162
- void set_options_from_request(VALUE self, VALUE request) {
177
+ static void set_options_from_request(VALUE self, VALUE request) {
163
178
  struct curl_state *state;
164
179
  Data_Get_Struct(self, struct curl_state, state);
165
180
 
166
181
  CURL* curl = state->handle;
167
182
 
183
+ VALUE headers = rb_iv_get(request, "@headers");
184
+ if (!NIL_P(headers)) {
185
+ if (rb_type(headers) != T_HASH) {
186
+ rb_raise(rb_eArgError, "Headers must be passed in a hash.");
187
+ }
188
+
189
+ rb_iterate(rb_each, headers, each_http_header, self);
190
+ }
191
+
168
192
  ID action = SYM2ID(rb_iv_get(request, "@action"));
169
193
  if (action == rb_intern("get")) {
170
194
  curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
171
- } else if (action == rb_intern("post")) {
172
- VALUE data = rb_iv_get(request, "@upload_data");
173
-
174
- state->upload_buf = StringValuePtr(data);
175
- int len = RSTRING_LEN(data);
176
195
 
177
- curl_easy_setopt(curl, CURLOPT_POST, 1);
178
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, state->upload_buf);
179
- curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
180
- } else if (action == rb_intern("put")) {
196
+ VALUE download_file = rb_iv_get(request, "@file_name");
197
+ if (!NIL_P(download_file)) {
198
+ state->download_file = open_file(download_file, "w");
199
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, state->download_file);
200
+ } else {
201
+ state->download_file = NULL;
202
+ }
203
+ } else if (action == rb_intern("post") || action == rb_intern("put")) {
181
204
  VALUE data = rb_iv_get(request, "@upload_data");
182
-
183
- state->upload_buf = StringValuePtr(data);
184
- int len = RSTRING_LEN(data);
185
-
186
- curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
187
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, &session_read_handler);
188
- curl_easy_setopt(curl, CURLOPT_READDATA, &state->upload_buf);
189
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, len);
190
- } else if (action == rb_intern("delete")) {
191
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
205
+ VALUE filename = rb_iv_get(request, "@file_name");
206
+
207
+ if (!NIL_P(data)) {
208
+ state->upload_buf = StringValuePtr(data);
209
+ int len = RSTRING_LEN(data);
210
+
211
+ if (action == rb_intern("post")) {
212
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
213
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, state->upload_buf);
214
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
215
+ } else {
216
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
217
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, &session_read_handler);
218
+ curl_easy_setopt(curl, CURLOPT_READDATA, &state->upload_buf);
219
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, len);
220
+ }
221
+ } else if (!NIL_P(filename)) {
222
+ set_chunked_encoding(state);
223
+
224
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
225
+
226
+ if (action == rb_intern("post")) {
227
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
228
+ }
229
+
230
+ state->upload_file = open_file(filename, "r");
231
+ curl_easy_setopt(curl, CURLOPT_READDATA, state->upload_file);
232
+ }
192
233
  } else if (action == rb_intern("head")) {
193
234
  curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
235
+ } else {
236
+ VALUE action_name = rb_funcall(request, rb_intern("action_name"), 0);
237
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, StringValuePtr(action_name));
194
238
  }
195
239
 
240
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
196
241
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
197
242
 
198
243
  VALUE url = rb_iv_get(request, "@url");
@@ -218,17 +263,11 @@ void set_options_from_request(VALUE self, VALUE request) {
218
263
  curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
219
264
  }
220
265
 
221
- VALUE headers = rb_iv_get(request, "@headers");
222
- if (!NIL_P(headers)) {
223
- if (rb_type(headers) != T_HASH) {
224
- rb_raise(rb_eArgError, "Headers must be passed in a hash.");
225
- }
226
-
227
- rb_iterate(rb_each, headers, each_http_header, self);
266
+ VALUE proxy = rb_iv_get(request, "@proxy");
267
+ if (!NIL_P(proxy)) {
268
+ curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(proxy));
228
269
  }
229
270
 
230
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
231
-
232
271
  VALUE credentials = rb_funcall(request, rb_intern("credentials"), 0);
233
272
  if (!NIL_P(credentials)) {
234
273
  curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
@@ -237,7 +276,7 @@ void set_options_from_request(VALUE self, VALUE request) {
237
276
  }
238
277
 
239
278
  // Use the info in a Curl handle to create a new Response object.
240
- VALUE create_response(CURL* curl) {
279
+ static VALUE create_response(CURL* curl) {
241
280
  VALUE response = rb_class_new_instance(0, 0,
242
281
  rb_const_get(mPatron, rb_intern("Response")));
243
282
 
@@ -257,7 +296,7 @@ VALUE create_response(CURL* curl) {
257
296
  }
258
297
 
259
298
  // Raise an exception based on the Curl error code.
260
- VALUE select_error(CURLcode code) {
299
+ static VALUE select_error(CURLcode code) {
261
300
  VALUE error = Qnil;
262
301
  switch (code) {
263
302
  case CURLE_UNSUPPORTED_PROTOCOL: error = eUnsupportedProtocol; break;
@@ -287,14 +326,19 @@ static VALUE perform_request(VALUE self) {
287
326
  curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
288
327
 
289
328
  // body
290
- VALUE body_buffer = rb_str_buf_new(32768);
291
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
292
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
329
+ VALUE body_buffer = Qnil;
330
+ if (!state->download_file) {
331
+ body_buffer = rb_str_buf_new(32768);
332
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
333
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
334
+ }
293
335
 
294
336
  CURLcode ret = curl_easy_perform(curl);
295
337
  if (CURLE_OK == ret) {
296
338
  VALUE response = create_response(curl);
297
- rb_iv_set(response, "@body", body_buffer);
339
+ if (!NIL_P(body_buffer)) {
340
+ rb_iv_set(response, "@body", body_buffer);
341
+ }
298
342
  rb_funcall(response, rb_intern("parse_headers"), 1, header_buffer);
299
343
  return response;
300
344
  } else {
@@ -315,6 +359,16 @@ static VALUE cleanup(VALUE self) {
315
359
  state->headers = NULL;
316
360
  }
317
361
 
362
+ if (state->download_file) {
363
+ fclose(state->download_file);
364
+ state->download_file = NULL;
365
+ }
366
+
367
+ if (state->upload_file) {
368
+ fclose(state->upload_file);
369
+ state->upload_file = NULL;
370
+ }
371
+
318
372
  state->upload_buf = NULL;
319
373
 
320
374
  return Qnil;
@@ -330,7 +384,7 @@ VALUE session_handle_request(VALUE self, VALUE request) {
330
384
  //
331
385
 
332
386
  void Init_session_ext() {
333
- curl_global_init(CURL_GLOBAL_NOTHING);
387
+ curl_global_init(CURL_GLOBAL_ALL);
334
388
  rb_require("patron/error");
335
389
 
336
390
  mPatron = rb_define_module("Patron");
@@ -34,9 +34,10 @@ module Patron #:nodoc:
34
34
  # Returns the version number of the Patron library as a string
35
35
  def self.version
36
36
  cwd = Pathname(__FILE__).dirname
37
- yaml = YAML.load_file(cwd.expand_path / '../VERSION.yml')
37
+ yaml = YAML.load_file(cwd.expand_path + '../VERSION.yml')
38
38
  major = (yaml['major'] || yaml[:major]).to_i
39
39
  minor = (yaml['minor'] || yaml[:minor]).to_i
40
- "#{major}.#{minor}"
40
+ patch = (yaml['patch'] || yaml[:patch]).to_i
41
+ "#{major}.#{minor}.#{patch}"
41
42
  end
42
43
  end
@@ -30,6 +30,8 @@ module Patron
30
30
  # used in every request.
31
31
  class Request
32
32
 
33
+ VALID_ACTIONS = [:get, :put, :post, :delete, :head, :copy]
34
+
33
35
  def initialize
34
36
  @action = :get
35
37
  @headers = {}
@@ -38,12 +40,12 @@ module Patron
38
40
  @max_redirects = -1
39
41
  end
40
42
 
41
- attr_accessor :url, :username, :password, :upload_data
43
+ attr_accessor :url, :username, :password, :upload_data, :file_name, :proxy
42
44
  attr_reader :action, :timeout, :connect_timeout, :max_redirects, :headers
43
45
 
44
46
  def action=(new_action)
45
- if ![:get, :put, :post, :delete, :head].include?(new_action)
46
- raise ArgumentError, "Action must be one of :get, :put, :post, :delete or :head"
47
+ if !VALID_ACTIONS.include?(new_action)
48
+ raise ArgumentError, "Action must be one of #{VALID_ACTIONS.join(', ')}"
47
49
  end
48
50
 
49
51
  @action = new_action
@@ -81,6 +83,10 @@ module Patron
81
83
  @headers = new_headers
82
84
  end
83
85
 
86
+ def action_name
87
+ @action.to_s.upcase
88
+ end
89
+
84
90
  def credentials
85
91
  return nil if username.nil? || password.nil?
86
92
  "#{username}:#{password}"
@@ -48,6 +48,7 @@ module Patron
48
48
  @status_line = header.strip
49
49
  else
50
50
  parts = header.split(':', 2)
51
+ parts[1].strip! unless parts[1].nil?
51
52
  @headers[parts[0]] = parts[1]
52
53
  end
53
54
  end
@@ -50,12 +50,15 @@ module Patron
50
50
  # Username and password for http authentication
51
51
  attr_accessor :username, :password
52
52
 
53
+ # HTTP proxy URL
54
+ attr_accessor :proxy
55
+
53
56
  # Standard set of headers that are used in all requests.
54
57
  attr_reader :headers
55
58
 
56
59
  private :ext_initialize, :handle_request
57
60
 
58
- # Create an instance of the Session class.
61
+ # Create a new Session object.
59
62
  def initialize
60
63
  ext_initialize
61
64
  @headers = {}
@@ -64,36 +67,73 @@ module Patron
64
67
  @max_redirects = -1
65
68
  end
66
69
 
70
+ ###################################################################
71
+ ### Standard HTTP methods
72
+ ###
73
+
67
74
  # Retrieve the contents of the specified +url+ optionally sending the
68
75
  # specified headers. If the +base_url+ varaible is set then it is prepended
69
76
  # to the +url+ parameter. Any custom headers are merged with the contents
70
77
  # of the +headers+ instance variable. The results are returned in a
71
78
  # Response object.
72
79
  def get(url, headers = {})
73
- do_request(:get, url, headers)
80
+ request(:get, url, headers)
81
+ end
82
+
83
+ # Retrieve the contents of the specified +url+ as with #get, but the
84
+ # content at the URL is downloaded directly into the specified file.
85
+ def get_file(url, filename, headers = {})
86
+ request(:get, url, headers, :file => filename)
74
87
  end
75
88
 
76
89
  # As #get but sends an HTTP HEAD request.
77
90
  def head(url, headers = {})
78
- do_request(:head, url, headers)
91
+ request(:head, url, headers)
79
92
  end
80
93
 
94
+ # As #get but sends an HTTP DELETE request.
81
95
  def delete(url, headers = {})
82
- do_request(:delete, url, headers)
96
+ request(:delete, url, headers)
83
97
  end
84
98
 
99
+ # Uploads the passed +data+ to the specified +url+ using HTTP PUT. +data+
100
+ # must be a string.
85
101
  def put(url, data, headers = {})
86
- do_request(:put, url, headers, data)
102
+ request(:put, url, headers, :data => data)
103
+ end
104
+
105
+ # Uploads the contents of a file to the specified +url+ using HTTP PUT.
106
+ def put_file(url, filename, headers = {})
107
+ request(:put, url, headers, :file => filename)
87
108
  end
88
109
 
110
+ # Uploads the passed +data+ to the specified +url+ using HTTP POST. +data+
111
+ # must be a string.
89
112
  def post(url, data, headers = {})
90
- do_request(:post, url, headers, data)
113
+ request(:post, url, headers, :data => data)
114
+ end
115
+
116
+ # Uploads the contents of a file to the specified +url+ using HTTP POST.
117
+ def post_file(url, filename, headers = {})
118
+ request(:post, url, headers, :file => filename)
119
+ end
120
+
121
+ ###################################################################
122
+ ### WebDAV methods
123
+ ###
124
+
125
+ # Sends a WebDAV COPY request to the specified +url+.
126
+ def copy(url, dest, headers = {})
127
+ headers['Destination'] = dest
128
+ request(:copy, url, headers)
91
129
  end
92
130
 
93
- private
131
+ ###################################################################
132
+ ### Basic API methods
133
+ ###
94
134
 
95
- # Creates a new Request object from the parameters and instance variables.
96
- def do_request(action, url, headers, data = nil)
135
+ # Send an HTTP request to the specified +url+.
136
+ def request(action, url, headers, options = {})
97
137
  req = Request.new
98
138
  req.action = action
99
139
  req.timeout = self.timeout
@@ -102,7 +142,9 @@ module Patron
102
142
  req.headers = self.headers.merge(headers)
103
143
  req.username = self.username
104
144
  req.password = self.password
105
- req.upload_data = data
145
+ req.upload_data = options[:data]
146
+ req.file_name = options[:file]
147
+ req.proxy = proxy
106
148
 
107
149
  req.url = self.base_url.to_s + url.to_s
108
150
  raise ArgumentError, "Empty URL" if req.url.empty?
@@ -33,7 +33,7 @@ describe Patron::Request do
33
33
  describe :action do
34
34
 
35
35
  it "should accept :get, :put, :post, :delete and :head" do
36
- [:get, :put, :post, :delete, :head].each do |action|
36
+ [:get, :put, :post, :delete, :head, :copy].each do |action|
37
37
  lambda {@request.action = action}.should_not raise_error
38
38
  end
39
39
  end
@@ -51,7 +51,7 @@ describe Patron::Request do
51
51
  end
52
52
 
53
53
  it "should raise an exception when assigned 0" do
54
- lambda {@request.timeout = -1}.should raise_error(ArgumentError)
54
+ lambda {@request.timeout = 0}.should raise_error(ArgumentError)
55
55
  end
56
56
 
57
57
  end
@@ -0,0 +1,40 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Copyright (c) 2009 Phillip Toland <phil.toland@gmail.com>
4
+ ##
5
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ ## of this software and associated documentation files (the "Software"), to deal
7
+ ## in the Software without restriction, including without limitation the rights
8
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ ## copies of the Software, and to permit persons to whom the Software is
10
+ ## furnished to do so, subject to the following conditions:
11
+ ##
12
+ ## The above copyright notice and this permission notice shall be included in
13
+ ## all copies or substantial portions of the Software.
14
+ ##
15
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ ## THE SOFTWARE.
22
+ ##
23
+ ## -------------------------------------------------------------------
24
+ require File.dirname(__FILE__) + '/spec_helper.rb'
25
+ require 'webrick'
26
+ require 'base64'
27
+ require 'fileutils'
28
+
29
+ describe Patron::Response do
30
+ before(:each) do
31
+ @session = Patron::Session.new
32
+ @session.base_url = "http://localhost:9001"
33
+ end
34
+
35
+ it "should strip extra spaces from header values" do
36
+ response = @session.get("/test")
37
+ # All digits, no spaces
38
+ response.headers['Content-Length'].should match(/^\d+$/)
39
+ end
40
+ end
@@ -24,6 +24,7 @@
24
24
  require File.dirname(__FILE__) + '/spec_helper.rb'
25
25
  require 'webrick'
26
26
  require 'base64'
27
+ require 'fileutils'
27
28
 
28
29
  describe Patron::Session do
29
30
 
@@ -39,6 +40,10 @@ describe Patron::Session do
39
40
  unescaped.should == string
40
41
  end
41
42
 
43
+ it "should raise an error when passed an invalid action" do
44
+ lambda { @session.request(:bogus, "/test", {}) }.should raise_error(ArgumentError)
45
+ end
46
+
42
47
  it "should raise an error when no URL is provided" do
43
48
  @session.base_url = nil
44
49
  lambda {@session.get(nil)}.should raise_error(ArgumentError)
@@ -50,6 +55,15 @@ describe Patron::Session do
50
55
  body.request_method.should == "GET"
51
56
  end
52
57
 
58
+ it "should download content with :get and a file path" do
59
+ tmpfile = "/tmp/patron_test.yaml"
60
+ response = @session.get_file "/test", tmpfile
61
+ response.body.should be_nil
62
+ body = YAML::load_file(tmpfile)
63
+ body.request_method.should == "GET"
64
+ FileUtils.rm tmpfile
65
+ end
66
+
53
67
  it "should include custom headers in a request" do
54
68
  response = @session.get("/test", {"User-Agent" => "PatronTest"})
55
69
  body = YAML::load(response.body)
@@ -103,6 +117,18 @@ describe Patron::Session do
103
117
  body.request_method.should == "DELETE"
104
118
  end
105
119
 
120
+ it "should send a COPY request with :copy" do
121
+ response = @session.copy("/test", "/test2")
122
+ body = YAML::load(response.body)
123
+ body.request_method.should == "COPY"
124
+ end
125
+
126
+ it "should include a Destination header in COPY requests" do
127
+ response = @session.copy("/test", "/test2")
128
+ body = YAML::load(response.body)
129
+ body.header['destination'].first.should == "/test2"
130
+ end
131
+
106
132
  it "should upload data with :put" do
107
133
  data = "upload data"
108
134
  response = @session.put("/test", data)
@@ -111,6 +137,18 @@ describe Patron::Session do
111
137
  body.header['content-length'].should == [data.size.to_s]
112
138
  end
113
139
 
140
+ it "should upload a file with :put" do
141
+ response = @session.put_file("/test", "VERSION.yml")
142
+ body = YAML::load(response.body)
143
+ body.request_method.should == "PUT"
144
+ end
145
+
146
+ it "should use chunked encoding when uploading a file with :put" do
147
+ response = @session.put_file("/test", "VERSION.yml")
148
+ body = YAML::load(response.body)
149
+ body.header['transfer-encoding'].first.should == "chunked"
150
+ end
151
+
114
152
  it "should upload data with :post" do
115
153
  data = "upload data"
116
154
  response = @session.post("/test", data)
@@ -119,6 +157,18 @@ describe Patron::Session do
119
157
  body.header['content-length'].should == [data.size.to_s]
120
158
  end
121
159
 
160
+ it "should upload a file with :post" do
161
+ response = @session.post_file("/test", "VERSION.yml")
162
+ body = YAML::load(response.body)
163
+ body.request_method.should == "POST"
164
+ end
165
+
166
+ it "should use chunked encoding when uploading a file with :post" do
167
+ response = @session.post_file("/test", "VERSION.yml")
168
+ body = YAML::load(response.body)
169
+ body.header['transfer-encoding'].first.should == "chunked"
170
+ end
171
+
122
172
  it "should pass credentials as http basic auth" do
123
173
  @session.username = "foo"
124
174
  @session.password = "bar"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phillip Toland
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-03 00:00:00 -05:00
12
+ date: 2009-07-20 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -34,6 +34,7 @@ files:
34
34
  - lib/patron/session.rb
35
35
  - spec/patron_spec.rb
36
36
  - spec/request_spec.rb
37
+ - spec/response_spec.rb
37
38
  - spec/session_spec.rb
38
39
  - spec/spec.opts
39
40
  - spec/spec_helper.rb
@@ -77,5 +78,6 @@ summary: Patron HTTP client
77
78
  test_files:
78
79
  - spec/patron_spec.rb
79
80
  - spec/request_spec.rb
81
+ - spec/response_spec.rb
80
82
  - spec/session_spec.rb
81
83
  - spec/spec_helper.rb