mechanize 1.0.1.beta.20110107104205 → 2.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mechanize might be problematic. Click here for more details.

Files changed (89) hide show
  1. data.tar.gz.sig +2 -0
  2. data/{lib/mechanize/chain/post_connect_hook.rb → .gemtest} +0 -0
  3. data/CHANGELOG.rdoc +51 -6
  4. data/EXAMPLES.rdoc +5 -3
  5. data/GUIDE.rdoc +72 -32
  6. data/LICENSE.rdoc +20 -340
  7. data/Manifest.txt +20 -27
  8. data/README.rdoc +12 -9
  9. data/Rakefile +5 -2
  10. data/examples/spider.rb +13 -2
  11. data/lib/mechanize.rb +545 -267
  12. data/lib/mechanize/content_type_error.rb +1 -1
  13. data/lib/mechanize/cookie.rb +72 -65
  14. data/lib/mechanize/cookie_jar.rb +197 -148
  15. data/lib/mechanize/element_matcher.rb +35 -0
  16. data/lib/mechanize/file.rb +3 -1
  17. data/lib/mechanize/file_connection.rb +17 -0
  18. data/lib/mechanize/file_request.rb +26 -0
  19. data/lib/mechanize/file_response.rb +61 -47
  20. data/lib/mechanize/form.rb +57 -58
  21. data/lib/mechanize/form/image_button.rb +2 -3
  22. data/lib/mechanize/form/multi_select_list.rb +71 -55
  23. data/lib/mechanize/form/select_list.rb +34 -62
  24. data/lib/mechanize/monkey_patch.rb +13 -11
  25. data/lib/mechanize/page.rb +277 -270
  26. data/lib/mechanize/page/image.rb +6 -2
  27. data/lib/mechanize/redirect_limit_reached_error.rb +1 -1
  28. data/lib/mechanize/redirect_not_get_or_head_error.rb +1 -1
  29. data/lib/mechanize/response_code_error.rb +3 -3
  30. data/lib/mechanize/unsupported_scheme_error.rb +1 -1
  31. data/lib/mechanize/uri_resolver.rb +82 -0
  32. data/lib/mechanize/util.rb +76 -60
  33. data/test/helper.rb +35 -5
  34. data/test/htdocs/dir with spaces/foo.html +1 -0
  35. data/test/htdocs/rails_3_encoding_hack_form_test.html +27 -0
  36. data/test/htdocs/tc_base_images.html +10 -0
  37. data/test/htdocs/tc_images.html +8 -0
  38. data/test/htdocs/test_click.html +11 -0
  39. data/test/servlets.rb +3 -2
  40. data/test/test_authenticate.rb +5 -5
  41. data/test/test_errors.rb +8 -8
  42. data/test/test_follow_meta.rb +4 -4
  43. data/test/test_form_as_hash.rb +4 -4
  44. data/test/test_forms.rb +3 -7
  45. data/test/test_hash_api.rb +2 -2
  46. data/test/test_headers.rb +1 -1
  47. data/test/test_images.rb +19 -0
  48. data/test/test_mech.rb +6 -6
  49. data/test/test_mechanize.rb +687 -0
  50. data/test/{test_cookie_class.rb → test_mechanize_cookie.rb} +52 -45
  51. data/test/test_mechanize_cookie_jar.rb +400 -0
  52. data/test/test_mechanize_file.rb +7 -1
  53. data/test/test_mechanize_file_request.rb +19 -0
  54. data/test/test_mechanize_file_response.rb +21 -0
  55. data/test/test_mechanize_form_image_button.rb +12 -0
  56. data/test/test_mechanize_page.rb +165 -0
  57. data/test/test_mechanize_uri_resolver.rb +29 -0
  58. data/test/{test_util.rb → test_mechanize_util.rb} +1 -1
  59. data/test/test_multi_select.rb +12 -0
  60. data/test/test_post_form.rb +7 -0
  61. data/test/test_redirect_verb_handling.rb +6 -6
  62. data/test/test_scheme.rb +0 -7
  63. data/test/test_verbs.rb +3 -3
  64. metadata +106 -72
  65. metadata.gz.sig +0 -0
  66. data/lib/mechanize/chain.rb +0 -36
  67. data/lib/mechanize/chain/auth_headers.rb +0 -78
  68. data/lib/mechanize/chain/body_decoding_handler.rb +0 -50
  69. data/lib/mechanize/chain/connection_resolver.rb +0 -28
  70. data/lib/mechanize/chain/custom_headers.rb +0 -21
  71. data/lib/mechanize/chain/handler.rb +0 -9
  72. data/lib/mechanize/chain/header_resolver.rb +0 -48
  73. data/lib/mechanize/chain/parameter_resolver.rb +0 -22
  74. data/lib/mechanize/chain/pre_connect_hook.rb +0 -20
  75. data/lib/mechanize/chain/request_resolver.rb +0 -31
  76. data/lib/mechanize/chain/response_body_parser.rb +0 -36
  77. data/lib/mechanize/chain/response_header_handler.rb +0 -34
  78. data/lib/mechanize/chain/response_reader.rb +0 -39
  79. data/lib/mechanize/chain/ssl_resolver.rb +0 -40
  80. data/lib/mechanize/chain/uri_resolver.rb +0 -75
  81. data/test/chain/test_argument_validator.rb +0 -14
  82. data/test/chain/test_auth_headers.rb +0 -25
  83. data/test/chain/test_custom_headers.rb +0 -18
  84. data/test/chain/test_header_resolver.rb +0 -27
  85. data/test/chain/test_parameter_resolver.rb +0 -35
  86. data/test/chain/test_request_resolver.rb +0 -29
  87. data/test/chain/test_response_reader.rb +0 -24
  88. data/test/test_cookie_jar.rb +0 -324
  89. data/test/test_page.rb +0 -124
