require 'bindata/sanitize'
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 endian(endian = nil)
@endian ||= default_endian
if [:little, :big].include?(endian)
@endian = endian
elsif endian != nil
raise ArgumentError,
"unknown value for endian '#{endian}' in #{self}", caller(1)
end
@endian
end
def hide(*args)
@hide ||= default_hide
@hide.concat(args.collect { |name| name.to_s })
@hide
end
def fields #:nodoc:
@fields ||= default_fields
end
def method_missing(symbol, *args) #:nodoc:
name, params = args
if name.is_a?(Hash)
params = name
name = nil
end
type = symbol
name = name.to_s
params ||= {}
append_field(type, name, params)
end
def sanitize_parameters!(params, sanitizer) #:nodoc:
params[:fields] = fields
params[:endian] = endian unless endian.nil?
params[:hide] = hide unless hide.empty?
super(params, sanitizer)
end
#-------------
private
def parent_record
ancestors[1..-1].find { |cls|
cls.ancestors[1..-1].include?(BinData::Record)
}
end
def default_endian
rec = parent_record
rec ? rec.endian : nil
end
def default_hide
rec = parent_record
rec ? rec.hide.dup : []
end
def default_fields
rec = parent_record
if rec
Sanitizer.new.clone_sanitized_fields(rec.fields)
else
Sanitizer.new.create_sanitized_fields
end
end
def append_field(type, name, params)
ensure_valid_name(name)
fields.add_field(type, name, params, endian)
rescue UnknownTypeError => err
raise TypeError, "unknown type '#{err.message}' for #{self}", caller(2)
end
def ensure_valid_name(name)
if fields.field_names.include?(name)
raise SyntaxError, "duplicate field '#{name}' in #{self}", caller(3)
end
if self.instance_methods.collect { |meth| meth.to_s }.include?(name)
raise NameError.new("", name),
"field '#{name}' shadows an existing method in #{self}", caller(3)
end
if self::RESERVED.include?(name)
raise NameError.new("", name),
"field '#{name}' is a reserved name in #{self}", caller(3)
end
end
end
end
end