require'bindata/base'require'bindata/trace'moduleBinData# A Choice is a collection of data objects of which only one is active# 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"## mychoice = 'big'# choices = {'big' => :uint16be, 'little' => :uint16le}# a = BinData::Choice.new(:choices => choices,# :selection => lambda { mychoice })# a.value = 256# a.to_binary_s #=> "\001\000"# mychoice.replace 'little'# a.selection #=> 'little'# a.to_binary_s #=> "\000\001"### == Parameters## Parameters may be provided at initialisation to control the behaviour of# an object. These params are:## <tt>:choices</tt>:: Either an array or a hash specifying the possible# data objects. The format of the# array/hash.values is a list of symbols# representing the data object type. If a choice# is to have params passed to it, then it should# be provided as [type_symbol, hash_params]. An# implementation constraint is that the hash may# not contain symbols as keys.# <tt>:selection</tt>:: An index/key into the :choices array/hash which# specifies the currently active choice.# <tt>:copy_on_change</tt>:: If set to true, copy the value of the previous# selection to the current selection whenever the# selection changes. Default is false.classChoice<BinData::Baseregister_selfmandatory_parameters:choices,:selectionoptional_parameter:copy_on_changeclass<<selfdefsanitize_parameters!(params,sanitizer)#:nodoc:ifparams.needs_sanitizing?(:choices)choices=choices_as_hash(params[:choices])ensure_valid_keys(choices)params[:choices]=sanitizer.create_sanitized_choices(choices)endend#-------------privatedefchoices_as_hash(choices)ifchoices.respond_to?(:to_ary)key_array_by_index(choices.to_ary)elsechoicesendenddefkey_array_by_index(array)result={}array.each_with_indexdo|el,i|result[i]=elunlessel.nil?endresultenddefensure_valid_keys(choices)ifchoices.has_key?(nil)raiseArgumentError,":choices hash may not have nil key"endifchoices.keys.detect{|key|key.is_a?(Symbol)}raiseArgumentError,":choices hash may not have symbols for keys"endendenddefinitialize(parameters={},parent=nil)super@choices={}@last_selection=nilend# A convenience method that returns the current selection.defselectioneval_parameter(: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::Wrapper# choice :selection => :selection# attr_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 #=> "Type1"## pc.selection = 17# pc #=> "Type2"defselection=(sel)raiseNoMethodError,"See rdoc BinData::Choice.selection= for details"enddefclear#:nodoc:current_choice.clearenddefclear?#:nodoc:current_choice.clear?enddefassign(val)current_choice.assign(val)enddefsnapshotcurrent_choice.snapshotenddefrespond_to?(symbol,include_private=false)#:nodoc:current_choice.respond_to?(symbol,include_private)||superenddefmethod_missing(symbol,*args,&block)#:nodoc:current_choice.__send__(symbol,*args,&block)enddefdo_read(io)#:nodoc:trace_selectioncurrent_choice.do_read(io)enddefdo_write(io)#:nodoc:current_choice.do_write(io)enddefdo_num_bytes#:nodoc:current_choice.do_num_bytesend#---------------privatedeftrace_selectionBinData::trace_messagedo|tracer|selection_string=eval_parameter(:selection).inspecttracer.trace_obj("#{debug_name}-selection-",selection_string)endenddefcurrent_choiceselection=eval_parameter(:selection)ifselection.nil?raiseIndexError,":selection returned nil for #{debug_name}"endobj=get_or_instantiate_choice(selection)copy_previous_value_if_required(selection,obj)objenddefget_or_instantiate_choice(selection)obj=@choices[selection]ifobj.nil?obj=instantiate_choice(selection)@choices[selection]=objendobjenddefinstantiate_choice(selection)prototype=get_parameter(:choices)[selection]ifprototype.nil?raiseIndexError,"selection '#{selection}' does not exist in :choices for #{debug_name}"endprototype.instantiate(self)enddefcopy_previous_value_if_required(selection,obj)prev=get_previous_choice(selection)ifshould_copy_value?(prev,obj)obj.assign(prev)endremember_current_selection(selection)enddefshould_copy_value?(prev,cur)prev!=nilandeval_parameter(:copy_on_change)==trueenddefget_previous_choice(selection)ifselection!=@last_selectionand@last_selection!=nil@choices[@last_selection]elsenilendenddefremember_current_selection(selection)ifselection!=@last_selection@last_selection=selectionendendendend