@@ -0,0 +1,35 @@
1
+ module Mechanize::ElementMatcher
2
+
3
+ def elements_with singular, plural = "#{singular}s"
4
+ class_eval <<-CODE
5
+ def #{plural}_with criteria = {}
6
+ criteria = if String === criteria then
7
+ {:name => criteria}
8
+ else
9
+ criteria.map do |k, v|
10
+ k = :dom_id if k.to_sym == :id
11
+ [k, v]
12
+ end
13
+ end
14
+
15
+ f = #{plural}.find_all do |thing|
16
+ criteria.all? do |k,v|
17
+ v === thing.send(k)
18
+ end
19
+ end
20
+ yield f if block_given?
21
+ f
22
+ end
23
+
24
+ def #{singular}_with criteria = {}
25
+ f = #{plural}_with(criteria).first
26
+ yield f if block_given?
27
+ f
28
+ end
29
+
30
+ alias :#{singular} :#{singular}_with
31
+ CODE
32
+ end
33
+
34
+ end
35
+
@@ -29,7 +29,9 @@ class Mechanize
29
29
  alias :content :body
30
30
 
31
31
  def initialize(uri=nil, response=nil, body=nil, code=nil)
32
- @uri, @body, @code = uri, body, code
32
+ @uri = uri
33
+ @body = body
34
+ @code = code
33
35
  @response = Headers.new
34
36
 
35
37
  # Copy the headers in to a hash to prevent memory leaks
@@ -0,0 +1,17 @@
1
+ ##
2
+ # Wrapper to make a file URI work like an http URI
3
+
4
+ class Mechanize::FileConnection
5
+
6
+ @instance = nil
7
+
8
+ def self.new *a
9
+ @instance ||= super
10
+ end
11
+
12
+ def request uri, request
13
+ yield Mechanize::FileResponse.new Mechanize::Util.uri_unescape uri.path
14
+ end
15
+
16
+ end
17
+
@@ -0,0 +1,26 @@
1
+ ##
2
+ # A wrapper for a file URI that makes a request that works like a
3
+ # Net::HTTPRequest
4
+
5
+ class Mechanize::FileRequest
6
+
7
+ attr_accessor :uri
8
+
9
+ def initialize uri
10
+ @uri = uri
11
+ end
12
+
13
+ def add_field *a
14
+ end
15
+
16
+ alias []= add_field
17
+
18
+ def path
19
+ @uri.path
20
+ end
21
+
22
+ def each_header
23
+ end
24
+
25
+ end
26
+
@@ -1,60 +1,74 @@
1
- class Mechanize
2
- ###
3
- # Fake response for dealing with file:/// requests
4
- class FileResponse
5
- def initialize(file_path)
6
- @file_path = file_path
7
- end
1
+ ##
2
+ # Fake response for dealing with file:/// requests
3
+
4
+ class Mechanize::FileResponse
5
+ def initialize(file_path)
6
+ @file_path = file_path
7
+ end
8
+
9
+ def read_body
10
+ raise Mechanize::ResponseCodeError, self unless File.exist? @file_path
8
11
 
