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