bindata 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

Files changed (47) hide show
  1. data/ChangeLog +18 -0
  2. data/NEWS +59 -0
  3. data/README +22 -23
  4. data/TODO +18 -12
  5. data/examples/gzip.rb +4 -4
  6. data/lib/bindata.rb +4 -3
  7. data/lib/bindata/array.rb +202 -132
  8. data/lib/bindata/base.rb +147 -166
  9. data/lib/bindata/{single.rb → base_primitive.rb} +82 -56
  10. data/lib/bindata/bits.rb +31 -770
  11. data/lib/bindata/choice.rb +157 -82
  12. data/lib/bindata/float.rb +25 -27
  13. data/lib/bindata/int.rb +144 -177
  14. data/lib/bindata/io.rb +59 -49
  15. data/lib/bindata/lazy.rb +80 -50
  16. data/lib/bindata/params.rb +134 -26
  17. data/lib/bindata/{single_value.rb → primitive.rb} +71 -64
  18. data/lib/bindata/{multi_value.rb → record.rb} +52 -70
  19. data/lib/bindata/registry.rb +49 -17
  20. data/lib/bindata/rest.rb +6 -10
  21. data/lib/bindata/sanitize.rb +55 -70
  22. data/lib/bindata/string.rb +60 -42
  23. data/lib/bindata/stringz.rb +34 -35
  24. data/lib/bindata/struct.rb +197 -152
  25. data/lib/bindata/trace.rb +35 -0
  26. data/spec/array_spec.rb +128 -112
  27. data/spec/{single_spec.rb → base_primitive_spec.rb} +102 -61
  28. data/spec/base_spec.rb +190 -185
  29. data/spec/bits_spec.rb +126 -98
  30. data/spec/choice_spec.rb +89 -98
  31. data/spec/example.rb +19 -0
  32. data/spec/float_spec.rb +28 -44
  33. data/spec/int_spec.rb +217 -127
  34. data/spec/io_spec.rb +41 -24
  35. data/spec/lazy_spec.rb +95 -49
  36. data/spec/primitive_spec.rb +191 -0
  37. data/spec/{multi_value_spec.rb → record_spec.rb} +124 -89
  38. data/spec/registry_spec.rb +53 -12
  39. data/spec/rest_spec.rb +2 -3
  40. data/spec/sanitize_spec.rb +47 -73
  41. data/spec/spec_common.rb +13 -1
  42. data/spec/string_spec.rb +34 -23
  43. data/spec/stringz_spec.rb +10 -18
  44. data/spec/struct_spec.rb +91 -63
  45. data/spec/system_spec.rb +291 -0
  46. metadata +12 -8
  47. data/spec/single_value_spec.rb +0 -131
@@ -6,7 +6,7 @@ module BinData
6
6
  #
7
7
  # require 'bindata'
8
8
  #
9
- # class Tuple < BinData::MultiValue
9
+ # class Tuple < BinData::Record
10
10
  # int8 :x
11
11
  # int8 :y
12
12
  # int8 :z
@@ -37,23 +37,34 @@ module BinData
37
37
  # <tt>:endian</tt>:: Either :little or :big. This specifies the default
38
38
  # endian of any numerics in this struct, or in any
39
39
  # nested data objects.
40
+ #
41
+ # == Field Parameters
42
+ #
43
+ # Fields may have have extra parameters as listed below:
44
+ #
45
+ # [<tt>:onlyif</tt>] Used to indicate a data object is optional.
46
+ # if +false+, this object will not be included in any
47
+ # calls to #read, #write, #num_bytes or #snapshot.
40
48
  class Struct < BinData::Base
41
49
 
50
+ register(self.name, self)
51
+
42
52
  # These reserved words may not be used as field names
43
53
  RESERVED = (::Hash.instance_methods +
44
54
  %w{alias and begin break case class def defined do else elsif
45
55
  end ensure false for if in module next nil not or redo
46
56
  rescue retry return self super then true undef unless until
47
57
  when while yield} +
48
- %w{array element index offset value} ).uniq
49
-
50
- # Register this class
51
- register(self.name, self)
58
+ %w{array element index value} ).uniq
52
59
 
