bindata 0.8.1 → 0.9.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.
@@ -7,6 +7,17 @@ module BinData
7
7
  # For convenience, the zero terminator is not necessary when setting the
8
8
  # value. Likewise, the returned value will not be zero terminated.
9
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.value #=> "abcd"
18
+ # obj.num_bytes #=> 5
19
+ # obj.to_s #=> "abcd\000"
20
+ #
10
21
  # == Parameters
11
22
  #
12
23
  # Stringz objects accept all the params that BinData::Single
@@ -15,6 +26,10 @@ module BinData
15
26
  # <tt>:max_length</tt>:: The maximum length of the string including the zero
16
27
  # byte.
17
28
  class Stringz < Single
29
+
30
+ # Register this class
31
+ register(self.name, self)
32
+
18
33
  # These are the parameters used by this class.
19
34
  optional_parameters :max_length
20
35
 
@@ -1,41 +1,24 @@
1
1
  require 'bindata/base'
2
+ require 'bindata/sanitize'
2
3
 
3
4
  module BinData
4
5
  # A Struct is an ordered collection of named data objects.
5
6
  #
6
7
  # require 'bindata'
7
8
  #
8
- # class Tuple < BinData::Struct
9
+ # class Tuple < BinData::MultiValue
9
10
  # int8 :x
10
11
  # int8 :y
11
12
  # int8 :z
12
13
  # end
13
14
  #
14
- # class SomeStruct < BinData::Struct
15
- # hide 'a'
16
- #
17
- # int32le :a
18
- # int16le :b
19
- # tuple nil
20
- # end
21
- #
22
- # obj = SomeStruct.new
15
+ # obj = BinData::Struct.new(:hide => :a,
16
+ # :fields => [ [:int32le, :a],
17
+ # [:int16le, :b],
18
+ # [:tuple, :nil] ])
23
19
  # obj.field_names =># ["b", "x", "y", "z"]
24
20
  #
25
21
  #
26
- # class PascalString < BinData::Struct
27
- # delegate :data
28
- #
29
- # uint8 :len, :value => lambda { data.length }
30
- # string :data, :read_length => :len
31
- # end
32
- #
33
- # str = PascalString.new
34
- # str.value = "a test string"
35
- # str.single_value? =># true
36
- # str.len =># 13
37
- # str.num_bytes =># 17
38
- #
39
22
  # == Parameters
40
23
  #
41
24
  # Parameters may be provided at initialisation to control the behaviour of
@@ -52,9 +35,14 @@ module BinData
52
35
  # from the outside world. Hidden fields don't appear
53
36
  # in #snapshot or #field_names but are still accessible
54
37
  # by name.
55
- # <tt>:delegate</tt>:: Forwards unknown methods calls and unknown params
56
- # to this field.
57
- class Struct < Base
38
+ # <tt>:endian</tt>:: Either :little or :big. This specifies the default
39
+ # endian of any numerics in this struct, or in any
40
+ # nested data objects.
41
+ class Struct < BinData::Base
42
+
43
+ # Register this class
44
+ register(self.name, self)
45
+
58
46
  # A hash that can be accessed via attributes.
59
47
  class Snapshot < Hash #:nodoc:
60
48
  def method_missing(symbol, *args)
@@ -62,18 +50,17 @@ module BinData
62
50
  end
63
51
  end
64
52
 
65
- # Register this class
66
- register(self.name, self)
67
-
68
53
  class << self
69
- # Register the names of all subclasses of this class.
54
+ #### DEPRECATION HACK to allow inheriting from BinData::Struct
55
+ #
70
56
  def inherited(subclass) #:nodoc:
71
- register(subclass.name, subclass)
72
- end
57
+ if subclass != MultiValue
58
+ # warn about deprecated method - remove before releasing 1.0
59
+ warn "warning: inheriting from BinData::Struct in deprecated. Inherit from BinData::MultiValue instead."
73
60
 
74
- # Returns or sets the endianess of numerics used in this stucture.
75
- # Endianess is applied to the fields of this structure.
76
- # Valid values are :little and :big.
61
+ register(subclass.name, subclass)
62
+ end
63
+ end
77
64
  def endian(endian = nil)
78
65
  @endian ||= nil
79
66
  if [:little, :big].include?(endian)
@@ -83,19 +70,6 @@ module BinData
83
70
  end
84
71
  @endian
85
72
  end
86
-
87
- # Returns the name of the delegate field for this struct. The delegate
88
- # is set to +name+ if given.
89
- def delegate(name=nil)
90
- @delegate ||= nil
91
- if name != nil
92
- @delegate = name.to_s
93
- end
94
- @delegate
95
- end
96
-
97
- # Returns the names of any hidden fields in this struct. Any given args
98
- # are appended to the hidden list.
99
73
  def hide(*args)