9
- def read_body
10
- if ::File.exists?(@file_path)
11
- if directory?
12
- yield dir_body
13
- else
14
- yield ::File.read(@file_path)
15
- end
16
- else
17
- yield ''
12
+ if directory?
13
+ yield dir_body
14
+ else
15
+ open @file_path, 'rb' do |io|
16
+ yield io.read
18
17
  end
19
18
  end
19
+ end
20
20
 
21
- def code
22
- ::File.exists?(@file_path) ? 200 : 400
23
- end
21
+ def code
22
+ File.exist?(@file_path) ? 200 : 404
23
+ end
24
24
 
25
- def content_length
26
- return dir_body.length if directory?
27
- ::File.exists?(@file_path) ? ::File.stat(@file_path).size : 0
28
- end
25
+ def content_length
26
+ return dir_body.length if directory?
27
+ File.exist?(@file_path) ? File.stat(@file_path).size : 0
28
+ end
29
29
 
30
- def each_header; end
30
+ def each_header; end
31
31
 
32
- def [](key)
33
- return nil unless key.downcase == 'content-type'
34
- return 'text/html' if directory?
35
- return 'text/html' if ['.html', '.xhtml'].any? { |extn|
36
- @file_path =~ /#{extn}$/
37
- }
38
- nil
39
- end
32
+ def [](key)
33
+ return nil unless key.downcase == 'content-type'
34
+ return 'text/html' if directory?
35
+ return 'text/html' if ['.html', '.xhtml'].any? { |extn|
36
+ @file_path =~ /#{extn}$/
37
+ }
38
+ nil
39
+ end
40
40
 
41
- def each
42
- end
41
+ def each
42
+ end
43
43
 
44
- def get_fields(key)
45
- []
46
- end
44
+ def get_fields(key)
45
+ []
46
+ end
47
47
 
48
- private
49
- def dir_body
50
- '<html><body>' +
51
- Dir[::File.join(@file_path, '*')].map { |f|
52
- "<a href=\"file://#{f}\">#{::File.basename(f)}</a>"
53
- }.join("\n") + '</body></html>'
54
- end
48
+ def http_version
49
+ '0'
50
+ end
55
51
 
56
- def directory?
57
- ::File.directory?(@file_path)
58
- end
52
+ def message
53
+ File.exist?(@file_path) ? 'OK' : 'Not Found'
54
+ end
55
+
56
+ private
57
+
58
+ def dir_body
59
+ body = %w[<html><body>]
60
+ body.concat Dir[File.join(@file_path, '*')].map { |f|
61
+ "<a href=\"file://#{f}\">#{File.basename(f)}</a>"
62
+ }
63
+ body << %w[</body></html>]
64
+
65
+ body = body.join "\n"
66
+ body.force_encoding Encoding::BINARY if body.respond_to? :force_encoding
67
+ body
68
+ end
69
+
70
+ def directory?
71
+ File.directory?(@file_path)
59
72
  end
60
73
  end
74
+
@@ -1,12 +1,13 @@
1
+ require 'mechanize/element_matcher'
1
2
  require 'mechanize/form/field'
2
- require 'mechanize/form/file_upload'
3
3
  require 'mechanize/form/button'
4
+ require 'mechanize/form/file_upload'
4
5
  require 'mechanize/form/image_button'
6
+ require 'mechanize/form/multi_select_list'
7
+ require 'mechanize/form/option'
5
8
  require 'mechanize/form/radio_button'
6
9
  require 'mechanize/form/check_box'
7
- require 'mechanize/form/multi_select_list'
8
10
  require 'mechanize/form/select_list'
9
- require 'mechanize/form/option'
10
11
 
11
12
  class Mechanize
12
13
  # =Synopsis
@@ -22,6 +23,9 @@ class Mechanize
22
23
  # form['name'] = 'Aaron'
23
24
  # puts form['name']
24
25
  class Form
26
+
27
+ extend Mechanize::ElementMatcher
28
+
25
29
  attr_accessor :method, :action, :name
26
30
 
27
31
  attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
@@ -47,13 +51,13 @@ class Mechanize
47
51
 
48
52
  # Returns whether or not the form contains a field with +field_name+