53
60
  # A hash that can be accessed via attributes.
54
61
  class Snapshot < Hash #:nodoc:
62
+ def respond_to?(symbol, include_private = false)
63
+ has_key?(symbol.to_s) || super(symbol, include_private)
64
+ end
65
+
55
66
  def method_missing(symbol, *args)
56
- self[symbol.id2name] || super
67
+ self[symbol.to_s] || super
57
68
  end
58
69
  end
59
70
 
@@ -61,91 +72,97 @@ module BinData
61
72
  #### DEPRECATION HACK to warn about inheriting from BinData::Struct
62
73
  #
63
74
  def inherited(subclass) #:nodoc:
64
- if subclass != MultiValue
75
+ if subclass != Record
65
76
  # warn about deprecated method - remove before releasing 1.0
66
- fail "error: inheriting from BinData::Struct has been deprecated. Inherit from BinData::MultiValue instead."
77
+ fail "error: inheriting from BinData::Struct has been deprecated. Inherit from BinData::Record instead."
67
78
  end
68
79
  end
69
80
  #
70
81
  #### DEPRECATION HACK to allow inheriting from BinData::Struct
71
82
 
72
83
 
73
- # Ensures that +params+ is of the form expected by #initialize.
74
84
  def sanitize_parameters!(sanitizer, params)
75
- # possibly override endian
76
- endian = params[:endian]
77
- if endian != nil
85
+ ensure_valid_endian(params)
86
+
87
+ if params.has_key?(:fields)
88
+ sfields = sanitized_fields(sanitizer, params[:fields], params[:endian])
89
+ field_names = sanitized_field_names(sfields)
90
+ hfield_names = hidden_field_names(params[:hide])
91
+
92
+ ensure_field_names_are_valid(field_names)
93
+
94
+ params[:fields] = sfields
95
+ params[:hide] = (hfield_names & field_names)
96
+ end
97
+
98
+ super(sanitizer, params)
99
+ end
100
+
101
+ #-------------
102
+ private
103
+
104
+ def ensure_valid_endian(params)
105
+ if params.has_key?(:endian)
106
+ endian = params[:endian]
78
107
  unless [:little, :big].include?(endian)
79
108
  raise ArgumentError, "unknown value for endian '#{endian}'"
80
109
  end
81
-
82
- params[:endian] = endian
83
110
  end
111
+ end
84
112
 
85
- if params.has_key?(:fields)
86
- sanitizer.with_endian(endian) do
87
- # ensure names of fields are strings and that params is sanitized
88
- all_fields = params[:fields].collect do |ftype, fname, fparams|
89
- fname = fname.to_s
90
- klass = sanitizer.lookup_klass(ftype)
91
- sanitized_fparams = sanitizer.sanitize_params(klass, fparams)
92
- [klass, fname, sanitized_fparams]
93
- end
94
- params[:fields] = all_fields
113
+ def sanitized_fields(sanitizer, fields, endian)
114
+ result = nil
115
+ sanitizer.with_endian(endian) do
116
+ result = fields.collect do |ftype, fname, fparams|
117
+ sanitized_field(sanitizer, ftype, fname, fparams)
95
118
  end
119
+ end
120
+ result
121
+ end
96
122
 
97
- # now params are sanitized, check that parameter names are okay
98
- field_names = []
99
- instance_methods = self.instance_methods
100
- reserved_names = RESERVED
101
-
102
- params[:fields].each do |fklass, fname, fparams|
123
+ def sanitized_field(sanitizer, ftype, fname, fparams)
124
+ fname = fname.to_s
125
+ fclass = sanitizer.lookup_class(ftype)
126
+ sanitized_fparams = sanitizer.sanitized_params(fclass, fparams)
127
+ [fclass, fname, sanitized_fparams]
128
+ end
103
129
 
104
- # check that name doesn't shadow an existing method
105
- if instance_methods.include?(fname)
106
- raise NameError.new("Rename field '#{fname}' in #{self}, " +
107
- "as it shadows an existing method.", fname)
108
- end
130
+ def sanitized_field_names(sanitized_fields)
131
+ sanitized_fields.collect { |fclass, fname, fparams| fname }
132
+ end
109
133
 
