aliyun-oss-ex 0.7.0.1402831795

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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +19 -0
  3. data/INSTALL +35 -0
  4. data/README +443 -0
  5. data/Rakefile +334 -0
  6. data/bin/oss +6 -0
  7. data/bin/setup.rb +11 -0
  8. data/lib/aliyun/oss.rb +55 -0
  9. data/lib/aliyun/oss/acl.rb +132 -0
  10. data/lib/aliyun/oss/authentication.rb +222 -0
  11. data/lib/aliyun/oss/base.rb +241 -0
  12. data/lib/aliyun/oss/bucket.rb +320 -0
  13. data/lib/aliyun/oss/connection.rb +279 -0
  14. data/lib/aliyun/oss/error.rb +70 -0
  15. data/lib/aliyun/oss/exceptions.rb +134 -0
  16. data/lib/aliyun/oss/extensions.rb +405 -0
  17. data/lib/aliyun/oss/logging.rb +304 -0
  18. data/lib/aliyun/oss/object.rb +612 -0
  19. data/lib/aliyun/oss/owner.rb +45 -0
  20. data/lib/aliyun/oss/parsing.rb +100 -0
  21. data/lib/aliyun/oss/response.rb +181 -0
  22. data/lib/aliyun/oss/service.rb +52 -0
  23. data/lib/aliyun/oss/version.rb +14 -0
  24. data/support/faster-xml-simple/lib/faster_xml_simple.rb +188 -0
  25. data/support/faster-xml-simple/test/regression_test.rb +48 -0
  26. data/support/faster-xml-simple/test/test_helper.rb +18 -0
  27. data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +47 -0
  28. data/support/rdoc/code_info.rb +212 -0
  29. data/test/acl_test.rb +70 -0
  30. data/test/authentication_test.rb +114 -0
  31. data/test/base_test.rb +137 -0
  32. data/test/bucket_test.rb +75 -0
  33. data/test/connection_test.rb +218 -0
  34. data/test/error_test.rb +71 -0
  35. data/test/extensions_test.rb +346 -0
  36. data/test/fixtures.rb +90 -0
  37. data/test/fixtures/buckets.yml +133 -0
  38. data/test/fixtures/errors.yml +34 -0
  39. data/test/fixtures/headers.yml +3 -0
  40. data/test/fixtures/logging.yml +15 -0
  41. data/test/fixtures/loglines.yml +5 -0
  42. data/test/fixtures/logs.yml +7 -0
  43. data/test/fixtures/policies.yml +16 -0
  44. data/test/logging_test.rb +90 -0
  45. data/test/mocks/fake_response.rb +27 -0
  46. data/test/object_test.rb +221 -0
  47. data/test/parsing_test.rb +67 -0
  48. data/test/remote/acl_test.rb +28 -0
  49. data/test/remote/bucket_test.rb +147 -0
  50. data/test/remote/logging_test.rb +86 -0
  51. data/test/remote/object_test.rb +350 -0
  52. data/test/remote/test_file.data +0 -0
  53. data/test/remote/test_helper.rb +34 -0
  54. data/test/response_test.rb +69 -0
  55. data/test/service_test.rb +24 -0
  56. data/test/test_helper.rb +110 -0
  57. metadata +177 -0
