[pacman-dev] [PATCH v2] libmakepkg: add optional argument support to parseopts
Ethan Sommer
e5ten.arch at gmail.com
Wed Oct 23 22:57:24 UTC 2019
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
Signed-off-by: Ethan Sommer <e5ten.arch at gmail.com>
---
scripts/libmakepkg/util/parseopts.sh.in | 110 +++++++++++++++---------
test/scripts/parseopts_test.sh | 12 ++-
2 files changed, 77 insertions(+), 45 deletions(-)
diff --git a/scripts/libmakepkg/util/parseopts.sh.in b/scripts/libmakepkg/util/parseopts.sh.in
index c056cb1e..9a215648 100644
--- a/scripts/libmakepkg/util/parseopts.sh.in
+++ b/scripts/libmakepkg/util/parseopts.sh.in
@@ -18,16 +18,17 @@
# 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.
#
# 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 +50,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 +89,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 +162,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
+
tap_finish
--
2.23.0
More information about the pacman-dev
mailing list