110
- # check that name isn't reserved
111
- if reserved_names.include?(fname)
112
- raise NameError.new("Rename field '#{fname}' in #{self}, " +
113
- "as it is a reserved name.", fname)
114
- end
134
+ def hidden_field_names(hidden)
135
+ (hidden || []).collect { |h| h.to_s }
136
+ end
115
137
 
116
- # check for multiple definitions
117
- if field_names.include?(fname)
118
- raise NameError.new("field '#{fname}' in #{self}, " +
119
- "is defined multiple times.", fname)
120
- end
138
+ def ensure_field_names_are_valid(field_names)
139
+ instance_methods = self.instance_methods
140
+ reserved_names = RESERVED
121
141
 
122
- field_names << fname
142
+ field_names.each do |name|
143
+ if instance_methods.include?(name)
144
+ raise NameError.new("Rename field '#{name}' in #{self}, " +
145
+ "as it shadows an existing method.", name)
123
146
  end
124
-
125
- # collect all hidden names that correspond to a field name
126
- hide = []
127
- if params.has_key?(:hide)
128
- hidden = (params[:hide] || []).collect { |h| h.to_s }
129
- all_field_names = params[:fields].collect { |k,n,p| n }
130
- hide = hidden & all_field_names
147
+ if reserved_names.include?(name)
148
+ raise NameError.new("Rename field '#{name}' in #{self}, " +
149
+ "as it is a reserved name.", name)
150
+ end
151
+ if field_names.count(name) != 1
152
+ raise NameError.new("field '#{name}' in #{self}, " +
153
+ "is defined multiple times.", name)
131
154
  end
132
- params[:hide] = hide
133
155
  end
134
-
135
- super(sanitizer, params)
136
156
  end
137
157
  end
138
158
 
139
- # These are the parameters used by this class.
140
- bindata_mandatory_parameter :fields
141
- bindata_optional_parameters :endian, :hide
159
+ mandatory_parameter :fields
160
+ optional_parameters :endian, :hide
142
161
 
143
- # Creates a new Struct.
144
162
  def initialize(params = {}, parent = nil)
145
163
  super(params, parent)
146
164
 
147
- # extract field names but don't instantiate the fields
148
- @field_names = no_eval_param(:fields).collect { |k, n, p| n }
165
+ @field_names = get_parameter(:fields).collect { |c, n, p| n }
149
166
  @field_objs = []
150
167
  end
151
168
 
@@ -155,7 +172,8 @@ module BinData
155
172
  if name.nil?
156
173
  @field_objs.each { |f| f.clear unless f.nil? }
157
174
  else
158
- obj = find_obj_for_name(name.to_s)
175
+ warn "'obj.clear(name)' is deprecated. Replacing with 'obj.name.clear'"
176
+ obj = find_obj_for_name(name)
159
177
  obj.clear unless obj.nil?
160
178
  end
161
179
  end
@@ -164,149 +182,176 @@ module BinData
164
182
  # is given, returns whether all fields are clear.
165
183
  def clear?(name = nil)
166
184
  if name.nil?
167
- @field_objs.each do |f|
168
- return false unless f.nil? or f.clear?
169
- end
170
- true
185
+ @field_objs.inject(true) { |all_clear, f| all_clear and (f.nil? or f.clear?) }
171
186
  else
172
- obj = find_obj_for_name(name.to_s)
187
+ warn "'obj.clear?(name)' is deprecated. Replacing with 'obj.name.clear?'"
188
+ obj = find_obj_for_name(name)
173
189
  obj.nil? ? true : obj.clear?
174
190
  end
175
191
  end
176
192
 
177
- # Returns whether this data object contains a single value. Single
178
- # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
179
- def single_value?
180
- return false
181
- end
182
-
183
193
  # Returns a list of the names of all fields accessible through this
184
194
  # object. +include_hidden+ specifies whether to include hidden names
185
195
  # in the listing.
186
196
  def field_names(include_hidden = false)
