def keyword_args(*args)
      argshash = args[-1].is_a?(Hash) ? args.pop : {}
      argshash = args.hashify(:optional).merge(argshash)
      others_OK = argshash.delete(:OTHERS)
      ret = {}

      # defaults, required, and checked
      required = []
      check = {}
      argshash.each do |key, val|
        # construct fleshed-out attribute hash for all args
        attrs = case val
                when Hash
                  val[:default] ||= [] if val[:enumerable]
                  val
                when :required
                  { :required => true }
                when :optional
                  {}
                when :enumerable
                  { :enumerable => true, :default => [] }
                when Array
                  { :valid => val }
                when Class, Module
                  { :valid => val }
                else
                  { :default => val }
                end

        # extract required flag, validity checks, and default vlues from attribute hash
        required << key if attrs[:required]
        check[key] = case valid = attrs[:valid]
                     when Enumerable
                       [:one_of, valid]
                     when Class, Module
                       [:is_a, valid]
                     else
                       [:ok]
                     end
        check[key][2] = [:allow_nil, :enumerable].select{|mod| attrs[mod]}
        ret[key] = attrs[:default] if attrs.include?(:default)
      end

      # check validity of keys
      unless others_OK or (others = self.keys - argshash.keys).empty?
        raise ArgumentError, "Invalid keyword arg#{others.length>1 && "s" or ""} #{others.collect{|a| a.inspect}.join(', ')}", caller
      end

      # process values, checking validity
      self.each do |key, val|
        code, valid, mods = check[key]
        mods ||= []
        val = [ val ] unless mods.include?(:enumerable) and val.is_a?(Enumerable)
        ok = val.all? { |v|
          if mods.include?(:allow_nil) and v.nil?
            true
          else
            case code
            when nil     then true      # for OTHERS args, there's no code
            when :ok     then true
            when :one_of then valid.include?(v)
            when :is_a   then v.is_a?(valid)
            end
          end
        }
        val = val.first unless mods.include?(:enumerable)
        raise ArgumentError, "Invalid value for keyword arg #{key.inspect} => #{val.inspect}", caller unless ok
        ret[key] = val
        required.delete(key)
      end

      unless required.empty?
        raise ArgumentError, "Missing required keyword arg#{required.length>1 && "s" or ""} #{required.collect{|a| a.inspect}.join(', ')}", caller
      end

      (class << ret ; self ; end).class_eval do
        argshash.keys.each do |key|
          define_method(key) do
            self[key]
          end
        end
      end

      ret
    end
def keyword_args(*args)
      argshash = args[-1].is_a?(Hash) ? args.pop : {}
      argshash = args.hashify(:optional).merge(argshash)
      others_OK = argshash.delete(:OTHERS)
      ret = {}

      # defaults, required, and checked
      required = []
      check = {}
      argshash.each do |key, val|
        # construct fleshed-out attribute hash for all args
        attrs = case val
                when Hash
                  val[:default] ||= [] if val[:enumerable]
                  val
                when :required
                  { :required => true }
                when :optional
                  {}
                when :enumerable
                  { :enumerable => true, :default => [] }
                when Array
                  { :valid => val }
                when Class, Module
                  { :valid => val }
                else
                  { :default => val }
                end

        # extract required flag, validity checks, and default vlues from attribute hash
        required << key if attrs[:required]
        check[key] = case valid = attrs[:valid]
                     when Enumerable
                       [:one_of, valid]
                     when Class, Module
                       [:is_a, valid]
                     else
                       [:ok]
                     end
        check[key][2] = [:allow_nil, :enumerable].select{|mod| attrs[mod]}
        ret[key] = attrs[:default] if attrs.include?(:default)
      end

      # check validity of keys
      unless others_OK or (others = self.keys - argshash.keys).empty?
        raise ArgumentError, "Invalid keyword arg#{others.length>1 && "s" or ""} #{others.collect{|a| a.inspect}.join(', ')}", caller
      end

      # process values, checking validity
      self.each do |key, val|
        code, valid, mods = check[key]
        mods ||= []
        val = [ val ] unless mods.include?(:enumerable) and val.is_a?(Enumerable)
        ok = val.all? { |v|
          if mods.include?(:allow_nil) and v.nil?
            true
          else
            case code
            when nil     then true      # for OTHERS args, there's no code
            when :ok     then true
            when :one_of then valid.include?(v)
            when :is_a   then v.is_a?(valid)
            end
          end
        }
        val = val.first unless mods.include?(:enumerable)
        raise ArgumentError, "Invalid value for keyword arg #{key.inspect} => #{val.inspect}", caller unless ok
        ret[key] = val
        required.delete(key)
      end

      unless required.empty?
        raise ArgumentError, "Missing required keyword arg#{required.length>1 && "s" or ""} #{required.collect{|a| a.inspect}.join(', ')}", caller
      end

      (class << ret ; self ; end).class_eval do
        argshash.keys.each do |key|
          define_method(key) do
            self[key]
          end
        end
      end

      ret
    end
given an argument hash and a description of acceptable keyword args and
 their default values, checks validity of the arguments and raises any
 errors, otherwise returns a new hash containing all arg values with
 defaults filled in as needed.

 args = hash.keyword_args(:a, :b, :c,        # these are all optional args
                          :d => :optional,   # same as listing it the arg up front
                          :e => :required,   # raises an error if arg not provided
                          :f => "hello"      # default value
                          :g => [:x,:y,:z]   # valid values
                          :h => Integer      # valid type
                          :i => :enumerable  # expect/coerce an enumerable, check all items, default is []
                          :j => { :valid => Integer, :allow_nil => true}   # Integer or nil
                          :j => { :valid => [:x,:y,:z], :default => :x, :required => true }   # combo
                          )

 by default this will raise an error if the hash contains any keys not
 listed.  however, if :OTHERS is specified as a keyword arg, that test
 will be disabled and any other key/value pairs will be passed through.

 Returns a Struct whose values are the