On Wed, Oct 23, 2019 at 08:38:08PM -0400, Ethan Sommer wrote: > Adds a "?" suffix that can be used to indicate that an option's argument is > optional. > > This allows options to have a default behaviour when the user doesn't > specify one, e.g.: --color=[when] being able to behave like --color=auto > when only --color is passed > > Options with optional arguments given on the command line will be returned > in the form "--opt=optarg" and "-o=optarg". Despite that not being the > syntax for passing an argument with a shortopt (trying to pass -o=foo > would make -o's argument "=foo"), this is done to allow the caller to split > the option and its optarg easily > > Signed-off-by: Ethan Sommer <e5ten.a...@gmail.com> > --- > scripts/libmakepkg/util/parseopts.sh.in | 116 +++++++++++++++--------- > test/scripts/parseopts_test.sh | 12 ++- > 2 files changed, 83 insertions(+), 45 deletions(-) > > diff --git a/scripts/libmakepkg/util/parseopts.sh.in > b/scripts/libmakepkg/util/parseopts.sh.in > index c056cb1e..42540438 100644 > --- a/scripts/libmakepkg/util/parseopts.sh.in > +++ b/scripts/libmakepkg/util/parseopts.sh.in > @@ -18,16 +18,23 @@ > # along with this program. If not, see <http://www.gnu.org/licenses/>. > # > # A getopt_long-like parser which portably supports longopts and > -# shortopts with some GNU extensions. It does not allow for options > -# with optional arguments. For both short and long opts, options > -# requiring an argument should be suffixed with a colon. After the > -# first argument containing the short opts, any number of valid long > -# opts may be be passed. The end of the options delimiter must then be > -# added, followed by the user arguments to the calling program. > +# shortopts with some GNU extensions. For both short and long opts, > +# options requiring an argument should be suffixed with a colon, and > +# options with optional arguments should be suffixed with a question > +# mark. After the first argument containing the short opts, any number > +# of valid long opts may be be passed. The end of the options delimiter > +# must then be added, followed by the user arguments to the calling > +# program. > +# > +# Options with optional arguments will be returned as "--longopt=optarg" > +# for longopts, or "-o=optarg" for shortopts. This isn't actually a valid > +# way to pass an optional argument with a shortopt on the command line, > +# but is done by parseopts to enable the caller script to split the option > +# and its optarg easily. > # > # Recommended Usage: > -# OPT_SHORT='fb:z' > -# OPT_LONG=('foo' 'bar:' 'baz') > +# OPT_SHORT='fb:zq?' > +# OPT_LONG=('foo' 'bar:' 'baz' 'qux?') > # if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then > # exit 1 > # fi > @@ -49,29 +56,30 @@ parseopts() { > longoptmatch() { > local o longmatch=() > for o in "${longopts[@]}"; do > - if [[ ${o%:} = "$1" ]]; then > + if [[ ${o%[:?]} = "$1" ]]; then > longmatch=("$o") > break > fi > - [[ ${o%:} = "$1"* ]] && longmatch+=("$o") > + [[ ${o%[:?]} = "$1"* ]] && longmatch+=("$o") > done > > case ${#longmatch[*]} in > 1) > - # success, override with opt and return arg req > (0 == none, 1 == required) > - opt=${longmatch%:} > - if [[ $longmatch = *: ]]; then > - return 1 > - else > - return 0 > - fi ;; > + # success, override with opt and return arg req > (0 == none, 1 == required, 2 == optional) > + opt=${longmatch%[:?]} > + case $longmatch in > + *:) return 1 ;; > + *\?) return 2 ;; > + *) return 0 ;; > + esac > + ;; > 0) > # fail, no match found > return 255 ;; > *) > # fail, ambiguous match > printf "${0##*/}: $(gettext "option '%s' is > ambiguous; possibilities:")" "--$1" > - printf " '%s'" "${longmatch[@]%:}" > + printf " '%s'" "${longmatch[@]%[:?]}" > printf '\n' > return 254 ;; > esac >&2 > @@ -87,32 +95,47 @@ parseopts() { > for (( i = 1; i < ${#1}; i++ )); do > opt=${1:i:1} > > - # option doesn't exist > - if [[ $shortopts != *$opt* ]]; then > - printf "${0##*/}: $(gettext > "invalid option") -- '%s'\n" "$opt" >&2 > - OPTRET=(--) > - return 1 > - fi > - > - OPTRET+=("-$opt") > - # option requires optarg > - if [[ $shortopts = *$opt:* ]]; then > - # if we're not at the end of > the option chunk, the rest is the optarg > - if (( i < ${#1} - 1 )); then > - OPTRET+=("${1:i+1}") > - break > - # if we're at the end, grab the > the next positional, if it exists > - elif (( i == ${#1} - 1 )) && [[ > $2 ]]; then > - OPTRET+=("$2") > - shift > - break > - # parse failure > - else > - printf "${0##*/}: > $(gettext "option requires an argument") -- '%s'\n" "$opt" >&2 > + case $shortopts in > + # option requires optarg > + *$opt:*) > + # if we're not at the > end of the option chunk, the rest is the optarg > + if (( i < ${#1} - 1 )); > then > + > OPTRET+=("-$opt" "${1:i+1}") > + break > + # if we're at the end, > grab the the next positional, if it exists > + elif (( i == ${#1} - 1 > )) && [[ $2 ]]; then > + > OPTRET+=("-$opt" "$2") > + shift > + break > + # parse failure > + else > + printf > "${0##*/}: $(gettext "option requires an argument") -- '%s'\n" "$opt" >&2 > + OPTRET=(--) > + return 1 > + fi > + ;; > + # option's optarg is optional > + *$opt\?*) > + # if we're not at the > end of the option chunk, the rest is the optarg > + if (( i < ${#1} - 1 )); > then > + > OPTRET+=("-$opt=${1:i+1}") > + break > + # option has no optarg > + else > + > OPTRET+=("-$opt") > + fi > + ;; > + # option has no optarg > + *$opt*) > + OPTRET+=("-$opt") > + ;; > + # option doesn't exist > + *) > + printf "${0##*/}: > $(gettext "invalid option") -- '%s'\n" "$opt" >&2 > OPTRET=(--) > return 1 > - fi > - fi > + ;; > + esac > done > ;; > --?*=*|--?*) # long option > @@ -145,6 +168,15 @@ parseopts() { > return 1 > fi > ;; > + 2) > + # --longopt=optarg > + if [[ $1 = *=* ]]; then > + > OPTRET+=("--$opt=$optarg") > + # --longopt > + else > + OPTRET+=("--$opt") > + fi > + ;; > 254) > # ambiguous option -- error was > reported for us by longoptmatch() > OPTRET=(--) > diff --git a/test/scripts/parseopts_test.sh b/test/scripts/parseopts_test.sh > index 9674c6a6..7d14bf29 100755 > --- a/test/scripts/parseopts_test.sh > +++ b/test/scripts/parseopts_test.sh > @@ -16,12 +16,12 @@ if ! type -t parseopts &>/dev/null; then > fi > > # borrow opts from makepkg > -OPT_SHORT="AcdefFghiLmop:rRsV" > +OPT_SHORT="AcdefFghiLmop:rRsVb;" > OPT_LONG=('allsource' 'asroot' 'ignorearch' 'check' 'clean:' 'cleanall' > 'nodeps' > 'noextract' 'force' 'forcever:' 'geninteg' 'help' 'holdver' > 'install' 'key:' 'log' 'nocolor' 'nobuild' 'nocheck' 'noprepare' > 'nosign' 'pkg:' 'rmdeps' > 'repackage' 'skipinteg' 'sign' 'source' 'syncdeps' 'version' > 'config:' > - 'noconfirm' 'noprogressbar') > + 'noconfirm' 'noprogressbar' 'opt;') > > tap_parse() { > local result=$1 tokencount=$2; shift 2 > @@ -31,7 +31,7 @@ tap_parse() { > unset OPTRET > } > > -tap_plan 50 > +tap_plan 54 > > # usage: tap_parse <expected result> <token count> test-params... > # a failed tap_parse will match only the end of options marker '--' > @@ -111,4 +111,10 @@ tap_parse '--force --' 2 --force > # exact match on possible stem (opt has optarg) > tap_parse '--clean foo --' 3 --clean=foo > > +# long opt with empty, non-empty, and no optional arg > +tap_parse '--opt= --opt=foo --opt --' 4 --opt= --opt=foo --opt > + > +# short opt with and without optional arg, and non-option arg > +tap_parse '-b=foo -A -b -- foo' 5 -bfoo -Ab foo
I think I mentioned it before and I stil think it's weird -- short opts never use = for associating a value. can we just depend on '-afoo' meaning '-a' with the optional value 'foo' and drop the equals? > + Do these tests really pass? We agreed on '?' rather than ';' as the symbol for optional. > tap_finish > -- > 2.23.0