fmalamitsas-aws-s3 0.6.2.1254423625

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