lib/bindata/choice.rb in bindata-0.9.2 vs lib/bindata/choice.rb in bindata-0.9.3

- old
+ new

@@ -2,36 +2,38 @@ require 'bindata/base' require 'bindata/sanitize' module BinData # A Choice is a collection of data objects of which only one is active - # at any particular time. + # at any particular time. Method calls will be delegated to the active + # choice. # # require 'bindata' # # type1 = [:string, {:value => "Type1"}] # type2 = [:string, {:value => "Type2"}] # + # choices = {5 => type1, 17 => type2} + # a = BinData::Choice.new(:choices => choices, :selection => 5) + # a.value # => "Type1" + # # choices = [ type1, type2 ] # a = BinData::Choice.new(:choices => choices, :selection => 1) # a.value # => "Type2" # # choices = [ nil, nil, nil, type1, nil, type2 ] # a = BinData::Choice.new(:choices => choices, :selection => 3) # a.value # => "Type1" # - # choices = {5 => type1, 17 => type2} - # a = BinData::Choice.new(:choices => choices, :selection => 5) - # a.value # => "Type1" - # # mychoice = 'big' # choices = {'big' => :uint16be, 'little' => :uint16le} # a = BinData::Choice.new(:choices => choices, # :selection => lambda { mychoice }) # a.value = 256 # a.to_s #=> "\001\000" - # mychoice[0..-1] = 'little' + # mychoice.replace 'little' + # a.selection #=> 'little' # a.to_s #=> "\000\001" # # # == Parameters # @@ -52,66 +54,95 @@ # Register this class register(self.name, self) # These are the parameters used by this class. - mandatory_parameters :choices, :selection + bindata_mandatory_parameters :choices, :selection class << self - # Returns a sanitized +params+ that is of the form expected - # by #initialize. - def sanitize_parameters(sanitizer, params) - params = params.dup - + # Ensures that +params+ is of the form expected by #initialize. + def sanitize_parameters!(sanitizer, params) if params.has_key?(:choices) choices = params[:choices] - case choices - when ::Hash - new_choices = {} - choices.keys.each do |key| - # ensure valid hash keys - if Symbol === key - msg = ":choices hash may not have symbols for keys" - raise ArgumentError, msg - elsif key.nil? - raise ArgumentError, ":choices hash may not have nil key" - end - - # collect sanitized choice values - type, param = choices[key] - new_choices[key] = sanitizer.sanitize(type, param) + # convert array to hash keyed by index + if ::Array === choices + tmp = {} + choices.each_with_index do |el, i| + tmp[i] = el unless el.nil? end - params[:choices] = new_choices - when ::Array - choices.collect! do |type, param| - if type.nil? - # allow sparse arrays - nil - else - sanitizer.sanitize(type, param) - end - end - params[:choices] = choices - else - raise ArgumentError, "unknown type for :choices (#{choices.class})" + choices = tmp end + + # ensure valid hash keys + if choices.has_key?(nil) + raise ArgumentError, ":choices hash may not have nil key" + end + if choices.keys.detect { |k| Symbol === k } + raise ArgumentError, ":choices hash may not have symbols for keys" + end + + # sanitize each choice + new_choices = {} + choices.each_pair do |key, val| + type, param = val + klass = sanitizer.lookup_klass(type) + sanitized_params = sanitizer.sanitize_params(klass, param) + new_choices[key] = [klass, sanitized_params] + end + params[:choices] = new_choices end super(sanitizer, params) end end - def initialize(params = {}, env = nil) - super(params, env) + def initialize(params = {}, parent = nil) + super(params, parent) - # prepare collection of instantiated choice objects - @choices = (param(:choices) === ::Array) ? [] : {} + @choices = {} @last_key = nil end + # A convenience method that returns the current selection. + def selection + eval_param(:selection) + end + + # This method does not exist. This stub only exists to document why. + # There is no #selection= method to complement the #selection method. + # This is deliberate to promote the declarative nature of BinData. + # + # If you really *must* be able to programmatically adjust the selection + # then try something like the following. + # + # class ProgrammaticChoice < BinData::MultiValue + # choice :data, :choices => :choices, :selection => :selection + # attrib_accessor :selection + # end + # + # type1 = [:string, {:value => "Type1"}] + # type2 = [:string, {:value => "Type2"}] + # + # choices = {5 => type1, 17 => type2} + # pc = ProgrammaticChoice.new(:choices => choices) + # + # pc.selection = 5 + # pc.data #=> "Type1" + # + # pc.selection = 17 + # pc.data #=> "Type2" + def selection=(v) + raise NoMethodError + end + + # A choice represents a specific object. + def obj + the_choice + end + def_delegators :the_choice, :clear, :clear?, :single_value? def_delegators :the_choice, :done_read, :_snapshot def_delegators :the_choice, :_do_read, :_do_write, :_do_num_bytes # Override to include selected data object. @@ -139,14 +170,14 @@ end obj = @choices[key] if obj.nil? # instantiate choice object - choice_klass, choice_params = param(:choices)[key] + choice_klass, choice_params = no_eval_param(:choices)[key] if choice_klass.nil? raise IndexError, "selection #{key} does not exist in :choices" end - obj = choice_klass.new(choice_params, create_env) + obj = choice_klass.new(choice_params, self) @choices[key] = obj end # for single_values copy the value when the selected object changes if key != @last_key