49
53
  def has_field?(field_name)
50
- ! fields.find { |f| f.name.eql? field_name }.nil?
54
+ fields.find { |f| f.name == field_name }
51
55
  end
52
56
 
53
57
  alias :has_key? :has_field?
54
58
 
55
59
  def has_value?(value)
56
- ! fields.find { |f| f.value.eql? value }.nil?
60
+ fields.find { |f| f.value == value }
57
61
  end
58
62
 
59
63
  def keys; fields.map { |f| f.name }; end
@@ -66,11 +70,11 @@ class Mechanize
66
70
  def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
67
71
  def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
68
72
 
69
- def submit_button?(button_name) !! submits.find{|f| f.name == button_name}; end
70
- def reset_button?(button_name) !! resets.find{|f| f.name == button_name}; end
71
- def text_field?(field_name) !! texts.find{|f| f.name == field_name}; end
72
- def hidden_field?(field_name) !! hiddens.find{|f| f.name == field_name}; end
73
- def textarea_field?(field_name) !!textareas.find{|f| f.name == field_name}; end
73
+ def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
74
+ def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
75
+ def text_field?(field_name) texts.find{|f| f.name == field_name}; end
76
+ def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
77
+ def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
74
78
 
75
79
  # This method is a shortcut to get form's DOM id.
76
80
  # Common usage:
@@ -105,8 +109,8 @@ class Mechanize
105
109
  value = nil
106
110
  index = 0
107
111
  [v].flatten.each do |val|
108
- index = val.to_i unless value.nil?
109
- value = val if value.nil?
112
+ index = val.to_i if value
113
+ value = val unless value
110
114
  end
111
115
  self.fields_with(:name => k.to_s).[](index).value = value
112
116
  end
@@ -128,20 +132,22 @@ class Mechanize
128
132
  # form['name'] = 'Aaron'
129
133
  def []=(field_name, value)
130
134
  f = field(field_name)
131
- if f.nil?
132
- add_field!(field_name, value)
133
- else
135
+ if f
134
136
  f.value = value
137
+ else
138
+ add_field!(field_name, value)
135
139
  end
136
140
  end
137
141
 
138
142
  # Treat form fields like accessors.
139
- def method_missing(id,*args)
140
- method = id.to_s.gsub(/=$/, '')
143
+ def method_missing(meth, *args)
144
+ method = meth.to_s.gsub(/=$/, '')
145
+
141
146
  if field(method)
142
147
  return field(method).value if args.empty?
143
148
  return field(method).value = args[0]
144
149
  end
150
+
145
151
  super
146
152
  end
147
153
 
@@ -206,7 +212,8 @@ class Mechanize
206
212
  qval = proc_query(f)
207
213
  query.push(*qval)
208
214
  elsif checked.size > 1
209
- raise "multiple radiobuttons are checked in the same group!"
215
+ raise Mechanize::Error,
216
+ "multiple radiobuttons are checked in the same group!"
210
217
  end
211
218
  end
212
219
 
@@ -228,14 +235,22 @@ class Mechanize
228
235
  # multi-part post,
229
236
  def request_data
230
237
  query_params = build_query()
238
+
231
239
  case @enctype.downcase
232
240
  when /^multipart\/form-data/
233
241
  boundary = rand_string(20)
234
242
  @enctype = "multipart/form-data; boundary=#{boundary}"
235
- params = []
236
- query_params.each { |k,v| params << param_to_multipart(k, v) unless k.nil? }
237
- @file_uploads.each { |f| params << file_to_multipart(f) }
238
- params.collect { |p| "--#{boundary}\r\n#{p.respond_to?(:force_encoding) ? p.force_encoding('ASCII-8BIT') : p}" }.join('') +
243
+
244
+ params = query_params.map do |k,v|
245
+ param_to_multipart(k, v) if k
246
+ end.compact
247
+
248
+ params.concat @file_uploads.map { |f| file_to_multipart(f) }
249
+
250
+ params.map do |part|
251
+ part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
252
+ "--#{boundary}\r\n#{part}"
253
+ end.join('') +
239
254
  "--#{boundary}--\r\n"
240
255
  else
241
256
  Mechanize::Util.build_query_string(query_params)
@@ -263,6 +278,8 @@ class Mechanize
263
278
  # field.value = 'hello!'
264
279
  # end
265
280
 
