aai10-mechanize 2.0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. data/.autotest +6 -0
  2. data/.gitignore +9 -0
  3. data/CHANGELOG.rdoc +652 -0
  4. data/EXAMPLES.rdoc +187 -0
  5. data/FAQ.rdoc +11 -0
  6. data/GUIDE.rdoc +163 -0
  7. data/LICENSE.rdoc +20 -0
  8. data/Manifest.txt +172 -0
  9. data/README.rdoc +63 -0
  10. data/Rakefile +36 -0
  11. data/aai10-mechanize.gemspec +20 -0
  12. data/examples/flickr_upload.rb +22 -0
  13. data/examples/mech-dump.rb +5 -0
  14. data/examples/proxy_req.rb +7 -0
  15. data/examples/rubyforge.rb +20 -0
  16. data/examples/spider.rb +21 -0
  17. data/lib/mechanize.rb +664 -0
  18. data/lib/mechanize/content_type_error.rb +14 -0
  19. data/lib/mechanize/cookie.rb +116 -0
  20. data/lib/mechanize/cookie_jar.rb +202 -0
  21. data/lib/mechanize/element_matcher.rb +35 -0
  22. data/lib/mechanize/file.rb +80 -0
  23. data/lib/mechanize/file_connection.rb +17 -0
  24. data/lib/mechanize/file_request.rb +26 -0
  25. data/lib/mechanize/file_response.rb +74 -0
  26. data/lib/mechanize/file_saver.rb +37 -0
  27. data/lib/mechanize/form.rb +478 -0
  28. data/lib/mechanize/form/button.rb +9 -0
  29. data/lib/mechanize/form/check_box.rb +11 -0
  30. data/lib/mechanize/form/field.rb +44 -0
  31. data/lib/mechanize/form/file_upload.rb +23 -0
  32. data/lib/mechanize/form/image_button.rb +20 -0
  33. data/lib/mechanize/form/multi_select_list.rb +83 -0
  34. data/lib/mechanize/form/option.rb +49 -0
  35. data/lib/mechanize/form/radio_button.rb +48 -0
  36. data/lib/mechanize/form/select_list.rb +40 -0
  37. data/lib/mechanize/headers.rb +25 -0
  38. data/lib/mechanize/history.rb +83 -0
  39. data/lib/mechanize/http.rb +3 -0
  40. data/lib/mechanize/http/agent.rb +738 -0
  41. data/lib/mechanize/inspect.rb +88 -0
  42. data/lib/mechanize/monkey_patch.rb +37 -0
  43. data/lib/mechanize/page.rb +408 -0
  44. data/lib/mechanize/page/base.rb +8 -0
  45. data/lib/mechanize/page/frame.rb +27 -0
  46. data/lib/mechanize/page/image.rb +30 -0
  47. data/lib/mechanize/page/label.rb +20 -0
  48. data/lib/mechanize/page/link.rb +82 -0
  49. data/lib/mechanize/page/meta_refresh.rb +56 -0
  50. data/lib/mechanize/pluggable_parsers.rb +101 -0
  51. data/lib/mechanize/redirect_limit_reached_error.rb +16 -0
  52. data/lib/mechanize/redirect_not_get_or_head_error.rb +19 -0
  53. data/lib/mechanize/response_code_error.rb +22 -0
  54. data/lib/mechanize/response_read_error.rb +27 -0
  55. data/lib/mechanize/robots_disallowed_error.rb +29 -0
  56. data/lib/mechanize/unsupported_scheme_error.rb +8 -0
  57. data/lib/mechanize/util.rb +113 -0
  58. data/test/data/htpasswd +1 -0
  59. data/test/data/server.crt +16 -0
  60. data/test/data/server.csr +12 -0
  61. data/test/data/server.key +15 -0
  62. data/test/data/server.pem +15 -0
  63. data/test/helper.rb +175 -0
  64. data/test/htdocs/alt_text.html +10 -0
  65. data/test/htdocs/bad_form_test.html +9 -0
  66. data/test/htdocs/button.jpg +0 -0
  67. data/test/htdocs/canonical_uri.html +9 -0
  68. data/test/htdocs/dir with spaces/foo.html +1 -0
  69. data/test/htdocs/empty_form.html +6 -0
  70. data/test/htdocs/file_upload.html +26 -0
  71. data/test/htdocs/find_link.html +41 -0
  72. data/test/htdocs/form_multi_select.html +16 -0
  73. data/test/htdocs/form_multival.html +37 -0
  74. data/test/htdocs/form_no_action.html +18 -0
  75. data/test/htdocs/form_no_input_name.html +16 -0
  76. data/test/htdocs/form_select.html +16 -0
  77. data/test/htdocs/form_select_all.html +16 -0
  78. data/test/htdocs/form_select_none.html +17 -0
  79. data/test/htdocs/form_select_noopts.html +10 -0
  80. data/test/htdocs/form_set_fields.html +14 -0
  81. data/test/htdocs/form_test.html +188 -0
  82. data/test/htdocs/frame_referer_test.html +10 -0
  83. data/test/htdocs/frame_test.html +30 -0
  84. data/test/htdocs/google.html +13 -0
  85. data/test/htdocs/iframe_test.html +16 -0
  86. data/test/htdocs/index.html +6 -0
  87. data/test/htdocs/link with space.html +5 -0
  88. data/test/htdocs/meta_cookie.html +11 -0
  89. data/test/htdocs/no_title_test.html +6 -0
  90. data/test/htdocs/nofollow.html +9 -0
  91. data/test/htdocs/noindex.html +9 -0
  92. data/test/htdocs/norobots.html +8 -0
  93. data/test/htdocs/rails_3_encoding_hack_form_test.html +27 -0
  94. data/test/htdocs/rel_nofollow.html +8 -0
  95. data/test/htdocs/relative/tc_relative_links.html +21 -0
  96. data/test/htdocs/robots.html +8 -0
  97. data/test/htdocs/robots.txt +2 -0
  98. data/test/htdocs/tc_bad_charset.html +9 -0
  99. data/test/htdocs/tc_bad_links.html +5 -0
  100. data/test/htdocs/tc_base_images.html +10 -0
  101. data/test/htdocs/tc_base_link.html +8 -0
  102. data/test/htdocs/tc_blank_form.html +11 -0
  103. data/test/htdocs/tc_charset.html +6 -0
  104. data/test/htdocs/tc_checkboxes.html +19 -0
  105. data/test/htdocs/tc_encoded_links.html +5 -0
  106. data/test/htdocs/tc_field_precedence.html +11 -0
  107. data/test/htdocs/tc_follow_meta.html +8 -0
  108. data/test/htdocs/tc_form_action.html +48 -0
  109. data/test/htdocs/tc_images.html +8 -0
  110. data/test/htdocs/tc_links.html +18 -0
  111. data/test/htdocs/tc_meta_in_body.html +9 -0
  112. data/test/htdocs/tc_no_attributes.html +16 -0
  113. data/test/htdocs/tc_pretty_print.html +17 -0
  114. data/test/htdocs/tc_radiobuttons.html +17 -0
  115. data/test/htdocs/tc_referer.html +16 -0
  116. data/test/htdocs/tc_relative_links.html +19 -0
  117. data/test/htdocs/tc_textarea.html +23 -0
  118. data/test/htdocs/test_bad_encoding.html +52 -0
  119. data/test/htdocs/test_click.html +11 -0
  120. data/test/htdocs/unusual______.html +5 -0
  121. data/test/servlets.rb +402 -0
  122. data/test/ssl_server.rb +48 -0
  123. data/test/test_cookies.rb +129 -0
  124. data/test/test_form_action.rb +52 -0
  125. data/test/test_form_as_hash.rb +59 -0
  126. data/test/test_form_button.rb +46 -0
  127. data/test/test_frames.rb +34 -0
  128. data/test/test_headers.rb +33 -0
  129. data/test/test_history.rb +118 -0
  130. data/test/test_history_added.rb +16 -0
  131. data/test/test_html_unscape_forms.rb +46 -0
  132. data/test/test_if_modified_since.rb +20 -0
  133. data/test/test_images.rb +19 -0
  134. data/test/test_mechanize.rb +852 -0
  135. data/test/test_mechanize_cookie.rb +345 -0
  136. data/test/test_mechanize_cookie_jar.rb +433 -0
  137. data/test/test_mechanize_file.rb +53 -0
  138. data/test/test_mechanize_file_request.rb +19 -0
  139. data/test/test_mechanize_file_response.rb +21 -0
  140. data/test/test_mechanize_form.rb +576 -0
  141. data/test/test_mechanize_form_check_box.rb +37 -0
  142. data/test/test_mechanize_form_encoding.rb +120 -0
  143. data/test/test_mechanize_form_field.rb +21 -0
  144. data/test/test_mechanize_form_image_button.rb +12 -0
  145. data/test/test_mechanize_form_textarea.rb +51 -0
  146. data/test/test_mechanize_http_agent.rb +697 -0
  147. data/test/test_mechanize_link.rb +84 -0
  148. data/test/test_mechanize_page_encoding.rb +147 -0
  149. data/test/test_mechanize_page_link.rb +382 -0
  150. data/test/test_mechanize_page_meta_refresh.rb +115 -0
  151. data/test/test_mechanize_redirect_not_get_or_head_error.rb +18 -0
  152. data/test/test_mechanize_subclass.rb +22 -0
  153. data/test/test_mechanize_util.rb +92 -0
  154. data/test/test_multi_select.rb +118 -0
  155. data/test/test_no_attributes.rb +13 -0
  156. data/test/test_option.rb +18 -0
  157. data/test/test_pluggable_parser.rb +136 -0
  158. data/test/test_post_form.rb +37 -0
  159. data/test/test_pretty_print.rb +22 -0
  160. data/test/test_radiobutton.rb +75 -0
  161. data/test/test_redirect_limit_reached.rb +39 -0
  162. data/test/test_redirect_ok.rb +25 -0
  163. data/test/test_referer.rb +81 -0
  164. data/test/test_relative_links.rb +40 -0
  165. data/test/test_request.rb +13 -0
  166. data/test/test_response_code.rb +53 -0
  167. data/test/test_robots.rb +72 -0
  168. data/test/test_save_file.rb +48 -0
  169. data/test/test_scheme.rb +48 -0
  170. data/test/test_select.rb +119 -0
  171. data/test/test_select_all.rb +15 -0
  172. data/test/test_select_none.rb +15 -0
  173. data/test/test_select_noopts.rb +18 -0
  174. data/test/test_set_fields.rb +44 -0
  175. data/test/test_ssl_server.rb +20 -0
  176. metadata +360 -0
