From 905a8f80ad5635a44cda6200b055262e441ce330 Mon Sep 17 00:00:00 2001 From: Douglas Brunk Date: Mon, 27 Apr 2020 11:48:07 -0400 Subject: [PATCH 01/10] Fix argument and option parsing for strings with single and double quotes. --- shflags | 11 +++- shflags_parsing_quotes_test.sh | 99 ++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100755 shflags_parsing_quotes_test.sh diff --git a/shflags b/shflags index 5cfab3b..1b7748a 100644 --- a/shflags +++ b/shflags @@ -871,7 +871,7 @@ _flags_parseGetopt() { ;; ${__FLAGS_TYPE_STRING}) - eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" + eval "FLAGS_${_flags_usName_}=\${_flags_arg_}" ;; esac @@ -894,7 +894,7 @@ _flags_parseGetopt() { # Give user back non-flag arguments. FLAGS_ARGV='' while [ $# -gt 0 ]; do - FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} }'$1'" + FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} } `_flags_escape "$1"`" shift done @@ -903,6 +903,13 @@ _flags_parseGetopt() { return ${flags_return} } +_flags_escape() { + [ -n "${BASH_VERSION:-}" ] && printf "%q\n" "$1" && return 0 + [ -n "${ZSH_VERSION:-}" ]&& printf "%q\n" "$1" && return 0 + [ -n "${KSH_VERSION:-}" ]&& printf "%q\n" "$1" && return 0 + printf "%s\n" "$1" | sed -e "s/'/'\"'\"'/g" -e "1s/^/'/" -e "\$s/\$/'/" +} + # Perform some path using built-ins. # # Args: diff --git a/shflags_parsing_quotes_test.sh b/shflags_parsing_quotes_test.sh new file mode 100755 index 0000000..fc017ef --- /dev/null +++ b/shflags_parsing_quotes_test.sh @@ -0,0 +1,99 @@ +#! /bin/sh +# vim:et:ft=sh:sts=2:sw=2 +# +# shFlags unit test for the flag definition methods +# +# Copyright 2008-2017 Kate Ward. All Rights Reserved. +# Released under the Apache 2.0 license. +# +# Author: kate.ward@forestent.com (Kate Ward) +# https://github.com/kward/shflags +# +### ShellCheck (http://www.shellcheck.net/) +# Disable source following. +# shellcheck disable=SC1090,SC1091 + +# 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 + +# These variables will be overridden by the test helpers. +returnF="${TMPDIR:-/tmp}/return" +stdoutF="${TMPDIR:-/tmp}/STDOUT" +stderrF="${TMPDIR:-/tmp}/STDERR" + +# Load test helpers. +. ./shflags_test_helpers + +testOptionStringsWithQuotes() { + _testValidOptionStrings -s "Single Quote Flag's Test" + _testValidOptionStrings -s "Double Quote \"Flag\" Test" + _testValidOptionStrings -s "Mixed Quote's \"Flag\" Test" + _testValidOptionStrings -s 'Mixed Quote'\''s "Flag" Test' +} + +testArgumentStringsWithQuotes() { + _testValidArgumentStrings "Single Quote Flag's Test" + _testValidArgumentStrings "Double Quote \"Flag\" Test" + _testValidArgumentStrings "Mixed Quote's \"Flag\" Test" +} + +_testValidOptionStrings() { + flag=$1 + value=$2 + + FLAGS "${flag}" "${value}" >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "'FLAGS ${flag} ${value}' returned a non-zero result (${r3turn})" \ + ${r3turn} + # shellcheck disable=SC2154 + 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}" +} + +_testValidArgumentStrings() { + quoted_string="$1" + FLAGS "$quoted_string" >"${stdoutF}" 2>"${stderrF}" + r3turn=$? + assertTrue "'FLAGS $quoted_string' returned a non-zero result (${r3turn})" \ + ${r3turn} + eval set -- "${FLAGS_ARGV}" + assertEquals "$quoted_string" "$1" +} + +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. +# shellcheck disable=SC2034 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. "${TH_SHUNIT}" From 920cbcad716307ecb75e28a979b8adbbadb9fa4f Mon Sep 17 00:00:00 2001 From: Douglas Brunk Date: Mon, 27 Apr 2020 12:15:18 -0400 Subject: [PATCH 02/10] Clean up code with comments and indentation for upstream submission. --- shflags | 18 ++++++++++++++---- shflags_parsing_quotes_test.sh | 3 --- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/shflags b/shflags index 1b7748a..a2d3cde 100644 --- a/shflags +++ b/shflags @@ -903,11 +903,21 @@ _flags_parseGetopt() { return ${flags_return} } +# Escape quotes in a string to pass on the command line +# +# Optimize escaping using the printf builtin '%q' when available +# +# Args: +# $1: string: string to escape +# Output: +# string: the escaped string +# Returns: +# bool: success of string escaping _flags_escape() { - [ -n "${BASH_VERSION:-}" ] && printf "%q\n" "$1" && return 0 - [ -n "${ZSH_VERSION:-}" ]&& printf "%q\n" "$1" && return 0 - [ -n "${KSH_VERSION:-}" ]&& printf "%q\n" "$1" && return 0 - printf "%s\n" "$1" | sed -e "s/'/'\"'\"'/g" -e "1s/^/'/" -e "\$s/\$/'/" + [ -n "${BASH_VERSION:-}" ] && printf "%q\n" "$1" && return 0 + [ -n "${ZSH_VERSION:-}" ] && printf "%q\n" "$1" && return 0 + [ -n "${KSH_VERSION:-}" ] && printf "%q\n" "$1" && return 0 + printf "%s\n" "$1" | sed -e "s/'/'\"'\"'/g" -e "1s/^/'/" -e "\$s/\$/'/" } # Perform some path using built-ins. diff --git a/shflags_parsing_quotes_test.sh b/shflags_parsing_quotes_test.sh index fc017ef..daddb57 100755 --- a/shflags_parsing_quotes_test.sh +++ b/shflags_parsing_quotes_test.sh @@ -13,9 +13,6 @@ # Disable source following. # shellcheck disable=SC1090,SC1091 -# TODO(kward): assert on FLAGS errors -# TODO(kward): testNonStandardIFS() - # Exit immediately if a pipeline or subshell exits with a non-zero status. #set -e From ada4ae25da6d685c54fc758b566677213d71d56a Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Fri, 18 Aug 2023 11:19:22 +0200 Subject: [PATCH 03/10] Updated versions to latest. --- lib/versions | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/versions b/lib/versions index b5533ab..e4ae518 100755 --- a/lib/versions +++ b/lib/versions @@ -51,6 +51,10 @@ versions_osName() { 10.13|10.13.[0-9]*) os_name_='macOS High Sierra' ;; 10.14|10.14.[0-9]*) os_name_='macOS Mojave' ;; 10.15|10.15.[0-9]*) os_name_='macOS Catalina' ;; + 11.*) os_name_='macOS Big Sur' ;; + 12.*) os_name_='macOS Monterey' ;; + 13.*) os_name_='macOS Ventura' ;; + 14.*) os_name_='macOS Sonoma' ;; *) os_name_='macOS' ;; esac ;; From 7e8ee36ecf27db2fbf8f50f702813fb690be5ecb Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Fri, 18 Aug 2023 11:55:02 +0200 Subject: [PATCH 04/10] Updated shunit2 to lates. --- lib/shunit2 | 219 +++++++++++++++++++++------------------------------- 1 file changed, 90 insertions(+), 129 deletions(-) diff --git a/lib/shunit2 b/lib/shunit2 index 2850370..57a45da 100755 --- a/lib/shunit2 +++ b/lib/shunit2 @@ -1,22 +1,22 @@ #! /bin/sh # vim:et:ft=sh:sts=2:sw=2 # -# Copyright 2008-2020 Kate Ward. All Rights Reserved. +# shUnit2 -- Unit testing framework for Unix shell scripts. +# +# Copyright 2008-2021 Kate Ward. All Rights Reserved. # Released under the Apache 2.0 license. # http://www.apache.org/licenses/LICENSE-2.0 # -# shUnit2 -- Unit testing framework for Unix shell scripts. -# https://github.com/kward/shunit2 -# # Author: kate.ward@forestent.com (Kate Ward) +# https://github.com/kward/shunit2 # # shUnit2 is a xUnit based unit test framework for Bourne shell scripts. It is # based on the popular JUnit unit testing framework for Java. # -# $() are not fully portable (POSIX != portable). -# shellcheck disable=SC2006 -# expr may be antiquated, but it is the only solution in some cases. +# `expr` may be antiquated, but it is the only solution in some cases. # shellcheck disable=SC2003 +# Allow usage of legacy backticked `...` notation instead of $(...). +# shellcheck disable=SC2006 # Return if shunit2 already loaded. if test -n "${SHUNIT_VERSION:-}"; then @@ -38,51 +38,20 @@ fi # Determine some reasonable command defaults. __SHUNIT_CMD_ECHO_ESC='echo -e' -# shellcheck disable=SC2039 +# shellcheck disable=SC2039,SC3037 if ${__SHUNIT_BUILTIN} [ "`echo -e test`" = '-e test' ]; then __SHUNIT_CMD_ECHO_ESC='echo' fi -__SHUNIT_UNAME_S=`uname -s` -case "${__SHUNIT_UNAME_S}" in - BSD) __SHUNIT_CMD_EXPR='gexpr' ;; - *) __SHUNIT_CMD_EXPR='expr' ;; -esac -__SHUNIT_CMD_TPUT='tput' - # Commands a user can override if needed. -SHUNIT_CMD_EXPR=${SHUNIT_CMD_EXPR:-${__SHUNIT_CMD_EXPR}} +__SHUNIT_CMD_TPUT='tput' SHUNIT_CMD_TPUT=${SHUNIT_CMD_TPUT:-${__SHUNIT_CMD_TPUT}} -# Enable color output. Options are 'never', 'always', or 'auto'. +# Enable color output. Options are 'auto', 'always', or 'never'. SHUNIT_COLOR=${SHUNIT_COLOR:-auto} -# Logging functions. -_shunit_warn() { - ${__SHUNIT_CMD_ECHO_ESC} "${__shunit_ansi_yellow}shunit2:WARN${__shunit_ansi_none} $*" >&2 -} -_shunit_error() { - ${__SHUNIT_CMD_ECHO_ESC} "${__shunit_ansi_red}shunit2:ERROR${__shunit_ansi_none} $*" >&2 -} -_shunit_fatal() { - ${__SHUNIT_CMD_ECHO_ESC} "${__shunit_ansi_red}shunit2:FATAL${__shunit_ansi_none} $*" >&2 - exit ${SHUNIT_ERROR} -} - -# Specific shell checks. -if ${__SHUNIT_BUILTIN} [ -n "${ZSH_VERSION:-}" ]; then - setopt |grep "^shwordsplit$" >/dev/null - if ${__SHUNIT_BUILTIN} [ $? -ne ${SHUNIT_TRUE} ]; then - _shunit_fatal 'zsh shwordsplit option is required for proper operation' - fi - if ${__SHUNIT_BUILTIN} [ -z "${SHUNIT_PARENT:-}" ]; then - _shunit_fatal "zsh does not pass \$0 through properly. please declare \ -\"SHUNIT_PARENT=\$0\" before calling shUnit2" - fi -fi - # -# Constants +# Internal constants. # __SHUNIT_MODE_SOURCED='sourced' @@ -100,25 +69,6 @@ __SHUNIT_ANSI_GREEN='\033[1;32m' __SHUNIT_ANSI_YELLOW='\033[1;33m' __SHUNIT_ANSI_CYAN='\033[1;36m' -# Set the constants readonly. -__shunit_constants=`set |grep '^__SHUNIT_' |cut -d= -f1` -echo "${__shunit_constants}" |grep '^Binary file' >/dev/null && \ - __shunit_constants=`set |grep -a '^__SHUNIT_' |cut -d= -f1` -for __shunit_const in ${__shunit_constants}; do - if ${__SHUNIT_BUILTIN} [ -z "${ZSH_VERSION:-}" ]; then - readonly "${__shunit_const}" - else - case ${ZSH_VERSION} in - [123].*) readonly "${__shunit_const}" ;; - *) - # Declare readonly constants globally. - # shellcheck disable=SC2039 - readonly -g "${__shunit_const}" - esac - fi -done -unset __shunit_const __shunit_constants - # # Internal variables. # @@ -151,12 +101,63 @@ __shunit_assertsPassed=0 __shunit_assertsFailed=0 __shunit_assertsSkipped=0 +# +# Internal functions. +# + +# Logging. +_shunit_warn() { + ${__SHUNIT_CMD_ECHO_ESC} "${__shunit_ansi_yellow}shunit2:WARN${__shunit_ansi_none} $*" >&2 +} +_shunit_error() { + ${__SHUNIT_CMD_ECHO_ESC} "${__shunit_ansi_red}shunit2:ERROR${__shunit_ansi_none} $*" >&2 +} +_shunit_fatal() { + ${__SHUNIT_CMD_ECHO_ESC} "${__shunit_ansi_red}shunit2:FATAL${__shunit_ansi_none} $*" >&2 + exit ${SHUNIT_ERROR} +} + # # Macros. # # shellcheck disable=SC2016,SC2089 -_SHUNIT_LINENO_='eval __shunit_lineno=""; if ${__SHUNIT_BUILTIN} [ "${1:-}" = "--lineno" ]; then if ${__SHUNIT_BUILTIN} [ -n "$2" ]; then __shunit_lineno="[$2] "; fi; shift 2; fi' +_SHUNIT_LINENO_='eval __shunit_lineno=""; if ${__SHUNIT_BUILTIN} [ "${1:-}" = "--lineno" ] && ${__SHUNIT_BUILTIN} [ -n "${2:-}" ]; then __shunit_lineno="[${2}]"; shift 2; fi;' + +# +# Setup. +# + +# Specific shell checks. +if ${__SHUNIT_BUILTIN} [ -n "${ZSH_VERSION:-}" ]; then + setopt |grep "^shwordsplit$" >/dev/null + if ${__SHUNIT_BUILTIN} [ $? -ne ${SHUNIT_TRUE} ]; then + _shunit_fatal 'zsh shwordsplit option is required for proper operation' + fi + if ${__SHUNIT_BUILTIN} [ -z "${SHUNIT_PARENT:-}" ]; then + _shunit_fatal "zsh does not pass \$0 through properly. please declare \ +\"SHUNIT_PARENT=\$0\" before calling shUnit2" + fi +fi + +# Set the constants readonly. +__shunit_constants=`set |grep '^__SHUNIT_' |cut -d= -f1` +echo "${__shunit_constants}" |grep '^Binary file' >/dev/null && \ + __shunit_constants=`set |grep -a '^__SHUNIT_' |cut -d= -f1` +for __shunit_const in ${__shunit_constants}; do + if ${__SHUNIT_BUILTIN} [ -z "${ZSH_VERSION:-}" ]; then + readonly "${__shunit_const}" + else + case ${ZSH_VERSION} in + [123].*) readonly "${__shunit_const}" ;; + *) + # Declare readonly constants globally. + # shellcheck disable=SC2039,SC3045 + readonly -g "${__shunit_const}" + esac + fi +done +unset __shunit_const __shunit_constants #----------------------------------------------------------------------------- # Assertion functions. @@ -329,7 +330,7 @@ assertNotContains() { # shellcheck disable=SC2016,SC2034 _ASSERT_NOT_CONTAINS_='eval assertNotContains --lineno "${LINENO:-}"' -# Assert that a value is null (i.e. an empty string) +# Assert that a value is null (i.e. an empty string). # # Args: # message: string: failure message [optional] @@ -339,7 +340,8 @@ _ASSERT_NOT_CONTAINS_='eval assertNotContains --lineno "${LINENO:-}"' assertNull() { # shellcheck disable=SC2090 ${_SHUNIT_LINENO_} - if ${__SHUNIT_BUILTIN} [ $# -lt 1 -o $# -gt 2 ]; then + if ${__SHUNIT_BUILTIN} [ $# -gt 2 ]; then + # Allowing 0 arguments as $1 might actually be null. _shunit_error "assertNull() requires one or two arguments; $# given" _shunit_assertFail return ${SHUNIT_ERROR} @@ -353,7 +355,9 @@ assertNull() { shunit_message_="${shunit_message_}$1" shift fi - assertTrue "${shunit_message_}" "[ -z '$1' ]" + + ${__SHUNIT_BUILTIN} test -z "${1:-}" + assertTrue "${shunit_message_}" $? shunit_return=$? unset shunit_message_ @@ -362,7 +366,7 @@ assertNull() { # shellcheck disable=SC2016,SC2034 _ASSERT_NULL_='eval assertNull --lineno "${LINENO:-}"' -# Assert that a value is not null (i.e. a non-empty string) +# Assert that a value is not null (i.e. a non-empty string). # # Args: # message: string: failure message [optional] @@ -387,12 +391,12 @@ assertNotNull() { shunit_message_="${shunit_message_}$1" shift fi - shunit_actual_=`_shunit_escapeCharactersInString "${1:-}"` - test -n "${shunit_actual_}" + + ${__SHUNIT_BUILTIN} test -n "${1:-}" assertTrue "${shunit_message_}" $? shunit_return=$? - unset shunit_actual_ shunit_message_ + unset shunit_message_ return ${shunit_return} } # shellcheck disable=SC2016,SC2034 @@ -702,11 +706,12 @@ failFound() { shunit_message_="${shunit_message_}$1" shift fi + shunit_content_=$1 shunit_message_=${shunit_message_%% } - _shunit_assertFail "${shunit_message_:+${shunit_message_} }found" + _shunit_assertFail "${shunit_message_:+${shunit_message_} }found:<${shunit_content_}>" - unset shunit_message_ + unset shunit_message_ shunit_content_ return ${SHUNIT_FALSE} } # shellcheck disable=SC2016,SC2034 @@ -826,8 +831,11 @@ _FAIL_NOT_SAME_='eval failNotSame --lineno "${LINENO:-}"' # the total of asserts and fails will not be altered. # # Args: -# None -startSkipping() { __shunit_skip=${SHUNIT_TRUE}; } +# message: string: message to provide to user [optional] +startSkipping() { + if ${__SHUNIT_BUILTIN} [ $# -gt 0 ]; then _shunit_warn "[skipping] $*"; fi + __shunit_skip=${SHUNIT_TRUE} +} # Resume the normal recording behavior of assert and fail calls. # @@ -947,7 +955,7 @@ _shunit_mktempDir() { fi # The standard `mktemp` didn't work. Use our own. - # shellcheck disable=SC2039 + # shellcheck disable=SC2039,SC3028 if ${__SHUNIT_BUILTIN} [ -r '/dev/urandom' -a -x '/usr/bin/od' ]; then _shunit_random_=`/usr/bin/od -vAn -N4 -tx4 Date: Fri, 18 Aug 2023 13:10:03 +0200 Subject: [PATCH 05/10] Fixed shellcheck errors. --- shflags | 81 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/shflags b/shflags index a2d3cde..1fdbc06 100644 --- a/shflags +++ b/shflags @@ -1,6 +1,6 @@ # vim:et:ft=sh:sts=2:sw=2 # -# Copyright 2008-2020 Kate Ward. All Rights Reserved. +# Copyright 2008-2023 Kate Ward. All Rights Reserved. # Released under the Apache License 2.0 license. # http://www.apache.org/licenses/LICENSE-2.0 # @@ -150,16 +150,16 @@ __FLAGS_LEVEL_DEFAULT=${FLAGS_LEVEL_WARN} __flags_level=${__FLAGS_LEVEL_DEFAULT} # Current logging level. _flags_debug() { - if [ ${__flags_level} -le ${FLAGS_LEVEL_DEBUG} ]; then echo "flags:DEBUG $*" >&2; fi + if [ "${__flags_level}" -le "${FLAGS_LEVEL_DEBUG}" ]; then echo "flags:DEBUG $*" >&2; fi } _flags_info() { - if [ ${__flags_level} -le ${FLAGS_LEVEL_INFO} ]; then echo "flags:INFO $*" >&2; fi + if [ "${__flags_level}" -le "${FLAGS_LEVEL_INFO}" ]; then echo "flags:INFO $*" >&2; fi } _flags_warn() { - if [ ${__flags_level} -le ${FLAGS_LEVEL_WARN} ]; then echo "flags:WARN $*" >&2; fi + if [ "${__flags_level}" -le "${FLAGS_LEVEL_WARN}" ]; then echo "flags:WARN $*" >&2; fi } _flags_error() { - if [ ${__flags_level} -le ${FLAGS_LEVEL_ERROR} ]; then echo "flags:ERROR $*" >&2; fi + if [ "${__flags_level}" -le "${FLAGS_LEVEL_ERROR}" ]; then echo "flags:ERROR $*" >&2; fi } _flags_fatal() { echo "flags:FATAL $*" >&2 @@ -167,7 +167,7 @@ _flags_fatal() { } # Get the logging level. -flags_loggingLevel() { echo ${__flags_level}; } +flags_loggingLevel() { echo "${__flags_level}"; } # Set the logging level by overriding the `__flags_level` variable. # @@ -274,7 +274,7 @@ for __flags_const in ${__flags_constants}; do [123].*) readonly "${__flags_const}" ;; *) # Declare readonly constants globally. - # shellcheck disable=SC2039 + # shellcheck disable=SC2039,SC3045 readonly -g "${__flags_const}" ;; esac done @@ -370,7 +370,7 @@ _flags_define() { # '!' 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}) + "${__FLAGS_TYPE_BOOLEAN}") if _flags_validBool "${_flags_default_}"; then case ${_flags_default_} in true|t|0) _flags_default_=${FLAGS_TRUE} ;; @@ -382,7 +382,7 @@ _flags_define() { fi ;; - ${__FLAGS_TYPE_FLOAT}) + "${__FLAGS_TYPE_FLOAT}") if _flags_validFloat "${_flags_default_}"; then : else @@ -391,7 +391,7 @@ _flags_define() { fi ;; - ${__FLAGS_TYPE_INTEGER}) + "${__FLAGS_TYPE_INTEGER}") if _flags_validInt "${_flags_default_}"; then : else @@ -400,7 +400,7 @@ _flags_define() { fi ;; - ${__FLAGS_TYPE_STRING}) ;; # Everything in shell is a valid string. + "${__FLAGS_TYPE_STRING}") ;; # Everything in shell is a valid string. *) flags_error="unrecognized flag type '${_flags_type_}'" @@ -470,7 +470,7 @@ _flags_genOptStr() { _flags_fatal 'call to _flags_type_ failed' fi case ${_flags_optStrType_} in - ${__FLAGS_OPTSTR_SHORT}) + "${__FLAGS_OPTSTR_SHORT}") _flags_shortName_="`_flags_getFlagInfo \ "${_flags_usName_}" "${__FLAGS_INFO_SHORT}"`" if [ "${_flags_shortName_}" != "${__FLAGS_NULL}" ]; then @@ -481,7 +481,7 @@ _flags_genOptStr() { fi ;; - ${__FLAGS_OPTSTR_LONG}) + "${__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}" ] && \ @@ -519,7 +519,7 @@ _flags_getFlagInfo() { eval "${_flags_strToEval_}" if [ -n "${_flags_infoValue_}" ]; then # Special value '§' indicates no help string provided. - [ "${_flags_gFI_info_}" = ${__FLAGS_INFO_HELP} \ + [ "${_flags_gFI_info_}" = "${__FLAGS_INFO_HELP}" \ -a "${_flags_infoValue_}" = '§' ] && _flags_infoValue_='' flags_return=${FLAGS_TRUE} else @@ -684,7 +684,7 @@ _flags_validInt() { # integer: a FLAGS success condition _flags_getoptStandard() { flags_return=${FLAGS_TRUE} - _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` + _flags_shortOpts_=`_flags_genOptStr "${__FLAGS_OPTSTR_SHORT}"` # Check for spaces in passed options. for _flags_opt_ in "$@"; do @@ -722,10 +722,10 @@ _flags_getoptStandard() { # integer: a FLAGS success condition _flags_getoptEnhanced() { flags_return=${FLAGS_TRUE} - _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` + _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_longOpts_=`_flags_genOptStr "${__FLAGS_OPTSTR_LONG}"` __flags_opts=`${FLAGS_GETOPT_CMD} \ -o "${_flags_shortOpts_}" \ @@ -761,6 +761,7 @@ _flags_parseGetopt() { set -- $@ else # Note the quotes around the `$@` -- they are essential! + # shellcheck disable=SC2294 eval set -- "$@" fi @@ -826,12 +827,11 @@ _flags_parseGetopt() { # 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}` + [ "${_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 + "${__FLAGS_TYPE_BOOLEAN}") + if [ "${_flags_len_}" -eq "${__FLAGS_LEN_LONG}" ]; then if [ "${_flags_arg_}" != "${__FLAGS_NULL}" ]; then eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" else @@ -850,7 +850,7 @@ _flags_parseGetopt() { fi ;; - ${__FLAGS_TYPE_FLOAT}) + "${__FLAGS_TYPE_FLOAT}") if _flags_validFloat "${_flags_arg_}"; then eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" else @@ -860,7 +860,7 @@ _flags_parseGetopt() { fi ;; - ${__FLAGS_TYPE_INTEGER}) + "${__FLAGS_TYPE_INTEGER}") if _flags_validInt "${_flags_arg_}"; then eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" else @@ -870,7 +870,7 @@ _flags_parseGetopt() { fi ;; - ${__FLAGS_TYPE_STRING}) + "${__FLAGS_TYPE_STRING}") eval "FLAGS_${_flags_usName_}=\${_flags_arg_}" ;; esac @@ -888,7 +888,7 @@ _flags_parseGetopt() { # Shift the option and non-boolean arguments out. shift - [ "${_flags_type_}" != ${__FLAGS_TYPE_BOOLEAN} ] && shift + [ "${_flags_type_}" != "${__FLAGS_TYPE_BOOLEAN}" ] && shift done # Give user back non-flag arguments. @@ -940,6 +940,7 @@ _flags_math() { flags_return=$? unset _flags_expr_ else + # shellcheck disable=SC2294 eval expr "$@" flags_return=$? fi @@ -978,7 +979,7 @@ _flags_strlen() { # None # Returns: # bool: true if built-ins should be used -_flags_useBuiltin() { return ${__FLAGS_USE_BUILTIN}; } +_flags_useBuiltin() { return "${__FLAGS_USE_BUILTIN}"; } #------------------------------------------------------------------------------ # public functions @@ -996,12 +997,12 @@ _flags_useBuiltin() { return ${__FLAGS_USE_BUILTIN}; } # 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} "$@"; } +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} "$@"; } +DEFINE_float() { _flags_define "${__FLAGS_TYPE_FLOAT}" "$@"; } +DEFINE_integer() { _flags_define "${__FLAGS_TYPE_INTEGER}" "$@"; } +DEFINE_string() { _flags_define "${__FLAGS_TYPE_STRING}" "$@"; } # Parse the flags. # @@ -1118,13 +1119,13 @@ flags_help() { flags_usName_=`_flags_underscoreName "${flags_name_}"` flags_default_=`_flags_getFlagInfo \ - "${flags_usName_}" ${__FLAGS_INFO_DEFAULT}` + "${flags_usName_}" "${__FLAGS_INFO_DEFAULT}"` flags_help_=`_flags_getFlagInfo \ - "${flags_usName_}" ${__FLAGS_INFO_HELP}` + "${flags_usName_}" "${__FLAGS_INFO_HELP}"` flags_short_=`_flags_getFlagInfo \ - "${flags_usName_}" ${__FLAGS_INFO_SHORT}` + "${flags_usName_}" "${__FLAGS_INFO_SHORT}"` flags_type_=`_flags_getFlagInfo \ - "${flags_usName_}" ${__FLAGS_INFO_TYPE}` + "${flags_usName_}" "${__FLAGS_INFO_TYPE}"` [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ flags_flagStr_="-${flags_short_}" @@ -1133,23 +1134,23 @@ flags_help() { [ "${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} \ + [ "${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}) + "${__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_TYPE_FLOAT}"|"${__FLAGS_TYPE_INTEGER}") flags_defaultStr_=${flags_default_} ;; - ${__FLAGS_TYPE_STRING}) flags_defaultStr_="'${flags_default_}'" ;; + "${__FLAGS_TYPE_STRING}") flags_defaultStr_="'${flags_default_}'" ;; esac flags_defaultStr_="(default: ${flags_defaultStr_})" @@ -1218,7 +1219,7 @@ flags_reset() { __flags_definedNames=' ' # Reset logging level back to default. - flags_setLoggingLevel ${__FLAGS_LEVEL_DEFAULT} + flags_setLoggingLevel "${__FLAGS_LEVEL_DEFAULT}" unset flags_name_ flags_type_ flags_strToEval_ flags_usName_ } From d60611d3685362d01005eef84b859db18bc31171 Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Fri, 18 Aug 2023 14:42:57 +0200 Subject: [PATCH 06/10] Added test to ensure 'set -o pipefail' doesn't break shFlags. --- shflags_issue_57.sh | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100755 shflags_issue_57.sh diff --git a/shflags_issue_57.sh b/shflags_issue_57.sh new file mode 100755 index 0000000..2969f5f --- /dev/null +++ b/shflags_issue_57.sh @@ -0,0 +1,67 @@ +#! /bin/sh +# vim:et:ft=sh:sts=2:sw=2 +# +# shFlags unit test for Issue #57. +# https://github.com/kward/shflags/issues/57 +# +# Copyright 2023 Kate Ward. All Rights Reserved. +# Released under the Apache 2.0 license. +# +# Author: kate.ward@forestent.com (Kate Ward) +# https://github.com/kward/shflags +# +### ShellCheck (http://www.shellcheck.net/) +# Disable source following. +# shellcheck disable=SC1090,SC1091 +# $() are not fully portable (POSIX != portable). +# shellcheck disable=SC2006 + +# These variables will be overridden by the test helpers. +returnF="${TMPDIR:-/tmp}/return" +stdoutF="${TMPDIR:-/tmp}/STDOUT" +stderrF="${TMPDIR:-/tmp}/STDERR" + +# Load test helpers. +. ./shflags_test_helpers + +# Test proper functionality with 'set -o pipefail' enabled. +testIssue57() { + set -o pipefail + + th_clearReturn + ( + FLAGS -h >"${stdoutF}" 2>"${stderrF}" + echo $? >"${returnF}" + ) + + assertFalse \ + 'short help request should have returned a false exit code.' \ + "$(th_queryReturn)" + ( grep 'show this help' "${stderrF}" >/dev/null ) + r3turn=$? + assertTrue \ + 'short request for help should have produced some help output.' \ + ${r3turn} + [ ${r3turn} -ne "${FLAGS_TRUE}" ] && th_showOutput + + return ${SHUNIT_TRUE} +} + +oneTimeSetUp() { + th_oneTimeSetUp + + if flags_getoptIsStd; then + th_warn 'Standard version of getopt found. Enhanced tests will be skipped.' + return + fi + th_warn 'Enhanced version of getopt found. Standard tests will be skipped.' +} + +setUp() { + flags_reset +} + +# Load and run shUnit2. +# shellcheck disable=SC2034 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. "${TH_SHUNIT}" From 2bc59f3e5ffd8105364360ec20f6a0c6348f726f Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Fri, 18 Aug 2023 14:46:05 +0200 Subject: [PATCH 07/10] Fixed logic to removed unnecessary return. --- shflags_issue_57.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shflags_issue_57.sh b/shflags_issue_57.sh index 2969f5f..51f9788 100755 --- a/shflags_issue_57.sh +++ b/shflags_issue_57.sh @@ -26,6 +26,7 @@ stderrF="${TMPDIR:-/tmp}/STDERR" # Test proper functionality with 'set -o pipefail' enabled. testIssue57() { + # shellcheck disable=SC3040 set -o pipefail th_clearReturn @@ -42,9 +43,7 @@ testIssue57() { assertTrue \ 'short request for help should have produced some help output.' \ ${r3turn} - [ ${r3turn} -ne "${FLAGS_TRUE}" ] && th_showOutput - - return ${SHUNIT_TRUE} + [ ${r3turn} -eq "${FLAGS_TRUE}" ] || th_showOutput } oneTimeSetUp() { From f7fd9e94c78717c85ad61740d06187cd609671a2 Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Fri, 18 Aug 2023 14:46:35 +0200 Subject: [PATCH 08/10] Noted Issue #57 info. --- doc/CHANGES-1.3.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/CHANGES-1.3.md b/doc/CHANGES-1.3.md index 248afe6..79c5d22 100644 --- a/doc/CHANGES-1.3.md +++ b/doc/CHANGES-1.3.md @@ -6,12 +6,14 @@ *A new series was started due to the major changes required for 'set -e' support.* -Upgraded shUnit2 to 2.1.9pre, which includes 'set -e' support. +Upgraded shUnit2 to HEAD, which includes 'set -e' support. Fixed #9. shFlags now works properly with 'set -e' enabled. Fixed #50. The `FLAGS_ARGC` variable is no longer is no longer exported. The variable was marked obsolete in 1.0.3, and it is finally being removed. +Issue #57. Added `shflags_issue_57.sh` to ensure 'set -o pipefail' doesn't break functionality. + --- ## 1.2.x stable series From 79b5905b4a663b682ee3edff6401389e893cef65 Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Thu, 24 Aug 2023 13:39:58 +0200 Subject: [PATCH 09/10] Added release notes from v1.3.0 release. --- doc/RELEASE_NOTES-1.3.0.md | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 doc/RELEASE_NOTES-1.3.0.md diff --git a/doc/RELEASE_NOTES-1.3.0.md b/doc/RELEASE_NOTES-1.3.0.md new file mode 100644 index 0000000..80b491e --- /dev/null +++ b/doc/RELEASE_NOTES-1.3.0.md @@ -0,0 +1,65 @@ +# shFlags 1.3.0 Release Notes + +https://github.com/kward/shflags + +## Preface + +This document covers any known issues and workarounds for the stated release of +shFlags. + +## Release info + +This is the first release in the new testing series. The primary change from +1.2.3 was reworking things so that 'set -e' is supported. + +Please see the `CHANGES-1.3.md` file for a complete list of changes. + +### Notable changes + +The obsolete `FLAGS_ARGC` variable was removed. + +### Notable bug fixes + +Some rewrites to ensure shell 'set -e' (as well as 'set -u' and +'set -o pipefail') are supported as expected. + +## General info + +### The unit tests + +shFlags is designed to work on as many environments as possible, but not all +environments are created equal. As such, not all of the unit tests will succeed +on every platform. The unit tests are therefore designed to fail, indicating to +the tester that the supported functionality is not present, but an additional +test is present to verify that shFlags properly caught the limitation and +presented the user with an appropriate error message. + +shFlags tries to support both the standard and enhanced versions of `getopt`. As +each responds differently, and not everything is supported on the standard +version, some unit tests will be skipped (i.e. ASSERTS will not be thrown) when +the standard version of `getopt` is detected. The reason being that there is no +point testing for functionality that is positively known not to exist. A tally +of skipped tests will be kept for later reference. + +### Standard vs Enhanced getopt + +Here is a matrix of the supported features of the various `getopt` variants. + +Feature | std | enh +--------------------------------------- | --- | --- +short option names | Y | Y +long option names | N | Y +spaces in string options | N | Y +intermixing of flag and non-flag values | N | Y + +## Known Issues + +The `getopt` version provided by default with all versions of Mac OS X (up to +and including 10.13.0) and Solaris (up to and including Solaris 10 and +OpenSolaris) is the standard version. + +## Workarounds + +The Zsh shell requires the `shwordsplit` option to be set and the special +`FLAGS_PARENT` variable must be defined. See `src/shflags_test_helpers` to see +how the unit tests do this. From 4f5009891b616b67c0ba4c39d754dd78b6d51a08 Mon Sep 17 00:00:00 2001 From: Douglas Brunk Date: Mon, 27 Apr 2020 11:48:07 -0400 Subject: [PATCH 10/10] Fix argument and option parsing for strings with single and double quotes. --- shflags_parsing_quotes_test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shflags_parsing_quotes_test.sh b/shflags_parsing_quotes_test.sh index daddb57..fc017ef 100755 --- a/shflags_parsing_quotes_test.sh +++ b/shflags_parsing_quotes_test.sh @@ -13,6 +13,9 @@ # Disable source following. # shellcheck disable=SC1090,SC1091 +# TODO(kward): assert on FLAGS errors +# TODO(kward): testNonStandardIFS() + # Exit immediately if a pipeline or subshell exits with a non-zero status. #set -e