diff options
author | Kate Ward <kate.ward@forestent.com> | 2016-01-10 16:53:04 +0100 |
---|---|---|
committer | Kate Ward <kate.ward@forestent.com> | 2016-01-10 16:53:04 +0100 |
commit | 4b66ecdc0d3f6b89d3132b75f5aca1773a29a1a3 (patch) | |
tree | 9668b6e23a484f660f25ff34c98876db0276ffe6 /src | |
parent | b11509fad7f5e9e66a734116fcec7c418419ee1f (diff) | |
download | shflags-4b66ecdc0d3f6b89d3132b75f5aca1773a29a1a3.tar.gz |
restructured source for GitHub
Diffstat (limited to 'src')
-rw-r--r-- | src/shflags | 1156 | ||||
-rwxr-xr-x | src/shflags_test.sh | 122 | ||||
-rwxr-xr-x | src/shflags_test_defines.sh | 223 | ||||
-rw-r--r-- | src/shflags_test_helpers | 122 | ||||
-rwxr-xr-x | src/shflags_test_parsing.sh | 371 | ||||
-rwxr-xr-x | src/shflags_test_private.sh | 253 | ||||
-rwxr-xr-x | src/shflags_test_public.sh | 186 |
7 files changed, 2433 insertions, 0 deletions
diff --git a/src/shflags b/src/shflags new file mode 100644 index 0000000..726b206 --- /dev/null +++ b/src/shflags @@ -0,0 +1,1156 @@ +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# shFlags -- Advanced command-line flag library for Unix shell scripts. +# http://code.google.com/p/shflags/ +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# This module implements something like the google-gflags library available +# from http://code.google.com/p/google-gflags/. +# +# FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take +# a name, default value, help-string, and optional 'short' name (one-letter +# name). Some flags have other arguments, which are described with the flag. +# +# DEFINE_string: takes any input, and intreprets it as a string. +# +# DEFINE_boolean: does not take any arguments. Say --myflag to set +# FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false. For short +# flags, passing the flag on the command-line negates the default value, i.e. +# if the default is true, passing the flag sets the value to false. +# +# DEFINE_float: takes an input and intreprets it as a floating point number. As +# shell does not support floats per-se, the input is merely validated as +# being a valid floating point value. +# +# DEFINE_integer: takes an input and intreprets it as an integer. +# +# SPECIAL FLAGS: There are a few flags that have special meaning: +# --help (or -?) prints a list of all the flags in a human-readable fashion +# --flagfile=foo read flags from foo. (not implemented yet) +# -- as in getopt(), terminates flag-processing +# +# EXAMPLE USAGE: +# +# -- begin hello.sh -- +# #! /bin/sh +# . ./shflags +# DEFINE_string name 'world' "somebody's name" n +# FLAGS "$@" || exit $? +# eval set -- "${FLAGS_ARGV}" +# echo "Hello, ${FLAGS_name}." +# -- end hello.sh -- +# +# $ ./hello.sh -n Kate +# Hello, Kate. +# +# CUSTOMIZABLE BEHAVIOR: +# +# A script can override the default 'getopt' command by providing the path to +# an alternate implementation by defining the FLAGS_GETOPT_CMD variable. +# +# NOTES: +# +# * Not all systems include a getopt version that supports long flags. On these +# systems, only short flags are recognized. + +#============================================================================== +# shFlags +# +# Shared attributes: +# flags_error: last error message +# flags_output: last function output (rarely valid) +# flags_return: last return value +# +# __flags_longNames: list of long names for all flags +# __flags_shortNames: list of short names for all flags +# __flags_boolNames: list of boolean flag names +# +# __flags_opts: options parsed by getopt +# +# Per-flag attributes: +# FLAGS_<flag_name>: contains value of flag named 'flag_name' +# __flags_<flag_name>_default: the default flag value +# __flags_<flag_name>_help: the flag help string +# __flags_<flag_name>_short: the flag short name +# __flags_<flag_name>_type: the flag type +# +# Notes: +# - lists of strings are space separated, and a null value is the '~' char. + +# return if FLAGS already loaded +[ -n "${FLAGS_VERSION:-}" ] && return 0 +FLAGS_VERSION='1.0.4pre' + +# return values that scripts can use +FLAGS_TRUE=0 +FLAGS_FALSE=1 +FLAGS_ERROR=2 + +# determine some reasonable command defaults +__FLAGS_UNAME_S=`uname -s` +case "${__FLAGS_UNAME_S}" in + BSD) __FLAGS_EXPR_CMD='gexpr' ;; + *) __FLAGS_EXPR_CMD='expr' ;; +esac + +# commands a user can override if needed +FLAGS_EXPR_CMD=${FLAGS_EXPR_CMD:-${__FLAGS_EXPR_CMD}} +FLAGS_GETOPT_CMD=${FLAGS_GETOPT_CMD:-getopt} + +# specific shell checks +if [ -n "${ZSH_VERSION:-}" ]; then + setopt |grep "^shwordsplit$" >/dev/null + if [ $? -ne ${FLAGS_TRUE} ]; then + _flags_fatal 'zsh shwordsplit option is required for proper zsh operation' + fi + if [ -z "${FLAGS_PARENT:-}" ]; then + _flags_fatal "zsh does not pass \$0 through properly. please declare' \ +\"FLAGS_PARENT=\$0\" before calling shFlags" + fi +fi + +# can we use built-ins? +( echo "${FLAGS_TRUE#0}"; ) >/dev/null 2>&1 +if [ $? -eq ${FLAGS_TRUE} ]; then + __FLAGS_USE_BUILTIN=${FLAGS_TRUE} +else + __FLAGS_USE_BUILTIN=${FLAGS_FALSE} +fi + +# +# constants +# + +# reserved flag names +__FLAGS_RESERVED_LIST=' ARGC ARGV ERROR FALSE GETOPT_CMD HELP PARENT TRUE ' +__FLAGS_RESERVED_LIST="${__FLAGS_RESERVED_LIST} VERSION " + +# getopt version +__FLAGS_GETOPT_VERS_STD=0 +__FLAGS_GETOPT_VERS_ENH=1 +__FLAGS_GETOPT_VERS_BSD=2 + +${FLAGS_GETOPT_CMD} >/dev/null 2>&1 +case $? in + 0) __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_STD} ;; # bsd getopt + 2) + # TODO(kward): look into '-T' option to test the internal getopt() version + if [ "`${FLAGS_GETOPT_CMD} --version`" = '-- ' ]; then + __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_STD} + else + __FLAGS_GETOPT_VERS=${__FLAGS_GETOPT_VERS_ENH} + fi + ;; + *) _flags_fatal 'unable to determine getopt version' ;; +esac + +# getopt optstring lengths +__FLAGS_OPTSTR_SHORT=0 +__FLAGS_OPTSTR_LONG=1 + +__FLAGS_NULL='~' + +# flag info strings +__FLAGS_INFO_DEFAULT='default' +__FLAGS_INFO_HELP='help' +__FLAGS_INFO_SHORT='short' +__FLAGS_INFO_TYPE='type' + +# flag lengths +__FLAGS_LEN_SHORT=0 +__FLAGS_LEN_LONG=1 + +# flag types +__FLAGS_TYPE_NONE=0 +__FLAGS_TYPE_BOOLEAN=1 +__FLAGS_TYPE_FLOAT=2 +__FLAGS_TYPE_INTEGER=3 +__FLAGS_TYPE_STRING=4 + +# set the constants readonly +__flags_constants=`set |awk -F= '/^FLAGS_/ || /^__FLAGS_/ {print $1}'` +for __flags_const in ${__flags_constants}; do + # skip certain flags + case ${__flags_const} in + FLAGS_HELP) continue ;; + FLAGS_PARENT) continue ;; + esac + # set flag readonly + if [ -z "${ZSH_VERSION:-}" ]; then + readonly ${__flags_const} + else # handle zsh + case ${ZSH_VERSION} in + [123].*) readonly ${__flags_const} ;; + *) readonly -g ${__flags_const} ;; # declare readonly constants globally + esac + fi +done +unset __flags_const __flags_constants + +# +# internal variables +# + +# space separated lists +__flags_boolNames=' ' # boolean flag names +__flags_longNames=' ' # long flag names +__flags_shortNames=' ' # short flag names +__flags_definedNames=' ' # defined flag names (used for validation) + +__flags_columns='' # screen width in columns +__flags_opts='' # temporary storage for parsed getopt flags + +#------------------------------------------------------------------------------ +# private functions +# + +# logging functions +_flags_debug() { echo "flags:DEBUG $@" >&2; } +_flags_warn() { echo "flags:WARN $@" >&2; } +_flags_error() { echo "flags:ERROR $@" >&2; } +_flags_fatal() { echo "flags:FATAL $@" >&2; exit ${FLAGS_ERROR}; } + +# Define a flag. +# +# Calling this function will define the following info variables for the +# specified flag: +# FLAGS_flagname - the name for this flag (based upon the long flag name) +# __flags_<flag_name>_default - the default value +# __flags_flagname_help - the help string +# __flags_flagname_short - the single letter alias +# __flags_flagname_type - the type of flag (one of __FLAGS_TYPE_*) +# +# Args: +# _flags__type: integer: internal type of flag (__FLAGS_TYPE_*) +# _flags__name: string: long flag name +# _flags__default: default flag value +# _flags__help: string: help string +# _flags__short: string: (optional) short flag name +# Returns: +# integer: success of operation, or error +_flags_define() +{ + if [ $# -lt 4 ]; then + flags_error='DEFINE error: too few arguments' + flags_return=${FLAGS_ERROR} + _flags_error "${flags_error}" + return ${flags_return} + fi + + _flags_type_=$1 + _flags_name_=$2 + _flags_default_=$3 + _flags_help_=$4 + _flags_short_=${5:-${__FLAGS_NULL}} + + _flags_return_=${FLAGS_TRUE} + _flags_usName_=`_flags_underscoreName ${_flags_name_}` + + # check whether the flag name is reserved + _flags_itemInList ${_flags_usName_} "${__FLAGS_RESERVED_LIST}" + if [ $? -eq ${FLAGS_TRUE} ]; then + flags_error="flag name (${_flags_name_}) is reserved" + _flags_return_=${FLAGS_ERROR} + fi + + # require short option for getopt that don't support long options + if [ ${_flags_return_} -eq ${FLAGS_TRUE} \ + -a ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} \ + -a "${_flags_short_}" = "${__FLAGS_NULL}" ] + then + flags_error="short flag required for (${_flags_name_}) on this platform" + _flags_return_=${FLAGS_ERROR} + fi + + # check for existing long name definition + if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then + if _flags_itemInList ${_flags_usName_} ${__flags_definedNames}; then + flags_error="definition for ([no]${_flags_name_}) already exists" + _flags_warn "${flags_error}" + _flags_return_=${FLAGS_FALSE} + fi + fi + + # check for existing short name definition + if [ ${_flags_return_} -eq ${FLAGS_TRUE} \ + -a "${_flags_short_}" != "${__FLAGS_NULL}" ] + then + if _flags_itemInList "${_flags_short_}" ${__flags_shortNames}; then + flags_error="flag short name (${_flags_short_}) already defined" + _flags_warn "${flags_error}" + _flags_return_=${FLAGS_FALSE} + fi + fi + + # handle default value. note, on several occasions the 'if' portion of an + # if/then/else contains just a ':' which does nothing. a binary reversal via + # '!' is not done because it does not work on all shells. + if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then + case ${_flags_type_} in + ${__FLAGS_TYPE_BOOLEAN}) + if _flags_validBool "${_flags_default_}"; then + case ${_flags_default_} in + true|t|0) _flags_default_=${FLAGS_TRUE} ;; + false|f|1) _flags_default_=${FLAGS_FALSE} ;; + esac + else + flags_error="invalid default flag value '${_flags_default_}'" + _flags_return_=${FLAGS_ERROR} + fi + ;; + + ${__FLAGS_TYPE_FLOAT}) + if _flags_validFloat "${_flags_default_}"; then + : + else + flags_error="invalid default flag value '${_flags_default_}'" + _flags_return_=${FLAGS_ERROR} + fi + ;; + + ${__FLAGS_TYPE_INTEGER}) + if _flags_validInt "${_flags_default_}"; then + : + else + flags_error="invalid default flag value '${_flags_default_}'" + _flags_return_=${FLAGS_ERROR} + fi + ;; + + ${__FLAGS_TYPE_STRING}) ;; # everything in shell is a valid string + + *) + flags_error="unrecognized flag type '${_flags_type_}'" + _flags_return_=${FLAGS_ERROR} + ;; + esac + fi + + if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then + # store flag information + eval "FLAGS_${_flags_usName_}='${_flags_default_}'" + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_TYPE}=${_flags_type_}" + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}=\ +\"${_flags_default_}\"" + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_HELP}=\"${_flags_help_}\"" + eval "__flags_${_flags_usName_}_${__FLAGS_INFO_SHORT}='${_flags_short_}'" + + # append flag names to name lists + __flags_shortNames="${__flags_shortNames}${_flags_short_} " + __flags_longNames="${__flags_longNames}${_flags_name_} " + [ ${_flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} ] && \ + __flags_boolNames="${__flags_boolNames}no${_flags_name_} " + + # append flag names to defined names for later validation checks + __flags_definedNames="${__flags_definedNames}${_flags_usName_} " + [ ${_flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} ] && \ + __flags_definedNames="${__flags_definedNames}no${_flags_usName_} " + fi + + flags_return=${_flags_return_} + unset _flags_default_ _flags_help_ _flags_name_ _flags_return_ \ + _flags_short_ _flags_type_ _flags_usName_ + [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}" + return ${flags_return} +} + +# Underscore a flag name by replacing dashes with underscores. +# +# Args: +# unnamed: string: log flag name +# Output: +# string: underscored name +_flags_underscoreName() +{ + echo $1 |tr '-' '_' +} + +# Return valid getopt options using currently defined list of long options. +# +# This function builds a proper getopt option string for short (and long) +# options, using the current list of long options for reference. +# +# Args: +# _flags_optStr: integer: option string type (__FLAGS_OPTSTR_*) +# Output: +# string: generated option string for getopt +# Returns: +# boolean: success of operation (always returns True) +_flags_genOptStr() +{ + _flags_optStrType_=$1 + + _flags_opts_='' + + for _flags_name_ in ${__flags_longNames}; do + _flags_usName_=`_flags_underscoreName ${_flags_name_}` + _flags_type_=`_flags_getFlagInfo ${_flags_usName_} ${__FLAGS_INFO_TYPE}` + [ $? -eq ${FLAGS_TRUE} ] || _flags_fatal 'call to _flags_type_ failed' + case ${_flags_optStrType_} in + ${__FLAGS_OPTSTR_SHORT}) + _flags_shortName_=`_flags_getFlagInfo \ + ${_flags_usName_} ${__FLAGS_INFO_SHORT}` + if [ "${_flags_shortName_}" != "${__FLAGS_NULL}" ]; then + _flags_opts_="${_flags_opts_}${_flags_shortName_}" + # getopt needs a trailing ':' to indicate a required argument + [ ${_flags_type_} -ne ${__FLAGS_TYPE_BOOLEAN} ] && \ + _flags_opts_="${_flags_opts_}:" + fi + ;; + + ${__FLAGS_OPTSTR_LONG}) + _flags_opts_="${_flags_opts_:+${_flags_opts_},}${_flags_name_}" + # getopt needs a trailing ':' to indicate a required argument + [ ${_flags_type_} -ne ${__FLAGS_TYPE_BOOLEAN} ] && \ + _flags_opts_="${_flags_opts_}:" + ;; + esac + done + + echo "${_flags_opts_}" + unset _flags_name_ _flags_opts_ _flags_optStrType_ _flags_shortName_ \ + _flags_type_ _flags_usName_ + return ${FLAGS_TRUE} +} + +# Returns flag details based on a flag name and flag info. +# +# Args: +# string: underscored flag name +# string: flag info (see the _flags_define function for valid info types) +# Output: +# string: value of dereferenced flag variable +# Returns: +# integer: one of FLAGS_{TRUE|FALSE|ERROR} +_flags_getFlagInfo() +{ + # note: adding gFI to variable names to prevent naming conflicts with calling + # functions + _flags_gFI_usName_=$1 + _flags_gFI_info_=$2 + + _flags_infoVar_="__flags_${_flags_gFI_usName_}_${_flags_gFI_info_}" + _flags_strToEval_="_flags_infoValue_=\"\${${_flags_infoVar_}:-}\"" + eval "${_flags_strToEval_}" + if [ -n "${_flags_infoValue_}" ]; then + flags_return=${FLAGS_TRUE} + else + # see if the _flags_gFI_usName_ variable is a string as strings can be + # empty... + # note: the DRY principle would say to have this function call itself for + # the next three lines, but doing so results in an infinite loop as an + # invalid _flags_name_ will also not have the associated _type variable. + # Because it doesn't (it will evaluate to an empty string) the logic will + # try to find the _type variable of the _type variable, and so on. Not so + # good ;-) + _flags_typeVar_="__flags_${_flags_gFI_usName_}_${__FLAGS_INFO_TYPE}" + _flags_strToEval_="_flags_typeValue_=\"\${${_flags_typeVar_}:-}\"" + eval "${_flags_strToEval_}" + if [ "${_flags_typeValue_}" = "${__FLAGS_TYPE_STRING}" ]; then + flags_return=${FLAGS_TRUE} + else + flags_return=${FLAGS_ERROR} + flags_error="missing flag info variable (${_flags_infoVar_})" + fi + fi + + echo "${_flags_infoValue_}" + unset _flags_gFI_usName_ _flags_gfI_info_ _flags_infoValue_ _flags_infoVar_ \ + _flags_strToEval_ _flags_typeValue_ _flags_typeVar_ + [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}" + return ${flags_return} +} + +# Check for presense of item in a list. +# +# Passed a string (e.g. 'abc'), this function will determine if the string is +# present in the list of strings (e.g. ' foo bar abc '). +# +# Args: +# _flags_str_: string: string to search for in a list of strings +# unnamed: list: list of strings +# Returns: +# boolean: true if item is in the list +_flags_itemInList() { + _flags_str_=$1 + shift + + echo " ${*:-} " |grep " ${_flags_str_} " >/dev/null + if [ $? -eq 0 ]; then + flags_return=${FLAGS_TRUE} + else + flags_return=${FLAGS_FALSE} + fi + + unset _flags_str_ + return ${flags_return} +} + +# Returns the width of the current screen. +# +# Output: +# integer: width in columns of the current screen. +_flags_columns() +{ + if [ -z "${__flags_columns}" ]; then + # determine the value and store it + if eval stty size >/dev/null 2>&1; then + # stty size worked :-) + set -- `stty size` + __flags_columns=$2 + elif eval tput cols >/dev/null 2>&1; then + set -- `tput cols` + __flags_columns=$1 + else + __flags_columns=80 # default terminal width + fi + fi + echo ${__flags_columns} +} + +# Validate a boolean. +# +# Args: +# _flags__bool: boolean: value to validate +# Returns: +# bool: true if the value is a valid boolean +_flags_validBool() +{ + _flags_bool_=$1 + + flags_return=${FLAGS_TRUE} + case "${_flags_bool_}" in + true|t|0) ;; + false|f|1) ;; + *) flags_return=${FLAGS_FALSE} ;; + esac + + unset _flags_bool_ + return ${flags_return} +} + +# Validate a float. +# +# Args: +# _flags_float_: float: value to validate +# Returns: +# bool: true if the value is a valid integer +_flags_validFloat() +{ + flags_return=${FLAGS_FALSE} + [ -n "$1" ] || return ${flags_return} + _flags_float_=$1 + + if _flags_validInt ${_flags_float_}; then + flags_return=${FLAGS_TRUE} + elif _flags_useBuiltin; then + _flags_float_whole_=${_flags_float_%.*} + _flags_float_fraction_=${_flags_float_#*.} + if _flags_validInt ${_flags_float_whole_:-0} -a \ + _flags_validInt ${_flags_float_fraction_}; then + flags_return=${FLAGS_TRUE} + fi + unset _flags_float_whole_ _flags_float_fraction_ + else + flags_return=${FLAGS_TRUE} + case ${_flags_float_} in + -*) # negative floats + _flags_test_=`${FLAGS_EXPR_CMD} -- "${_flags_float_}" :\ + '\(-[0-9]*\.[0-9]*\)'` + ;; + *) # positive floats + _flags_test_=`${FLAGS_EXPR_CMD} -- "${_flags_float_}" :\ + '\([0-9]*\.[0-9]*\)'` + ;; + esac + [ "${_flags_test_}" != "${_flags_float_}" ] && flags_return=${FLAGS_FALSE} + unset _flags_test_ + fi + + unset _flags_float_ _flags_float_whole_ _flags_float_fraction_ + return ${flags_return} +} + +# Validate an integer. +# +# Args: +# _flags_int_: integer: value to validate +# Returns: +# bool: true if the value is a valid integer +_flags_validInt() +{ + flags_return=${FLAGS_FALSE} + [ -n "$1" ] || return ${flags_return} + _flags_int_=$1 + + case ${_flags_int_} in + -*.*) ;; # ignore negative floats (we'll invalidate them later) + -*) # strip possible leading negative sign + if _flags_useBuiltin; then + _flags_int_=${_flags_int_#-} + else + _flags_int_=`${FLAGS_EXPR_CMD} -- "${_flags_int_}" : '-\([0-9][0-9]*\)'` + fi + ;; + esac + + case ${_flags_int_} in + *[!0-9]*) flags_return=${FLAGS_FALSE} ;; + *) flags_return=${FLAGS_TRUE} ;; + esac + + unset _flags_int_ + return ${flags_return} +} + +# Parse command-line options using the standard getopt. +# +# Note: the flag options are passed around in the global __flags_opts so that +# the formatting is not lost due to shell parsing and such. +# +# Args: +# @: varies: command-line options to parse +# Returns: +# integer: a FLAGS success condition +_flags_getoptStandard() +{ + flags_return=${FLAGS_TRUE} + _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` + + # check for spaces in passed options + for _flags_opt_ in "$@"; do + # note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06 + _flags_match_=`echo "x${_flags_opt_}x" |sed 's/ //g'` + if [ "${_flags_match_}" != "x${_flags_opt_}x" ]; then + flags_error='the available getopt does not support spaces in options' + flags_return=${FLAGS_ERROR} + break + fi + done + + if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then + __flags_opts=`getopt ${_flags_shortOpts_} $@ 2>&1` + _flags_rtrn_=$? + if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then + _flags_warn "${__flags_opts}" + flags_error='unable to parse provided options with getopt.' + flags_return=${FLAGS_ERROR} + fi + fi + + unset _flags_match_ _flags_opt_ _flags_rtrn_ _flags_shortOpts_ + return ${flags_return} +} + +# Parse command-line options using the enhanced getopt. +# +# Note: the flag options are passed around in the global __flags_opts so that +# the formatting is not lost due to shell parsing and such. +# +# Args: +# @: varies: command-line options to parse +# Returns: +# integer: a FLAGS success condition +_flags_getoptEnhanced() +{ + flags_return=${FLAGS_TRUE} + _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` + _flags_boolOpts_=`echo "${__flags_boolNames}" \ + |sed 's/^ *//;s/ *$//;s/ /,/g'` + _flags_longOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_LONG}` + + __flags_opts=`${FLAGS_GETOPT_CMD} \ + -o ${_flags_shortOpts_} \ + -l "${_flags_longOpts_},${_flags_boolOpts_}" \ + -- "$@" 2>&1` + _flags_rtrn_=$? + if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then + _flags_warn "${__flags_opts}" + flags_error='unable to parse provided options with getopt.' + flags_return=${FLAGS_ERROR} + fi + + unset _flags_boolOpts_ _flags_longOpts_ _flags_rtrn_ _flags_shortOpts_ + return ${flags_return} +} + +# Dynamically parse a getopt result and set appropriate variables. +# +# This function does the actual conversion of getopt output and runs it through +# the standard case structure for parsing. The case structure is actually quite +# dynamic to support any number of flags. +# +# Args: +# argc: int: original command-line argument count +# @: varies: output from getopt parsing +# Returns: +# integer: a FLAGS success condition +_flags_parseGetopt() +{ + _flags_argc_=$1 + shift + + flags_return=${FLAGS_TRUE} + + if [ ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} ]; then + set -- $@ + else + # note the quotes around the `$@' -- they are essential! + eval set -- "$@" + fi + + # Provide user with the number of arguments to shift by later. + # NOTE: the FLAGS_ARGC variable is obsolete as of 1.0.3 because it does not + # properly give user access to non-flag arguments mixed in between flag + # arguments. Its usage was replaced by FLAGS_ARGV, and it is being kept only + # for backwards compatibility reasons. + FLAGS_ARGC=`_flags_math "$# - 1 - ${_flags_argc_}"` + + # handle options. note options with values must do an additional shift + while true; do + _flags_opt_=$1 + _flags_arg_=${2:-} + _flags_type_=${__FLAGS_TYPE_NONE} + _flags_name_='' + + # determine long flag name + case "${_flags_opt_}" in + --) shift; break ;; # discontinue option parsing + + --*) # long option + if _flags_useBuiltin; then + _flags_opt_=${_flags_opt_#*--} + else + _flags_opt_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : '--\(.*\)'` + fi + _flags_len_=${__FLAGS_LEN_LONG} + if _flags_itemInList "${_flags_opt_}" ${__flags_longNames}; then + _flags_name_=${_flags_opt_} + else + # check for negated long boolean version + if _flags_itemInList "${_flags_opt_}" ${__flags_boolNames}; then + if _flags_useBuiltin; then + _flags_name_=${_flags_opt_#*no} + else + _flags_name_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : 'no\(.*\)'` + fi + _flags_type_=${__FLAGS_TYPE_BOOLEAN} + _flags_arg_=${__FLAGS_NULL} + fi + fi + ;; + + -*) # short option + if _flags_useBuiltin; then + _flags_opt_=${_flags_opt_#*-} + else + _flags_opt_=`${FLAGS_EXPR_CMD} -- "${_flags_opt_}" : '-\(.*\)'` + fi + _flags_len_=${__FLAGS_LEN_SHORT} + if _flags_itemInList "${_flags_opt_}" ${__flags_shortNames}; then + # yes. match short name to long name. note purposeful off-by-one + # (too high) with awk calculations. + _flags_pos_=`echo "${__flags_shortNames}" \ + |awk 'BEGIN{RS=" ";rn=0}$0==e{rn=NR}END{print rn}' \ + e=${_flags_opt_}` + _flags_name_=`echo "${__flags_longNames}" \ + |awk 'BEGIN{RS=" "}rn==NR{print $0}' rn="${_flags_pos_}"` + fi + ;; + esac + + # die if the flag was unrecognized + if [ -z "${_flags_name_}" ]; then + flags_error="unrecognized option (${_flags_opt_})" + flags_return=${FLAGS_ERROR} + break + fi + + # set new flag value + _flags_usName_=`_flags_underscoreName ${_flags_name_}` + [ ${_flags_type_} -eq ${__FLAGS_TYPE_NONE} ] && \ + _flags_type_=`_flags_getFlagInfo \ + "${_flags_usName_}" ${__FLAGS_INFO_TYPE}` + case ${_flags_type_} in + ${__FLAGS_TYPE_BOOLEAN}) + if [ ${_flags_len_} -eq ${__FLAGS_LEN_LONG} ]; then + if [ "${_flags_arg_}" != "${__FLAGS_NULL}" ]; then + eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" + else + eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}" + fi + else + _flags_strToEval_="_flags_val_=\ +\${__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}}" + eval "${_flags_strToEval_}" + if [ ${_flags_val_} -eq ${FLAGS_FALSE} ]; then + eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" + else + eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}" + fi + fi + ;; + + ${__FLAGS_TYPE_FLOAT}) + if _flags_validFloat "${_flags_arg_}"; then + eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" + else + flags_error="invalid float value (${_flags_arg_})" + flags_return=${FLAGS_ERROR} + break + fi + ;; + + ${__FLAGS_TYPE_INTEGER}) + if _flags_validInt "${_flags_arg_}"; then + eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" + else + flags_error="invalid integer value (${_flags_arg_})" + flags_return=${FLAGS_ERROR} + break + fi + ;; + + ${__FLAGS_TYPE_STRING}) + eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" + ;; + esac + + # handle special case help flag + if [ "${_flags_usName_}" = 'help' ]; then + if [ ${FLAGS_help} -eq ${FLAGS_TRUE} ]; then + flags_help + flags_error='help requested' + flags_return=${FLAGS_TRUE} + break + fi + fi + + # shift the option and non-boolean arguements out. + shift + [ ${_flags_type_} != ${__FLAGS_TYPE_BOOLEAN} ] && shift + done + + # give user back non-flag arguments + FLAGS_ARGV='' + while [ $# -gt 0 ]; do + FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} }'$1'" + shift + done + + unset _flags_arg_ _flags_len_ _flags_name_ _flags_opt_ _flags_pos_ \ + _flags_strToEval_ _flags_type_ _flags_usName_ _flags_val_ + return ${flags_return} +} + +# Perform some path using built-ins. +# +# Args: +# $@: string: math expression to evaluate +# Output: +# integer: the result +# Returns: +# bool: success of math evaluation +_flags_math() +{ + if [ $# -eq 0 ]; then + flags_return=${FLAGS_FALSE} + elif _flags_useBuiltin; then + # Variable assignment is needed as workaround for Solaris Bourne shell, + # which cannot parse a bare $((expression)). + _flags_expr_='$(($@))' + eval echo ${_flags_expr_} + flags_return=$? + unset _flags_expr_ + else + eval expr $@ + flags_return=$? + fi + + return ${flags_return} +} + +# Cross-platform strlen() implementation. +# +# Args: +# _flags_str: string: to determine length of +# Output: +# integer: length of string +# Returns: +# bool: success of strlen evaluation +_flags_strlen() +{ + _flags_str_=${1:-} + + if [ -z "${_flags_str_}" ]; then + flags_output=0 + elif _flags_useBuiltin; then + flags_output=${#_flags_str_} + else + flags_output=`${FLAGS_EXPR_CMD} -- "${_flags_str_}" : '.*'` + fi + flags_return=$? + + unset _flags_str_ + echo ${flags_output} + return ${flags_return} +} + +# Use built-in helper function to enable unit testing. +# +# Args: +# None +# Returns: +# bool: true if built-ins should be used +_flags_useBuiltin() +{ + return ${__FLAGS_USE_BUILTIN} +} + +#------------------------------------------------------------------------------ +# public functions +# +# A basic boolean flag. Boolean flags do not take any arguments, and their +# value is either 1 (false) or 0 (true). For long flags, the false value is +# specified on the command line by prepending the word 'no'. With short flags, +# the presense of the flag toggles the current value between true and false. +# Specifying a short boolean flag twice on the command results in returning the +# value back to the default value. +# +# A default value is required for boolean flags. +# +# For example, lets say a Boolean flag was created whose long name was 'update' +# and whose short name was 'x', and the default value was 'false'. This flag +# could be explicitly set to 'true' with '--update' or by '-x', and it could be +# explicitly set to 'false' with '--noupdate'. +DEFINE_boolean() { _flags_define ${__FLAGS_TYPE_BOOLEAN} "$@"; } + +# Other basic flags. +DEFINE_float() { _flags_define ${__FLAGS_TYPE_FLOAT} "$@"; } +DEFINE_integer() { _flags_define ${__FLAGS_TYPE_INTEGER} "$@"; } +DEFINE_string() { _flags_define ${__FLAGS_TYPE_STRING} "$@"; } + +# Parse the flags. +# +# Args: +# unnamed: list: command-line flags to parse +# Returns: +# integer: success of operation, or error +FLAGS() +{ + # define a standard 'help' flag if one isn't already defined + [ -z "${__flags_help_type:-}" ] && \ + DEFINE_boolean 'help' false 'show this help' 'h' + + # parse options + if [ $# -gt 0 ]; then + if [ ${__FLAGS_GETOPT_VERS} -ne ${__FLAGS_GETOPT_VERS_ENH} ]; then + _flags_getoptStandard "$@" + else + _flags_getoptEnhanced "$@" + fi + flags_return=$? + else + # nothing passed; won't bother running getopt + __flags_opts='--' + flags_return=${FLAGS_TRUE} + fi + + if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then + _flags_parseGetopt $# "${__flags_opts}" + flags_return=$? + fi + + [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_fatal "${flags_error}" + return ${flags_return} +} + +# This is a helper function for determining the 'getopt' version for platforms +# where the detection isn't working. It simply outputs debug information that +# can be included in a bug report. +# +# Args: +# none +# Output: +# debug info that can be included in a bug report +# Returns: +# nothing +flags_getoptInfo() +{ + # platform info + _flags_debug "uname -a: `uname -a`" + _flags_debug "PATH: ${PATH}" + + # shell info + if [ -n "${BASH_VERSION:-}" ]; then + _flags_debug 'shell: bash' + _flags_debug "BASH_VERSION: ${BASH_VERSION}" + elif [ -n "${ZSH_VERSION:-}" ]; then + _flags_debug 'shell: zsh' + _flags_debug "ZSH_VERSION: ${ZSH_VERSION}" + fi + + # getopt info + ${FLAGS_GETOPT_CMD} >/dev/null + _flags_getoptReturn=$? + _flags_debug "getopt return: ${_flags_getoptReturn}" + _flags_debug "getopt --version: `${FLAGS_GETOPT_CMD} --version 2>&1`" + + unset _flags_getoptReturn +} + +# Returns whether the detected getopt version is the enhanced version. +# +# Args: +# none +# Output: +# none +# Returns: +# bool: true if getopt is the enhanced version +flags_getoptIsEnh() +{ + test ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_ENH} +} + +# Returns whether the detected getopt version is the standard version. +# +# Args: +# none +# Returns: +# bool: true if getopt is the standard version +flags_getoptIsStd() +{ + test ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_STD} +} + +# This is effectively a 'usage()' function. It prints usage information and +# exits the program with ${FLAGS_FALSE} if it is ever found in the command line +# arguments. Note this function can be overridden so other apps can define +# their own --help flag, replacing this one, if they want. +# +# Args: +# none +# Returns: +# integer: success of operation (always returns true) +flags_help() +{ + if [ -n "${FLAGS_HELP:-}" ]; then + echo "${FLAGS_HELP}" >&2 + else + echo "USAGE: ${FLAGS_PARENT:-$0} [flags] args" >&2 + fi + if [ -n "${__flags_longNames}" ]; then + echo 'flags:' >&2 + for flags_name_ in ${__flags_longNames}; do + flags_flagStr_='' + flags_boolStr_='' + flags_usName_=`_flags_underscoreName ${flags_name_}` + + flags_default_=`_flags_getFlagInfo \ + "${flags_usName_}" ${__FLAGS_INFO_DEFAULT}` + flags_help_=`_flags_getFlagInfo \ + "${flags_usName_}" ${__FLAGS_INFO_HELP}` + flags_short_=`_flags_getFlagInfo \ + "${flags_usName_}" ${__FLAGS_INFO_SHORT}` + flags_type_=`_flags_getFlagInfo \ + "${flags_usName_}" ${__FLAGS_INFO_TYPE}` + + [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ + flags_flagStr_="-${flags_short_}" + + if [ ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_ENH} ]; then + [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ + flags_flagStr_="${flags_flagStr_}," + # add [no] to long boolean flag names, except the 'help' flag + [ ${flags_type_} -eq ${__FLAGS_TYPE_BOOLEAN} \ + -a "${flags_usName_}" != 'help' ] && \ + flags_boolStr_='[no]' + flags_flagStr_="${flags_flagStr_}--${flags_boolStr_}${flags_name_}:" + fi + + case ${flags_type_} in + ${__FLAGS_TYPE_BOOLEAN}) + if [ ${flags_default_} -eq ${FLAGS_TRUE} ]; then + flags_defaultStr_='true' + else + flags_defaultStr_='false' + fi + ;; + ${__FLAGS_TYPE_FLOAT}|${__FLAGS_TYPE_INTEGER}) + flags_defaultStr_=${flags_default_} ;; + ${__FLAGS_TYPE_STRING}) flags_defaultStr_="'${flags_default_}'" ;; + esac + flags_defaultStr_="(default: ${flags_defaultStr_})" + + flags_helpStr_=" ${flags_flagStr_} ${flags_help_} ${flags_defaultStr_}" + _flags_strlen "${flags_helpStr_}" >/dev/null + flags_helpStrLen_=${flags_output} + flags_columns_=`_flags_columns` + + if [ ${flags_helpStrLen_} -lt ${flags_columns_} ]; then + echo "${flags_helpStr_}" >&2 + else + echo " ${flags_flagStr_} ${flags_help_}" >&2 + # note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06 + # because it doesn't like empty strings when used in this manner. + flags_emptyStr_="`echo \"x${flags_flagStr_}x\" \ + |awk '{printf "%"length($0)-2"s", ""}'`" + flags_helpStr_=" ${flags_emptyStr_} ${flags_defaultStr_}" + _flags_strlen "${flags_helpStr_}" >/dev/null + flags_helpStrLen_=${flags_output} + + if [ ${__FLAGS_GETOPT_VERS} -eq ${__FLAGS_GETOPT_VERS_STD} \ + -o ${flags_helpStrLen_} -lt ${flags_columns_} ]; then + # indented to match help string + echo "${flags_helpStr_}" >&2 + else + # indented four from left to allow for longer defaults as long flag + # names might be used too, making things too long + echo " ${flags_defaultStr_}" >&2 + fi + fi + done + fi + + unset flags_boolStr_ flags_default_ flags_defaultStr_ flags_emptyStr_ \ + flags_flagStr_ flags_help_ flags_helpStr flags_helpStrLen flags_name_ \ + flags_columns_ flags_short_ flags_type_ flags_usName_ + return ${FLAGS_TRUE} +} + +# Reset shflags back to an uninitialized state. +# +# Args: +# none +# Returns: +# nothing +flags_reset() +{ + for flags_name_ in ${__flags_longNames}; do + flags_usName_=`_flags_underscoreName ${flags_name_}` + flags_strToEval_="unset FLAGS_${flags_usName_}" + for flags_type_ in \ + ${__FLAGS_INFO_DEFAULT} \ + ${__FLAGS_INFO_HELP} \ + ${__FLAGS_INFO_SHORT} \ + ${__FLAGS_INFO_TYPE} + do + flags_strToEval_=\ +"${flags_strToEval_} __flags_${flags_usName_}_${flags_type_}" + done + eval ${flags_strToEval_} + done + + # reset internal variables + __flags_boolNames=' ' + __flags_longNames=' ' + __flags_shortNames=' ' + __flags_definedNames=' ' + + unset flags_name_ flags_type_ flags_strToEval_ flags_usName_ +} diff --git a/src/shflags_test.sh b/src/shflags_test.sh new file mode 100755 index 0000000..3d2c335 --- /dev/null +++ b/src/shflags_test.sh @@ -0,0 +1,122 @@ +#! /bin/sh +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# shFlags unit test suite runner. +# +# This script runs all the unit tests that can be found, and generates a nice +# report of the tests. + +MY_NAME=`basename $0` +MY_PATH=`dirname $0` + +PREFIX='shflags_test_' +SHELLS='/bin/sh /bin/bash /bin/dash /bin/ksh /bin/pdksh /bin/zsh' +TESTS='' +for test in ${PREFIX}[a-z]*.sh; do + TESTS="${TESTS} ${test}" +done + +# load libraries +. ../lib/versions +. ./shflags_test_helpers + +usage() +{ + echo "usage: ${MY_NAME} [-e key=val ...] [-s shell(s)] [-t test(s)]" +} + +env='' + +# process command line flags +while getopts 'e:hs:t:' opt; do + case ${opt} in + e) # set an environment variable + key=`expr "${OPTARG}" : '\([^=]*\)='` + val=`expr "${OPTARG}" : '[^=]*=\(.*\)'` + if [ -z "${key}" -o -z "${val}" ]; then + usage + exit 1 + fi + eval "${key}='${val}'" + export ${key} + env="${env:+${env} }${key}" + ;; + h) usage; exit 0 ;; # help output + s) shells=${OPTARG} ;; # list of shells to run + t) tests=${OPTARG} ;; # list of tests to run + *) usage; exit 1 ;; + esac +done +shift `expr ${OPTIND} - 1` + +# fill shells and/or tests +shells=${shells:-${SHELLS}} +tests=${tests:-${TESTS}} + +# error checking +if [ -z "${tests}" ]; then + th_error 'no tests found to run; exiting' + exit 1 +fi + +cat <<EOF +#------------------------------------------------------------------------------ +# System data +# + +# test run info +shells="${shells}" +tests="${tests}" +EOF +for key in ${env}; do + eval "echo \"${key}=\$${key}\"" +done +echo + +# output system data +echo "# system info" +echo "$ date" +date + +echo "$ uname -mprsv" +uname -mprsv + +# +# run tests +# + +for shell in ${shells}; do + echo + + cat <<EOF + +#------------------------------------------------------------------------------ +# Running the test suite with ${shell} +# +EOF + # check for existance of shell + if [ ! -x ${shell} ]; then + th_warn "unable to run tests with the ${shell} shell" + continue + fi + + shell_name=`basename ${shell}` + shell_version=`versions_shellVersion "${shell}"` + + echo "shell name: ${shell_name}" + echo "shell version: ${shell_version}" + + # execute the tests + for suite in ${tests}; do + suiteName=`expr "${suite}" : "${PREFIX}\(.*\).sh"` + echo + echo "--- Executing the '${suiteName}' test suite ---" + ( exec ${shell} ./${suite} 2>&1; ) + done +done diff --git a/src/shflags_test_defines.sh b/src/shflags_test_defines.sh new file mode 100755 index 0000000..a46b3c4 --- /dev/null +++ b/src/shflags_test_defines.sh @@ -0,0 +1,223 @@ +#! /bin/sh +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# shFlags unit test for the flag definition methods + +# load test helpers +. ./shflags_test_helpers + +#------------------------------------------------------------------------------ +# suite tests +# + +testFlagsDefine() +{ + # no arguments + _flags_define >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() with no arguments should have failed.' $? + assertErrorMsg '' 'no arguments' + + # one argument + _flags_define arg1 >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() call with one argument should fail' $? + assertErrorMsg '' 'one argument' + + # two arguments + _flags_define arg1 arg2 >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() call with two arguments should fail' $? + assertErrorMsg '' 'two arguments' + + # three arguments + _flags_define arg1 arg2 arg3 >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() call with three arguments should fail' $? + assertErrorMsg '' 'three arguments' + + # multiple definition -- assumes working boolean definition (tested elsewhere) + _flags_define ${__FLAGS_TYPE_BOOLEAN} multiDefBool true 'multi def #1' m + _flags_define ${__FLAGS_TYPE_BOOLEAN} multiDefBool false 'multi def #2' m \ + >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() with existing flag name should fail' $? + assertTrue \ + '_flags_define() should not overwrite previously defined default.' \ + "${FLAGS_multiDefBool:-}" + assertWarnMsg '' 'existing flag' + + # duplicate dashed and underscored definition + _flags_define ${__FLAGS_TYPE_STRING} long-name 'foo' 'dashed name' l + _flags_define ${__FLAGS_TYPE_STRING} long_name 'bar' 'underscored name' l \ + >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() with existing flag name should fail' $? + assertEquals \ + '_flags_define() should not overwrite previously defined default.' \ + "${FLAGS_long_name}" 'foo' + assertWarnMsg '' 'already exists' + + # TODO(kward): test requirement of enhanced getopt + + # invalid type + _flags_define invalid arg2 arg3 arg4 i >"${stdoutF}" 2>"${stderrF}" + assertFalse '_flags_define() with "invalid" type should have failed.' $? + assertErrorMsg 'unrecognized flag type' 'invalid type' +} + +testBoolean() +{ + # test true defaults + for default in 'true' 't' 0; do + flags_reset + DEFINE_boolean boolVal "${default}" 'my boolean' b + rtrn=$? + assertTrue \ + "DEFINE_boolean() call with default of '${default}' failed." \ + "${FLAGS_boolVal:-}" + assertTrue \ + "DEFINE_boolean() call with default of '${default}' returned faliure." \ + ${rtrn} + done + + # test false defaults + for default in 'false' 'f' 1; do + flags_reset + DEFINE_boolean boolVal "${default}" 'my boolean' b + rtrn=$? + assertFalse \ + "DEFINE_boolean() call with default of '${default}' failed." \ + "${FLAGS_boolVal:-}" + assertTrue \ + "DEFINE_boolean() call with default of '${default}' returned faliure." \ + ${rtrn} + done + + # test invalid default + flags_reset + DEFINE_boolean boolVal 'invalid' 'my boolean' b >"${stdoutF}" 2>"${stderrF}" + assertFalse 'DEFINE_boolean() call with invalid default did not fail.' $? + assertErrorMsg +} + +testFloat() +{ + # test valid defaults + for default in ${TH_FLOAT_VALID}; do + flags_reset + DEFINE_float floatVal ${default} "float: ${default}" f + rtrn=$? + assertSame "DEFINE_float() call with valid default failed." \ + ${default} "${FLAGS_floatVal:-}" + assertTrue \ + "DEFINE_float() call with valid default of '${default}' returned faliure." \ + ${rtrn} + done + + # test invalid defaults + flags_reset + DEFINE_float floatVal 'invalid' 'invalid float: string' f \ + >"${stdoutF}" 2>"${stderrF}" + assertFalse 'DEFINE_float() call with string value default did not fail.' $? + assertErrorMsg +} + +testInteger() +{ + # test valid defaults + for default in ${TH_INT_VALID}; do + flags_reset + DEFINE_integer intVal ${default} "integer: ${default}" i + rtrn=$? + assertSame \ + "DEFINE_integer() call with valid default failed." \ + ${default} "${FLAGS_intVal:-}" + assertTrue \ + "DEFINE_integer() call with valid default of '${default}' returned failure." \ + ${rtrn} + done + + # test invalid defaults + flags_reset + DEFINE_integer intVal 1.234 'invalid integer: float' i \ + >"${stdoutF}" 2>"${stderrF}" + assertFalse 'DEFINE_integer() call with float value default did not fail.' $? + assertErrorMsg 'invalid default' 'float default' + + DEFINE_integer intVal -1.234 'invalid integer: negative float' i \ + >"${stdoutF}" 2>"${stderrF}" + assertFalse \ + 'DEFINE_integer() call with negative float value default did not fail.' \ + $? + assertErrorMsg 'invalid default' 'negative float default' + + DEFINE_integer intVal 'invalid' 'invalid integer: string' i \ + >"${stdoutF}" 2>"${stderrF}" + assertFalse \ + 'DEFINE_integer() call with string value default did not fail.' \ + $? + assertErrorMsg 'invalid default' 'string default' +} + +testString() +{ + # test valid defaults + for default in \ + ${TH_BOOL_VALID} \ + ${TH_FLOAT_VALID} \ + ${TH_INT_VALID} \ + 'also valid' + do + flags_reset + DEFINE_string strVal "${default}" "string: ${default}" s + rtrn=$? + assertSame \ + "DEFINE_string() call with valid default failed." \ + "${default}" "${FLAGS_strVal:-}" + assertTrue \ + "DEFINE_string() call with valid default of '${default}' returned faliure." \ + ${rtrn} + done + + # test "empty" strings + flags_reset + DEFINE_string str '' "string: empty single quotes" s + rtrn=$? + assertSame \ + "DEFINE_string() call with valid default failed." \ + '' "${FLAGS_str:-}" +} + +testShortNameLength() +{ + # make sure short names are no longer than a single character + : +} + +testFlagNameIsReserved() +{ + ( DEFINE_string TRUE '' 'true is a reserved flag name' t \ + >"${stdoutF}" 2>"${stderrF}" ) + rtrn=$? + assertEquals ${FLAGS_ERROR} ${rtrn} + assertErrorMsg 'flag name (TRUE) is reserved' +} + +#------------------------------------------------------------------------------ +# suite functions +# + +oneTimeSetUp() +{ + th_oneTimeSetUp +} + +tearDown() +{ + flags_reset +} + +# load and run shUnit2 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. ${TH_SHUNIT} diff --git a/src/shflags_test_helpers b/src/shflags_test_helpers new file mode 100644 index 0000000..a96219e --- /dev/null +++ b/src/shflags_test_helpers @@ -0,0 +1,122 @@ +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# shFlags unit test common functions + +__th_skipping=0 + +# treat unset variables as an error +set -u + +# set shwordsplit for zsh +[ -n "${ZSH_VERSION:-}" ] && setopt shwordsplit + +# my name +TH_MY_NAME=`basename "$0"` + +# path to shFlags library. can be overridden by setting SHFLAGS_INC +TH_SHFLAGS=${SHFLAGS_INC:-./shflags} + +# path to shUnit2 library. can be overridden by setting SHUNIT_INC +TH_SHUNIT=${SHUNIT_INC:-../lib/shunit2} + +TH_BOOL_VALID='true t 0 false f 1' +TH_BOOL_INVALID='123 123.0 invalid' +TH_FLOAT_VALID='-1234.0 -1.0 -.123 0.0 0. .123 1.0 1234.0' +TH_FLOAT_INVALID='true false 1.2.3 -1.2.3 ""' +TH_INT_VALID='-1234 -1 0 1 1234' +TH_INT_INVALID='true false -1.0 -.123 0.0 .123 1.0 ""' + +# +# test helper functions +# + +# message functions +th_trace() { echo "test:TRACE $@" >&2; } +th_debug() { echo "test:DEBUG $@" >&2; } +th_info() { echo "test:INFO $@" >&2; } +th_warn() { echo "test:WARN $@" >&2; } +th_error() { echo "test:ERROR $@" >&2; } +th_fatal() { echo "test:FATAL $@" >&2; } + +th_oneTimeSetUp() +{ + # load shFlags + [ -n "${ZSH_VERSION:-}" ] && FLAGS_PARENT=$0 + . ${TH_SHFLAGS} + + # these files will be cleaned up automatically by shUnit2 + tmpDir=${SHUNIT_TMPDIR} + stdoutF="${tmpDir}/stdout" + stderrF="${tmpDir}/stderr" + returnF="${tmpDir}/return" + expectedF="${tmpDir}/expected" +} + +th_showOutput() +{ + _th_rtrn=$1 + _th_stdout=$2 + _th_stderr=$3 + + isSkipping + if [ $? -eq ${SHUNIT_FALSE} -a ${_th_rtrn} != ${FLAGS_TRUE} ]; then + if [ -n "${_th_stdout}" -a -s "${_th_stdout}" ]; then + echo '>>> STDOUT' >&2 + cat "${_th_stdout}" >&2 + fi + if [ -n "${_th_stderr}" -a -s "${_th_stderr}" ]; then + echo '>>> STDERR' >&2 + cat "${_th_stderr}" >&2 + fi + if [ -n "${_th_stdout}" -o -n "${_th_stderr}" ]; then + echo '<<< end output' >&2 + fi + fi + + unset _th_rtrn _th_stdout _th_stderr +} + +# Some shells, zsh on Solaris in particular, return immediately from a sub-shell +# when a non-zero return value is encountered. To properly catch these values, +# they are either written to disk, or recognized as an error the file is empty. +th_clearReturn() { cp /dev/null "${returnF}"; } +th_queryReturn() +{ + if [ -s "${returnF}" ]; then + th_return=`cat "${returnF}"` + else + th_return=${SHUNIT_ERROR} + fi +} + +_th_assertMsg() +{ + _th_alert_type_=$1 + _th_alert_msg_=$2 + _th_msg_=$3 + + case ${_th_alert_type_} in + WARN) _th_alert_str_='a warning' ;; + ERROR) _th_alert_str_='an error' ;; + FATAL) _th_alert_str_='a fatal' ;; + esac + [ -z "${_th_alert_msg_}" ] && _th_alert_msg_='.*' + [ -n "${_th_msg_}" ] && _th_msg_="(${_th_msg_}) " + + grep -- "^flags:${_th_alert_type_} ${_th_alert_msg_}" "${stderrF}" \ + >/dev/null + assertTrue \ + "FLAGS ${_th_msg_}failure did not generate ${_th_alert_str_} message" $? + + unset _th_alert_type_ _th_alert_msg_ _th_alert_str_ _th_msg_ +} + +assertWarnMsg() { _th_assertMsg 'WARN' "${1:-}" "${2:-}"; } +assertErrorMsg() { _th_assertMsg 'ERROR' "${1:-}" "${2:-}"; } +assertFatalMsg() { _th_assertMsg 'FATAL' "${1:-}" "${2:-}"; } diff --git a/src/shflags_test_parsing.sh b/src/shflags_test_parsing.sh new file mode 100755 index 0000000..664db5b --- /dev/null +++ b/src/shflags_test_parsing.sh @@ -0,0 +1,371 @@ +#! /bin/sh +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# shFlags unit test for the flag definition methods +# +# TODO(kward): assert on FLAGS errors +# TODO(kward): testNonStandardIFS() + +# exit immediately if a pipeline or subshell exits with a non-zero status. +#set -e + +# treat unset variables as an error +set -u + +# load test helpers +. ./shflags_test_helpers + +#------------------------------------------------------------------------------ +# suite tests +# + +testGetoptStandard() +{ + _flags_getoptStandard '-b' >"${stdoutF}" 2>"${stderrF}" + rslt=$? + assertTrue "didn't parse valid flag 'b'" ${rslt} + th_showOutput ${rslt} "${stdoutF}" "${stderrF}" + + _flags_getoptStandard '-x' >"${stdoutF}" 2>"${stderrF}" + assertFalse "parsed invalid flag 'x'" $? +} + +testGetoptEnhanced() +{ + flags_getoptIsEnh || return + + _flags_getoptEnhanced '-b' >"${stdoutF}" 2>"${stderrF}" + assertTrue "didn't parse valid flag 'b'" $? + _flags_getoptEnhanced '--bool' >"${stdoutF}" 2>"${stderrF}" + assertTrue "didn't parse valid flag 'bool'" $? + + _flags_getoptEnhanced '-x' >"${stdoutF}" 2>"${stderrF}" + assertFalse "parsed invalid flag 'x'" $? + _flags_getoptEnhanced '--xyz' >"${stdoutF}" 2>"${stderrF}" + assertFalse "parsed invalid flag 'xyz'" $? +} + +testValidBoolsShort() +{ + FLAGS -b >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "-b) FLAGS returned a non-zero result (${r3turn})" ${r3turn} + value=${FLAGS_bool:-} + assertTrue "-b) boolean was not true (${value})." "${value}" + assertFalse '-b) expected no output to STDERR' "[ -s '${stderrF}' ]" + test ${r3turn} -eq ${FLAGS_TRUE} -a ! -s "${stderrF}" + th_showOutput $? "${stdoutF}" "${stderrF}" + + DEFINE_boolean bool2 true '2nd boolean' B + FLAGS >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "-B) FLAGS returned a non-zero result (${r3turn})" ${r3turn} + value=${FLAGS_bool2:-} + assertTrue "-B) boolean was not true (${value})" ${value} + assertFalse '-B) expected no output to STDERR' "[ -s '${stderrF}' ]" + test ${r3turn} -eq ${FLAGS_TRUE} -a ! -s "${stderrF}" + th_showOutput $? "${stdoutF}" "${stderrF}" + + FLAGS -B >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "-B) FLAGS returned a non-zero result (${r3turn})" ${r3turn} + value=${FLAGS_bool2:-} + assertFalse "-B) boolean was not false (${value})" ${value} + assertFalse '-B) expected no output to STDERR' "[ -s '${stderrF}' ]" + test ${r3turn} -eq ${FLAGS_TRUE} -a ! -s "${stderrF}" + th_showOutput $? "${stdoutF}" "${stderrF}" +} + +# TODO(kate): separate into multiple functions to reflect correct usage +testValidBoolsLong() +{ + flags_getoptIsEnh || return + + # Note: the default value of bool is 'false'. + + # leave flag false + FLAGS --nobool >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "FLAGS returned a non-zero result (${r3turn})" ${r3turn} + assertFalse '--noXX flag resulted in true value.' ${FLAGS_bool:-} + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + + # flip flag true + FLAGS --bool >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "FLAGS returned a non-zero result (${r3turn})" ${r3turn} + assertTrue '--XX flag resulted in false value.' ${FLAGS_bool:-} + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + + # flip flag back false + FLAGS --nobool >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "FLAGS returned a non-zero result (${r3turn})" ${r3turn} + assertFalse '--noXX flag resulted in true value.' ${FLAGS_bool:-} + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" +} + +testValidFloats() +{ + _testValidFloats '-f' + flags_getoptIsEnh || return + _testValidFloats '--float' +} + +_testValidFloats() +{ + flag=$1 + for value in ${TH_FLOAT_VALID}; do + FLAGS ${flag} ${value} >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "FLAGS ${flag} ${value} returned non-zero result (${r3turn})" \ + ${r3turn} + assertEquals "float (${flag} ${value}) test failed." ${value} ${FLAGS_float} + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + done +} + +testInvalidFloats() +{ + _testInvalidFloats '-f' + flags_getoptIsEnh || return + _testInvalidFloats '--float' +} + +_testInvalidFloats() +{ + flag=$1 + for value in ${TH_FLOAT_INVALID}; do + th_clearReturn + ( + FLAGS ${flag} ${value} >"${stdoutF}" 2>"${stderrF}" + echo $? >"${returnF}" + ) + th_queryReturn + assertFalse "FLAGS (${value}) returned a zero result" ${th_return} + assertFalse 'expected no output to STDOUT' "[ -s '${stdoutF}' ]" + assertTrue 'expected output to STDERR' "[ -s '${stderrF}' ]" + done +} + +testValidIntegers() +{ + _testValidIntegers '-i' + flags_getoptIsEnh || return + _testValidIntegers '--int' +} + +_testValidIntegers() +{ + flag=$1 + for value in ${TH_INT_VALID}; do + FLAGS ${flag} ${value} >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "FLAGS (${value}) returned a non-zero result (${r3turn})" ${r3turn} + assertEquals "integer (${value}) test failed." ${value} ${FLAGS_int} + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + done +} + +testInvalidIntegers() +{ + _testInvalidIntegers '-i' + flags_getoptIsEnh || return + _testInvalidIntegers '--int' +} + +_testInvalidIntegers() +{ + flag=$1 + for value in ${TH_INT_INVALID}; do + th_clearReturn + ( + FLAGS ${flag} ${value} >"${stdoutF}" 2>"${stderrF}" + echo $? >"${returnF}" + ) + th_queryReturn + assertFalse "invalid integer (${value}) test returned success." ${th_return} + assertFalse 'expected no output to STDOUT' "[ -s '${stdoutF}' ]" + assertTrue 'expected output to STDERR' "[ -s '${stderrF}' ]" + done +} + +testValidStrings() +{ + _testValidStrings -s single_word + if flags_getoptIsEnh; then + _testValidStrings --str single_word + _testValidStrings --str 'string with spaces' + fi +} + +_testValidStrings() +{ + flag=$1 + value=$2 + + FLAGS ${flag} "${value}" >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "'FLAGS ${flag} ${value}' returned a non-zero result (${r3turn})" \ + ${r3turn} + assertEquals "string (${value}) test failed." "${value}" "${FLAGS_str}" + if [ ${r3turn} -eq ${FLAGS_TRUE} ]; then + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + else + # validate that an error is thrown for unsupported getopt uses + assertFatalMsg '.* spaces in options' + fi + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" +} + +testMultipleFlags() +{ + _testMultipleFlags '-b' '-i' '-f' '-s' + flags_getoptIsEnh || return + _testMultipleFlags '--bool' '--int' '--float' '--str' +} + +_testMultipleFlags() +{ + boolFlag=$1 + intFlag=$2 + floatFlag=$3 + strFlag=$4 + + FLAGS \ + ${boolFlag} \ + ${intFlag} 567 \ + ${floatFlag} 123.45678 \ + ${strFlag} 'some_string' \ + >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "use of multple flags returned a non-zero result" ${r3turn} + assertTrue 'boolean test failed.' ${FLAGS_bool} + assertNotSame 'float test failed.' 0 ${FLAGS_float} + assertNotSame 'integer test failed.' 0 ${FLAGS_int} + assertNotSame 'string test failed.' '' ${FLAGS_str} + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" +} + +_testNonFlagArgs() +{ + argc=$1 + shift + + FLAGS "$@" >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue 'parse returned non-zero value.' ${r3turn} + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + + eval set -- "${FLAGS_ARGV}" + assertEquals 'wrong count of argv arguments returned.' ${argc} $# + assertEquals 'wrong count of argc arguments returned.' 0 ${FLAGS_ARGC} +} + +testSingleNonFlagArg() +{ + _testNonFlagArgs 1 argOne +} + +testMultipleNonFlagArgs() +{ + _testNonFlagArgs 3 argOne argTwo arg3 +} + +testMultipleNonFlagStringArgsWithSpaces() +{ + flags_getoptIsEnh || return + _testNonFlagArgs 3 argOne 'arg two' arg3 +} + +testFlagsWithEquals() +{ + flags_getoptIsEnh || return + + FLAGS --str='str_flag' 'non_flag' >"${stdoutF}" 2>"${stderrF}" + assertTrue 'FLAGS returned a non-zero result' $? + assertEquals 'string flag not set properly' 'str_flag' "${FLAGS_str}" + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + + eval set -- "${FLAGS_ARGV}" + assertEquals 'wrong count of argv arguments returned.' 1 $# + assertEquals 'wrong count of argc arguments returned.' 1 ${FLAGS_ARGC} +} + +testComplicatedCommandLineStandard() +{ + flags_getoptIsEnh && return + + # Note: standard getopt stops parsing after first non-flag argument, which + # results in the remaining flags being treated as arguments instead. + FLAGS -i 1 non_flag_1 -s 'two' non_flag_2 -f 3 non_flag_3 \ + >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue 'FLAGS returned a non-zero result' ${r3turn} + assertEquals 'failed int test' 1 ${FLAGS_int} + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + + eval set -- "${FLAGS_ARGV}" + assertEquals 'incorrect number of argv values' 7 $# +} + +testComplicatedCommandLineEnhanced() +{ + flags_getoptIsEnh || return + + FLAGS -i 1 non_flag_1 --str='two' non_flag_2 --float 3 'non flag 3' \ + >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue 'FLAGS returned a non-zero result' ${r3turn} + assertEquals 'failed int test' 1 ${FLAGS_int} + assertEquals 'failed str test' 'two' "${FLAGS_str}" + assertEquals 'failed float test' 3 ${FLAGS_float} + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" + + eval set -- "${FLAGS_ARGV}" + assertEquals 'incorrect number of argv values' 3 $# +} + +#------------------------------------------------------------------------------ +# suite functions +# + +oneTimeSetUp() +{ + th_oneTimeSetUp + + if flags_getoptIsStd; then + th_warn 'Standard version of getopt found. Enhanced tests will be skipped.' + else + th_warn 'Enhanced version of getopt found. Standard tests will be skipped.' + fi +} + +setUp() +{ + DEFINE_boolean bool false 'boolean test' 'b' + DEFINE_float float 0.0 'float test' 'f' + DEFINE_integer int 0 'integer test' 'i' + DEFINE_string str '' 'string test' 's' +} + +tearDown() +{ + flags_reset +} + +# load and run shUnit2 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. ${TH_SHUNIT} diff --git a/src/shflags_test_private.sh b/src/shflags_test_private.sh new file mode 100755 index 0000000..d5c6112 --- /dev/null +++ b/src/shflags_test_private.sh @@ -0,0 +1,253 @@ +#! /bin/sh +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# shFlags unit test for the internal functions + +# load test helpers +. ./shflags_test_helpers + +#------------------------------------------------------------------------------ +# suite tests +# + +testColumns() +{ + cols=`_flags_columns` + value=`expr "${cols}" : '\([0-9]*\)'` + assertNotNull "unexpected screen width (${cols})" "${value}" +} + +testGenOptStr() +{ + _testGenOptStr '' '' + + DEFINE_boolean bool false 'boolean value' b + _testGenOptStr 'b' 'bool' + + DEFINE_float float 0.0 'float value' f + _testGenOptStr 'bf:' 'bool,float:' + + DEFINE_integer int 0 'integer value' i + _testGenOptStr 'bf:i:' 'bool,float:,int:' + + DEFINE_string str 0 'string value' s + _testGenOptStr 'bf:i:s:' 'bool,float:,int:,str:' + + DEFINE_boolean help false 'show help' h + _testGenOptStr 'bf:i:s:h' 'bool,float:,int:,str:,help' +} + +_testGenOptStr() +{ + short=$1 + long=$2 + + result=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` + assertTrue 'short option string generation failed' $? + assertEquals "${short}" "${result}" + + result=`_flags_genOptStr ${__FLAGS_OPTSTR_LONG}` + assertTrue 'long option string generation failed' $? + assertEquals "${long}" "${result}" +} + +testGetFlagInfo() +{ + __flags_blah_foobar='1234' + + rslt=`_flags_getFlagInfo 'blah' 'foobar'` + assertTrue 'request for valid flag info failed' $? + assertEquals 'invalid flag info returned' "${__flags_blah_foobar}" "${rslt}" + + rslt=`_flags_getFlagInfo 'blah' 'hubbabubba' >"${stdoutF}" 2>"${stderrF}"` + assertEquals 'invalid flag did not result in an error' ${FLAGS_ERROR} $? + assertErrorMsg 'missing flag info variable' +} + +testItemInList() +{ + list='this is a test' + + _flags_itemInList 'is' ${list} + assertTrue 'unable to find leading string (this)' $? + + _flags_itemInList 'is' ${list} + assertTrue 'unable to find string (is)' $? + + _flags_itemInList 'is' ${list} + assertTrue 'unable to find trailing string (test)' $? + + _flags_itemInList 'abc' ${list} + assertFalse 'found nonexistant string (abc)' $? + + _flags_itemInList '' ${list} + assertFalse 'empty strings should not match' $? + + _flags_itemInList 'blah' '' + assertFalse 'empty lists should not match' $? +} + +testValidBool() +{ + # valid values + for value in ${TH_BOOL_VALID}; do + _flags_validBool "${value}" + assertTrue "valid value (${value}) did not validate" $? + done + + # invalid values + for value in ${TH_BOOL_INVALID}; do + _flags_validBool "${value}" + assertFalse "invalid value (${value}) validated" $? + done +} + +_testValidFloat() +{ + # valid values + for value in ${TH_INT_VALID} ${TH_FLOAT_VALID}; do + _flags_validFloat "${value}" + assertTrue "valid value (${value}) did not validate" $? + done + + # invalid values + for value in ${TH_FLOAT_INVALID}; do + _flags_validFloat "${value}" + assertFalse "invalid value (${value}) validated" $? + done +} + +testValidFloatBuiltin() +{ + _flags_useBuiltin || startSkipping + _testValidFloat +} + +testValidFloatExpr() +{ + ( + _flags_useBuiltin() { return ${FLAGS_FALSE}; } + _testValidFloat + ) +} + +_testValidInt() +{ + # valid values + for value in ${TH_INT_VALID}; do + _flags_validInt "${value}" + assertTrue "valid value (${value}) did not validate" $? + done + + # invalid values + for value in ${TH_INT_INVALID}; do + _flags_validInt "${value}" + assertFalse "invalid value (${value}) should not validate" $? + done +} + +testValidIntBuiltin() +{ + _flags_useBuiltin || startSkipping + _testValidInt +} + +testValidIntExpr() +{ + ( + _flags_useBuiltin() { return ${FLAGS_FALSE}; } + _testValidInt + ) +} + +_testMath() +{ + result=`_flags_math 1` + assertTrue '1 failed' $? + assertEquals '1' 1 ${result} + + result=`_flags_math '1 + 2'` + assertTrue '1+2 failed' $? + assertEquals '1+2' 3 ${result} + + result=`_flags_math '1 + 2 + 3'` + assertTrue '1+2+3 failed' $? + assertEquals '1+2+3' 6 ${result} + + result=`_flags_math` + assertFalse 'missing math succeeded' $? +} + +testMathBuiltin() +{ + _flags_useBuiltin || startSkipping + _testMath +} + +testMathExpr() +{ + ( + _flags_useBuiltin() { return ${FLAGS_FALSE}; } + _testMath + ) +} + +_testStrlen() +{ + len=`_flags_strlen` + assertTrue 'missing argument failed' $? + assertEquals 'missing argument' 0 ${len} + + len=`_flags_strlen ''` + assertTrue 'empty argument failed' $? + assertEquals 'empty argument' 0 ${len} + + len=`_flags_strlen abc123` + assertTrue 'single-word failed' $? + assertEquals 'single-word' 6 ${len} + + len=`_flags_strlen 'This is a test'` + assertTrue 'multi-word failed' $? + assertEquals 'multi-word' 14 ${len} +} + +testStrlenBuiltin() +{ + _flags_useBuiltin || startSkipping + _testStrlen +} + +testStrlenExpr() +{ + ( + _flags_useBuiltin() { return ${FLAGS_FALSE}; } + _testStrlen + ) +} + +#------------------------------------------------------------------------------ +# suite functions +# + +oneTimeSetUp() +{ + th_oneTimeSetUp + + _flags_useBuiltin || \ + th_warn 'Shell built-ins not supported. Some tests will be skipped.' +} + +tearDown() +{ + flags_reset +} + +# load and run shUnit2 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. ${TH_SHUNIT} diff --git a/src/shflags_test_public.sh b/src/shflags_test_public.sh new file mode 100755 index 0000000..3877d80 --- /dev/null +++ b/src/shflags_test_public.sh @@ -0,0 +1,186 @@ +#! /bin/sh +# $Id$ +# vim:et:ft=sh:sts=2:sw=2 +# +# Copyright 2008 Kate Ward. All Rights Reserved. +# Released under the LGPL (GNU Lesser General Public License) +# +# Author: kate.ward@forestent.com (Kate Ward) +# +# shFlags unit test for the public functions + +# load test helpers +. ./shflags_test_helpers + +#------------------------------------------------------------------------------ +# suite tests +# + +testHelp() +{ + _testHelp '-h' + flags_getoptIsEnh || return + _testHelp '--help' +} + +_testHelp() +{ + flag=$1 + + # test default help output + th_clearReturn + ( + FLAGS ${flag} >"${stdoutF}" 2>"${stderrF}" + echo $? >"${returnF}" + ) + th_queryReturn + assertTrue \ + 'short help request should have returned a true exit code.' \ + ${th_return} + grep 'show this help' "${stderrF}" >/dev/null + grepped=$? + assertTrue \ + 'short request for help should have produced some help output.' \ + ${grepped} + [ ${grepped} -ne ${FLAGS_TRUE} ] && th_showOutput + + # test proper output when FLAGS_HELP set + ( + FLAGS_HELP='this is a test' + FLAGS ${flag} >"${stdoutF}" 2>"${stderrF}" + ) + grep 'this is a test' "${stderrF}" >/dev/null + grepped=$? + assertTrue 'setting FLAGS_HELP did not produce expected result' ${grepped} + [ ${grepped} -ne ${FLAGS_TRUE} ] && th_showOutput + + # test that "'" chars work in help string + ( + DEFINE_boolean b false "help string containing a ' char" b + FLAGS ${flag} >"${stdoutF}" 2>"${stderrF}" + ) + grep "help string containing a ' char" "${stderrF}" >/dev/null + grepped=$? + assertTrue "help strings containing apostrophes don't work" ${grepped} + [ ${grepped} -ne ${FLAGS_TRUE} ] && th_showOutput +} + +mock_flags_columns() +{ + echo 80 +} + +testStandardHelpOutput() +{ + flags_getoptIsStd || startSkipping + + DEFINE_boolean test_bool false 'test boolean' b + DEFINE_integer test_int 0 'test integer' i + DEFINE_string test_str '' 'test string' s + DEFINE_string long_desc 'blah' \ + 'testing of a long description to force wrap of default value' D + DEFINE_string long_default \ + 'this_is_a_long_default_value_to_force_alternate_indentation' \ + 'testing of long default value' F + help='USAGE: standard [flags] args' + + cat >"${expectedF}" <<EOF +${help} +flags: + -b test boolean (default: false) + -i test integer (default: 0) + -s test string (default: '') + -D testing of a long description to force wrap of default value + (default: 'blah') + -F testing of long default value + (default: 'this_is_a_long_default_value_to_force_alternate_indentation') + -h show this help (default: false) +EOF + ( + _flags_columns() { mock_flags_columns; } + FLAGS_HELP=${help}; + FLAGS -h >"${stdoutF}" 2>"${stderrF}" + ) + r3turn=$? + assertTrue 'a call for help should not return an error' ${r3turn} + + diff "${expectedF}" "${stderrF}" >/dev/null + r3turn=$? + assertTrue 'unexpected help output' ${r3turn} + th_showOutput ${r3turn} "${stdoutF}" "${stderrF}" +} + +testEnhancedHelpOutput() +{ + flags_getoptIsEnh || startSkipping + + DEFINE_boolean test_bool false 'test boolean' b + DEFINE_integer test_int 0 'test integer' i + DEFINE_string test_str '' 'test string' s + DEFINE_string long_desc 'blah' \ + 'testing of a long description to force wrap of default value' D + DEFINE_string long_default \ + 'this_is_a_long_default_value_to_force_alternate_indentation' \ + 'testing of long default value' F + help='USAGE: enhanced [flags] args' + + cat >"${expectedF}" <<EOF +${help} +flags: + -b,--[no]test_bool: test boolean (default: false) + -i,--test_int: test integer (default: 0) + -s,--test_str: test string (default: '') + -D,--long_desc: testing of a long description to force wrap of default value + (default: 'blah') + -F,--long_default: testing of long default value + (default: 'this_is_a_long_default_value_to_force_alternate_indentation') + -h,--help: show this help (default: false) +EOF + ( + _flags_columns() { mock_flags_columns; } + FLAGS_HELP=${help}; + FLAGS -h >"${stdoutF}" 2>"${stderrF}" + ) + r3turn=$? + assertTrue 'a call for help should not return an error' ${r3turn} + + diff "${expectedF}" "${stderrF}" >/dev/null + differed=$? + assertTrue 'unexpected help output' ${differed} + th_showOutput ${differed} "${stdoutF}" "${stderrF}" +} + +testNoHelp() +{ + flags_getoptIsEnh || startSkipping + + ( FLAGS --nohelp >"${stdoutF}" 2>"${stderrF}" ) + r3turn=$? + assertTrue "FLAGS returned a non-zero result (${r3turn})" ${r3turn} + assertFalse 'expected no output to STDOUT' "[ -s '${stdoutF}' ]" + assertFalse 'expected no output to STDERR' "[ -s '${stderrF}' ]" +} + +#------------------------------------------------------------------------------ +# suite functions +# + +oneTimeSetUp() +{ + th_oneTimeSetUp + + if flags_getoptIsStd; then + th_warn 'Standard version of getopt found. Enhanced tests will be skipped.' + else + th_warn 'Enhanced version of getopt found. Standard tests will be skipped.' + fi +} + +setUp() +{ + flags_reset +} + +# load and run shUnit2 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. ${TH_SHUNIT} |