require 'bindata/registry'
require 'bindata/struct'
module BinData
# A Record is a declarative wrapper around Struct.
#
# require 'bindata'
#
# class Tuple < BinData::Record
# int8 :x
# int8 :y
# int8 :z
# end
#
# class SomeDataType < BinData::Record
# hide 'a'
#
# int32le :a
# int16le :b
# tuple :s
# end
#
# obj = SomeDataType.new
# obj.field_names =># ["b", "s"]
#
#
# == 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. 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.
# :endian:: Either :little or :big. This specifies the default
# endian of any numerics in this struct, or in any
# nested data objects.
class Record < BinData::Struct
class << self
def inherited(subclass) #:nodoc:
# Register the names of all subclasses of this class.
register(subclass.name, subclass)
end
def recursive?
# A Record can self reference itself.
true
end
def endian(endian = nil)
@endian ||= nil
if [:little, :big].include?(endian)
@endian = endian
elsif endian != nil
raise ArgumentError, "unknown value for endian '#{endian}'", caller(1)
end
@endian
end
def hide(*args)
@hide ||= []
@hide.concat(args.collect { |name| name.to_s })
@hide
end
def method_missing(symbol, *args)
name, params = args
type = symbol
name = name.to_s
params ||= {}
ensure_type_exists(type)
ensure_valid_name(name)
append_field(type, name, params)
end
def sanitize_parameters!(sanitizer, params)
merge_endian!(params)
merge_fields!(params)
merge_hide!(params)
super(sanitizer, params)
end
#-------------
private
def ensure_type_exists(type)
unless RegisteredClasses.is_registered?(type, endian)
raise TypeError, "unknown type '#{type}' for #{self}", caller(2)
end
end
def ensure_valid_name(name)
@fields ||= []
@fields.each do |t, n, p|
if n == name
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller(4)
end
end
if self.instance_methods.include?(name)
raise NameError.new("", name),
"field '#{name}' shadows an existing method", caller(2)
end
if self::RESERVED.include?(name)
raise NameError.new("", name),
"field '#{name}' is a reserved name", caller(2)
end
end
def append_field(type, name, params)
@fields ||= []
@fields.push([type, name, params])
end
def merge_endian!(params)
endian = params[:endian] || self.endian
params[:endian] = endian unless endian.nil?
end
def merge_fields!(params)
@fields ||= []
fields = params[:fields] || @fields || []
params[:fields] = fields
end
def merge_hide!(params)
hide = params[:hide] || self.hide
params[:hide] = hide
end
end
end
class MultiValue < Record
class << self
def inherited(subclass) #:nodoc:
warn "BinData::MultiValue is deprecated. Replacing with BinData::Record"
super
end
end
end
end