class NumRu::Misc::KeywordOpt

Overview

A class to facilitate optional keyword arguments. More specifically, it helps the use of a Hash to mimic the keyword argument system. With this, you can set default values and description to each keyword argument.

Classes defined supplementarilly

class NumRu::Misc::HelpMessagingException < StandardError

This is for your convenience. See the usage example below.

Usage example

Suppose that you introduce keyword arguments “flag” and “number” to the method “hoge” in a class/module Foo. It can be done as follows:

require 'numru/misc'  # or, specifically, require 'numru/misc/keywordopt'
include NumRu

class Foo
  @@opt_hoge = Misc::KeywordOpt.new(
    ['flag',   false, 'whether or not ...'],
    ['number', 1,     'number of ...'],
    ['help',   false, 'show help message']
  )
  def hoge(regular_arg1, regular_arg2, options=nil)
    opt = @@opt_hoge.interpret(options)
    if opt['help']
      puts @@opt_hoge.help
      puts ' Current values='+opt.inspect
      raise Misc::HelpMessagingException, '** show help message and raise **'
    end
    # do what you want below
    # (options are set in the Hash opt: opt['flag'] and opt['number'])
  end
end

Here, the options are defined in the class variable @@opt_hoge with option names, default values, and descriptions (for help messaging). One can use the method hoge as follows:

foo = Foo.new
...
x = ...
y = ...
...
foo.hoge( x, y, {'flag'=>true, 'number'=>10} )

Or equivalently,

foo.hoge( x, y, 'flag'=>true, 'number'=>10 )

because ‘{}’ can be omitted here.

Tails of options names can be shortened as long as unambiguous:

foo.hoge( x, y, 'fla'=>true, 'num'=>10 )

To show the help message, call

foo.hoge( x, y, 'help'=>true )

This will cause the following help message printed with the exception HelpMessagingException raised.

<< Description of options >>
 option name => default value   description:
  "flag" =>     false   whether or not ...
  "number" =>   1       number of ...
  "help" =>     false   show help message
Current values={"help"=>true, "number"=>1, "flag"=>false}
NumRu::Misc::HelpMessagingException: ** help messaging done **
       from (irb):78:in "hoge"
       from (irb):83

Do not affraid to write long descriptions. The help method

breaks lines nicely if they are long.

Attributes

description[R]

protected method

val[R]

protected method

Public Class Methods

new(*args) click to toggle source

Constructor.

ARGUMENTS

  • args : (case 1) arrays of two or three elements: [option name, default value, description ], or [option name, default value] if you do not want to write descriptions. Option names and descriptions must be String. (case 2) another KeywordOpt. Cases 1 and 2 can be mixed.

    When case 2, a link to the other KeywordOpt is kept. Thus, change of values in it is reflected to the current one. However, the link is deleted if values are changed by #set.

RETURN VALUE

EXAMPLE

  • case 1

    opt = Misc::KeywordOpt.new(
      ['flag',   false, 'whether or not ...'],
      ['help',   false, 'show help message']
    )
    
  • case 2

    opt = Misc::KeywordOpt.new( optA, optB )
    
  • case 1 & 2

    opt = Misc::KeywordOpt.new(
      ['flag',   false, 'whether or not ...'],
      optA
    )
    
# File lib/numru/misc/keywordopt.rb, line 122
def initialize(*args)
  # USAGE:
  #    KeywordOpt.new([key,val,description],[key,val,description],..)
  #    where key is a String, and description can be omitted.
  @val=Hash.new
  @description=Hash.new
  @keys = []
  args.each{ |x|
    case x
    when Array
      unless (x[0]=='help') && @keys.include?(x[0])
        #^only 'help' can overwrap in the arguments
        @keys.push(x[0])
        @val[x[0]] = x[1]
        @description[x[0]] = ( (x.length>=3) ? x[2] : '' )
      end
    when KeywordOpt
      x.keys.each{|k|
        unless k=='help' && @keys.include?(k)
          #^only 'help' can overwrap in the arguments
          @keys.push(k)
          @val[k] = x  #.val[k]
          @description[k] = x.description[k]
        end
      }
      def @val.[](k)
        val = super(k)
        val.is_a?(KeywordOpt) ? val[k] : val
      end
      def @val.dup
        out = Hash.new
        each{|k,val| out[k] = (val.is_a?(KeywordOpt) ? val[k] : val)}
        out
      end
    else
      raise ArgumentError, "invalid argument: #{x.inspect}"
    end
  }
  @keys_sort = @keys.sort
  if @keys_sort.length != @keys_sort.uniq.length
    raise ArgumentError, "keys are not unique"
  end
end

Public Instance Methods

[](k) click to toggle source