100
74
  # note that fields are stored in an instance variable not a class var
101
75
  @hide ||= []
@@ -105,119 +79,185 @@ module BinData
105
79
  end
106
80
  @hide
107
81
  end
108
-
109
- # Used to define fields for this structure.
82
+ def fields
83
+ @fields || []
84
+ end
110
85
  def method_missing(symbol, *args)
111
86
  name, params = args
112
87
 
113
88
  type = symbol
114
- name = name.to_s unless name.nil?
89
+ name = (name.nil? or name == "") ? nil : name.to_s
115
90
  params ||= {}
116
91
 
117
- if lookup(type).nil?
118
- raise TypeError, "unknown type '#{type}' for #{self}", caller
119
- end
120
-
121
92
  # note that fields are stored in an instance variable not a class var
122
-
123
- # check for duplicate names
124
93
  @fields ||= []
125
- if @fields.detect { |t, n, p| n == name and n != nil }
126
- raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
127
- end
128
94
 
129
- # check that name doesn't shadow an existing method
130
- if self.instance_methods.include?(name)
131
- raise NameError.new("", name),
132
- "field '#{name}' shadows an existing method", caller
95
+ # check that type is known
96
+ if lookup(type, endian).nil?
97
+ raise TypeError, "unknown type '#{type}' for #{self}", caller
133
98
  end
134
99
 
135
- # check that name isn't reserved
136
- if Hash.instance_methods.include?(name) and delegate.nil?
137
- raise NameError.new("", name),
138
- "field '#{name}' is a reserved name", caller
100
+ # check that name is okay
101
+ if name != nil
102
+ # check for duplicate names
103
+ @fields.each do |t, n, p|
104
+ if n == name
105
+ raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
106
+ end
107
+ end
108
+
109
+ # check that name doesn't shadow an existing method
110
+ if self.instance_methods.include?(name)
111
+ raise NameError.new("", name),
112
+ "field '#{name}' shadows an existing method", caller
113
+ end
114
+
115
+ # check that name isn't reserved
116
+ if ::Hash.instance_methods.include?(name)
117
+ raise NameError.new("", name),
118
+ "field '#{name}' is a reserved name", caller
119
+ end
139
120
  end
140
121
 
141
122
  # remember this field. These fields will be recalled upon creating
142
123
  # an instance of this class
143
124
  @fields.push([type, name, params])
144
125
  end
126
+ def deprecated_hack(params, endian = nil)
127
+ params = params.dup
145
128
 
146
- # Returns all stored fields. Should only be called by #cleaned_params.
147
- def fields
148
- @fields || []
129
+ # possibly override endian
130
+ endian = params[:endian] || self.endian || endian
131
+ unless endian.nil?
132
+ params[:endian] = endian
133
+ end
134
+
135
+ params[:fields] = params[:fields] || self.fields
136
+ params[:hide] = params[:hide] || self.hide
137
+
138
+ [params, endian]
149
139
  end
150
- end
140
+ #
141
+ #### DEPRECATION HACK to allow inheriting from BinData::Struct
142
+
143
+
144
+ # Returns a sanitized +params+ that is of the form expected
145
+ # by #initialize.
146
+ def sanitize_parameters(params, endian = nil)
147
+ #### DEPRECATION HACK to allow inheriting from BinData::Struct
148
+ #
149
+ params, endian = deprecated_hack(params, endian)
150
+ #
151
+ #### DEPRECATION HACK to allow inheriting from BinData::Struct
152
+
153
+ params = params.dup
154
+
155
+ # possibly override endian
156
+ endian = params[:endian] || endian
157
+ if endian != nil
158
+ unless [:little, :big].include?(endian)
159
+ raise ArgumentError, "unknown value for endian '#{endian}'"
160
+ end
151
161
 
152
- # These are the parameters used by this class.
153
- mandatory_parameter :fields
154
- optional_parameters :endian, :hide, :delegate
162
+ params[:endian] = endian
163
+ end
155
164
 
156
- # Creates a new Struct.
157
- def initialize(params = {}, env = nil)
158
- super(cleaned_params(params), env)
159
-
160
- all_methods = methods
161
- all_reserved_methods = nil
162
- delegate_name = param(:delegate)
163
-
164
- # get all reserved field names
165
- res = param(:fields).find { |type, name, params| name == delegate_name }
166
- if res
167
- # all methods and field_names of the delegate are reserved.
168
- klass_name = res[0]
169
- delegate_klass = klass_lookup(klass_name)
170
- if delegate_klass.nil?
171
- raise TypeError, "unknown type '#{klass_name} for #{self}"
165
+ if params.has_key?(:fields)
166
+ # ensure the names of fields are strings and that params is sanitized
167
+ all_fields = params[:fields].collect do |ftype, fname, fparams|
168
+ fname = fname.nil? ? "" : fname.to_s
169
+ klass = lookup(ftype, endian)
170
+ raise TypeError, "unknown type '#{ftype}' for #{self}" if klass.nil?
171
+ [klass, fname, SanitizedParameters.new(klass, fparams, endian)]
172
+ end
173
+ params[:fields] = all_fields
174
+
175
+ # collect all hidden names that correspond to a field name
176
+ hide = []
177
+ if params.has_key?(:hide)
178
+ hidden = params[:hide] || []
179
+ hidden.each do |h|
180
+ next if h.nil? or h == ""
181
+ h = h.to_s
182
+ hide << h if all_fields.find { |k,n,p| n == h }
183
+ end
184
+ end
185
+ params[:hide] = hide
172
186
  end
