Class: BinData::Struct

Inherits:
Base
  • Object
show all
Defined in:
lib/bindata/struct.rb

Overview

A Struct is an ordered collection of named data objects.

require 'bindata'

class Tuple < BinData::Struct
  int8  :x
  int8  :y
  int8  :z
end

class SomeStruct < BinData::Struct
  hide 'a'

  int32le :a
  int16le :b
  tuple   nil
end

obj = SomeStruct.new
obj.field_names   =># ["b", "x", "y", "z"]

Parameters

Parameters may be provided at initialisation to control the behaviour of an object. These params are:

:fields

An array specifying the fields for this struct. Each element of the array is of the form [type, name, params]. Type is a symbol representing a registered type. Name is the name of this field. Name may be nil as in the example above. Params is an optional hash of parameters to pass to this field when instantiating it.

:hide

A list of the names of fields that are to be hidden from the outside world. Hidden fields don’t appear in #snapshot or #field_names but are still accessible by name.

Defined Under Namespace

Classes: Snapshot

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#do_read, #klass_lookup, lookup, mandatory_parameters, #num_bytes, optional_parameters, parameters, #read, read, register, #write

Constructor Details

#initialize(params = {}, env = nil) ⇒ Struct

Creates a new Struct.



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/bindata/struct.rb', line 126

def initialize(params = {}, env = nil)
  super(cleaned_params(params), env)

  # create instances of the fields
  @fields = param(:fields).collect do |type, name, params|
    klass = klass_lookup(type)
    raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
    if methods.include?(name)
      raise NameError.new("field '#{name}' shadows an existing method",name)
    end
    [name, klass.new(params, create_env)]
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/bindata/struct.rb', line 258

def method_missing(symbol, *args)
  name = symbol.id2name

  is_writer = (name[-1, 1] == "=")
  name.chomp!("=")

  # find the object that is responsible for name
  if (obj = find_obj_for_name(name))
    # pass on the request
    if obj.single_value? and is_writer
      obj.value = *args
    elsif obj.single_value?
      obj.value
    else
      obj
    end
  else
    super
  end
end

Class Method Details

.endian(endian = nil) ⇒ Object

Returns or sets the endianess of numerics used in this stucture. Endianess is propagated to nested data objects unless overridden in a nested Struct. Valid values are :little and :big.



62
63
64
65
66
67
68
69
70
# File 'lib/bindata/struct.rb', line 62

def endian(endian = nil)
  @endian ||= nil
  if [:little, :big].include?(endian)
    @endian = endian
  elsif endian != nil
    raise ArgumentError, "unknown value for endian '#{endian}'"
  end
  @endian
end

.fieldsObject

Returns all stored fields. Should only be called by #cleaned_params.



116
117
118
# File 'lib/bindata/struct.rb', line 116

def fields
  @fields || []
end

.hide(*args) ⇒ Object

Returns the names of any hidden fields in this struct. Any given args are appended to the hidden list.



74
75
76
77
78
79
80
81
82
# File 'lib/bindata/struct.rb', line 74

def hide(*args)
  # note that fields are stored in an instance variable not a class var
  @hide ||= []
  args.each do |name|
    next if name.nil?
    @hide << name.to_s
  end
  @hide
end

.inherited(subclass) ⇒ Object

Register the names of all subclasses of this class.



54
55
56
# File 'lib/bindata/struct.rb', line 54

def inherited(subclass) #:nodoc:
  register(subclass.name, subclass)
end

.method_missing(symbol, *args) ⇒ Object

Used to define fields for this structure.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/bindata/struct.rb', line 85

def method_missing(symbol, *args)
  name, params = args

  type = symbol
  name = name.to_s unless name.nil?
  params ||= {}

  if lookup(type).nil?
    raise TypeError, "unknown type '#{type}' for #{self}", caller
  end

  # note that fields are stored in an instance variable not a class var

  # check for duplicate names
  @fields ||= []
  if @fields.detect { |t, n, p| n == name and n != nil }
    raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
  end

  # check that name doesn't shadow an existing method
  if self.instance_methods.include?(name)
    raise NameError.new("", name),
          "field '#{name}' shadows an existing method", caller
  end

  # remember this field.  These fields will be recalled upon creating
  # an instance of this class
  @fields.push([type, name, params])
