lib/choice/parser.rb
def parse(options, args)
      # Return empty hash if the parsing adventure would be fruitless.
      return {} if options.nil? || !options || args.nil? || !args.is_a?(Array)

      # Operate on a copy of the inputs
      args = args.dup

      # If we are passed an array, make the best of it by converting it
      # to a hash.
      options = options.inject({}) do |hash, value|
        value.is_a?(Array) ? hash.merge(value.first => value[1]) : hash
      end if options.is_a? Array

      # Define local hashes we're going to use.  choices is where we store
      # the actual values we've pulled from the argument list.
      hashes, longs, required, validators, choices, arrayed = {}, {}, {}, {}, {}, {}
      hard_required = {}

      # We can define these on the fly because they are all so similar.
      params = %w[short cast filter action default valid]
      params.each { |param| hashes["#{param}s"] = {} }

      # Inspect each option and move its info into our local hashes.
      options.each do |name, obj|
        name = name.to_sym

        # Only take hashes or hash-like duck objects.
        raise HashExpectedForOption unless obj.respond_to? :to_h
        obj = obj.to_h

        # Is this option required?
        hard_required[name] = true if obj['required']

        # Set the local hashes if the value exists on this option object.
        params.each { |param| hashes["#{param}s"][name] = obj[param] if obj[param] }

        # If there is a validate statement, make it a regex or proc.
        validators[name] = make_validation(obj['validate']) if obj['validate']

        # Parse the long option. If it contains a =, figure out if the
        # argument is required or optional.  Optional arguments are formed
        # like [=ARG], whereas required are just ARG (in --long=ARG style).
        if obj['long'] && obj['long'] =~ /(=|\[| )/
          # Save the separator we used, as we're gonna need it, then split
          sep = $1
          option, *argument = obj['long'].split(sep)

          # The actual name of the long switch
          longs[name] = option

          # Preserve the original argument, as it may contain [ or =,
          # by joining with the character we split on.  Add a [ in front if
          # we split on that.
          argument = (sep == '[' ? '[' : '') << Array(argument).join(sep)

          # Do we expect multiple arguments which get turned into an array?
          arrayed[name] = true if argument =~ /^\[?=?\*(.+)\]?$/

          # Is this long required or optional?
          required[name] = true unless argument =~ /^\[=?\*?(.+)\]$/
        elsif obj['long']
          # We can't have a long as a switch when valid is set -- die.
          raise ArgumentRequiredWithValid if obj['valid']

          # Set without any checking if it's just --long
          longs[name] = obj['long']
        end

        # If we were given a list of valid arguments with 'valid,' this option
        # is definitely required.
        required[name] = true if obj['valid']
      end

      rest = []

      # Go through the arguments and try to figure out whom they belong to
      # at this point.
      while arg = args.shift
        if hashes['shorts'].value?(arg)
          # Set the value to the next element in the args array since
          # this is a short.

          # If the next argument isn't a value, set this value to true
          if args.empty? || args.first.match(/^-/)
            value = true
          else
            value = args.shift
          end

          # Add this value to the choices hash with the key of the option's
          # name.  If we expect an array, tack this argument on.
          name = hashes['shorts'].key(arg)
          if arrayed[name]
            choices[name] ||= []
            choices[name] << value unless value.nil?
            choices[name]  += arrayize_arguments(args)
          else
            choices[name] = value
          end

        elsif (m = arg.match(/^(--[^=]+)=?/)) && longs.value?(m[1])
          # The joke here is we always accept both --long=VALUE and --long VALUE.

          # Grab values from --long=VALUE format
          name, value = arg.split('=', 2)
          name = longs.key(name)

          if value.nil? && args.first !~ /^-/
            # Grab value otherwise if not in --long=VALUE format.  Assume --long VALUE.
            # Value is nil if we don't have a = and the next argument is no good
            value = args.shift
          end

          # If we expect an array, tack this argument on.
          if arrayed[name]
            # If this is arrayed and the value isn't nil, set it.
            choices[name] ||= []
            choices[name] << value unless value.nil?
            choices[name] += arrayize_arguments(args)
          else
            # If we set the value to nil, that means nothing was set and we
            # need to set the value to true.  We'll find out later if that's
            # acceptable or not.
            choices[name] = value.nil? ? true : value
          end

        else
          # If we're here, we have no idea what the passed argument is.  Die.
          if arg =~ /^-/
            raise UnknownOption
          else
            rest << arg
          end
        end
      end

      # Okay, we got all the choices.  Now go through and run any filters or
      # whatever on them.
      choices.each do |name, value|
        # Check to make sure we have all the required arguments.
        raise ArgumentRequired if required[name] && value === true

        # Validate the argument if we need to, against a regexp or a block.
        if validators[name]
          if validators[name].is_a?(Regexp) && validators[name] =~ value
          elsif validators[name].is_a?(Proc) && validators[name].call(value)
          else raise ArgumentValidationFails
          end
        end

        # Make sure the argument is valid
        raise InvalidArgument unless Array(value).all? { |v| hashes['valids'][name].include?(v) } if hashes['valids'][name]

        # Cast the argument using the method defined in the constant hash.
        value = value.send(CAST_METHODS[hashes['casts'][name]]) if hashes['casts'].include?(name)

        # Run the value through a filter and re-set it with the return.
        value = hashes['filters'][name].call(value) if hashes['filters'].include?(name)

        # Run an action block if there is one associated.
        hashes['actions'][name].call(value) if hashes['actions'].include?(name)

        # Now that we've done all that, re-set the element of the choice hash
        # with the (potentially) new value.
        if arrayed[name] && choices[name].empty?
          choices[name] = true
        else
          choices[name] = value
        end
      end

      # Die if we're missing any required arguments
      hard_required.each do |name, value|
        raise ArgumentRequired unless choices[name]
      end

      # Home stretch.  Go through all the defaults defined and if a choice
      # does not exist in our choices hash, set its value to the requested
      # default.
      hashes['defaults'].each do |name, value|
        choices[name] = value unless choices[name]
      end

      # Return the choices hash and the rest of the args
      [ choices, rest ]
    end
def parse(options, args)
      # Return empty hash if the parsing adventure would be fruitless.
      return {} if options.nil? || !options || args.nil? || !args.is_a?(Array)

      # Operate on a copy of the inputs
      args = args.dup

      # If we are passed an array, make the best of it by converting it
      # to a hash.
      options = options.inject({}) do |hash, value|
        value.is_a?(Array) ? hash.merge(value.first => value[1]) : hash
      end if options.is_a? Array

      # Define local hashes we're going to use.  choices is where we store
      # the actual values we've pulled from the argument list.
      hashes, longs, required, validators, choices, arrayed = {}, {}, {}, {}, {}, {}
      hard_required = {}

      # We can define these on the fly because they are all so similar.
      params = %w[short cast filter action default valid]
      params.each { |param| hashes["#{param}s"] = {} }

      # Inspect each option and move its info into our local hashes.
      options.each do |name, obj|
        name = name.to_sym

        # Only take hashes or hash-like duck objects.
        raise HashExpectedForOption unless obj.respond_to? :to_h
        obj = obj.to_h

        # Is this option required?
        hard_required[name] = true if obj['required']

        # Set the local hashes if the value exists on this option object.
        params.each { |param| hashes["#{param}s"][name] = obj[param] if obj[param] }

        # If there is a validate statement, make it a regex or proc.
        validators[name] = make_validation(obj['validate']) if obj['validate']

        # Parse the long option. If it contains a =, figure out if the
        # argument is required or optional.  Optional arguments are formed
        # like [=ARG], whereas required are just ARG (in --long=ARG style).
        if obj['long'] && obj['long'] =~ /(=|\[| )/
          # Save the separator we used, as we're gonna need it, then split
          sep = $1
          option, *argument = obj['long'].split(sep)

          # The actual name of the long switch
          longs[name] = option

          # Preserve the original argument, as it may contain [ or =,
          # by joining with the character we split on.  Add a [ in front if
          # we split on that.
          argument = (sep == '[' ? '[' : '') << Array(argument).join(sep)

          # Do we expect multiple arguments which get turned into an array?
          arrayed[name] = true if argument =~ /^\[?=?\*(.+)\]?$/

          # Is this long required or optional?
          required[name] = true unless argument =~ /^\[=?\*?(.+)\]$/
        elsif obj['long']
          # We can't have a long as a switch when valid is set -- die.
          raise ArgumentRequiredWithValid if obj['valid']

          # Set without any checking if it's just --long
          longs[name] = obj['long']
        end

        # If we were given a list of valid arguments with 'valid,' this option
        # is definitely required.
        required[name] = true if obj['valid']
      end

      rest = []

      # Go through the arguments and try to figure out whom they belong to
      # at this point.
      while arg = args.shift
        if hashes['shorts'].value?(arg)
          # Set the value to the next element in the args array since
          # this is a short.

          # If the next argument isn't a value, set this value to true
          if args.empty? || args.first.match(/^-/)
            value = true
          else
            value = args.shift
          end

          # Add this value to the choices hash with the key of the option's
          # name.  If we expect an array, tack this argument on.
          name = hashes['shorts'].key(arg)
          if arrayed[name]
            choices[name] ||= []
            choices[name] << value unless value.nil?
            choices[name]  += arrayize_arguments(args)
          else
            choices[name] = value
          end

        elsif (m = arg.match(/^(--[^=]+)=?/)) && longs.value?(m[1])
          # The joke here is we always accept both --long=VALUE and --long VALUE.

          # Grab values from --long=VALUE format
          name, value = arg.split('=', 2)
          name = longs.key(name)

          if value.nil? && args.first !~ /^-/
            # Grab value otherwise if not in --long=VALUE format.  Assume --long VALUE.
            # Value is nil if we don't have a = and the next argument is no good
            value = args.shift
          end

          # If we expect an array, tack this argument on.
          if arrayed[name]
            # If this is arrayed and the value isn't nil, set it.
            choices[name] ||= []
            choices[name] << value unless value.nil?
            choices[name] += arrayize_arguments(args)
          else
            # If we set the value to nil, that means nothing was set and we
            # need to set the value to true.  We'll find out later if that's
            # acceptable or not.
            choices[name] = value.nil? ? true : value
          end

        else
          # If we're here, we have no idea what the passed argument is.  Die.
          if arg =~ /^-/
            raise UnknownOption
          else
            rest << arg
          end
        end
      end

      # Okay, we got all the choices.  Now go through and run any filters or
      # whatever on them.
      choices.each do |name, value|
        # Check to make sure we have all the required arguments.
        raise ArgumentRequired if required[name] && value === true

        # Validate the argument if we need to, against a regexp or a block.
        if validators[name]
          if validators[name].is_a?(Regexp) && validators[name] =~ value
          elsif validators[name].is_a?(Proc) && validators[name].call(value)
          else raise ArgumentValidationFails
          end
        end

        # Make sure the argument is valid
        raise InvalidArgument unless Array(value).all? { |v| hashes['valids'][name].include?(v) } if hashes['valids'][name]

        # Cast the argument using the method defined in the constant hash.
        value = value.send(CAST_METHODS[hashes['casts'][name]]) if hashes['casts'].include?(name)

        # Run the value through a filter and re-set it with the return.
        value = hashes['filters'][name].call(value) if hashes['filters'].include?(name)

        # Run an action block if there is one associated.
        hashes['actions'][name].call(value) if hashes['actions'].include?(name)

        # Now that we've done all that, re-set the element of the choice hash
        # with the (potentially) new value.
        if arrayed[name] && choices[name].empty?
          choices[name] = true
        else
          choices[name] = value
        end
      end

      # Die if we're missing any required arguments
      hard_required.each do |name, value|
        raise ArgumentRequired unless choices[name]
      end

      # Home stretch.  Go through all the defaults defined and if a choice
      # does not exist in our choices hash, set its value to the requested
      # default.
      hashes['defaults'].each do |name, value|
        choices[name] = value unless choices[name]
      end

      # Return the choices hash and the rest of the args
      [ choices, rest ]
    end
def parse ( options , args ) # Return empty hash if the parsing adventure would be fruitless. return { } if options . nil? || ! options || args . nil? || ! args . is_a? ( Array ) # Operate on a copy of the inputs args = args . dup # If we are passed an array, make the best of it by converting it # to a hash. options = options . inject ( { } ) do | hash , value | value . is_a? ( Array ) ? hash . merge ( value . first => value [ 1 ] ) : hash end if options . is_a? Array # Define local hashes we're going to use.  choices is where we store # the actual values we've pulled from the argument list. hashes , longs , required , validators , choices , arrayed = { } , { } , { } , { } , { } , { } hard_required = { } # We can define these on the fly because they are all so similar. params = %w[ short cast filter action default valid ] params . each { | param | hashes [ "#{param}s" ] = { } } # Inspect each option and move its info into our local hashes. options . each do | name , obj | name = name . to_sym # Only take hashes or hash-like duck objects. raise HashExpectedForOption unless obj . respond_to? :to_h obj = obj . to_h # Is this option required? hard_required [ name ] = true if obj [ 'required' ] # Set the local hashes if the value exists on this option object. params . each { | param | hashes [ "#{param}s" ] [ name ] = obj [ param ] if obj [ param ] } # If there is a validate statement, make it a regex or proc. validators [ name ] = make_validation ( obj [ 'validate' ] ) if obj [ 'validate' ] # Parse the long option. If it contains a =, figure out if the # argument is required or optional.  Optional arguments are formed # like [=ARG], whereas required are just ARG (in --long=ARG style). if obj [ 'long' ] && obj [ 'long' ] =~ / \[ / # Save the separator we used, as we're gonna need it, then split sep = $1 option , * argument = obj [ 'long' ] . split ( sep ) # The actual name of the long switch longs [ name ] = option # Preserve the original argument, as it may contain [ or =, # by joining with the character we split on.  Add a [ in front if # we split on that. argument = ( sep == '[' ? '[' : '' ) << Array ( argument ) . join ( sep ) # Do we expect multiple arguments which get turned into an array? arrayed [ name ] = true if argument =~ / \[ \* \] / # Is this long required or optional? required [ name ] = true unless argument =~ / \[ \* \] / elsif obj [ 'long' ] # We can't have a long as a switch when valid is set -- die. raise ArgumentRequiredWithValid if obj [ 'valid' ] # Set without any checking if it's just --long longs [ name ] = obj [ 'long' ] end # If we were given a list of valid arguments with 'valid,' this option # is definitely required. required [ name ] = true if obj [ 'valid' ] end rest = [ ] # Go through the arguments and try to figure out whom they belong to # at this point. while arg = args . shift if hashes [ 'shorts' ] . value? ( arg ) # Set the value to the next element in the args array since # this is a short. # If the next argument isn't a value, set this value to true if args . empty? || args . first . match ( / / ) value = true else value = args . shift end # Add this value to the choices hash with the key of the option's # name.  If we expect an array, tack this argument on. name = hashes [ 'shorts' ] . key ( arg ) if arrayed [ name ] choices [ name ] ||= [ ] choices [ name ] << value unless value . nil? choices [ name ] += arrayize_arguments ( args ) else choices [ name ] = value end elsif ( m = arg . match ( / / ) ) && longs . value? ( m [ 1 ] ) # The joke here is we always accept both --long=VALUE and --long VALUE. # Grab values from --long=VALUE format name , value = arg . split ( '=' , 2 ) name = longs . key ( name ) if value . nil? && args . first !~ / / # Grab value otherwise if not in --long=VALUE format.  Assume --long VALUE. # Value is nil if we don't have a = and the next argument is no good value = args . shift end # If we expect an array, tack this argument on. if arrayed [ name ] # If this is arrayed and the value isn't nil, set it. choices [ name ] ||= [ ] choices [ name ] << value unless value . nil? choices [ name ] += arrayize_arguments ( args ) else # If we set the value to nil, that means nothing was set and we # need to set the value to true.  We'll find out later if that's # acceptable or not. choices [ name ] = value . nil? ? true : value end else # If we're here, we have no idea what the passed argument is.  Die. if arg =~ / / raise UnknownOption else rest << arg end end end # Okay, we got all the choices.  Now go through and run any filters or # whatever on them. choices . each do | name , value | # Check to make sure we have all the required arguments. raise ArgumentRequired if required [ name ] && value === true # Validate the argument if we need to, against a regexp or a block. if validators [ name ] if validators [ name ] . is_a? ( Regexp ) && validators [ name ] =~ value elsif validators [ name ] . is_a? ( Proc ) && validators [ name ] . call ( value ) else raise ArgumentValidationFails end end # Make sure the argument is valid raise InvalidArgument unless Array ( value ) . all? { | v | hashes [ 'valids' ] [ name ] . include? ( v ) } if hashes [ 'valids' ] [ name ] # Cast the argument using the method defined in the constant hash. value = value . send ( CAST_METHODS [ hashes [ 'casts' ] [ name ] ] ) if hashes [ 'casts' ] . include? ( name ) # Run the value through a filter and re-set it with the return. value = hashes [ 'filters' ] [ name ] . call ( value ) if hashes [ 'filters' ] . include? ( name ) # Run an action block if there is one associated. hashes [ 'actions' ] [ name ] . call ( value ) if hashes [ 'actions' ] . include? ( name ) # Now that we've done all that, re-set the element of the choice hash # with the (potentially) new value. if arrayed [ name ] && choices [ name ] . empty? choices [ name ] = true else choices [ name ] = value end end # Die if we're missing any required arguments hard_required . each do | name , value | raise ArgumentRequired unless choices [ name ] end # Home stretch.  Go through all the defaults defined and if a choice # does not exist in our choices hash, set its value to the requested # default. hashes [ 'defaults' ] . each do | name , value | choices [ name ] = value unless choices [ name ] end # Return the choices hash and the rest of the args [ choices , rest ] end
Perhaps this method does too much.  It is, however, a parser.
 You pass it an array of arrays, the first element of each element being
 the option's name and the second element being a hash of the option's
 info.  You also pass in your current arguments, so it knows what to
 check against.
Perhaps this method does too much . It is however a parser . You pass it an array of arrays the first element of each element being the option s name and the second element being a hash of the option s info . You also pass in your current arguments so it knows what to check against .