jbangert-bindata 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/BSDL +22 -0
  3. data/COPYING +52 -0
  4. data/ChangeLog.rdoc +204 -0
  5. data/Gemfile +2 -0
  6. data/INSTALL +11 -0
  7. data/NEWS.rdoc +164 -0
  8. data/README.md +54 -0
  9. data/Rakefile +13 -0
  10. data/bindata.gemspec +31 -0
  11. data/doc/manual.haml +407 -0
  12. data/doc/manual.md +1649 -0
  13. data/examples/NBT.txt +149 -0
  14. data/examples/gzip.rb +161 -0
  15. data/examples/ip_address.rb +22 -0
  16. data/examples/list.rb +124 -0
  17. data/examples/nbt.rb +178 -0
  18. data/lib/bindata.rb +33 -0
  19. data/lib/bindata/alignment.rb +83 -0
  20. data/lib/bindata/array.rb +335 -0
  21. data/lib/bindata/base.rb +388 -0
  22. data/lib/bindata/base_primitive.rb +214 -0
  23. data/lib/bindata/bits.rb +87 -0
  24. data/lib/bindata/choice.rb +216 -0
  25. data/lib/bindata/count_bytes_remaining.rb +35 -0
  26. data/lib/bindata/deprecated.rb +50 -0
  27. data/lib/bindata/dsl.rb +312 -0
  28. data/lib/bindata/float.rb +80 -0
  29. data/lib/bindata/int.rb +184 -0
  30. data/lib/bindata/io.rb +274 -0
  31. data/lib/bindata/lazy.rb +105 -0
  32. data/lib/bindata/offset.rb +91 -0
  33. data/lib/bindata/params.rb +135 -0
  34. data/lib/bindata/primitive.rb +135 -0
  35. data/lib/bindata/record.rb +110 -0
  36. data/lib/bindata/registry.rb +92 -0
  37. data/lib/bindata/rest.rb +35 -0
  38. data/lib/bindata/sanitize.rb +290 -0
  39. data/lib/bindata/skip.rb +48 -0
  40. data/lib/bindata/string.rb +145 -0
  41. data/lib/bindata/stringz.rb +96 -0
  42. data/lib/bindata/struct.rb +388 -0
  43. data/lib/bindata/trace.rb +94 -0
  44. data/lib/bindata/version.rb +3 -0
  45. data/setup.rb +1585 -0
  46. data/spec/alignment_spec.rb +61 -0
  47. data/spec/array_spec.rb +331 -0
  48. data/spec/base_primitive_spec.rb +238 -0
  49. data/spec/base_spec.rb +376 -0
  50. data/spec/bits_spec.rb +163 -0
  51. data/spec/choice_spec.rb +263 -0
  52. data/spec/count_bytes_remaining_spec.rb +38 -0
  53. data/spec/deprecated_spec.rb +31 -0
  54. data/spec/example.rb +21 -0
  55. data/spec/float_spec.rb +37 -0
  56. data/spec/int_spec.rb +216 -0
  57. data/spec/io_spec.rb +352 -0
  58. data/spec/lazy_spec.rb +217 -0
  59. data/spec/primitive_spec.rb +202 -0
  60. data/spec/record_spec.rb +530 -0
  61. data/spec/registry_spec.rb +108 -0
  62. data/spec/rest_spec.rb +26 -0
  63. data/spec/skip_spec.rb +27 -0
  64. data/spec/spec_common.rb +58 -0
  65. data/spec/string_spec.rb +300 -0
  66. data/spec/stringz_spec.rb +118 -0
  67. data/spec/struct_spec.rb +350 -0
  68. data/spec/system_spec.rb +380 -0
  69. data/tasks/manual.rake +36 -0
  70. data/tasks/rspec.rake +17 -0
  71. metadata +208 -0