281
+ elements_with :field
282
+
266
283
  ##
267
284
  # :method: button_with(criteria)
268
285
  #
@@ -279,6 +296,8 @@ class Mechanize
279
296
  # button.value = 'hello!'
280
297
  # end
281
298
 
299
+ elements_with :button
300
+
282
301
  ##
283
302
  # :method: file_upload_with(criteria)
284
303
  #
@@ -295,6 +314,8 @@ class Mechanize
295
314
  # field.value = 'foo!'
296
315
  # end
297
316
 
317
+ elements_with :file_upload
318
+
298
319
  ##
299
320
  # :method: radiobutton_with(criteria)
300
321
  #
@@ -311,6 +332,8 @@ class Mechanize
311
332
  # field.check
312
333
  # end
313
334
 
335
+ elements_with :radiobutton
336
+
314
337
  ##
315
338
  # :method: checkbox_with(criteria)
316
339
  #
@@ -327,36 +350,10 @@ class Mechanize
327
350
  # field.check
328
351
  # end
329
352
 
330
- # Woo! meta programming time
331
- { :field => :fields,
332
- :button => :buttons,
333
- :file_upload => :file_uploads,
334
- :radiobutton => :radiobuttons,
335
- :checkbox => :checkboxes,
336
- }.each do |singular,plural|
337
- eval(<<-eomethod)
338
- def #{plural}_with criteria = {}
339
- criteria = {:name => criteria} if String === criteria
340
- f = #{plural}.find_all do |thing|
341
- criteria.all? do |k,v|
342
- k = :dom_id if(k.to_s == "id")
343
- v === thing.send(k)
344
- end
345
- end
346
- yield f if block_given?
347
- f
348
- end
349
-
350
- def #{singular}_with criteria = {}
351
- f = #{plural}_with(criteria).first
352
- yield f if block_given?
353
- f
354
- end
355
- alias :#{singular} :#{singular}_with
356
- eomethod
357
- end
353
+ elements_with :checkbox, :checkboxes
358
354
 
359
355
  private
356
+
360
357
  def parse
361
358
  @fields = []
362
359
  @buttons = []
@@ -368,7 +365,7 @@ class Mechanize
368
365
  form_node.search('input').each do |node|
369
366
  type = (node['type'] || 'text').downcase
370
367
  name = node['name']
371
- next if name.nil? && !(type == 'submit' || type =='button' || type == 'image')
368
+ next if name.nil? && !%w[submit button image].include?(type)
372
369
  case type
373
370
  when 'radio'
374
371
  @radiobuttons << RadioButton.new(node, self)
@@ -397,13 +394,13 @@ class Mechanize
397
394
 
398
395
  # Find all textarea tags
399
396
  form_node.search('textarea').each do |node|
400
- next if node['name'].nil?
397
+ next unless node['name']
401
398
  @fields << Field.new(node, node.inner_text)
402
399
  end
403
400
 
404
401
  # Find all select tags
405
402
  form_node.search('select').each do |node|
406
- next if node['name'].nil?
403
+ next unless node['name']
407
404
  if node.has_attribute? 'multiple'
408
405
  @fields << MultiSelectList.new(node)
409
406
  else
@@ -444,13 +441,14 @@ class Mechanize
444
441
  "filename=\"#{mime_value_quote(file_name)}\"\r\n" +
445
442
  "Content-Transfer-Encoding: binary\r\n"
446
443
 
447
- if file.file_data.nil? and ! file.file_name.nil?
448
- file.file_data = ::File.open(file.file_name, "rb") { |f| f.read }
449
- file.mime_type = WEBrick::HTTPUtils.mime_type(file.file_name,
450
- WEBrick::HTTPUtils::DefaultMimeTypes)
444
+ if file.file_data.nil? and file.file_name
445
+ file.file_data = open(file.file_name, "rb") { |f| f.read }
446
+ file.mime_type =
447
+ WEBrick::HTTPUtils.mime_type(file.file_name,
448
+ WEBrick::HTTPUtils::DefaultMimeTypes)
451
449
  end
452
450
 
453
- if file.mime_type != nil
451
+ if file.mime_type
454
452
  body << "Content-Type: #{file.mime_type}\r\n"
455
453
  end
456
454
 
@@ -463,5 +461,6 @@ class Mechanize
463
461
 
464
462
  body
465
463
  end
464
+
466
465
  end
467
466
  end