end

Instance Method Details

#_do_read(io) ⇒ Object

Reads the values for all fields in this object from io.



162
163
164
# File 'lib/bindata/struct.rb', line 162

def _do_read(io)
  bindata_objects.each { |f| f.do_read(io) }
end

#_num_bytes(name) ⇒ Object

Returns the number of bytes it will take to write the field represented by name. If name is nil then returns the number of bytes required to write all fields.



179
180
181
182
183
184
185
# File 'lib/bindata/struct.rb', line 179

def _num_bytes(name)
  if name.nil?
    bindata_objects.inject(0) { |sum, f| sum + f.num_bytes }
  else
    find_obj_for_name(name.to_s).num_bytes
  end
end

#_write(io) ⇒ Object

Writes the values for all fields in this object to io.



172
173
174
# File 'lib/bindata/struct.rb', line 172

def _write(io)
  bindata_objects.each { |f| f.write(io) }
end

#clear(name = nil) ⇒ Object

Clears the field represented by name. If no name is given, clears all fields in the struct.



142
143
144
145
146
147
148
# File 'lib/bindata/struct.rb', line 142

def clear(name = nil)
  if name.nil?
    bindata_objects.each { |f| f.clear }
  else
    find_obj_for_name(name.to_s).clear
  end
end

#clear?(name = nil) ⇒ Boolean

Returns if the field represented by name is clear?. If no name is given, returns whether all fields are clear.

Returns:

  • (Boolean)


152
153
154
155
156
157
158
159
# File 'lib/bindata/struct.rb', line 152

def clear?(name = nil)
  if name.nil?
    bindata_objects.each { |f| return false if not f.clear? }
    true
  else
    find_obj_for_name(name.to_s).clear?
  end
end

#done_readObject

To be called after calling #read.



167
168
169
# File 'lib/bindata/struct.rb', line 167

def done_read
  bindata_objects.each { |f| f.done_read }
end

#field_names(include_hidden = false) ⇒ Object

Returns a list of the names of all fields accessible through this object. include_hidden specifies whether to include hidden names in the listing.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/bindata/struct.rb', line 202

def field_names(include_hidden = false)
  # single values don't have any fields
  return [] if single_value?

  names = []
  @fields.each do |name, obj|
    if name != ""
      names << name unless (param(:hide).include?(name) and !include_hidden)
    else
      names.concat(obj.field_names)
    end
  end
  names
end

#find_obj_for_name(name) ⇒ Object

Returns the data object that stores values for name.



218
219
220
221
222
223
224
225
226
227
# File 'lib/bindata/struct.rb', line 218

def find_obj_for_name(name)
  @fields.each do |n, o|
    if n == name
      return o
    elsif n == "" and o.field_names.include?(name)
      return o.find_obj_for_name(name)
    end
  end
  nil
end

#offset_of(field) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/bindata/struct.rb', line 229

def offset_of(field)
  field_name = field.to_s
  offset = 0
  @fields.each do |name, obj|
    if name != ""
      break if name == field_name
      offset += obj.num_bytes
    elsif obj.field_names.include?(field_name)
      offset += obj.offset_of(field)
      break
    end
  end
  offset
end

#orig_respond_to?Object

Override to include field names.



245
# File 'lib/bindata/struct.rb', line 245

alias_method :orig_respond_to?, :respond_to?

#respond_to?(symbol, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


246
247
248
249
# File 'lib/bindata/struct.rb', line 246

def respond_to?(symbol, include_private = false)
  orig_respond_to?(symbol, include_private) ||
    field_names(true).include?(symbol.id2name.chomp("="))
end

#single_value?Boolean

Returns whether this data object contains a single value. Single value data objects respond to #value and #value=.

Returns:

  • (Boolean)


253
254
255
256
# File 'lib/bindata/struct.rb', line 253

def single_value?
  # need to use original respond_to? to prevent infinite recursion
  orig_respond_to?(:value)
end

#snapshotObject

Returns a snapshot of this struct as a hash.



188
189
190
191
192
193
194
195
196
197
# File 'lib/bindata/struct.rb', line 188

def snapshot
  # allow structs to fake single value
  return value if single_value?

  hash = Snapshot.new
  field_names.each do |name|
    hash[name] = find_obj_for_name(name).snapshot
  end
  hash
end