187
- # collect field names
188
- names = []
189
- hidden = no_eval_param(:hide)
190
- @field_names.each do |name|
191
- if include_hidden or not hidden.include?(name)
192
- names << name
193
- end
197
+ if include_hidden
198
+ @field_names.dup
199
+ else
200
+ hidden = get_parameter(:hide)
201
+ @field_names - hidden
194
202
  end
195
- names
196
203
  end
197
204
 
198
- # To be called after calling #read.
199
- def done_read
200
- @field_objs.each { |f| f.done_read unless f.nil? }
205
+ def respond_to?(symbol, include_private = false)
206
+ super(symbol, include_private) ||
207
+ field_names(true).include?(symbol.to_s.chomp("="))
201
208
  end
202
209
 
203
- def offset_of(field)
204
- idx = @field_names.index(field.to_s)
205
- if idx
206
- instantiate_all
207
-
208
- offset = 0
209
- (0...idx).each do |i|
210
- this_offset = @field_objs[i].do_num_bytes
211
- if ::Float === offset and ::Integer === this_offset
212
- offset = offset.ceil
213
- end
214
- offset += this_offset
215
- end
216
- offset
210
+ def method_missing(symbol, *args, &block)
211
+ obj = find_obj_for_name(symbol)
212
+ if obj
213
+ invoke_field(obj, symbol, args)
217
214
  else
218
- nil
215
+ super
219
216
  end
220
217
  end
221
218
 
222
- # Override to include field names
223
- alias_method :orig_respond_to?, :respond_to?
224
- def respond_to?(symbol, include_private = false)
225
- orig_respond_to?(symbol, include_private) ||
226
- field_names(true).include?(symbol.id2name.chomp("="))
219
+ def debug_name_of(child)
220
+ field_name = @field_names[find_index_of(child)]
221
+ "#{debug_name}.#{field_name}"
227
222
  end
228
223
 
229
- def method_missing(symbol, *args, &block)
230
- name = symbol.id2name
224
+ def offset_of(child)
225
+ if child.class == ::String
226
+ fail "error: 'offset_of(\"fieldname\")' is deprecated. Use 'fieldname.offset' instead"
227
+ end
228
+
229
+ instantiate_all_objs
230
+ sum = sum_num_bytes_below_index(find_index_of(child))
231
+ child_offset = (::Integer === child.do_num_bytes) ? sum.ceil : sum.floor
232
+
233
+ offset + child_offset
234
+ end
231
235
 
236
+ #---------------
237
+ private
238
+
239
+ def invoke_field(obj, symbol, args)
240
+ name = symbol.to_s
232
241
  is_writer = (name[-1, 1] == "=")
233
- name.chomp!("=")
234
-
235
- # find the object that is responsible for name
236
- if (obj = find_obj_for_name(name))
237
- # pass on the request
238
- if obj.single_value? and is_writer
239
- obj.value = *args
240
- elsif obj.single_value?
241
- obj.value
242
- else
243
- obj
244
- end
242
+
243
+ if is_writer
244
+ obj.assign(*args)
245
245
  else
246
- super
246
+ obj
247
247
  end
248
248
  end
249
249
 
250
- #---------------
251
- private
250
+ def find_index_of(obj)
251
+ @field_objs.find_index { |el| el.equal?(obj) }
252
+ end
252
253
 
253
- # Returns the data object that stores values for +name+.
254
254
  def find_obj_for_name(name)
255
- idx = @field_names.index(name)
256
- if idx
257
- instantiate_obj(idx)
258
- @field_objs[idx].obj
255
+ field_name = name.to_s.chomp("=")
256
+ index = @field_names.find_index(field_name)
257
+ if index
258
+ instantiate_obj_at(index)
259
+ @field_objs[index]
259
260
  else
260
261
  nil
261
262
  end
262
263
  end
263
264
 
264
- # Instantiates all fields.
265
- def instantiate_all
266
- @field_names.each_with_index { |name, i| instantiate_obj(i) }
265
+ def instantiate_all_objs
266
+ @field_names.each_index { |i| instantiate_obj_at(i) }
267
267
  end
268
268
 
