120 lines
3.5 KiB
Ruby
120 lines
3.5 KiB
Ruby
require 'forwardable'
|
|
|
|
module BinData
|
|
class Sanitizer
|
|
class << self
|
|
# Sanitize +params+ for +obj+.
|
|
# Returns sanitized parameters.
|
|
def sanitize(obj, params)
|
|
sanitizer = self.new
|
|
klass, new_params = sanitizer.sanitize(obj.class, params)
|
|
new_params
|
|
end
|
|
|
|
# Returns true if +type+ is registered.
|
|
def type_exists?(type, endian = nil)
|
|
lookup(type, endian) != nil
|
|
end
|
|
|
|
# Returns the class matching a previously registered +name+.
|
|
def lookup(name, endian)
|
|
name = name.to_s
|
|
klass = Registry.instance.lookup(name)
|
|
if klass.nil? and endian != nil
|
|
# lookup failed so attempt endian lookup
|
|
if /^u?int\d{1,3}$/ =~ name
|
|
new_name = name + ((endian == :little) ? "le" : "be")
|
|
klass = Registry.instance.lookup(new_name)
|
|
elsif ["float", "double"].include?(name)
|
|
new_name = name + ((endian == :little) ? "_le" : "_be")
|
|
klass = Registry.instance.lookup(new_name)
|
|
end
|
|
end
|
|
klass
|
|
end
|
|
end
|
|
|
|
# Create a new Sanitizer.
|
|
def initialize
|
|
@seen = []
|
|
@endian = nil
|
|
end
|
|
|
|
# Executes the given block with +endian+ set as the current endian.
|
|
def with_endian(endian, &block)
|
|
if endian != nil
|
|
saved_endian = @endian
|
|
@endian = endian
|
|
yield
|
|
@endian = saved_endian
|
|
else
|
|
yield
|
|
end
|
|
end
|
|
|
|
# Sanitizes +params+ for +type+.
|
|
# Returns [klass, sanitized_params]
|
|
def sanitize(type, params)
|
|
if Class === type
|
|
klass = type
|
|
else
|
|
klass = self.class.lookup(type, @endian)
|
|
raise TypeError, "unknown type '#{type}'" if klass.nil?
|
|
end
|
|
|
|
new_params = params.nil? ? {} : params.dup
|
|
|
|
if @seen.include?(klass)
|
|
# This klass is defined recursively. Remember the current endian
|
|
# and delay sanitizing the parameters until later.
|
|
if @endian != nil and klass.accepted_parameters.include?(:endian) and
|
|
not new_params.has_key?(:endian)
|
|
new_params[:endian] = @endian
|
|
end
|
|
else
|
|
# subclasses of MultiValue may be defined recursively
|
|
# TODO: define a class field instead
|
|
possibly_recursive = (BinData.const_defined?(:MultiValue) and
|
|
klass.ancestors.include?(BinData.const_get(:MultiValue)))
|
|
@seen.push(klass) if possibly_recursive
|
|
|
|
klass.sanitize_parameters!(self, new_params)
|
|
new_params = SanitizedParameters.new(klass, new_params)
|
|
end
|
|
|
|
[klass, new_params]
|
|
end
|
|
end
|
|
|
|
# A BinData object accepts arbitrary parameters. This class ensures that
|
|
# the parameters have been sanitized, and categorizes them according to
|
|
# whether they are BinData::Base.accepted_parameters or are extra.
|
|
class SanitizedParameters
|
|
extend Forwardable
|
|
|
|
# Sanitize the given parameters.
|
|
def initialize(klass, params)
|
|
@hash = params
|
|
@accepted_parameters = {}
|
|
@extra_parameters = {}
|
|
|
|
# partition parameters into known and extra parameters
|
|
@hash.each do |k,v|
|
|
k = k.to_sym
|
|
if v.nil?
|
|
raise ArgumentError, "parameter :#{k} has nil value in #{klass}"
|
|
end
|
|
|
|
if klass.accepted_parameters.include?(k)
|
|
@accepted_parameters[k] = v
|
|
else
|
|
@extra_parameters[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
attr_reader :accepted_parameters, :extra_parameters
|
|
|
|
def_delegators :@hash, :[], :has_key?, :include?, :keys
|
|
end
|
|
end |