Home | History | Annotate | Download | only in lib
      1 # $Id: shflags 189 2013-01-15 00:13:10Z kate.ward (a] forestent.com $
      2 # vim:et:ft=sh:sts=2:sw=2
      3 #
      4 # Copyright 2008 Kate Ward. All Rights Reserved.
      5 # Released under the LGPL (GNU Lesser General Public License)
      6 #
      7 # shFlags -- Advanced command-line flag library for Unix shell scripts.
      8 # http://code.google.com/p/shflags/
      9 #
     10 # Author: kate.ward (a] forestent.com (Kate Ward)
     11 #
     12 # This module implements something like the google-gflags library available
     13 # from http://code.google.com/p/google-gflags/.
     14 #
     15 # FLAG TYPES: This is a list of the DEFINE_*'s that you can do.  All flags take
     16 # a name, default value, help-string, and optional 'short' name (one-letter
     17 # name).  Some flags have other arguments, which are described with the flag.
     18 #
     19 # DEFINE_string: takes any input, and intreprets it as a string.
     20 #
     21 # DEFINE_boolean: does not take any arguments. Say --myflag to set
     22 #   FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false. For short
     23 #   flags, passing the flag on the command-line negates the default value, i.e.
     24 #   if the default is true, passing the flag sets the value to false.
     25 #
     26 # DEFINE_float: takes an input and intreprets it as a floating point number. As
     27 #   shell does not support floats per-se, the input is merely validated as
     28 #   being a valid floating point value.
     29 #
     30 # DEFINE_integer: takes an input and intreprets it as an integer.
     31 #
     32 # SPECIAL FLAGS: There are a few flags that have special meaning:
     33 #   --help (or -?)  prints a list of all the flags in a human-readable fashion
     34 #   --flagfile=foo  read flags from foo.  (not implemented yet)
     35 #   --              as in getopt(), terminates flag-processing
     36 #
     37 # EXAMPLE USAGE:
     38 #
     39 #   -- begin hello.sh --
     40 #   #! /bin/sh
     41 #   . ./shflags
     42 #   DEFINE_string name 'world' "somebody's name" n
     43 #   FLAGS "$@" || exit $?
     44 #   eval set -- "${FLAGS_ARGV}"
     45 #   echo "Hello, ${FLAGS_name}."
     46 #   -- end hello.sh --
     47 #
     48 #   $ ./hello.sh -n Kate
     49 #   Hello, Kate.
     50 #
     51 # CUSTOMIZABLE BEHAVIOR:
     52 #
     53 # A script can override the default 'getopt' command by providing the path to
     54 # an alternate implementation by defining the FLAGS_GETOPT_CMD variable.
     55 #
     56 # NOTES:
     57 #
     58 # * Not all systems include a getopt version that supports long flags. On these
     59 #   systems, only short flags are recognized.
     60 
     61 #==============================================================================
     62 # shFlags
     63 #
     64 # Shared attributes:
     65 #   flags_error:  last error message
     66 #   flags_output: last function output (rarely valid)
     67 #   flags_return: last return value
     68 #
     69 #   __flags_longNames: list of long names for all flags
     70 #   __flags_shortNames: list of short names for all flags
     71 #   __flags_boolNames: list of boolean flag names
     72 #
     73 #   __flags_opts: options parsed by getopt
     74 #
     75 # Per-flag attributes:
     76 #   FLAGS_<flag_name>: contains value of flag named 'flag_name'
     77 #   __flags_<flag_name>_default: the default flag value
     78 #   __flags_<flag_name>_help: the flag help string
     79 #   __flags_<flag_name>_short: the flag short name
     80 #   __flags_<flag_name>_type: the flag type
     81 #
     82 # Notes:
     83 # - lists of strings are space separated, and a null value is the '~' char.
     84 
     85 # return if FLAGS already loaded
     86 [ -n "${FLAGS_VERSION:-}" ] && return 0
     87 FLAGS_VERSION='1.0.4pre'
     88 
     89 # return values that scripts can use
     90 FLAGS_TRUE=0
     91 FLAGS_FALSE=1
     92 FLAGS_ERROR=2
     93 
     94 # determine some reasonable command defaults
     95 __FLAGS_UNAME_S=`uname -s`
     96 case "${__FLAGS_UNAME_S}" in
     97   BSD) __FLAGS_EXPR_CMD='gexpr' ;;
     98   *) __FLAGS_EXPR_CMD='expr' ;;
     99 esac
    100 
    101 # commands a user can override if needed
    102 FLAGS_EXPR_CMD=${FLAGS_EXPR_CMD:-${__FLAGS_EXPR_CMD}}
    103 FLAGS_GETOPT_CMD=${FLAGS_GETOPT_CMD:-getopt}
    104 
    105 # specific shell checks
    106 if [ -n "${ZSH_VERSION:-}" ]; then
    107   setopt |grep "^shwordsplit$" >/dev/null
    108   if [ $? -ne ${FLAGS_TRUE} ]; then
    109     _flags_fatal 'zsh shwordsplit option is required for proper zsh operation'
    110   fi
    111   if [ -z "${FLAGS_PARENT:-}" ]; then
    112     _flags_fatal "zsh does not pass \$0 through properly. please declare' \
    113 \"FLAGS_PARENT=\$0\" before calling shFlags"
    114   fi
    115 fi
    116 
    117 # can we use built-ins?
    118 ( echo "${FLAGS_TRUE#0}"; ) >/dev/null 2>&1
    119 if [ $? -eq ${FLAGS_TRUE} ]; then
    120   __FLAGS_USE_BUILTIN=${FLAGS_TRUE}
    121 else
    122   __FLAGS_USE_BUILTIN=${FLAGS_FALSE}
    123 fi
    124 
    125 #
    126 # constants
    127 #
    128 
    129 # reserved flag names
    130 __FLAGS_RESERVED_LIST=' ARGC ARGV ERROR FALSE GETOPT_CMD HELP PARENT TRUE '
    131 __FLAGS_RESERVED_LIST="${__FLAGS_RESERVED_LIST} VERSION "
    132 
    133 # getopt version
    134 __FLAGS_GETOPT_VERS_STD=0
    135 __FLAGS_GETOPT_VERS_ENH=1
    136 __FLAGS_GETOPT_VERS_BSD=2
    137 
    138 ${FLAGS_GETOPT_CMD} >/dev/null 2>&1
    139 case $? in
    140   0) __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_STD} ;;  # bsd getopt
    141   2)
    142     # TODO(kward): look into '-T' option to test the internal getopt() version
    143     if [ "`${FLAGS_GETOPT_CMD} --version`" = '-- ' ]; then
    144       __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_STD}
    145     else
    146       __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_ENH}
    147     fi
    148     ;;
    149   *) _flags_fatal 'unable to determine getopt version' ;;
    150 esac
    151 
    152 # getopt optstring lengths
    153 __FLAGS_OPTSTR_SHORT=0
    154 __FLAGS_OPTSTR_LONG=1
    155 
    156 __FLAGS_NULL='~'
    157 
    158 # flag info strings
    159 __FLAGS_INFO_DEFAULT='default'
    160 __FLAGS_INFO_HELP='help'
    161 __FLAGS_INFO_SHORT='short'
    162 __FLAGS_INFO_TYPE='type'
    163 
    164 # flag lengths
    165 __FLAGS_LEN_SHORT=0
    166 __FLAGS_LEN_LONG=1
    167 
    168 # flag types
    169 __FLAGS_TYPE_NONE=0
    170 __FLAGS_TYPE_BOOLEAN=1
    171 __FLAGS_TYPE_FLOAT=2
    172 __FLAGS_TYPE_INTEGER=3
    173 __FLAGS_TYPE_STRING=4
    174 
    175 # set the constants readonly
    176 __flags_constants=`set |awk -F= '/^FLAGS_/ || /^__FLAGS_/ {print $1}'`
    177 for __flags_const in ${__flags_constants}; do
    178   # skip certain flags
    179   case ${__flags_const} in
    180     FLAGS_HELP) continue ;;
    181     FLAGS_PARENT) continue ;;
    182   esac
    183   # set flag readonly
    184   if [ -z "${ZSH_VERSION:-}" ]; then
    185     readonly ${__flags_const}
    186   else  # handle zsh
    187     case ${ZSH_VERSION} in
    188       [123].*) readonly ${__flags_const} ;;
    189       *) readonly -g ${__flags_const} ;;  # declare readonly constants globally
    190     esac
    191   fi
    192 done
    193 unset __flags_const __flags_constants
    194 
    195 #
    196 # internal variables
    197 #
    198 
    199 # space separated lists
    200 __flags_boolNames=' '  # boolean flag names
    201 __flags_longNames=' '  # long flag names
    202 __flags_shortNames=' '  # short flag names
    203 __flags_definedNames=' ' # defined flag names (used for validation)
    204 
    205 __flags_columns=''  # screen width in columns
    206 __flags_opts=''  # temporary storage for parsed getopt flags
    207 
    208 #------------------------------------------------------------------------------
    209 # private functions
    210 #
    211 
    212 # logging functions
    213 _flags_debug() { echo "flags:DEBUG $@" >&2; }
    214 _flags_warn() { echo "flags:WARN $@" >&2; }
    215 _flags_error() { echo "flags:ERROR $@" >&2; }
    216 _flags_fatal() { echo "flags:FATAL $@" >&2; exit ${FLAGS_ERROR}; }
    217 
    218 # Define a flag.
    219 #
    220 # Calling this function will define the following info variables for the
    221 # specified flag:
    222 #   FLAGS_flagname - the name for this flag (based upon the long flag name)
    223 #   __flags_<flag_name>_default - the default value
    224 #   __flags_flagname_help - the help string
    225 #   __flags_flagname_short - the single letter alias
    226 #   __flags_flagname_type - the type of flag (one of __FLAGS_TYPE_*)
    227 #
    228 # Args:
    229 #   _flags__type: integer: internal type of flag (__FLAGS_TYPE_*)
    230 #   _flags__name: string: long flag name
    231 #   _flags__default: default flag value
    232 #   _flags__help: string: help string
    233 #   _flags__short: string: (optional) short flag name
    234 # Returns:
    235 #   integer: success of operation, or error
    236 _flags_define()
    237 {
    238   if [ $# -lt 4 ]; then
    239     flags_error='DEFINE error: too few arguments'
    240     flags_return=${FLAGS_ERROR}
    241     _flags_error "${flags_error}"
    242     return ${flags_return}
    243   fi
    244 
    245   _flags_type_=$1
    246   _flags_name_=$2
    247   _flags_default_=$3
    248   _flags_help_=$4
    249   _flags_short_=${5:-${__FLAGS_NULL}}
    250 
    251   _flags_return_=${FLAGS_TRUE}
    252   _flags_usName_=`_flags_underscoreName ${_flags_name_}`
    253 
    254   # check whether the flag name is reserved
    255   _flags_itemInList ${_flags_usName_} "${__FLAGS_RESERVED_LIST}"
    256   if [ $? -eq ${FLAGS_TRUE} ]; then
    257     flags_error="flag name (${_flags_name_}) is reserved"
    258     _flags_return_=${FLAGS_ERROR}
    259   fi
    260 
    261   # require short option for getopt that don't support long options
    262   if [ ${_flags_return_} -eq ${FLAGS_TRUE} \
    263       -a ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} \
    264       -a "${_flags_short_}" = "${__FLAGS_NULL}" ]
    265   then
    266     flags_error="short flag required for (${_flags_name_}) on this platform"
    267     _flags_return_=${FLAGS_ERROR}
    268   fi
    269 
    270   # check for existing long name definition
    271   if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then
    272     if _flags_itemInList ${_flags_usName_} ${__flags_definedNames}; then
    273       flags_error="definition for ([no]${_flags_name_}) already exists"
    274       _flags_warn "${flags_error}"
    275       _flags_return_=${FLAGS_FALSE}
    276     fi
    277   fi
    278 
    279   # check for existing short name definition
    280   if [ ${_flags_return_} -eq ${FLAGS_TRUE} \
    281       -a "${_flags_short_}" != "${__FLAGS_NULL}" ]
    282   then
    283     if _flags_itemInList "${_flags_short_}" ${__flags_shortNames}; then
    284       flags_error="flag short name (${_flags_short_}) already defined"
    285       _flags_warn "${flags_error}"
    286       _flags_return_=${FLAGS_FALSE}
    287     fi
    288   fi
    289 
    290   # handle default value. note, on several occasions the 'if' portion of an
    291   # if/then/else contains just a ':' which does nothing. a binary reversal via
    292   # '!' is not done because it does not work on all shells.
    293   if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then
    294     case ${_flags_type_} in
    295       ${__FLAGS_TYPE_BOOLEAN})
    296         if _flags_validBool "${_flags_default_}"; then
    297           case ${_flags_default_} in
    298             true|t|0) _flags_default_=${FLAGS_TRUE} ;;
    299             false|f|1) _flags_default_=${FLAGS_FALSE} ;;
    300           esac
    301         else
    302           flags_error="invalid default flag value '${_flags_default_}'"
    303           _flags_return_=${FLAGS_ERROR}
    304         fi
    305         ;;
    306 
    307       ${__FLAGS_TYPE_FLOAT})
    308         if _flags_validFloat "${_flags_default_}"; then
    309           :
    310         else
    311           flags_error="invalid default flag value '${_flags_default_}'"
    312           _flags_return_=${FLAGS_ERROR}
    313         fi
    314         ;;
    315 
    316       ${__FLAGS_TYPE_INTEGER})
    317         if _flags_validInt "${_flags_default_}"; then
    318           :
    319         else
    320           flags_error="invalid default flag value '${_flags_default_}'"
    321           _flags_return_=${FLAGS_ERROR}
    322         fi
    323         ;;
    324 
    325       ${__FLAGS_TYPE_STRING}) ;;  # everything in shell is a valid string
    326 
    327       *)
    328         flags_error="unrecognized flag type '${_flags_type_}'"
    329         _flags_return_=${FLAGS_ERROR}
    330         ;;
    331     esac
    332   fi
    333 
    334   if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then
    335     # store flag information
    336     eval "FLAGS_${_flags_usName_}='${_flags_default_}'"
    337     eval "__flags_${_flags_usName_}_${__FLAGS_INFO_TYPE}=${_flags_type_}"
    338     eval "__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}=\
    339 \"${_flags_default_}\""
    340     eval "__flags_${_flags_usName_}_${__FLAGS_INFO_HELP}=\"${_flags_help_}\""
    341     eval "__flags_${_flags_usName_}_${__FLAGS_INFO_SHORT}='${_flags_short_}'"
    342 
    343     # append flag names to name lists
    344     __flags_shortNames="${__flags_shortNames}${_flags_short_} "
    345     __flags_longNames="${__flags_longNames}${_flags_name_} "
    346     [ ${_flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} ] && \
    347         __flags_boolNames="${__flags_boolNames}no${_flags_name_} "
    348 
    349     # append flag names to defined names for later validation checks
    350     __flags_definedNames="${__flags_definedNames}${_flags_usName_} "
    351     [ ${_flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} ] && \
    352         __flags_definedNames="${__flags_definedNames}no${_flags_usName_} "
    353   fi
    354 
    355   flags_return=${_flags_return_}
    356   unset _flags_default_ _flags_help_ _flags_name_ _flags_return_ \
    357       _flags_short_ _flags_type_ _flags_usName_
    358   [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}"
    359   return ${flags_return}
    360 }
    361 
    362 # Underscore a flag name by replacing dashes with underscores.
    363 #
    364 # Args:
    365 #   unnamed: string: log flag name
    366 # Output:
    367 #   string: underscored name
    368 _flags_underscoreName()
    369 {
    370   echo $1 |tr '-' '_'
    371 }
    372 
    373 # Return valid getopt options using currently defined list of long options.
    374 #
    375 # This function builds a proper getopt option string for short (and long)
    376 # options, using the current list of long options for reference.
    377 #
    378 # Args:
    379 #   _flags_optStr: integer: option string type (__FLAGS_OPTSTR_*)
    380 # Output:
    381 #   string: generated option string for getopt
    382 # Returns:
    383 #   boolean: success of operation (always returns True)
    384 _flags_genOptStr()
    385 {
    386   _flags_optStrType_=$1
    387 
    388   _flags_opts_=''
    389 
    390   for _flags_name_ in ${__flags_longNames}; do
    391     _flags_usName_=`_flags_underscoreName ${_flags_name_}`
    392     _flags_type_=`_flags_getFlagInfo ${_flags_usName_} ${__FLAGS_INFO_TYPE}`
    393     [ $? -eq ${FLAGS_TRUE} ] || _flags_fatal 'call to _flags_type_ failed'
    394     case ${_flags_optStrType_} in
    395       ${__FLAGS_OPTSTR_SHORT})
    396         _flags_shortName_=`_flags_getFlagInfo \
    397             ${_flags_usName_} ${__FLAGS_INFO_SHORT}`
    398         if [ "${_flags_shortName_}" != "${__FLAGS_NULL}" ]; then
    399           _flags_opts_="${_flags_opts_}${_flags_shortName_}"
    400           # getopt needs a trailing ':' to indicate a required argument
    401           [ ${_flags_type_} -ne ${__FLAGS_TYPE_BOOLEAN} ] && \
    402               _flags_opts_="${_flags_opts_}:"
    403         fi
    404         ;;
    405 
    406       ${__FLAGS_OPTSTR_LONG})
    407         _flags_opts_="${_flags_opts_:+${_flags_opts_},}${_flags_name_}"
    408         # getopt needs a trailing ':' to indicate a required argument
    409         [ ${_flags_type_} -ne ${__FLAGS_TYPE_BOOLEAN} ] && \
    410             _flags_opts_="${_flags_opts_}:"
    411         ;;
    412     esac
    413   done
    414 
    415   echo "${_flags_opts_}"
    416   unset _flags_name_ _flags_opts_ _flags_optStrType_ _flags_shortName_ \
    417       _flags_type_ _flags_usName_
    418   return ${FLAGS_TRUE}
    419 }
    420 
    421 # Returns flag details based on a flag name and flag info.
    422 #
    423 # Args:
    424 #   string: underscored flag name
    425 #   string: flag info (see the _flags_define function for valid info types)
    426 # Output:
    427 #   string: value of dereferenced flag variable
    428 # Returns:
    429 #   integer: one of FLAGS_{TRUE|FALSE|ERROR}
    430 _flags_getFlagInfo()
    431 {
    432   # note: adding gFI to variable names to prevent naming conflicts with calling
    433   # functions
    434   _flags_gFI_usName_=$1
    435   _flags_gFI_info_=$2
    436 
    437   _flags_infoVar_="__flags_${_flags_gFI_usName_}_${_flags_gFI_info_}"
    438   _flags_strToEval_="_flags_infoValue_=\"\${${_flags_infoVar_}:-}\""
    439   eval "${_flags_strToEval_}"
    440   if [ -n "${_flags_infoValue_}" ]; then
    441     flags_return=${FLAGS_TRUE}
    442   else
    443     # see if the _flags_gFI_usName_ variable is a string as strings can be
    444     # empty...
    445     # note: the DRY principle would say to have this function call itself for
    446     # the next three lines, but doing so results in an infinite loop as an
    447     # invalid _flags_name_ will also not have the associated _type variable.
    448     # Because it doesn't (it will evaluate to an empty string) the logic will
    449     # try to find the _type variable of the _type variable, and so on. Not so
    450     # good ;-)
    451     _flags_typeVar_="__flags_${_flags_gFI_usName_}_${__FLAGS_INFO_TYPE}"
    452     _flags_strToEval_="_flags_typeValue_=\"\${${_flags_typeVar_}:-}\""
    453     eval "${_flags_strToEval_}"
    454     if [ "${_flags_typeValue_}" = "${__FLAGS_TYPE_STRING}" ]; then
    455       flags_return=${FLAGS_TRUE}
    456     else
    457       flags_return=${FLAGS_ERROR}
    458       flags_error="missing flag info variable (${_flags_infoVar_})"
    459     fi
    460   fi
    461 
    462   echo "${_flags_infoValue_}"
    463   unset _flags_gFI_usName_ _flags_gfI_info_ _flags_infoValue_ _flags_infoVar_ \
    464       _flags_strToEval_ _flags_typeValue_ _flags_typeVar_
    465   [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}"
    466   return ${flags_return}
    467 }
    468 
    469 # Check for presense of item in a list.
    470 #
    471 # Passed a string (e.g. 'abc'), this function will determine if the string is
    472 # present in the list of strings (e.g.  ' foo bar abc ').
    473 #
    474 # Args:
    475 #   _flags_str_: string: string to search for in a list of strings
    476 #   unnamed: list: list of strings
    477 # Returns:
    478 #   boolean: true if item is in the list
    479 _flags_itemInList() {
    480   _flags_str_=$1
    481   shift
    482 
    483   echo " ${*:-} " |grep " ${_flags_str_} " >/dev/null
    484   if [ $? -eq 0 ]; then
    485     flags_return=${FLAGS_TRUE}
    486   else
    487     flags_return=${FLAGS_FALSE}
    488   fi
    489 
    490   unset _flags_str_
    491   return ${flags_return}
    492 }
    493 
    494 # Returns the width of the current screen.
    495 #
    496 # Output:
    497 #   integer: width in columns of the current screen.
    498 _flags_columns()
    499 {
    500   if [ -z "${__flags_columns}" ]; then
    501     # determine the value and store it
    502     if eval stty size >/dev/null 2>&1; then
    503       # stty size worked :-)
    504       set -- `stty size`
    505       __flags_columns=$2
    506     elif eval tput cols >/dev/null 2>&1; then
    507       set -- `tput cols`
    508       __flags_columns=$1
    509     else
    510       __flags_columns=80  # default terminal width
    511     fi
    512   fi
    513   echo ${__flags_columns}
    514 }
    515 
    516 # Validate a boolean.
    517 #
    518 # Args:
    519 #   _flags__bool: boolean: value to validate
    520 # Returns:
    521 #   bool: true if the value is a valid boolean
    522 _flags_validBool()
    523 {
    524   _flags_bool_=$1
    525 
    526   flags_return=${FLAGS_TRUE}
    527   case "${_flags_bool_}" in
    528     true|t|0) ;;
    529     false|f|1) ;;
    530     *) flags_return=${FLAGS_FALSE} ;;
    531   esac
    532 
    533   unset _flags_bool_
    534   return ${flags_return}
    535 }
    536 
    537 # Validate a float.
    538 #
    539 # Args:
    540 #   _flags_float_: float: value to validate
    541 # Returns:
    542 #   bool: true if the value is a valid integer
    543 _flags_validFloat()
    544 {
    545   flags_return=${FLAGS_FALSE}
    546   [ -n "$1" ] || return ${flags_return}
    547   _flags_float_=$1
    548 
    549   if _flags_validInt ${_flags_float_}; then
    550     flags_return=${FLAGS_TRUE}
    551   elif _flags_useBuiltin; then
    552     _flags_float_whole_=${_flags_float_%.*}
    553     _flags_float_fraction_=${_flags_float_#*.}
    554     if _flags_validInt ${_flags_float_whole_:-0} -a \
    555       _flags_validInt ${_flags_float_fraction_}; then
    556       flags_return=${FLAGS_TRUE}
    557     fi
    558     unset _flags_float_whole_ _flags_float_fraction_
    559   else
    560     flags_return=${FLAGS_TRUE}
    561     case ${_flags_float_} in
    562       -*)  # negative floats
    563         _flags_test_=`${FLAGS_EXPR_CMD} -- "${_flags_float_}" :\
    564             '\(-[0-9]*\.[0-9]*\)'`
    565         ;;
    566       *)  # positive floats
    567         _flags_test_=`${FLAGS_EXPR_CMD} -- "${_flags_float_}" :\
    568             '\([0-9]*\.[0-9]*\)'`
    569         ;;
    570     esac
    571     [ "${_flags_test_}" != "${_flags_float_}" ] && flags_return=${FLAGS_FALSE}
    572     unset _flags_test_
    573   fi
    574 
    575   unset _flags_float_ _flags_float_whole_ _flags_float_fraction_
    576   return ${flags_return}
    577 }
    578 
    579 # Validate an integer.
    580 #
    581 # Args:
    582 #   _flags_int_: integer: value to validate
    583 # Returns:
    584 #   bool: true if the value is a valid integer
    585 _flags_validInt()
    586 {
    587   flags_return=${FLAGS_FALSE}
    588   [ -n "$1" ] || return ${flags_return}
    589   _flags_int_=$1
    590 
    591   case ${_flags_int_} in
    592     -*.*) ;;  # ignore negative floats (we'll invalidate them later)
    593     -*)  # strip possible leading negative sign
    594       if _flags_useBuiltin; then
    595         _flags_int_=${_flags_int_#-}
    596       else
    597         _flags_int_=`${FLAGS_EXPR_CMD} -- "${_flags_int_}" : '-\([0-9][0-9]*\)'`
    598       fi
    599       ;;
    600   esac
    601 
    602   case ${_flags_int_} in
    603     *[!0-9]*) flags_return=${FLAGS_FALSE} ;;
    604     *) flags_return=${FLAGS_TRUE} ;;
    605   esac
    606 
    607   unset _flags_int_
    608   return ${flags_return}
    609 }
    610 
    611 # Parse command-line options using the standard getopt.
    612 #
    613 # Note: the flag options are passed around in the global __flags_opts so that
    614 # the formatting is not lost due to shell parsing and such.
    615 #
    616 # Args:
    617 #   @: varies: command-line options to parse
    618 # Returns:
    619 #   integer: a FLAGS success condition
    620 _flags_getoptStandard()
    621 {
    622   flags_return=${FLAGS_TRUE}
    623   _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}`
    624 
    625   # check for spaces in passed options
    626   for _flags_opt_ in "$@"; do
    627     # note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06
    628     _flags_match_=`echo "x${_flags_opt_}x" |sed 's/ //g'`
    629     if [ "${_flags_match_}" != "x${_flags_opt_}x" ]; then
    630       flags_error='the available getopt does not support spaces in options'
    631       flags_return=${FLAGS_ERROR}
    632       break
    633     fi
    634   done
    635 
    636   if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then
    637     __flags_opts=`getopt ${_flags_shortOpts_} $@ 2>&1`
    638     _flags_rtrn_=$?
    639     if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then
    640       _flags_warn "${__flags_opts}"
    641       flags_error='unable to parse provided options with getopt.'
    642       flags_return=${FLAGS_ERROR}
    643     fi
    644   fi
    645 
    646   unset _flags_match_ _flags_opt_ _flags_rtrn_ _flags_shortOpts_
    647   return ${flags_return}
    648 }
    649 
    650 # Parse command-line options using the enhanced getopt.
    651 #
    652 # Note: the flag options are passed around in the global __flags_opts so that
    653 # the formatting is not lost due to shell parsing and such.
    654 #
    655 # Args:
    656 #   @: varies: command-line options to parse
    657 # Returns:
    658 #   integer: a FLAGS success condition
    659 _flags_getoptEnhanced()
    660 {
    661   flags_return=${FLAGS_TRUE}
    662   _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}`
    663   _flags_boolOpts_=`echo "${__flags_boolNames}" \
    664       |sed 's/^ *//;s/ *$//;s/ /,/g'`
    665   _flags_longOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_LONG}`
    666 
    667   __flags_opts=`${FLAGS_GETOPT_CMD} \
    668       -o ${_flags_shortOpts_} \
    669       -l "${_flags_longOpts_},${_flags_boolOpts_}" \
    670       -- "$@" 2>&1`
    671   _flags_rtrn_=$?
    672   if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then
    673     _flags_warn "${__flags_opts}"
    674     flags_error='unable to parse provided options with getopt.'
    675     flags_return=${FLAGS_ERROR}
    676   fi
    677 
    678   unset _flags_boolOpts_ _flags_longOpts_ _flags_rtrn_ _flags_shortOpts_
    679   return ${flags_return}
    680 }
    681 
    682 # Dynamically parse a getopt result and set appropriate variables.
    683 #
    684 # This function does the actual conversion of getopt output and runs it through
    685 # the standard case structure for parsing. The case structure is actually quite
    686 # dynamic to support any number of flags.
    687 #
    688 # Args:
    689 #   argc: int: original command-line argument count
    690 #   @: varies: output from getopt parsing
    691 # Returns:
    692 #   integer: a FLAGS success condition
    693 _flags_parseGetopt()
    694 {
    695   _flags_argc_=$1
    696   shift
    697 
    698   flags_return=${FLAGS_TRUE}
    699 
    700   if [ ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} ]; then
    701     set -- $@
    702   else
    703     # note the quotes around the `$@' -- they are essential!
    704     eval set -- "$@"
    705   fi
    706 
    707   # Provide user with the number of arguments to shift by later.
    708   # NOTE: the FLAGS_ARGC variable is obsolete as of 1.0.3 because it does not
    709   # properly give user access to non-flag arguments mixed in between flag
    710   # arguments. Its usage was replaced by FLAGS_ARGV, and it is being kept only
    711   # for backwards compatibility reasons.
    712   FLAGS_ARGC=`_flags_math "$# - 1 - ${_flags_argc_}"`
    713 
    714   # handle options. note options with values must do an additional shift
    715   while true; do
    716     _flags_opt_=$1
    717     _flags_arg_=${2:-}
    718     _flags_type_=${__FLAGS_TYPE_NONE}
    719     _flags_name_=''
    720 
    721     # determine long flag name
    722     case "${_flags_opt_}" in
    723       --) shift; break ;;  # discontinue option parsing
    724 
    725       --*)  # long option
    726         if _flags_useBuiltin; then
    727           _flags_opt_=${_flags_opt_#*--}
    728         else
    729           _flags_opt_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : '--\(.*\)'`
    730         fi
    731         _flags_len_=${__FLAGS_LEN_LONG}
    732         if _flags_itemInList "${_flags_opt_}" ${__flags_longNames}; then
    733           _flags_name_=${_flags_opt_}
    734         else
    735           # check for negated long boolean version
    736           if _flags_itemInList "${_flags_opt_}" ${__flags_boolNames}; then
    737             if _flags_useBuiltin; then
    738               _flags_name_=${_flags_opt_#*no}
    739             else
    740               _flags_name_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : 'no\(.*\)'`
    741             fi
    742             _flags_type_=${__FLAGS_TYPE_BOOLEAN}
    743             _flags_arg_=${__FLAGS_NULL}
    744           fi
    745         fi
    746         ;;
    747 
    748       -*)  # short option
    749         if _flags_useBuiltin; then
    750           _flags_opt_=${_flags_opt_#*-}
    751         else
    752           _flags_opt_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : '-\(.*\)'`
    753         fi
    754         _flags_len_=${__FLAGS_LEN_SHORT}
    755         if _flags_itemInList "${_flags_opt_}" ${__flags_shortNames}; then
    756           # yes. match short name to long name. note purposeful off-by-one
    757           # (too high) with awk calculations.
    758           _flags_pos_=`echo "${__flags_shortNames}" \
    759               |awk 'BEGIN{RS=" ";rn=0}$0==e{rn=NR}END{print rn}' \
    760                   e=${_flags_opt_}`
    761           _flags_name_=`echo "${__flags_longNames}" \
    762               |awk 'BEGIN{RS=" "}rn==NR{print $0}' rn="${_flags_pos_}"`
    763         fi
    764         ;;
    765     esac
    766 
    767     # die if the flag was unrecognized
    768     if [ -z "${_flags_name_}" ]; then
    769       flags_error="unrecognized option (${_flags_opt_})"
    770       flags_return=${FLAGS_ERROR}
    771       break
    772     fi
    773 
    774     # set new flag value
    775     _flags_usName_=`_flags_underscoreName ${_flags_name_}`
    776     [ ${_flags_type_} -eq ${__FLAGS_TYPE_NONE} ] && \
    777         _flags_type_=`_flags_getFlagInfo \
    778             "${_flags_usName_}" ${__FLAGS_INFO_TYPE}`
    779     case ${_flags_type_} in
    780       ${__FLAGS_TYPE_BOOLEAN})
    781         if [ ${_flags_len_} -eq ${__FLAGS_LEN_LONG} ]; then
    782           if [ "${_flags_arg_}" != "${__FLAGS_NULL}" ]; then
    783             eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}"
    784           else
    785             eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}"
    786           fi
    787         else
    788           _flags_strToEval_="_flags_val_=\
    789 \${__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}}"
    790           eval "${_flags_strToEval_}"
    791           if [ ${_flags_val_} -eq ${FLAGS_FALSE} ]; then
    792             eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}"
    793           else
    794             eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}"
    795           fi
    796         fi
    797         ;;
    798 
    799       ${__FLAGS_TYPE_FLOAT})
    800         if _flags_validFloat "${_flags_arg_}"; then
    801           eval "FLAGS_${_flags_usName_}='${_flags_arg_}'"
    802         else
    803           flags_error="invalid float value (${_flags_arg_})"
    804           flags_return=${FLAGS_ERROR}
    805           break
    806         fi
    807         ;;
    808 
    809       ${__FLAGS_TYPE_INTEGER})
    810         if _flags_validInt "${_flags_arg_}"; then
    811           eval "FLAGS_${_flags_usName_}='${_flags_arg_}'"
    812         else
    813           flags_error="invalid integer value (${_flags_arg_})"
    814           flags_return=${FLAGS_ERROR}
    815           break
    816         fi
    817         ;;
    818 
    819       ${__FLAGS_TYPE_STRING})
    820         eval "FLAGS_${_flags_usName_}='${_flags_arg_}'"
    821         ;;
    822     esac
    823 
    824     # handle special case help flag
    825     if [ "${_flags_usName_}" = 'help' ]; then
    826       if [ ${FLAGS_help} -eq ${FLAGS_TRUE} ]; then
    827         flags_help
    828         flags_error='help requested'
    829         flags_return=${FLAGS_TRUE}
    830         break
    831       fi
    832     fi
    833 
    834     # shift the option and non-boolean arguements out.
    835     shift
    836     [ ${_flags_type_} != ${__FLAGS_TYPE_BOOLEAN} ] && shift
    837   done
    838 
    839   # give user back non-flag arguments
    840   FLAGS_ARGV=''
    841   while [ $# -gt 0 ]; do
    842     FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} }'$1'"
    843     shift
    844   done
    845 
    846   unset _flags_arg_ _flags_len_ _flags_name_ _flags_opt_ _flags_pos_ \
    847       _flags_strToEval_ _flags_type_ _flags_usName_ _flags_val_
    848   return ${flags_return}
    849 }
    850 
    851 # Perform some path using built-ins.
    852 #
    853 # Args:
    854 #   $@: string: math expression to evaluate
    855 # Output:
    856 #   integer: the result
    857 # Returns:
    858 #   bool: success of math evaluation
    859 _flags_math()
    860 {
    861   if [ $# -eq 0 ]; then
    862     flags_return=${FLAGS_FALSE}
    863   elif _flags_useBuiltin; then
    864     # Variable assignment is needed as workaround for Solaris Bourne shell,
    865     # which cannot parse a bare $((expression)).
    866     _flags_expr_='$(($@))'
    867     eval echo ${_flags_expr_}
    868     flags_return=$?
    869     unset _flags_expr_
    870   else
    871     eval expr $@
    872     flags_return=$?
    873   fi
    874 
    875   return ${flags_return}
    876 }
    877 
    878 # Cross-platform strlen() implementation.
    879 #
    880 # Args:
    881 #   _flags_str: string: to determine length of
    882 # Output:
    883 #   integer: length of string
    884 # Returns:
    885 #   bool: success of strlen evaluation
    886 _flags_strlen()
    887 {
    888   _flags_str_=${1:-}
    889 
    890   if [ -z "${_flags_str_}" ]; then
    891     flags_output=0
    892   elif _flags_useBuiltin; then
    893     flags_output=${#_flags_str_}
    894   else
    895     flags_output=`${FLAGS_EXPR_CMD} -- "${_flags_str_}" : '.*'`
    896   fi
    897   flags_return=$?
    898 
    899   unset _flags_str_
    900   echo ${flags_output}
    901   return ${flags_return}
    902 }
    903 
    904 # Use built-in helper function to enable unit testing.
    905 #
    906 # Args:
    907 #   None
    908 # Returns:
    909 #   bool: true if built-ins should be used
    910 _flags_useBuiltin()
    911 {
    912   return ${__FLAGS_USE_BUILTIN}
    913 }
    914 
    915 #------------------------------------------------------------------------------
    916 # public functions
    917 # 
    918 # A basic boolean flag. Boolean flags do not take any arguments, and their
    919 # value is either 1 (false) or 0 (true). For long flags, the false value is
    920 # specified on the command line by prepending the word 'no'. With short flags,
    921 # the presense of the flag toggles the current value between true and false.
    922 # Specifying a short boolean flag twice on the command results in returning the
    923 # value back to the default value.
    924 #
    925 # A default value is required for boolean flags.
    926 #
    927 # For example, lets say a Boolean flag was created whose long name was 'update'
    928 # and whose short name was 'x', and the default value was 'false'. This flag
    929 # could be explicitly set to 'true' with '--update' or by '-x', and it could be
    930 # explicitly set to 'false' with '--noupdate'.
    931 DEFINE_boolean() { _flags_define ${__FLAGS_TYPE_BOOLEAN} "$@"; }
    932 
    933 # Other basic flags.
    934 DEFINE_float()   { _flags_define ${__FLAGS_TYPE_FLOAT} "$@"; }
    935 DEFINE_integer() { _flags_define ${__FLAGS_TYPE_INTEGER} "$@"; }
    936 DEFINE_string()  { _flags_define ${__FLAGS_TYPE_STRING} "$@"; }
    937 
    938 # Parse the flags.
    939 #
    940 # Args:
    941 #   unnamed: list: command-line flags to parse
    942 # Returns:
    943 #   integer: success of operation, or error
    944 FLAGS()
    945 {
    946   # define a standard 'help' flag if one isn't already defined
    947   [ -z "${__flags_help_type:-}" ] && \
    948       DEFINE_boolean 'help' false 'show this help' 'h'
    949 
    950   # parse options
    951   if [ $# -gt 0 ]; then
    952     if [ ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} ]; then
    953       _flags_getoptStandard "$@"
    954     else
    955       _flags_getoptEnhanced "$@"
    956     fi
    957     flags_return=$?
    958   else
    959     # nothing passed; won't bother running getopt
    960     __flags_opts='--'
    961     flags_return=${FLAGS_TRUE}
    962   fi
    963 
    964   if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then
    965     _flags_parseGetopt $# "${__flags_opts}"
    966     flags_return=$?
    967   fi
    968 
    969   [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_fatal "${flags_error}"
    970   return ${flags_return}
    971 }
    972 
    973 # This is a helper function for determining the 'getopt' version for platforms
    974 # where the detection isn't working. It simply outputs debug information that
    975 # can be included in a bug report.
    976 #
    977 # Args:
    978 #   none
    979 # Output:
    980 #   debug info that can be included in a bug report
    981 # Returns:
    982 #   nothing
    983 flags_getoptInfo()
    984 {
    985   # platform info
    986   _flags_debug "uname -a: `uname -a`"
    987   _flags_debug "PATH: ${PATH}"
    988 
    989   # shell info
    990   if [ -n "${BASH_VERSION:-}" ]; then
    991     _flags_debug 'shell: bash'
    992     _flags_debug "BASH_VERSION: ${BASH_VERSION}"
    993   elif [ -n "${ZSH_VERSION:-}" ]; then
    994     _flags_debug 'shell: zsh'
    995     _flags_debug "ZSH_VERSION: ${ZSH_VERSION}"
    996   fi
    997 
    998   # getopt info
    999   ${FLAGS_GETOPT_CMD} >/dev/null
   1000   _flags_getoptReturn=$?
   1001   _flags_debug "getopt return: ${_flags_getoptReturn}"
   1002   _flags_debug "getopt --version: `${FLAGS_GETOPT_CMD} --version 2>&1`"
   1003 
   1004   unset _flags_getoptReturn
   1005 }
   1006 
   1007 # Returns whether the detected getopt version is the enhanced version.
   1008 #
   1009 # Args:
   1010 #   none
   1011 # Output:
   1012 #   none
   1013 # Returns:
   1014 #   bool: true if getopt is the enhanced version
   1015 flags_getoptIsEnh()
   1016 {
   1017   test ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_ENH}
   1018 }
   1019 
   1020 # Returns whether the detected getopt version is the standard version.
   1021 #
   1022 # Args:
   1023 #   none
   1024 # Returns:
   1025 #   bool: true if getopt is the standard version
   1026 flags_getoptIsStd()
   1027 {
   1028   test ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_STD}
   1029 }
   1030 
   1031 # This is effectively a 'usage()' function. It prints usage information and
   1032 # exits the program with ${FLAGS_FALSE} if it is ever found in the command line
   1033 # arguments. Note this function can be overridden so other apps can define
   1034 # their own --help flag, replacing this one, if they want.
   1035 #
   1036 # Args:
   1037 #   none
   1038 # Returns:
   1039 #   integer: success of operation (always returns true)
   1040 flags_help()
   1041 {
   1042   if [ -n "${FLAGS_HELP:-}" ]; then
   1043     echo "${FLAGS_HELP}" >&2
   1044   else
   1045     echo "USAGE: ${FLAGS_PARENT:-$0} [flags] args" >&2
   1046   fi
   1047   if [ -n "${__flags_longNames}" ]; then
   1048     echo 'flags:' >&2
   1049     for flags_name_ in ${__flags_longNames}; do
   1050       flags_flagStr_=''
   1051       flags_boolStr_=''
   1052       flags_usName_=`_flags_underscoreName ${flags_name_}`
   1053 
   1054       flags_default_=`_flags_getFlagInfo \
   1055           "${flags_usName_}" ${__FLAGS_INFO_DEFAULT}`
   1056       flags_help_=`_flags_getFlagInfo \
   1057           "${flags_usName_}" ${__FLAGS_INFO_HELP}`
   1058       flags_short_=`_flags_getFlagInfo \
   1059           "${flags_usName_}" ${__FLAGS_INFO_SHORT}`
   1060       flags_type_=`_flags_getFlagInfo \
   1061           "${flags_usName_}" ${__FLAGS_INFO_TYPE}`
   1062 
   1063       [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \
   1064           flags_flagStr_="-${flags_short_}"
   1065 
   1066       if [ ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_ENH} ]; then
   1067         [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \
   1068             flags_flagStr_="${flags_flagStr_},"
   1069         # add [no] to long boolean flag names, except the 'help' flag
   1070         [ ${flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} \
   1071           -a "${flags_usName_}" != 'help' ] && \
   1072             flags_boolStr_='[no]'
   1073         flags_flagStr_="${flags_flagStr_}--${flags_boolStr_}${flags_name_}:"
   1074       fi
   1075 
   1076       case ${flags_type_} in
   1077         ${__FLAGS_TYPE_BOOLEAN})
   1078           if [ ${flags_default_} -eq ${FLAGS_TRUE} ]; then
   1079             flags_defaultStr_='true'
   1080           else
   1081             flags_defaultStr_='false'
   1082           fi
   1083           ;;
   1084         ${__FLAGS_TYPE_FLOAT}|${__FLAGS_TYPE_INTEGER})
   1085           flags_defaultStr_=${flags_default_} ;;
   1086         ${__FLAGS_TYPE_STRING}) flags_defaultStr_="'${flags_default_}'" ;;
   1087       esac
   1088       flags_defaultStr_="(default: ${flags_defaultStr_})"
   1089 
   1090       flags_helpStr_="  ${flags_flagStr_}  ${flags_help_} ${flags_defaultStr_}"
   1091       _flags_strlen "${flags_helpStr_}" >/dev/null
   1092       flags_helpStrLen_=${flags_output}
   1093       flags_columns_=`_flags_columns`
   1094 
   1095       if [ ${flags_helpStrLen_} -lt ${flags_columns_} ]; then
   1096         echo "${flags_helpStr_}" >&2
   1097       else
   1098         echo "  ${flags_flagStr_}  ${flags_help_}" >&2
   1099         # note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06
   1100         # because it doesn't like empty strings when used in this manner.
   1101         flags_emptyStr_="`echo \"x${flags_flagStr_}x\" \
   1102             |awk '{printf "%"length($0)-2"s", ""}'`"
   1103         flags_helpStr_="  ${flags_emptyStr_}  ${flags_defaultStr_}"
   1104         _flags_strlen "${flags_helpStr_}" >/dev/null
   1105         flags_helpStrLen_=${flags_output}
   1106 
   1107         if [ ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_STD} \
   1108             -o ${flags_helpStrLen_} -lt ${flags_columns_} ]; then
   1109           # indented to match help string
   1110           echo "${flags_helpStr_}" >&2
   1111         else
   1112           # indented four from left to allow for longer defaults as long flag
   1113           # names might be used too, making things too long
   1114           echo "    ${flags_defaultStr_}" >&2
   1115         fi
   1116       fi
   1117     done
   1118   fi
   1119 
   1120   unset flags_boolStr_ flags_default_ flags_defaultStr_ flags_emptyStr_ \
   1121       flags_flagStr_ flags_help_ flags_helpStr flags_helpStrLen flags_name_ \
   1122       flags_columns_ flags_short_ flags_type_ flags_usName_
   1123   return ${FLAGS_TRUE}
   1124 }
   1125 
   1126 # Reset shflags back to an uninitialized state.
   1127 #
   1128 # Args:
   1129 #   none
   1130 # Returns:
   1131 #   nothing
   1132 flags_reset()
   1133 {
   1134   for flags_name_ in ${__flags_longNames}; do
   1135     flags_usName_=`_flags_underscoreName ${flags_name_}`
   1136     flags_strToEval_="unset FLAGS_${flags_usName_}"
   1137     for flags_type_ in \
   1138         ${__FLAGS_INFO_DEFAULT} \
   1139         ${__FLAGS_INFO_HELP} \
   1140         ${__FLAGS_INFO_SHORT} \
   1141         ${__FLAGS_INFO_TYPE}
   1142     do
   1143       flags_strToEval_=\
   1144 "${flags_strToEval_} __flags_${flags_usName_}_${flags_type_}"
   1145     done
   1146     eval ${flags_strToEval_}
   1147   done
   1148 
   1149   # reset internal variables
   1150   __flags_boolNames=' '
   1151   __flags_longNames=' '
   1152   __flags_shortNames=' '
   1153   __flags_definedNames=' '
   1154 
   1155   unset flags_name_ flags_type_ flags_strToEval_ flags_usName_
   1156 }
   1157