diff --git a/main.sh b/main.sh index 6c815b2..c91abf9 100755 --- a/main.sh +++ b/main.sh @@ -159,28 +159,34 @@ while read -r __b3bp_tmp_line; do __b3bp_tmp_long_opt="${__b3bp_tmp_long_opt%% *}" fi - # map long name back to short name - printf -v "__b3bp_tmp_short_opt_${__b3bp_tmp_long_opt//-/_}" '%s' "${__b3bp_tmp_opt}" + # map opt long name to+from opt short name + printf -v "__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" '%s' "${__b3bp_tmp_opt}" + printf -v "__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt}" '%s' "${__b3bp_tmp_long_opt//-/_}" # check if option takes an argument - if [[ ! "${__b3bp_tmp_line}" =~ \[.*\] ]]; then - __b3bp_tmp_init="0" # it's a flag. init with 0 - printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "0" - else + if [[ "${__b3bp_tmp_line}" =~ \[.*\] ]]; then __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg __b3bp_tmp_init="" # it has an arg. init with "" printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "1" + elif [[ "${__b3bp_tmp_line}" =~ \{.*\} ]]; then + __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg + __b3bp_tmp_init="" # it has an arg. init with "" + # remember that this option requires an argument + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" + else + __b3bp_tmp_init="0" # it's a flag. init with 0 + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "0" fi __b3bp_tmp_opts="${__b3bp_tmp_opts:-}${__b3bp_tmp_opt}" fi [ -z "${__b3bp_tmp_opt:-}" ] && continue - if [[ "${__b3bp_tmp_line}" =~ \.\ Default ]]; then + if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Default= ]]; then # ignore default value if option does not have an argument __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" - if [ "${!__b3bp_tmp_varname}" = "1" ]; then + if [ "${!__b3bp_tmp_varname}" != "0" ]; then __b3bp_tmp_init="${__b3bp_tmp_line##*Default=}" __b3bp_tmp_re='^"(.*)"$' if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then @@ -194,6 +200,11 @@ while read -r __b3bp_tmp_line; do fi fi + if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Required\. ]]; then + # remember that this option requires an argument + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" + fi + printf -v "arg_${__b3bp_tmp_opt:0:1}" '%s' "${__b3bp_tmp_init}" done <<< "${__usage:-}" @@ -218,13 +229,13 @@ if [ -n "${__b3bp_tmp_opts:-}" ]; then # --key=value format __b3bp_tmp_long_opt=${OPTARG/=*/} # Set opt to the short option corresponding to the long option - __b3bp_tmp_varname="__b3bp_tmp_short_opt_${__b3bp_tmp_long_opt//-/_}" + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" OPTARG=${OPTARG#*=} else # --key value format # Map long name to short version of option - __b3bp_tmp_varname="__b3bp_tmp_short_opt_${OPTARG//-/_}" + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${OPTARG//-/_}" printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" # Only assign OPTARG if option takes an argument __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}" @@ -253,6 +264,25 @@ if [ -n "${__b3bp_tmp_opts:-}" ]; then fi +### Automatic validation of required option arguments +############################################################################## + +for __b3bp_tmp_varname in ${!__b3bp_tmp_has_arg_*}; do + # validate only options which required an argument + [ "${!__b3bp_tmp_varname}" = "2" ] || continue + + __b3bp_tmp_opt_short="${__b3bp_tmp_varname##*_}" + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt_short}" + [ -n "${!__b3bp_tmp_varname}" ] && continue + + __b3bp_tmp_varname="__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt_short}" + printf -v "__b3bp_tmp_opt_long" '%s' "${!__b3bp_tmp_varname}" + [ -n "${__b3bp_tmp_opt_long:-}" ] && __b3bp_tmp_opt_long=" (--${__b3bp_tmp_opt_long//_/-})" + + help "Option -${__b3bp_tmp_opt_short}${__b3bp_tmp_opt_long:-} requires an argument" +done + + ### Cleanup Environment variables ############################################################################## diff --git a/test/fixture/main-usage-validation.exitcode b/test/fixture/main-usage-validation.exitcode new file mode 100644 index 0000000..573541a --- /dev/null +++ b/test/fixture/main-usage-validation.exitcode @@ -0,0 +1 @@ +0 diff --git a/test/fixture/main-usage-validation.stdio b/test/fixture/main-usage-validation.stdio new file mode 100644 index 0000000..ff7e59b --- /dev/null +++ b/test/fixture/main-usage-validation.stdio @@ -0,0 +1,155 @@ +ACCPTST:STDIO_REPLACE_DATETIMES +# complain about -3 + + Option -3 (--three) requires an argument + + -0 --zero Do nothing. + -1 --one Do one thing. Required. + More description. + -2 --two Do two things. + More. Required. Description. + -3 --three [arg] Do three things. + Required. + -4 --four {arg} Do four things. + -5 --five {arg} Do five things. Required. Maybe. + -6 --six [arg] Do six things. Not Required. + Required, it is not. + -7 --seven [arg] Required. Or bust. + -8 --eight [arg] Do eight things. + More.Required.Description. + -a [arg] Do A. Required. + Default="do-a" + -b {arg} Do B.Default="do-b" + -c [arg] Required. Default="do-c" + -d {arg} Default="do-d" + + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. + +# complain about -4 + + Option -4 (--four) requires an argument + + -0 --zero Do nothing. + -1 --one Do one thing. Required. + More description. + -2 --two Do two things. + More. Required. Description. + -3 --three [arg] Do three things. + Required. + -4 --four {arg} Do four things. + -5 --five {arg} Do five things. Required. Maybe. + -6 --six [arg] Do six things. Not Required. + Required, it is not. + -7 --seven [arg] Required. Or bust. + -8 --eight [arg] Do eight things. + More.Required.Description. + -a [arg] Do A. Required. + Default="do-a" + -b {arg} Do B.Default="do-b" + -c [arg] Required. Default="do-c" + -d {arg} Default="do-d" + + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. + +# complain about -5 + + Option -5 (--five) requires an argument + + -0 --zero Do nothing. + -1 --one Do one thing. Required. + More description. + -2 --two Do two things. + More. Required. Description. + -3 --three [arg] Do three things. + Required. + -4 --four {arg} Do four things. + -5 --five {arg} Do five things. Required. Maybe. + -6 --six [arg] Do six things. Not Required. + Required, it is not. + -7 --seven [arg] Required. Or bust. + -8 --eight [arg] Do eight things. + More.Required.Description. + -a [arg] Do A. Required. + Default="do-a" + -b {arg} Do B.Default="do-b" + -c [arg] Required. Default="do-c" + -d {arg} Default="do-d" + + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. + +# complain about -8 (because -7 syntax is not supported) + + Option -8 (--eight) requires an argument + + -0 --zero Do nothing. + -1 --one Do one thing. Required. + More description. + -2 --two Do two things. + More. Required. Description. + -3 --three [arg] Do three things. + Required. + -4 --four {arg} Do four things. + -5 --five {arg} Do five things. Required. Maybe. + -6 --six [arg] Do six things. Not Required. + Required, it is not. + -7 --seven [arg] Required. Or bust. + -8 --eight [arg] Do eight things. + More.Required.Description. + -a [arg] Do A. Required. + Default="do-a" + -b {arg} Do B.Default="do-b" + -c [arg] Required. Default="do-c" + -d {arg} Default="do-d" + + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. + +# complain about -d (because -d syntax is not supported) + + Option -d requires an argument + + -0 --zero Do nothing. + -1 --one Do one thing. Required. + More description. + -2 --two Do two things. + More. Required. Description. + -3 --three [arg] Do three things. + Required. + -4 --four {arg} Do four things. + -5 --five {arg} Do five things. Required. Maybe. + -6 --six [arg] Do six things. Not Required. + Required, it is not. + -7 --seven [arg] Required. Or bust. + -8 --eight [arg] Do eight things. + More.Required.Description. + -a [arg] Do A. Required. + Default="do-a" + -b {arg} Do B.Default="do-b" + -c [arg] Required. Default="do-c" + -d {arg} Default="do-d" + + This is Bash3 Boilerplate's help text. Feel free to add any description of your + program or elaborate more on command-line arguments. This section is not + parsed and will be added as-is to the help. + +# complain about nothing +{datetime} UTC [ info] arg_0: 0 +{datetime} UTC [ info] arg_1: 0 +{datetime} UTC [ info] arg_2: 0 +{datetime} UTC [ info] arg_3: arg3 +{datetime} UTC [ info] arg_4: arg4 +{datetime} UTC [ info] arg_5: arg5 +{datetime} UTC [ info] arg_6: +{datetime} UTC [ info] arg_7: +{datetime} UTC [ info] arg_8: arg8 +{datetime} UTC [ info] arg_a: do-a +{datetime} UTC [ info] arg_b: do-b +{datetime} UTC [ info] arg_c: do-c +{datetime} UTC [ info] arg_d: argd diff --git a/test/scenario/main-help/run.sh b/test/scenario/main-help/run.sh index fe7ac5d..f7b6ac7 100755 --- a/test/scenario/main-help/run.sh +++ b/test/scenario/main-help/run.sh @@ -12,4 +12,4 @@ __root="$(cd "$(dirname $(dirname $(dirname "${__dir}")))" && pwd)" echo "ACCPTST:STDIO_REPLACE_DATETIMES" -bash "${__root}/main.sh" -h +bash "${__root}/main.sh" -f /tmp/x -h diff --git a/test/scenario/main-usage-validation/run.sh b/test/scenario/main-usage-validation/run.sh new file mode 100755 index 0000000..e0cc98f --- /dev/null +++ b/test/scenario/main-usage-validation/run.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -o pipefail +set -o errexit +set -o nounset +# set -o xtrace + +# Set magic variables for current FILE & DIR +__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +__file="${__dir}/$(basename "${BASH_SOURCE[0]}")" +__base="$(basename ${__file} .sh)" +__root="$(cd "$(dirname $(dirname $(dirname "${__dir}")))" && pwd)" + +# Set __usage and source main.sh +read -r -d '' __usage <<-'EOF' || true # exits non-zero when EOF encountered + -0 --zero Do nothing. + -1 --one Do one thing. Required. + More description. + -2 --two Do two things. + More. Required. Description. + -3 --three [arg] Do three things. + Required. + -4 --four {arg} Do four things. + -5 --five {arg} Do five things. Required. Maybe. + -6 --six [arg] Do six things. Not Required. + Required, it is not. + -7 --seven [arg] Required. Or bust. + -8 --eight [arg] Do eight things. + More.Required.Description. + -a [arg] Do A. Required. + Default="do-a" + -b {arg} Do B.Default="do-b" + -c [arg] Required. Default="do-c" + -d {arg} Default="do-d" +EOF + +export __usage +export NO_COLOR="true" + +echo "ACCPTST:STDIO_REPLACE_DATETIMES" + +echo "# complain about -3" +(source "${__root}/main.sh") || true + +echo "# complain about -4" +(source "${__root}/main.sh" -3 arg3) || true + +echo "# complain about -5" +(source "${__root}/main.sh" -3 arg3 -4 arg4) || true + +echo "# complain about -8 (because -7 syntax is not supported)" +(source "${__root}/main.sh" -3 arg3 -4 arg4 -5 arg5) || true + +echo "# complain about -d (because -d syntax is not supported)" +(source "${__root}/main.sh" -3 arg3 -4 arg4 -5 arg5 -8 arg8) || true + +echo "# complain about nothing" +( + source "${__root}/main.sh" -3 arg3 -4 arg4 -5 arg5 -8 arg8 -d argd + for argument in ${!arg_*}; do info "${argument}: ${!argument}"; done +)