@@ -0,0 +1,8 @@
1
+ class Mechanize
2
+ class Page < Mechanize::File
3
+ # This class encapsulates a Base tag. Mechanize treats base tags just
4
+ # like 'a' tags. Base objects will contain links, but most likely will
5
+ # have no text.
6
+ class Base < Link; end
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ # This class encapsulates a 'frame' tag. Frame objects can be treated just
2
+ # like Link objects. They contain #src, the #link they refer to and a #name,
3
+ # the name of the frame they refer to. #src and #name are aliased to #href
4
+ # and #text respectively so that a Frame object can be treated just like a
5
+ # Link.
6
+
7
+ class Mechanize::Page::Frame < Mechanize::Page::Link
8
+
9
+ alias :src :href
10
+
11
+ attr_reader :text
12
+ alias :name :text
13
+
14
+ def initialize(node, mech, referer)
15
+ super(node, mech, referer)
16
+ @node = node
17
+ @text = node['name']
18
+ @href = node['src']
19
+ @content = nil
20
+ end
21
+
22
+ def content
23
+ @content ||= @mech.get @href, [], page
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,30 @@
1
+ class Mechanize
2
+ class Page < Mechanize::File
3
+ class Image
4
+ attr_reader :node
5
+ attr_reader :page
6
+
7
+ def initialize(node, page)
8
+ @node = node
9
+ @page = page
10
+ end
11
+
12
+ def src
13
+ @node['src']
14
+ end
15
+
16
+ def url
17
+ case src
18
+ when %r{^https?://}
19
+ src
20
+ else
21
+ if page.bases[0]
22
+ (page.bases[0].href + src).to_s
23
+ else
24
+ (page.uri + src).to_s
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ class Mechanize
2
+ class Page < Mechanize::File
3
+ class Label
4
+ attr_reader :node
5
+ attr_reader :text
6
+ attr_reader :page
7
+ alias :to_s :text
8
+
9
+ def initialize(node, page)
10
+ @node = node
11
+ @text = node.inner_text
12
+ @page = page
13
+ end
14
+
15
+ def for
16
+ (id = @node['for']) && page.search("##{id}") || nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,82 @@
1
+ ##
2
+ # This class encapsulates links. It contains the text and the URI for
3
+ # 'a' tags parsed out of an HTML page. If the link contains an image,
4
+ # the alt text will be used for that image.
5
+ #
6
+ # For example, the text for the following links with both be 'Hello World':
7
+ #
8
+ # <a href="http://example">Hello World</a>
9
+ # <a href="http://example"><img src="test.jpg" alt="Hello World"></a>
10
+
11
+ class Mechanize::Page::Link
12
+ attr_reader :node
13
+ attr_reader :href
14
+ attr_reader :attributes
15
+ attr_reader :page
16
+ alias :referer :page
17
+
18
+ def initialize(node, mech, page)
19
+ @node = node
20
+ @attributes = node
21
+ @href = node['href']
22
+ @mech = mech
23
+ @page = page
24
+ @text = nil
25
+ @uri = nil
26
+ end
27
+
28
+ # Click on this link
29
+ def click
30
+ @mech.click self
31
+ end
32
+
33
+ # This method is a shorthand to get link's DOM id.
34
+ # Common usage:
35
+ # page.link_with(:dom_id => "links_exact_id")
36
+ def dom_id
37
+ node['id']
38
+ end
39
+
40
+ # A list of words in the rel attribute, all lower-cased.
41
+ def rel
42
+ @rel ||= (val = attributes['rel']) ? val.downcase.split(' ') : []
43
+ end
44
+
45
+ # Test if the rel attribute includes +kind+.
46
+ def rel? kind
47
+ rel.include? kind
48
+ end
49
+
50
+ # The text content of this link
51
+ def text
52
+ return @text if @text
53
+
54
+ @text = @node.inner_text
55
+
56
+ # If there is no text, try to find an image and use it's alt text
57
+ if (@text.nil? or @text.empty?) and imgs = @node.search('img') then
58
+ @text = imgs.map do |e|
59
+ e['alt']
60
+ end.join
61
+ end
62
+
63
+ @text
64
+ end
65
+
66
+ alias :to_s :text
67
+
68
+ # A URI for the #href for this link. The link is first parsed as a raw
69
+ # link. If that fails parsing an escaped link is attepmted.
70
+
71
+ def uri
72
+ @uri ||= if @href then
73
+ begin
74
+ URI.parse @href
75
+ rescue URI::InvalidURIError
76
+ URI.parse WEBrick::HTTPUtils.escape @href
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+
@@ -0,0 +1,56 @@
1
+ ##
2
+ # This class encapsulates a meta element with a refresh http-equiv. Mechanize
3
+ # treats meta refresh elements just like 'a' tags. MetaRefresh objects will
4
+ # contain links, but most likely will have no text.
5
+
6
+ class Mechanize::Page::MetaRefresh < Mechanize::Page::Link
7
+
8
+ attr_reader :delay
9
+
10
+ ##
11
+ # Matches the content attribute of a meta refresh element. After the match:
12
+ #
13
+ # $1:: delay
14
+ # $3:: url
15
+ CONTENT_REGEXP = /^\s*(\d+\.?\d*)(;|;\s*url=\s*['"]?(\S*?)['"]?)?\s*$/i
16
+
17
+ ##
18
+ # Parses the delay and url from the content attribute of a meta refresh
19
+ # element. Parse requires the uri of the current page to infer a url when
20
+ # no url is specified.
21
+ #
22
+ # Returns a MetaRefresh instance.
23
+ #
24
+ # Returns nil if the delay and url cannot be parsed.
25
+
26
+ def self.parse content, base_uri
27
+ return unless content =~ CONTENT_REGEXP
28
+
29
+ delay, refresh_uri = $1, $3
30
+
31
+ dest = base_uri
32
+ dest += refresh_uri if refresh_uri
33
+
34
+ return delay, dest
35
+ end
36
+
37
+ def self.from_node node, page, uri
38
+ http_equiv = node['http-equiv']
39
+ return unless http_equiv and http_equiv.downcase == 'refresh'
40
+
41
+ delay, uri = parse node['content'], uri
42
+
43
+ return unless delay
44
+
45
+ new node, page, delay, uri.to_s
46
+ end
47
+
48
+ def initialize node, page, delay, href
49
+ super node, page.mech, page
50
+
51
+ @delay = delay.to_i
52
+ @href = href
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,101 @@
1
+ require 'mechanize/file'
2
+ require 'mechanize/file_saver'
3
+ require 'mechanize/page'
4
+
5
+ class Mechanize
6
+ # = Synopsis
7
+ # This class is used to register and maintain pluggable parsers for
8
+ # Mechanize to use.
9
+ #
10
+ # A Pluggable Parser is a parser that Mechanize uses for any particular
11
+ # content type. Mechanize will ask PluggableParser for the class it
12
+ # should initialize given any content type. This class allows users to
13
+ # register their own pluggable parsers, or modify existing pluggable
14
+ # parsers.
15
+ #
16
+ # PluggableParser returns a Mechanize::File object for content types
17
+ # that it does not know how to handle. Mechanize::File provides
18
+ # basic functionality for any content type, so it is a good class to
19
+ # extend when building your own parsers.
20
+ # == Example
21
+ # To create your own parser, just create a class that takes four
22
+ # parameters in the constructor. Here is an example of registering
23
+ # a pluggable parser that handles CSV files:
24
+ # class CSVParser < Mechanize::File
25
+ # attr_reader :csv
26
+ # def initialize(uri=nil, response=nil, body=nil, code=nil)
27
+ # super(uri, response, body, code)
28
+ # @csv = CSV.parse(body)
29
+ # end
30
+ # end
31
+ # agent = Mechanize.new
32
+ # agent.pluggable_parser.csv = CSVParser
33
+ # agent.get('http://example.com/test.csv') # => CSVParser
34
+ # Now any page that returns the content type of 'text/csv' will initialize
35
+ # a CSVParser and return that object to the caller.
36
+ #
37
+ # To register a pluggable parser for a content type that pluggable parser
38
+ # does not know about, just use the hash syntax:
39
+ # agent.pluggable_parser['text/something'] = SomeClass
40
+ #
41
+ # To set the default parser, just use the 'defaut' method:
42
+ # agent.pluggable_parser.default = SomeClass
43
+ # Now all unknown content types will be instances of SomeClass.
44
+ class PluggableParser
45
+ CONTENT_TYPES = {
46
+ :html => 'text/html',
47
+ :wap => 'application/vnd.wap.xhtml+xml',
48
+ :xhtml => 'application/xhtml+xml',
49
+ :pdf => 'application/pdf',
50
+ :csv => 'text/csv',
51
+ :xml => 'text/xml',
52
+ }
53
+
54
+ attr_accessor :default
55
+
56
+ def initialize
57
+ @parsers = { CONTENT_TYPES[:html] => Page,
58
+ CONTENT_TYPES[:xhtml] => Page,
59
+ CONTENT_TYPES[:wap] => Page,
60
+ }
61
+ @default = File
62
+ end
63
+
64
+ def parser(content_type)
65
+ content_type.nil? ? default : @parsers[content_type] || default
66
+ end
67
+
68
+ def register_parser(content_type, klass)
69
+ @parsers[content_type] = klass
70
+ end
71
+
72
+ def html=(klass)
73
+ register_parser(CONTENT_TYPES[:html], klass)
74
+ register_parser(CONTENT_TYPES[:xhtml], klass)
75
+ end
76
+
77
+ def xhtml=(klass)
78
+ register_parser(CONTENT_TYPES[:xhtml], klass)
79
+ end
80
+
81
+ def pdf=(klass)
82
+ register_parser(CONTENT_TYPES[:pdf], klass)
83
+ end
84
+
85
+ def csv=(klass)
86
+ register_parser(CONTENT_TYPES[:csv], klass)
87
+ end
88
+
89
+ def xml=(klass)
90
+ register_parser(CONTENT_TYPES[:xml], klass)
91
+ end
92
+
93
+ def [](content_type)
94
+ @parsers[content_type]
95
+ end
96
+
97
+ def []=(content_type, klass)
98
+ @parsers[content_type] = klass
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,16 @@
1
+ class Mechanize
2
+ # Thrown when too many redirects are sent
3
+ class RedirectLimitReachedError < Mechanize::Error
4
+ attr_reader :page, :response_code, :redirects
5
+ def initialize(page, redirects)
6
+ @page = page
7
+ @redirects = redirects
8
+ @response_code = page.code
9
+ end
10
+
11
+ def to_s
12
+ "Maximum redirect limit (#{redirects}) reached"
13
+ end
14
+ alias :inspect :to_s
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ class Mechanize
2
+ # Thrown when a POST, PUT, or DELETE request results in a redirect
3
+ # see RFC 2616 10.3.2, 10.3.3 http://www.ietf.org/rfc/rfc2616.txt
4
+ class RedirectNotGetOrHeadError < Mechanize::Error
5
+ attr_reader :page, :response_code, :verb, :uri
6
+ def initialize(page, verb)
7
+ @page = page
8
+ @verb = verb
9
+ @uri = page.uri
10
+ @response_code = page.code
11
+ end
12
+
13
+ def to_s
14
+ method = @verb.to_s.upcase
15
+ "#{@response_code} redirect received after a #{method} request"
16
+ end
17
+ alias :inspect :to_s
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ class Mechanize
2
+ # =Synopsis
3
+ # This error is thrown when Mechanize encounters a response code it does
4
+ # not know how to handle. Currently, this exception will be thrown
5
+ # if Mechanize encounters response codes other than 200, 301, or 302.
6
+ # Any other response code is up to the user to handle.
7
+ class ResponseCodeError < Mechanize::Error
8
+ attr_reader :response_code
9
+ attr_reader :page
10
+
11
+ def initialize(page)
12
+ @page = page
13
+ @response_code = page.code.to_s
14
+ end
15
+
16
+ def to_s
17
+ "#{@response_code} => #{Net::HTTPResponse::CODE_TO_OBJ[@response_code]}"
18
+ end
19
+
20
+ def inspect; to_s; end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ ##
2
+ # Raised when Mechanize encounters an error while reading the response body
3
+ # from the server. Contains the response headers and the response body up to
4
+ # the error along with the initial error.
5
+
6
+ class Mechanize::ResponseReadError < Mechanize::Error
7
+
8
+ attr_reader :body_io
9
+ attr_reader :error
10
+ attr_reader :response
11
+
12
+ ##
13
+ # Creates a new ResponseReadError with the +error+ raised, the +response+
14
+ # and the +body_io+ for content read so far.
15
+
16
+ def initialize error, response, body_io
17
+ @error = error
18
+ @response = response
19
+ @body_io = body_io
20
+ end
21
+
22
+ def message # :nodoc:
23
+ "#{@error.message} (#{self.class})"
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,29 @@
1
+ class Mechanize
2
+ # Exception that is raised when an access to a resource is
3
+ # disallowed by robots.txt or by HTML document itself.
4
+ class RobotsDisallowedError < Mechanize::Error
5
+ def initialize(url)
6
+ if url.is_a?(URI)
7
+ @url = url.to_s
8
+ @uri = url
9
+ else
10
+ @url = url.to_s
11
+ end
12
+ end
13
+
14
+ # Returns the URL (string) of the resource that caused this error.
15
+ attr_reader :url
16
+
17
+ # Returns the URL (URI object) of the resource that caused this
18
+ # error. URI::InvalidURIError may be raised if the URL happens to
19
+ # be invalid or not understood by the URI library.
20
+ def uri
21
+ @uri ||= URI.parse(url)
22
+ end
23
+
24
+ def to_s
25
+ "Robots access is disallowed for URL: #{url}"
26
+ end
27
+ alias :inspect :to_s
28
+ end
29
+ end