jbangert-bindata 1.5.0

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 (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