Returns a value associated with the key (exact matching unlike interpret)

# File lib/numru/misc/keywordopt.rb, line 302
def [](k)
  v = @val[k]
  if v.is_a?(KeywordOpt)
    v = v.val[k]
  end
  v
end
help() click to toggle source

Returns a help message

RETURN VALUE

  • a String describing the option names, default values, and descriptions

# File lib/numru/misc/keywordopt.rb, line 294
def help
   "  option name\tdefault value\t# description:\n" +
   @keys.collect{|k| 
     __line_feed("  #{k.inspect}\t#{@val[k].inspect}\t# #{@description[k]}", 66)
   }.join("\n")
end
interpret(hash) click to toggle source

Interprets a hash that specifies option values.

ARGUMENTS

  • hash (Hash or nil) : a hash with string keys matching option names (initializedwhen constructed). The matching is case sensitive and done such that the tail of a option name can be omitted as long as unambiguous (for example, ‘num’ for ‘number’). If the argument is nil, the current values are returned. If there are two options like ‘max’ and ‘maxval’, to use a key ‘max’ (identical to the former paramer) is allowed, although it matches ‘maxval’ as well. (Again ‘ma’ is regarded ambiguous.)

RETURN VALUE

  • a Hash containing the option values (default values overwritten with hash).

POSSIBLE EXCEPTION

  • hash has a key that does not match any of the option names.

  • hash has a key that is ambiguous

# File lib/numru/misc/keywordopt.rb, line 185
def interpret(hash)
  return @val.dup if hash.nil?
  ##
  len = @val.length
  im = 0
  out = @val.dup
  hash.keys.sort.each do |key|
    rkey = /^#{key}/
    loop do
      if rkey =~ @keys_sort[im]
        if im<len-1 && rkey=~@keys_sort[im+1] &&
           key != @keys_sort[im]   # not identical
          raise ArgumentError, "Ambiguous key specification '#{key}'." 
        end
        out[@keys_sort[im]]=hash[key]
        break
      end
      im += 1
      if im==len
        raise ArgumentError, "'#{key}' does not match any of the keys."
      end
    end
  end
  out
end
keys() click to toggle source

Returns the keys.

# File lib/numru/misc/keywordopt.rb, line 311
def keys
  @keys.dup
end
select_existent(hash_or_keys) click to toggle source

Copies hash_or_keys, exclude ones that are not included in the option (by comparing keys), and returns it. I.e. select only the ones exsitent.

NOTE: ambiguity is not checked, so the resultant value is not necessarily accepted by #interpret.

ARGUMENTS

  • hash_or_keys (Hash or Array)

RETURN VALUE

  • a Hash or Array depending on the class of the argument hash_or_keys

# File lib/numru/misc/keywordopt.rb, line 223
def select_existent(hash_or_keys)
  hash_or_keys = hash_or_keys.dup         # not to alter the original
  len = @val.length
  im = 0
  kys = ( Array === hash_or_keys ? hash_or_keys : hash_or_keys.keys )
  kys.sort.each do |key|
    rkey = /^#{key}/
    loop do
      break if rkey =~ @keys_sort[im]
      im += 1
      if im==len
        hash_or_keys.delete(key)
        im = 0           # rewind
        break
      end
    end
  end
  hash_or_keys
end
set(hash) click to toggle source

Similar to #interpret but changes internal values.

ARGUMENTS

  • hash (Hash) : see #interpret. (Here, nil is not permitted though)

RETURN VALUE

  • a Hash containing the values replaced (the ones before calling this method)

POSSIBLE EXCEPTION

  • the argument is not a Hash

  • others are same as in #interpret

# File lib/numru/misc/keywordopt.rb, line 255
def set(hash)
  raise ArgumentError, "not a hash" if !hash.is_a?(Hash)
  ##
  replaced = Hash.new
  len = @val.length
  im = 0
  hash.keys.sort.each do |key|
    rkey = /^#{key}/
    loop do
      if rkey =~ @keys_sort[im]
        if im<len-1 && rkey=~@keys_sort[im+1]
          raise "Ambiguous key specification '#{key}'." 
        end
        replaced[@keys_sort[im]] = @val[@keys_sort[im]]
        @val[@keys_sort[im]]=hash[key]
        break
      end
      im += 1
      raise "'#{key}' does not match any of the keys." if im==len
    end
  end
  replaced
end

Private Instance Methods

__line_feed(str, len) click to toggle source
# File lib/numru/misc/keywordopt.rb, line 279
def __line_feed(str, len)
  if str.length >= len
    idx = str[0...len].rindex(/\s/)
    if idx
      str = str[0...idx] + "\n\t\t\t# " + __line_feed(str[(idx+1)..-1],50)
    end
  end
  str
end