@@ -0,0 +1,48 @@
1
+ require "bindata/base_primitive"
2
+
3
+ module BinData
4
+ # Skip will skip over bytes from the input stream. If the stream is not
5
+ # seekable, then the bytes are consumed and discarded.
6
+ #
7
+ # When writing, skip will write <tt>:length</tt> number of zero bytes.
8
+ #
9
+ # require 'bindata'
10
+ #
11
+ # class A < BinData::Record
12
+ # skip :length => 5
13
+ # string :a, :read_length => 5
14
+ # end
15
+ #
16
+ # obj = A.read("abcdefghij")
17
+ # obj.a #=> "fghij"
18
+ #
19
+ # == Parameters
20
+ #
21
+ # Skip objects accept all the params that BinData::BasePrimitive
22
+ # does, as well as the following:
23
+ #
24
+ # <tt>:length</tt>:: The number of bytes to skip.
25
+ #
26
+ class Skip < BinData::BasePrimitive
27
+
28
+ mandatory_parameter :length
29
+
30
+ #---------------
31
+ private
32
+
33
+ def value_to_binary_string(val)
34
+ len = eval_parameter(:length)
35
+ "\000" * len
36
+ end
37
+
38
+ def read_and_return_value(io)
39
+ len = eval_parameter(:length)
40
+ io.seekbytes(len)
41
+ ""
42
+ end
43
+
44
+ def sensible_default
45
+ ""
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,145 @@
1
+ require "bindata/base_primitive"
2
+
3
+ module BinData
4
+ # A String is a sequence of bytes. This is the same as strings in Ruby 1.8.
5
+ # The issue of character encoding is ignored by this class.
6
+ #
7
+ # require 'bindata'
8
+ #
9
+ # data = "abcdefghij"
10
+ #
11
+ # obj = BinData::String.new(:read_length => 5)
12
+ # obj.read(data)
13
+ # obj #=> "abcde"
14
+ #
15
+ # obj = BinData::String.new(:length => 6)
16
+ # obj.read(data)
17
+ # obj #=> "abcdef"
18
+ # obj.assign("abcdefghij")
19
+ # obj #=> "abcdef"
20
+ # obj.assign("abcd")
21
+ # obj #=> "abcd\000\000"
22
+ #
23
+ # obj = BinData::String.new(:length => 6, :trim_padding => true)
24
+ # obj.assign("abcd")
25
+ # obj #=> "abcd"
26
+ # obj.to_binary_s #=> "abcd\000\000"
27
+ #
28
+ # obj = BinData::String.new(:length => 6, :pad_byte => 'A')
29
+ # obj.assign("abcd")
30
+ # obj #=> "abcdAA"
31
+ # obj.to_binary_s #=> "abcdAA"
32
+ #
33
+ # == Parameters
34
+ #
35
+ # String objects accept all the params that BinData::BasePrimitive
36
+ # does, as well as the following:
37
+ #
38
+ # <tt>:read_length</tt>:: The length in bytes to use when reading a value.
39
+ # <tt>:length</tt>:: The fixed length of the string. If a shorter
40
+ # string is set, it will be padded to this length.
41
+ # <tt>:pad_byte</tt>:: The byte to use when padding a string to a
42
+ # set length. Valid values are Integers and
43
+ # Strings of length 1. "\0" is the default.
44
+ # <tt>:pad_front</tt>:: Signifies that the padding occurs at the front
45
+ # of the string rather than the end. Default
46
+ # is false.
47
+ # <tt>:trim_padding</tt>:: Boolean, default false. If set, #value will
48
+ # return the value with all pad_bytes trimmed
49
+ # from the end of the string. The value will
50
+ # not be trimmed when writing.
51
+ class String < BinData::BasePrimitive
52
+
53
+ optional_parameters :read_length, :length, :trim_padding, :pad_front, :pad_left
54
+ default_parameters :pad_byte => "\0"
55
+ mutually_exclusive_parameters :read_length, :length
56
+ mutually_exclusive_parameters :length, :value
57
+
58
+ class << self
59
+
60
+ def sanitize_parameters!(params) #:nodoc:
61
+ params.warn_replacement_parameter(:initial_length, :read_length)
62
+
63
+ params.warn_renamed_parameter(:pad_char, :pad_byte) # Remove this line in the future
64
+
65
+ if params.has_parameter?(:pad_left)
66
+ params[:pad_front] = params.delete(:pad_left)
67
+ end
68
+
69
+ if params.has_parameter?(:pad_byte)
70
+ byte = params[:pad_byte]
71
+ params[:pad_byte] = sanitized_pad_byte(byte)
72
+ end
73
+ end
74
+
75
+ #-------------
76
+ private
77
+
78
+ def sanitized_pad_byte(byte)
79
+ result = byte.is_a?(Integer) ? byte.chr : byte.to_s
80
+ len = result.respond_to?(:bytesize) ? result.bytesize : result.length
81
+ if len > 1
82
+ raise ArgumentError, ":pad_byte must not contain more than 1 byte"
83
+ end
84
+ result
85
+ end
86
+ end
87
+
88
+ def assign(val)
89
+ super(binary_string(val))
90
+ end
91
+
92
+ def snapshot
93
+ # override to trim padding
94
+ result = super
95
+ result = clamp_to_length(result)
96
+
97
+ if get_parameter(:trim_padding)
98
+ result = trim_padding(result)
99
+ end
100
+ result
101
+ end
102
+
103
+ #---------------
104
+ private
105
+
106
+ def clamp_to_length(str)
107
+ str = binary_string(str)
108
+
109
+ len = eval_parameter(:length) || str.length
110
+ if str.length == len
111
+ str
112
+ elsif str.length > len
113
+ str.slice(0, len)
114
+ else
115
+ padding = (eval_parameter(:pad_byte) * (len - str.length))
116
+ if get_parameter(:pad_front)
117
+ padding + str
118
+ else
119
+ str + padding
120
+ end
121
+ end
122
+ end
123
+
124
+ def trim_padding(str)
125
+ if get_parameter(:pad_front)
126
+ str.sub(/\A#{eval_parameter(:pad_byte)}*/, "")
127
+ else
128
+ str.sub(/#{eval_parameter(:pad_byte)}*\z/, "")
129
+ end
130
+ end
131
+
132
+ def value_to_binary_string(val)
133
+ clamp_to_length(val)
134
+ end
135
+
136
+ def read_and_return_value(io)
137
+ len = eval_parameter(:read_length) || eval_parameter(:length) || 0
138
+ io.readbytes(len)
139
+ end
140
+
141
+ def sensible_default
142
+ ""
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,96 @@
1
+ require "bindata/base_primitive"
2
+
3
+ module BinData
4
+ # A BinData::Stringz object is a container for a zero ("\0") terminated
5
+ # string.
6
+ #
7
+ # For convenience, the zero terminator is not necessary when setting the
8
+ # value. Likewise, the returned value will not be zero terminated.
9
+ #
10
+ # require 'bindata'
11
+ #
12
+ # data = "abcd\x00efgh"
13
+ #
14
+ # obj = BinData::Stringz.new
15
+ # obj.read(data)
16
+ # obj.snapshot #=> "abcd"
17
+ # obj.num_bytes #=> 5
18
+ # obj.to_binary_s #=> "abcd\000"
19
+ #
20
+ # == Parameters
21
+ #
22
+ # Stringz objects accept all the params that BinData::BasePrimitive
23
+ # does, as well as the following:
24
+ #
25
+ # <tt>:max_length</tt>:: The maximum length of the string including the zero
26
+ # byte.
27
+ class Stringz < BinData::BasePrimitive
28
+
29
+ optional_parameters :max_length
30
+
31
+ def assign(val)
32
+ super(binary_string(val))
33
+ end
34
+
35
+ def snapshot
36
+ # override to always remove trailing zero bytes
37
+ result = super
38
+ trim_and_zero_terminate(result).chomp("\0")
39
+ end
40
+
41
+ #---------------
42
+ private
43
+
44
+ def value_to_binary_string(val)
45
+ trim_and_zero_terminate(val)
46
+ end
47
+
48
+ def read_and_return_value(io)
49
+ max_length = eval_parameter(:max_length)
50
+ str = ""
51
+ i = 0
52
+ ch = nil
53
+
54
+ # read until zero byte or we have read in the max number of bytes
55
+ while ch != "\0" and i != max_length
56
+ ch = io.readbytes(1)
57
+ str << ch
58
+ i += 1
59
+ end
60
+
61
+ trim_and_zero_terminate(str)
62
+ end
63
+
64
+ def sensible_default
65
+ ""
66
+ end
67
+
68
+ def trim_and_zero_terminate(str)
69
+ result = binary_string(str)
70
+ truncate_after_first_zero_byte!(result)
71
+ trim_to!(result, eval_parameter(:max_length))
72
+ append_zero_byte_if_needed!(result)
73
+ result
74
+ end
75
+
76
+ def truncate_after_first_zero_byte!(str)
77
+ str.sub!(/([^\0]*\0).*/, '\1')
78
+ end
79
+
80
+ def trim_to!(str, max_length = nil)
81
+ if max_length
82
+ max_length = 1 if max_length < 1
83
+ str.slice!(max_length)
84
+ if str.length == max_length and str[-1, 1] != "\0"
85
+ str[-1, 1] = "\0"
86
+ end
87
+ end
88
+ end
89
+
90
+ def append_zero_byte_if_needed!(str)
91
+ if str.length == 0 or str[-1, 1] != "\0"
92
+ str << "\0"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,388 @@
1
+ require 'bindata/base'
2
+
3
+ module BinData
4
+
5
+ class Base
6
+ optional_parameter :onlyif # Used by Struct
7
+ end
8
+
9
+ # A Struct is an ordered collection of named data objects.
10
+ #
11
+ # require 'bindata'
12
+ #
13
+ # class Tuple < BinData::Record
14
+ # int8 :x
15
+ # int8 :y
16
+ # int8 :z
17
+ # end
18
+ #
19
+ # obj = BinData::Struct.new(:hide => :a,
20
+ # :fields => [ [:int32le, :a],
21
+ # [:int16le, :b],
22
+ # [:tuple, :s] ])
23
+ # obj.field_names =># ["b", "s"]
24
+ #
25
+ #
26
+ # == Parameters
27
+ #
28
+ # Parameters may be provided at initialisation to control the behaviour of
29
+ # an object. These params are:
30
+ #
31
+ # <tt>:fields</tt>:: An array specifying the fields for this struct.
32
+ # Each element of the array is of the form [type, name,
33
+ # params]. Type is a symbol representing a registered
34
+ # type. Name is the name of this field. Params is an
35
+ # optional hash of parameters to pass to this field
36
+ # when instantiating it. If name is "" or nil, then
37
+ # that field is anonymous and behaves as a hidden field.
38
+ # <tt>:hide</tt>:: A list of the names of fields that are to be hidden
39
+ # from the outside world. Hidden fields don't appear
40
+ # in #snapshot or #field_names but are still accessible
41
+ # by name.
42
+ # <tt>:endian</tt>:: Either :little or :big. This specifies the default
43
+ # endian of any numerics in this struct, or in any
44
+ # nested data objects.
45
+ #
46
+ # == Field Parameters
47
+ #
48
+ # Fields may have have extra parameters as listed below:
49
+ #
50
+ # [<tt>:onlyif</tt>] Used to indicate a data object is optional.
51
+ # if +false+, this object will not be included in any
52
+ # calls to #read, #write, #num_bytes or #snapshot.
53
+ class Struct < BinData::Base
54
+
55
+ mandatory_parameter :fields
56
+ optional_parameters :endian, :hide
57
+
58
+ # These reserved words may not be used as field names
59
+ RESERVED = Hash[*
60
+ (Hash.instance_methods +
61
+ %w{alias and begin break case class def defined do else elsif
62
+ end ensure false for if in module next nil not or redo
63
+ rescue retry return self super then true undef unless until
64
+ when while yield} +
65
+ %w{array element index value} ).collect { |name| name.to_sym }.
66
+ uniq.collect { |key| [key, true] }.flatten
67
+ ]
68
+
69
+ class << self
70
+
71
+ def sanitize_parameters!(params) #:nodoc:
72
+ sanitize_endian(params)
73
+ sanitize_fields(params)
74
+ sanitize_hide(params)
75
+ end
76
+
77
+ #-------------
78
+ private
79
+
80
+ def sanitize_endian(params)
81
+ if params.needs_sanitizing?(:endian)
82
+ endian = params.create_sanitized_endian(params[:endian])
83
+ params[:endian] = endian
84
+ params.endian = endian # sync params[:endian] and params.endian
85
+ end
86
+ end
87
+
88
+ def sanitize_fields(params)
89
+ if params.needs_sanitizing?(:fields)
90
+ fields = params[:fields]
91
+
92
+ params[:fields] = params.create_sanitized_fields
93
+ fields.each do |ftype, fname, fparams|
94
+ params[:fields].add_field(ftype, fname, fparams)
95
+ end
96
+
97
+ field_names = sanitized_field_names(params[:fields])
98
+ ensure_field_names_are_valid(field_names)
99
+ end
100
+ end
101
+
102
+ def sanitize_hide(params)
103
+ if params.needs_sanitizing?(:hide) and params.has_parameter?(:fields)
104
+ field_names = sanitized_field_names(params[:fields])
105
+ hfield_names = hidden_field_names(params[:hide])
106
+
107
+ params[:hide] = (hfield_names & field_names)
108
+ end
109
+ end
110
+
111
+ def sanitized_field_names(sanitized_fields)
112
+ sanitized_fields.field_names.compact
113
+ end
114
+
115
+ def hidden_field_names(hidden)
116
+ (hidden || []).collect do |h|
117
+ unless Symbol === h
118
+ warn "Hidden field '#{h}' should be provided as a symbol. Using strings is deprecated"
119
+ end
120
+ h.to_sym
121
+ end
122
+ end
123
+
124
+ def ensure_field_names_are_valid(field_names)
125
+ reserved_names = RESERVED
126
+
127
+ field_names.each do |name|
128
+ if self.class.method_defined?(name)
129
+ raise NameError.new("Rename field '#{name}' in #{self}, " +
130
+ "as it shadows an existing method.", name)
131
+ end
132
+ if reserved_names.include?(name)
133
+ raise NameError.new("Rename field '#{name}' in #{self}, " +
134
+ "as it is a reserved name.", name)
135
+ end
136
+ if field_names.count(name) != 1
137
+ raise NameError.new("field '#{name}' in #{self}, " +
138
+ "is defined multiple times.", name)
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def initialize_shared_instance
145
+ @field_names = get_parameter(:fields).field_names.freeze
146
+ end
147
+
148
+ def initialize_instance
149
+ @field_objs = []
150
+ end
151
+
152
+ def clear #:nodoc:
153
+ @field_objs.each { |f| f.clear unless f.nil? }
154
+ end
155
+
156
+ def clear? #:nodoc:
157
+ @field_objs.all? { |f| f.nil? or f.clear? }
158
+ end
159
+
160
+ def assign(val)
161
+ clear
162
+ assign_fields(val)
163
+ end
164
+
165
+ def snapshot
166
+ snapshot = Snapshot.new
167
+ field_names.each do |name|
168
+ obj = find_obj_for_name(name)
169
+ snapshot[name] = obj.snapshot if include_obj(obj)
170
+ end
171
+ snapshot
172
+ end
173
+
174
+ # Returns a list of the names of all fields accessible through this
175
+ # object. +include_hidden+ specifies whether to include hidden names
176
+ # in the listing.
177
+ def field_names(include_hidden = false)
178
+ if include_hidden
179
+ @field_names.compact
180
+ else
181
+ hidden = get_parameter(:hide) || []
182
+ @field_names.compact - hidden
183
+ end.collect { |x| x.to_s }
184
+ end
185
+
186
+ def respond_to?(symbol, include_private = false) #:nodoc:
187
+ @field_names.include?(base_field_name(symbol)) || super
188
+ end
189
+
190
+ def method_missing(symbol, *args, &block) #:nodoc:
191
+ obj = find_obj_for_name(symbol)
192
+ if obj
193
+ invoke_field(obj, symbol, args)
194
+ else
195
+ super
196
+ end
197
+ end
198
+
199
+ def debug_name_of(child) #:nodoc:
200
+ field_name = @field_names[find_index_of(child)]
201
+ "#{debug_name}.#{field_name}"
202
+ end
203
+
204
+ def offset_of(child) #:nodoc:
205
+ instantiate_all_objs
206
+ sum = sum_num_bytes_below_index(find_index_of(child))
207
+ child.do_num_bytes.is_a?(Integer) ? sum.ceil : sum.floor
208
+ end
209
+
210
+ def do_read(io) #:nodoc:
211
+ instantiate_all_objs
212
+ @field_objs.each { |f| f.do_read(io) if include_obj(f) }
213
+ end
214
+
215
+ def do_write(io) #:nodoc
216
+ instantiate_all_objs
217
+ @field_objs.each { |f| f.do_write(io) if include_obj(f) }
218
+ end
219
+
220
+ def do_num_bytes #:nodoc:
221
+ instantiate_all_objs
222
+ sum_num_bytes_for_all_fields
223
+ end
224
+
225
+ def [](key)
226
+ find_obj_for_name(key)
227
+ end
228
+
229
+ def []=(key, value)
230
+ obj = find_obj_for_name(key)
231
+ if obj
232
+ obj.assign(value)
233
+ end
234
+ end
235
+
236
+ def has_key?(key)
237
+ @field_names.index(base_field_name(key))
238
+ end
239
+
240
+ def each_pair
241
+ @field_names.compact.each do |name|
242
+ yield [name, find_obj_for_name(name)]
243
+ end
244
+ end
245
+
246
+ #---------------
247
+ private
248
+
249
+ def base_field_name(name)
250
+ name.to_s.chomp("=").to_sym
251
+ end
252
+
253
+ def invoke_field(obj, symbol, args)
254
+ name = symbol.to_s
255
+ is_writer = (name[-1, 1] == "=")
256
+
257
+ if is_writer
258
+ obj.assign(*args)
259
+ else
260
+ obj
261
+ end
262
+ end
263
+
264
+ def find_index_of(obj)
265
+ @field_objs.index { |el| el.equal?(obj) }
266
+ end
267
+
268
+ def find_obj_for_name(name)
269
+ index = @field_names.index(base_field_name(name))
270
+ if index
271
+ instantiate_obj_at(index)
272
+ @field_objs[index]
273
+ else
274
+ nil
275
+ end
276
+ end
277
+
278
+ def instantiate_all_objs
279
+ @field_names.each_index { |i| instantiate_obj_at(i) }
280
+ end
281
+
282
+ def instantiate_obj_at(index)
283
+ if @field_objs[index].nil?
284
+ field = get_parameter(:fields)[index]
285
+ @field_objs[index] = field.instantiate(nil, self)
286
+ end
287
+ end
288
+
289
+ def assign_fields(val)
290
+ src = as_stringified_hash(val)
291
+
292
+ @field_names.compact.each do |name|
293
+ obj = find_obj_for_name(name)
294
+ if obj and src.has_key?(name)
295
+ obj.assign(src[name])
296
+ end
297
+ end
298
+ end
299
+
300
+ def as_stringified_hash(val)
301
+ if BinData::Struct === val
302
+ val
303
+ elsif val.nil?
304
+ {}
305
+ else
306
+ hash = Snapshot.new
307
+ val.each_pair { |k,v| hash[k] = v }
308
+ hash
309
+ end
310
+ end
311
+
312
+ def sum_num_bytes_for_all_fields
313
+ sum_num_bytes_below_index(@field_objs.length)
314
+ end
315
+
316
+ def sum_num_bytes_below_index(index)
317
+ sum = 0
318
+ (0...index).each do |i|
319
+ obj = @field_objs[i]
320
+ if include_obj(obj)
321
+ nbytes = obj.do_num_bytes
322
+ sum = (nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes
323
+ end
324
+ end
325
+
326
+ sum
327
+ end
328
+
329
+ def include_obj(obj)
330
+ not obj.has_parameter?(:onlyif) or obj.eval_parameter(:onlyif)
331
+ end
332
+
333
+ if RUBY_VERSION <= "1.9"
334
+ module OrderedHash #:nodoc:
335
+ def keys
336
+ @order ||= []
337
+ k = super
338
+ @order & k
339
+ end
340
+
341
+ def []=(key, value)
342
+ @order ||= []
343
+ @order << key
344
+ super(key, value)
345
+ end
346
+
347
+ def each
348
+ keys.each do |k|
349
+ yield [k, self[k]]
350
+ end
351
+ end
352
+
353
+ def each_pair
354
+ each do |el|
355
+ yield *el
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ # A hash that can be accessed via attributes.
362
+ class Snapshot < ::Hash #:nodoc:
363
+ include OrderedHash if RUBY_VERSION <= "1.9"
364
+
365
+ def has_key?(key)
366
+ super(key.to_s)
367
+ end
368
+
369
+ def [](key)
370
+ super(key.to_s)
371
+ end
372
+
373
+ def []=(key, value)
374
+ if value != nil
375
+ super(key.to_s, value)
376
+ end
377
+ end
378
+
379
+ def respond_to?(symbol, include_private = false)
380
+ has_key?(symbol) || super
381
+ end
382
+
383
+ def method_missing(symbol, *args)
384
+ self[symbol] || super
385
+ end
386
+ end
387
+ end
388
+ end