patron 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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