@@ -0,0 +1,70 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module OSS
4
+ # Anything you do that makes a request to OSS could result in an error. If it does, the Aliyun::OSS library will raise an exception
5
+ # specific to the error. All exception that are raised as a result of a request returning an error response inherit from the
6
+ # ResponseError exception. So should you choose to rescue any such exception, you can simple rescue ResponseError.
7
+ #
8
+ # Say you go to delete a bucket, but the bucket turns out to not be empty. This results in a BucketNotEmpty error (one of the many
9
+ # errors listed at http://docs.aliyunwebservices.com/AliyunOSS/2006-03-01/ErrorCodeList.html):
10
+ #
11
+ # begin
12
+ # Bucket.delete('jukebox')
13
+ # rescue ResponseError => error
14
+ # # ...
15
+ # end
16
+ #
17
+ # Once you've captured the exception, you can extract the error message from OSS, as well as the full error response, which includes
18
+ # things like the HTTP response code:
19
+ #
20
+ # error
21
+ # # => #<Aliyun::OSS::BucketNotEmpty The bucket you tried to delete is not empty>
22
+ # error.message
23
+ # # => "The bucket you tried to delete is not empty"
24
+ # error.response.code
25
+ # # => 409
26
+ #
27
+ # You could use this information to redisplay the error in a way you see fit, or just to log the error and continue on.
28
+ class Error
29
+ #:stopdoc:
30
+ attr_accessor :response
31
+ def initialize(error, response = nil)
32
+ @error = error
33
+ @response = response
34
+ @container = Aliyun::OSS
35
+ find_or_create_exception!
36
+ end
37
+
38
+ def raise
39
+ Kernel.raise exception.new(message, response)
40
+ end
41
+
42
+ private
43
+ attr_reader :error, :exception, :container
44
+
45
+ def find_or_create_exception!
46
+ @exception = container.const_defined?(code) ? find_exception : create_exception
47
+ end
48
+
49
+ def find_exception
50
+ exception_class = container.const_get(code)
51
+ Kernel.raise ExceptionClassClash.new(exception_class) unless exception_class.ancestors.include?(ResponseError)
52
+ exception_class
53
+ end
54
+
55
+ def create_exception
56
+ container.const_set(code, Class.new(ResponseError))
57
+ end
58
+
59
+ def method_missing(method, *args, &block)
60
+ # We actually want nil if the attribute is nil. So we use has_key? rather than [] + ||.
61
+ if error.has_key?(method.to_s)
62
+ error[method.to_s]
63
+ else
64
+ super
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ #:startdoc:
@@ -0,0 +1,134 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Aliyun
3
+ module OSS
4
+
5
+ # Abstract super class of all Aliyun::OSS exceptions
6
+ class OSSException < StandardError
7
+ end
8
+
9
+ # All responses with a code between 300 and 599 that contain an <Error></Error> body are wrapped in an
10
+ # ErrorResponse which contains an Error object. This Error class generates a custom exception with the name
11
+ # of the xml Error and its message. All such runtime generated exception classes descend from ResponseError
12
+ # and contain the ErrorResponse object so that all code that makes a request can rescue ResponseError and get
13
+ # access to the ErrorResponse.
14
+ class ResponseError < OSSException
15
+ attr_reader :response
16
+ def initialize(message, response)
17
+ @response = response
18
+ super(message)
19
+ end
20
+ end
21
+
22
+ #:stopdoc:
23
+
24
+ # Most ResponseError's are created just time on a need to have basis, but we explicitly define the
25
+ # InternalError exception because we want to explicitly rescue InternalError in some cases.
26
+ class InternalError < ResponseError
27
+ end
28
+
29
+ class NoSuchKey < ResponseError
30
+ end
31
+
32
+ class RequestTimeout < ResponseError
33
+ end
34
+
35
+ # Abstract super class for all invalid options.
36
+ class InvalidOption < OSSException
37
+ end
38
+
39
+ # Raised if an invalid value is passed to the <tt>:access</tt> option when creating a Bucket or an OSSObject.
40
+ class InvalidAccessControlLevel < InvalidOption
41
+ def initialize(valid_levels, access_level)
42
+ super("Valid access control levels are #{valid_levels.inspect}. You specified `#{access_level}'.")
43
+ end
44
+ end
45
+
46
+ # Raised if either the access key id or secret access key arguments are missing when establishing a connection.
47
+ class MissingAccessKey < InvalidOption
48
+ def initialize(missing_keys)
49
+ key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
50
+ super("You did not provide both required access keys. Please provide the #{key_list}.")
51
+ end
52
+ end
53
+
54
+ # Raised if a request is attempted before any connections have been established.
55
+ class NoConnectionEstablished < OSSException
56
+ end
57
+
58
+ # Raised if an unrecognized option is passed when establishing a connection.
59
+ class InvalidConnectionOption < InvalidOption
60
+ def initialize(invalid_options)
61
+ message = "The following connection options are invalid: #{invalid_options.join(', ')}. " +
62
+ "The valid connection options are: #{Connection::Options::VALID_OPTIONS.join(', ')}."
63
+ super(message)
64
+ end
65
+ end
66
+
67
+ # Raised if an invalid bucket name is passed when creating a new Bucket.
68
+ class InvalidBucketName < OSSException
69
+ def initialize(invalid_name)
70
+ message = "`#{invalid_name}' is not a valid bucket name. " +
71
+ "Bucket names must be between 3 and 255 bytes and " +
72
+ "can contain letters, numbers, dashes and underscores."
73
+ super(message)
74
+ end
75
+ end
76
+
77
+ # Raised if an invalid key name is passed when creating an OSSObject.
78
+ class InvalidKeyName < OSSException
79
+ def initialize(invalid_name)
80
+ message = "`#{invalid_name}' is not a valid key name. " +
81
+ "Key names must be no more than 1024 bytes long."
82
+ super(message)
83
+ end
84
+ end
85
+
86
+ # Raised if an invalid value is assigned to an OSSObject's specific metadata name.
87
+ class InvalidMetadataValue < OSSException
88
+ def initialize(invalid_names)
89
+ message = "The following metadata names have invalid values: #{invalid_names.join(', ')}. " +
90
+ "Metadata can not be larger than 2kilobytes."
91
+ super(message)
92
+ end
93
+ end
94
+
95
+ # Raised if the current bucket can not be inferred when not explicitly specifying the target bucket in the calling
96
+ # method's arguments.
97
+ class CurrentBucketNotSpecified < OSSException
98
+ def initialize(address)
99
+ message = "No bucket name can be inferred from your current connection's address (`#{address}')"
100
+ super(message)
101
+ end
102
+ end
103
+
104
+ # Raised when an orphaned OSSObject belonging to no bucket tries to access its (non-existant) bucket.
105
+ class NoBucketSpecified < OSSException
106
+ def initialize
107
+ super('The current object must have its bucket set')
108
+ end
109
+ end
110
+
111
+ # Raised if an attempt is made to save an OSSObject that does not have a key set.
112
+ class NoKeySpecified < OSSException
113
+ def initialize
114
+ super('The current object must have its key set')
115
+ end
116
+ end
117
+
118
+ # Raised if you try to save a deleted object.
119
+ class DeletedObject < OSSException
120
+ def initialize
121
+ super('You can not save a deleted object')
122
+ end
123
+ end
124
+
125
+ class ExceptionClassClash < OSSException #:nodoc:
126
+ def initialize(klass)
127
+ message = "The exception class you tried to create (`#{klass}') exists and is not an exception"
128
+ super(message)
129
+ end
130
+ end
131
+
132
+ #:startdoc:
133
+ end
134
+ end
@@ -0,0 +1,405 @@
1
+ # -*- encoding : utf-8 -*-
2
+ #:stopdoc:
3
+
4
+ class Hash
5
+ # By default, only instances of Hash itself are extractable.
6
+ # Subclasses of Hash may implement this method and return
7
+ # true to declare themselves as extractable. If a Hash
8
+ # is extractable, Array#extract_options! pops it from
9
+ # the Array when it is the last element of the Array.
10
+ def extractable_options?
11
+ instance_of?(Hash)
12
+ end
13
+
14
+ def to_query_string(include_question_mark = true)
15
+ query_string = ''
16
+ unless empty?
17
+ query_string << '?' if include_question_mark
18
+ query_string << inject([]) do |params, (key, value)|
19
+ params << "#{key}=#{value}"
20
+ end.join('&')
21
+ end
22
+ query_string
23
+ end
24
+
25
+ def to_normalized_options
26
+ # Convert all option names to downcased strings, and replace underscores with hyphens
27
+ inject({}) do |normalized_options, (name, value)|
28
+ normalized_options[name.to_header] = value.to_s
29
+ normalized_options
30
+ end
31
+ end
32
+
33
+ def to_normalized_options!
34
+ replace(to_normalized_options)
35
+ end
36
+ end
37
+
38
+ class Array
39
+ # Extracts options from a set of arguments. Removes and returns the last
40
+ # element in the array if it's a hash, otherwise returns a blank hash.
41
+ #
42
+ # def options(*args)
43
+ # args.extract_options!
44
+ # end
45
+ #
46
+ # options(1, 2) # => {}
47
+ # options(1, 2, a: :b) # => {:a=>:b}
48
+ def extract_options!
49
+ if last.is_a?(Hash) && last.extractable_options?
50
+ pop
51
+ else
52
+ {}
53
+ end
54
+ end
55
+ end
56
+
57
+ class String
58
+ if RUBY_VERSION <= '1.9'
59
+ def previous!
60
+ self[-1] -= 1
61
+ self
62
+ end
63
+ else
64
+ def previous!
65
+ self[-1] = (self[-1].ord - 1).chr
66
+ self
67
+ end
68
+ end
69
+
70
+ def tap
71
+ yield(self)
72
+ self
73
+ end unless ''.respond_to?(:tap)
74
+
75
+ def previous
76
+ dup.previous!
77
+ end
78
+
79
+ def to_header
80
+ downcase.tr('_', '-')
81
+ end
82
+
83
+ # ActiveSupport adds an underscore method to String so let's just use that one if
84
+ # we find that the method is already defined
85
+ def underscore
86
+ gsub(/::/, '/').
87
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
88
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
89
+ tr("-", "_").downcase
90
+ end unless public_method_defined? :underscore
91
+
92
+ if RUBY_VERSION >= '1.9'
93
+ def valid_utf8?
94
+ dup.force_encoding('UTF-8').valid_encoding?
95
+ end
96
+ else
97
+ def valid_utf8?
98
+ scan(Regexp.new('[^\x00-\xa0]', nil, 'u')) { |s| s.unpack('U') }
99
+ true
100
+ rescue ArgumentError
101
+ false
102
+ end
103
+ end
104
+
105
+ # All paths in in OSS have to be valid unicode so this takes care of
106
+ # cleaning up any strings that aren't valid utf-8 according to String#valid_utf8?
107
+ if RUBY_VERSION >= '1.9'
108
+ def remove_extended!
109
+ sanitized_string = ''
110
+ each_byte do |byte|
111
+ character = byte.chr
112
+ sanitized_string << character if character.ascii_only?
113
+ end
114
+ sanitized_string
115
+ end
116
+ else
117
+ def remove_extended!
118
+ #gsub!(/[\x80-\xFF]/) { "%02X" % $&[0] }
119
+ gsub!(Regexp.new('[\x80-\xFF]')) { "%02X" % $&[0] }
120
+ end
121
+ end
122
+
123
+ def remove_extended
124
+ dup.remove_extended!
125
+ end
126
+ end
127
+
128
+ class CoercibleString < String
129
+ class << self
130
+ def coerce(string)
131
+ new(string).coerce
132
+ end
133
+ end
134
+
135
+ def coerce
136
+ case self
137
+ when 'true'; true
138
+ when 'false'; false
139
+ # Don't coerce numbers that start with zero
140
+ when /^[1-9]+\d*$/; Integer(self)
141
+ when datetime_format; Time.parse(self)
142
+ else
143
+ self
144
+ end
145
+ end
146
+
147
+ private
148
+ # Lame hack since Date._parse is so accepting. OSS dates are of the form: '2006-10-29T23:14:47.000Z'
149
+ # so unless the string looks like that, don't even try, otherwise it might convert an object's
150
+ # key from something like '03 1-2-3-Apple-Tree.mp3' to Sat Feb 03 00:00:00 CST 2001.
151
+ def datetime_format
152
+ /^\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}/
153
+ end
154
+ end
155
+
156
+ class Symbol
157
+ def to_header
158
+ to_s.to_header
159
+ end
160
+ end
161
+
162
+ module Kernel
163
+ def __method__(depth = 0)
164
+ caller[depth][/`([^']+)'/, 1]
165
+ end if RUBY_VERSION <= '1.8.7'
166
+
167
+ def __called_from__
168
+ caller[1][/`([^']+)'/, 1]
169
+ end if RUBY_VERSION > '1.8.7'
170
+
171
+ def expirable_memoize(reload = false, storage = nil)
172
+ current_method = RUBY_VERSION > '1.8.7' ? __called_from__ : __method__(1)
173
+ storage = "@#{storage || current_method}"
174
+ if reload
175
+ instance_variable_set(storage, nil)
176
+ else
177
+ if cache = instance_variable_get(storage)
178
+ return cache
179
+ end
180
+ end
181
+ instance_variable_set(storage, yield)
182
+ end
183
+
184
+ def require_library_or_gem(library, gem_name = nil)
185
+ if RUBY_VERSION >= '1.9'
186
+ gem(gem_name || library, '>=0')
187
+ end
188
+ require library
189
+ rescue LoadError => library_not_installed
190
+ begin
191
+ require 'rubygems'
192
+ require library
193
+ rescue LoadError
194
+ raise library_not_installed
195
+ end
196
+ end
197
+ end
198
+
199
+ class Object
200
+ def returning(value)
201
+ yield(value)
202
+ value
203
+ end
204
+ end
205
+
206
+ class Module
207
+ def memoized(method_name)
208
+ original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
209
+ alias_method original_method, method_name
210
+ module_eval(<<-EVAL, __FILE__, __LINE__)
211
+ def #{method_name}(reload = false, *args, &block)
212
+ expirable_memoize(reload) do
213
+ send(:#{original_method}, *args, &block)
214
+ end
215
+ end
216
+ EVAL
217
+ end
218
+
219
+ def constant(name, value)
220
+ unless const_defined?(name)
221
+ const_set(name, value)
222
+ module_eval(<<-EVAL, __FILE__, __LINE__)
223
+ def self.#{name.to_s.downcase}
224
+ #{name.to_s}
225
+ end
226
+ EVAL
227
+ end
228
+ end
229
+
230
+ # Transforms MarcelBucket into
231
+ #
232
+ # class MarcelBucket < Aliyun::OSS::Bucket
233
+ # set_current_bucket_to 'marcel'
234
+ # end
235
+ def const_missing_from_oss_library(sym)
236
+ if sym.to_s =~ /^(\w+)(Bucket|OSSObject)$/
237
+ const = const_set(sym, Class.new(Aliyun::OSS.const_get($2)))
238
+ const.current_bucket = $1.underscore
239
+ const
240
+ else
241
+ const_missing_not_from_oss_library(sym)
242
+ end
243
+ end
244
+ alias_method :const_missing_not_from_oss_library, :const_missing
245
+ alias_method :const_missing, :const_missing_from_oss_library
246
+ end
247
+
248
+
249
+ class Class
250
+ def mattr_reader(*syms)
251
+ options = syms.extract_options!
252
+ syms.each do |sym|
253
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
254
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
255
+ @@#{sym} = nil unless defined? @@#{sym}
256
+
257
+ def self.#{sym}
258
+ @@#{sym}
259
+ end
260
+ EOS
261
+
262
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
263
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
264
+ def #{sym}
265
+ @@#{sym}
266
+ end
267
+ EOS
268
+ end
269
+ class_variable_set("@@#{sym}", yield) if block_given?
270
+ end
271
+ end
272
+ alias :cattr_reader :mattr_reader
273
+
274
+ def mattr_writer(*syms)
275
+ options = syms.extract_options!
276
+ syms.each do |sym|
277
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
278
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
279
+ @@#{sym} = nil unless defined? @@#{sym}
280
+
281
+ def self.#{sym}=(obj)
282
+ @@#{sym} = obj
283
+ end
284
+ EOS
285
+
286
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
287
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
288
+ def #{sym}=(obj)
289
+ @@#{sym} = obj
290
+ end
291
+ EOS
292
+ end
293
+ send("#{sym}=", yield) if block_given?
294
+ end
295
+ end
296
+ alias :cattr_writer :mattr_writer
297
+
298
+ def mattr_accessor(*syms, &blk)
299
+ mattr_reader(*syms, &blk)
300
+ mattr_writer(*syms, &blk)
301
+ end
302
+ alias :cattr_accessor :mattr_accessor
303
+ end if Class.instance_methods(false).grep(/^cattr_(?:reader|writer|accessor)$/).empty?
304
+
305
+ module SelectiveAttributeProxy
306
+ def self.included(klass)
307
+ klass.extend(ClassMethods)
308
+ klass.class_eval(<<-EVAL, __FILE__, __LINE__)
309
+ cattr_accessor :attribute_proxy
310
+ cattr_accessor :attribute_proxy_options
311
+
312
+ # Default name for attribute storage
313
+ self.attribute_proxy = :attributes
314
+ self.attribute_proxy_options = {:exclusively => true}
315
+
316
+ private
317
+ # By default proxy all attributes
318
+ def proxiable_attribute?(name)
319
+ return true unless self.class.attribute_proxy_options[:exclusively]
320
+ send(self.class.attribute_proxy).has_key?(name)
321
+ end
322
+
323
+ def method_missing(method, *args, &block)
324
+ # Autovivify attribute storage
325
+ if method == self.class.attribute_proxy
326
+ ivar = "@\#{method}"
327
+ instance_variable_set(ivar, {}) unless instance_variable_get(ivar).is_a?(Hash)
328
+ instance_variable_get(ivar)
329
+ # Delegate to attribute storage
330
+ elsif method.to_s =~ /^(\\w+)(=?)$/ && proxiable_attribute?($1)
331
+ attributes_hash_name = self.class.attribute_proxy
332
+ $2.empty? ? send(attributes_hash_name)[$1] : send(attributes_hash_name)[$1] = args.first
333
+ else
334
+ super
335
+ end
336
+ end
337
+ EVAL
338
+ end
339
+
340
+ module ClassMethods
341
+ def proxy_to(attribute_name, options = {})
342
+ if attribute_name.is_a?(Hash)
343
+ options = attribute_name
344
+ else
345
+ self.attribute_proxy = attribute_name
346
+ end
347
+ self.attribute_proxy_options = options
348
+ end
349
+ end
350
+ end
351
+
352
+ # When streaming data up, Net::HTTPGenericRequest hard codes a chunk size of 1k. For large files this
353
+ # is an unfortunately low chunk size, so here we make it use a much larger default size and move it into a method
354
+ # so that the implementation of send_request_with_body_stream doesn't need to be changed to change the chunk size (at least not anymore
355
+ # than I've already had to...).
356
+ module Net
357
+ class HTTPGenericRequest
358
+ def send_request_with_body_stream(sock, ver, path, f)
359
+ raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked?
360
+ unless content_type()
361
+ warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
362
+ set_content_type 'application/x-www-form-urlencoded'
363
+ end
364
+ write_header sock, ver, path
365
+ if chunked?
366
+ while s = f.read(chunk_size)
367
+ sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
368
+ end
369
+ sock.write "0\r\n\r\n"
370
+ else
371
+ while s = f.read(chunk_size)
372
+ sock.write s
373
+ end
374
+ end
375
+ end
376
+
377
+ def chunk_size
378
+ 1048576 # 1 megabyte
379
+ end
380
+ end
381
+
382
+ # Net::HTTP before 1.8.4 doesn't have the use_ssl? method or the Delete request type
383
+ class HTTP
384
+ def use_ssl?
385
+ @use_ssl
386
+ end unless public_method_defined? :use_ssl?
387
+
388
+ class Delete < HTTPRequest
389
+ METHOD = 'DELETE'
390
+ REQUEST_HAS_BODY = false
391
+ RESPONSE_HAS_BODY = true
392
+ end unless const_defined? :Delete
393
+ end
394
+ end
395
+
396
+ class XmlGenerator < String #:nodoc:
397
+ attr_reader :xml
398
+ def initialize
399
+ @xml = Builder::XmlMarkup.new(:indent => 2, :target => self)
400
+ super()
401
+ build
402
+ end
403
+ end
404
+ #:startdoc:
405
+