173
187
 
174
- delegate_params = res[2]
175
- delegate = delegate_klass.new(delegate_params, create_env)
176
- all_reserved_methods = delegate.methods + delegate.field_names -
177
- all_methods
188
+ # obtain SanitizedParameters
189
+ params = super(params, endian)
190
+
191
+ # now params are sanitized, check that parameter names are okay
192
+
193
+ field_names = []
194
+ instance_methods = self.instance_methods
195
+ reserved_names = ::Hash.instance_methods
196
+
197
+ params[:fields].each do |fklass, fname, fparams|
198
+
199
+ # check that name doesn't shadow an existing method
200
+ if instance_methods.include?(fname)
201
+ raise NameError.new("field '#{fname}' shadows an existing method in #{self}. Rename it.", fname)
202
+ end
203
+
204
+ # check that name isn't reserved
205
+ if reserved_names.include?(fname)
206
+ raise NameError.new("field '#{fname}' is a reserved name in #{self}. Rename it.", fname)
207
+ end
178
208
 
179
- # move accepted params from this object to the delegate object
180
- env_params = @env.params.dup
181
- delegate.accepted_parameters.each do |p|
182
- if (v = env_params.delete(p))
183
- delegate_params[p] = v
209
+ if fname == ""
210
+ fklass.all_possible_field_names(fparams).each do |name|
211
+ if field_names.include?(name)
212
+ raise NameError.new("field '#{name}' is defined multiple times in #{self}.", name)
213
+ end
214
+ field_names << name
215
+ end
216
+ else
217
+ if field_names.include?(fname)
218
+ raise NameError.new("field '#{fname}' is defined multiple times in #{self}.", fname)
219
+ end
220
+ field_names << fname
184
221
  end
185
222
  end
186
- @env.params = env_params
187
- else
188
- # no delegate so all instance methods of Hash are reserved
189
- all_reserved_methods = Hash.instance_methods - all_methods
223
+
224
+ params
190
225
  end
191
226
 
192
- # check if field names conflict with any reserved names
193
- field_names = param(:fields).collect { |f| f[1] }
194
- field_names_okay = (all_methods & field_names).empty? &&
195
- (all_reserved_methods & field_names).empty?
227
+ # Returns a list of the names of all possible field names for a Struct
228
+ # created with +sanitized_params+. Hidden names will not be included
229
+ # in the returned list.
230
+ def all_possible_field_names(sanitized_params)
231
+ unless SanitizedParameters === sanitized_params
232
+ raise ArgumentError, "parameters aren't sanitized"
233
+ end
196
234
 
197
- # create instances of the fields
198
- @fields = param(:fields).collect do |type, name, params|
199
- klass = klass_lookup(type)
200
- raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
201
- if not field_names_okay
202
- # at least one field names conflicts so test them all.
203
- # rationale - #include? is expensive so we avoid it if possible.
204
- if all_methods.include?(name)
205
- raise NameError.new("field '#{name}' shadows an existing method",name)
206
- end
207
- if all_reserved_methods.include?(name)
208
- raise NameError.new("field '#{name}' is a reserved name",name)
235
+ hidden_names = sanitized_params[:hide]
236
+
237
+ names = []
238
+ sanitized_params[:fields].each do |fklass, fname, fparams|
239
+ if fname == ""
240
+ names.concat(fklass.all_possible_field_names(fparams))
241
+ else
242
+ names << fname unless hidden_names.include?(fname)
209
243
  end
210
244
  end
211
- [name, klass.new(params, create_env)]
245
+
246
+ names
212
247
  end
213
248
  end
214
249
 
215
- # Returns a list of parameters that are accepted by this object
216
- def accepted_parameters
217
- if delegate_object != nil
218
- delegate_object.accepted_parameters
219
- else
220
- super
250
+ # These are the parameters used by this class.
251
+ mandatory_parameter :fields
252
+ optional_parameters :endian, :hide
253
+
254
+ # Creates a new Struct.
255
+ def initialize(params = {}, env = nil)
256
+ super(params, env)
257
+
258
+ # create instances of the fields
259
+ @fields = param(:fields).collect do |fklass, fname, fparams|
260
+ [fname, fklass.new(fparams, create_env)]
221
261
  end