269
- # Instantiates the field object at position +idx+.
270
- def instantiate_obj(idx)
271
- if @field_objs[idx].nil?
272
- fklass, fname, fparams = no_eval_param(:fields)[idx]
273
- @field_objs[idx] = fklass.new(fparams, self)
269
+ def instantiate_obj_at(index)
270
+ if @field_objs[index].nil?
271
+ fclass, fname, fparams = get_parameter(:fields)[index]
272
+ @field_objs[index] = fclass.new(fparams, self)
274
273
  end
275
274
  end
276
275
 
277
- # Reads the values for all fields in this object from +io+.
278
276
  def _do_read(io)
279
- instantiate_all
280
- @field_objs.each { |f| f.do_read(io) }
277
+ instantiate_all_objs
278
+ @field_objs.each { |f| f.do_read(io) if include_obj(f) }
279
+ end
280
+
281
+ def _done_read
282
+ @field_objs.each { |f| f.done_read if include_obj(f) }
281
283
  end
282
284
 
283
- # Writes the values for all fields in this object to +io+.
284
285
  def _do_write(io)
285
- instantiate_all
286
- @field_objs.each { |f| f.do_write(io) }
286
+ instantiate_all_objs
287
+ @field_objs.each { |f| f.do_write(io) if include_obj(f) }
287
288
  end
288
289
 
289
- # Returns the number of bytes it will take to write the field represented
290
- # by +name+. If +name+ is nil then returns the number of bytes required
291
- # to write all fields.
292
290
  def _do_num_bytes(name)
293
291
  if name.nil?
294
- instantiate_all
295
- (@field_objs.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
292
+ instantiate_all_objs
293
+ sum_num_bytes_for_all_fields.ceil
296
294
  else
297
- obj = find_obj_for_name(name.to_s)
295
+ warn "'obj.num_bytes(name)' is deprecated. Replacing with 'obj.name.num_bytes'"
296
+ obj = find_obj_for_name(name)
298
297
  obj.nil? ? 0 : obj.do_num_bytes
299
298
  end
300
299
  end
301
300
 
302
- # Returns a snapshot of this struct as a hash.
301
+ def _assign(val)
302
+ clear
303
+ assign_fields(as_snapshot(val))
304
+ end
305
+
306
+ def as_snapshot(val)
307
+ if val.class == Hash
308
+ snapshot = Snapshot.new
309
+ val.each_pair { |k,v| snapshot[k.to_s] = v unless v.nil? }
310
+ snapshot
311
+ elsif val.nil?
312
+ Snapshot.new
313
+ else
314
+ val
315
+ end
316
+ end
317
+
318
+ def assign_fields(snapshot)
319
+ field_names(true).each do |name|
320
+ obj = find_obj_for_name(name)
321
+ if obj and snapshot.respond_to?(name)
322
+ obj.assign(snapshot.__send__(name))
323
+ end
324
+ end
325
+ end
326
+
303
327
  def _snapshot
304
- hash = Snapshot.new
328
+ snapshot = Snapshot.new
305
329
  field_names.each do |name|
306
- ss = find_obj_for_name(name).snapshot
307
- hash[name] = ss unless ss.nil?
330
+ obj = find_obj_for_name(name)
331
+ snapshot[name] = obj.snapshot if include_obj(obj)
332
+ end
333
+ snapshot
334
+ end
335
+
336
+ def sum_num_bytes_for_all_fields
337
+ sum_num_bytes_below_index(@field_objs.length)
338
+ end
339
+
340
+ def sum_num_bytes_below_index(index)
341
+ sum = 0
342
+ (0...index).each do |i|
343
+ obj = @field_objs[i]
344
+ if include_obj(obj)
345
+ nbytes = obj.do_num_bytes
346
+ sum = ((::Integer === nbytes) ? sum.ceil : sum) + nbytes
347
+ end
308
348
  end
309
- hash
349
+
350
+ sum
351
+ end
352
+
353
+ def include_obj(obj)
354
+ not obj.has_parameter?(:onlyif) or obj.eval_parameter(:onlyif)
310
355
  end
311
356
  end
312
357
  end