222
262
  end
223
263
 
@@ -270,39 +310,36 @@ module BinData
270
310
 
271
311
  # Returns a snapshot of this struct as a hash.
272
312
  def snapshot
273
- if delegate_object != nil
274
- delegate_object.snapshot
275
- else
276
- hash = Snapshot.new
277
- field_names.each do |name|
278
- hash[name] = find_obj_for_name(name).snapshot
279
- end
280
- hash
313
+ hash = Snapshot.new
314
+ field_names.each do |name|
315
+ hash[name] = find_obj_for_name(name).snapshot
281
316
  end
317
+ hash
318
+ end
319
+
320
+ # Returns whether this data object contains a single value. Single
321
+ # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
322
+ def single_value?
323
+ return false
282
324
  end
283
325
 
284
326
  # Returns a list of the names of all fields accessible through this
285
327
  # object. +include_hidden+ specifies whether to include hidden names
286
328
  # in the listing.
287
329
  def field_names(include_hidden = false)
288
- if delegate_object != nil and !include_hidden
289
- # delegate if possible
290
- delegate_object.field_names
291
- else
292
- # collect field names
293
- names = []
294
- hidden = param(:hide)
295
- @fields.each do |name, obj|
296
- if name != ""
297
- if include_hidden or not hidden.include?(name)
298
- names << name
299
- end
300
- else
301
- names.concat(obj.field_names)
330
+ # collect field names
331
+ names = []
332
+ hidden = param(:hide)
333
+ @fields.each do |name, obj|
334
+ if name != ""
335
+ if include_hidden or not hidden.include?(name)
336
+ names << name
302
337
  end
338
+ else
339
+ names.concat(obj.field_names)
303
340
  end
304
- names
305
341
  end
342
+ names
306
343
  end
307
344
 
308
345
  # Returns the data object that stores values for +name+.
@@ -332,18 +369,11 @@ module BinData
332
369
  offset
333
370
  end
334
371
 
335
- # Override to include field names and delegate methods.
372
+ # Override to include field names
336
373
  alias_method :orig_respond_to?, :respond_to?
337
374
  def respond_to?(symbol, include_private = false)
338
375
  orig_respond_to?(symbol, include_private) ||
339
- field_names(true).include?(symbol.id2name.chomp("=")) ||
340
- delegate_object.respond_to?(symbol, include_private)
341
- end
342
-
343
- # Returns whether this data object contains a single value. Single
344
- # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
345
- def single_value?
346
- delegate_object ? delegate_object.single_value? : false
376
+ field_names(true).include?(symbol.id2name.chomp("="))
347
377
  end
348
378
 
349
379
  def method_missing(symbol, *args, &block)
@@ -362,8 +392,6 @@ module BinData
362
392
  else
363
393
  obj
364
394
  end
365
- elsif delegate_object.respond_to?(symbol)
366
- delegate_object.__send__(symbol, *args, &block)
367
395
  else
368
396
  super
369
397
  end
@@ -372,52 +400,9 @@ module BinData
372
400
  #---------------
373
401
  private
374
402
 
375
- # Returns the delegate object if any.
376
- def delegate_object
377
- if (name = param(:delegate))
378
- find_obj_for_name(name)
379
- else
380
- nil
381
- end
382
- end
383
-
384
403
  # Returns a list of all the bindata objects for this struct.
385
404
  def bindata_objects
386
405
  @fields.collect { |f| f[1] }
387
406
  end
388
-
389
- # Returns a hash of cleaned +params+. Cleaning means that param
390
- # values are converted to a desired format.
391
- def cleaned_params(params)
392
- new_params = params.dup
393
-
394
- # use fields defined in this class if no fields are passed as params
395
- fields = new_params[:fields] || self.class.fields
396
-
397
- # ensure the names of fields are strings and that params is a hash
398
- new_params[:fields] = fields.collect do |t, n, p|
399
- [t, n.to_s, (p || {}).dup]
400
- end
401
-
402
- # collect all non blank field names
403
- field_names = new_params[:fields].collect { |f| f[1] }
404
- field_names = field_names.delete_if { |n| n == "" }
405
-
406
- # collect all hidden names that correspond to a field name
407
- hide = []
408
- (new_params[:hide] || self.class.hide).each do |h|
409
- h = h.to_s
410
- hide << h if field_names.include?(h)
411
- end
412
- new_params[:hide] = hide
413
-
414
- # collect delegate name if it corresponds to a field name
415
- if (delegate = (new_params[:delegate] || self.class.delegate))
416
- delegate = delegate.to_s
417
- new_params[:delegate] = delegate if field_names.include?(delegate)
418
- end
419
-
420
- new_params
421
- end
422
407
  end
423
408
  end