libmakepkg: add optional argument support to parseopts
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.arch@gmail.com> Reviewed-by: Dave Reisner <dreisner@archlinux.org> Signed-off-by: Allan McRae <allan@archlinux.org>
This commit is contained in:
parent
f6564377a2
commit
7be7552329
2 changed files with 83 additions and 45 deletions
|
@ -18,16 +18,23 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
# A getopt_long-like parser which portably supports longopts and
|
# A getopt_long-like parser which portably supports longopts and
|
||||||
# shortopts with some GNU extensions. It does not allow for options
|
# shortopts with some GNU extensions. For both short and long opts,
|
||||||
# with optional arguments. For both short and long opts, options
|
# options requiring an argument should be suffixed with a colon, and
|
||||||
# requiring an argument should be suffixed with a colon. After the
|
# options with optional arguments should be suffixed with a question
|
||||||
# first argument containing the short opts, any number of valid long
|
# mark. After the first argument containing the short opts, any number
|
||||||
# opts may be be passed. The end of the options delimiter must then be
|
# of valid long opts may be be passed. The end of the options delimiter
|
||||||
# added, followed by the user arguments to the calling program.
|
# 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:
|
# Recommended Usage:
|
||||||
# OPT_SHORT='fb:z'
|
# OPT_SHORT='fb:zq?'
|
||||||
# OPT_LONG=('foo' 'bar:' 'baz')
|
# OPT_LONG=('foo' 'bar:' 'baz' 'qux?')
|
||||||
# if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
|
# if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
|
||||||
# exit 1
|
# exit 1
|
||||||
# fi
|
# fi
|
||||||
|
@ -49,29 +56,30 @@ parseopts() {
|
||||||
longoptmatch() {
|
longoptmatch() {
|
||||||
local o longmatch=()
|
local o longmatch=()
|
||||||
for o in "${longopts[@]}"; do
|
for o in "${longopts[@]}"; do
|
||||||
if [[ ${o%:} = "$1" ]]; then
|
if [[ ${o%[:?]} = "$1" ]]; then
|
||||||
longmatch=("$o")
|
longmatch=("$o")
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
[[ ${o%:} = "$1"* ]] && longmatch+=("$o")
|
[[ ${o%[:?]} = "$1"* ]] && longmatch+=("$o")
|
||||||
done
|
done
|
||||||
|
|
||||||
case ${#longmatch[*]} in
|
case ${#longmatch[*]} in
|
||||||
1)
|
1)
|
||||||
# success, override with opt and return arg req (0 == none, 1 == required)
|
# success, override with opt and return arg req (0 == none, 1 == required, 2 == optional)
|
||||||
opt=${longmatch%:}
|
opt=${longmatch%[:?]}
|
||||||
if [[ $longmatch = *: ]]; then
|
case $longmatch in
|
||||||
return 1
|
*:) return 1 ;;
|
||||||
else
|
*\?) return 2 ;;
|
||||||
return 0
|
*) return 0 ;;
|
||||||
fi ;;
|
esac
|
||||||
|
;;
|
||||||
0)
|
0)
|
||||||
# fail, no match found
|
# fail, no match found
|
||||||
return 255 ;;
|
return 255 ;;
|
||||||
*)
|
*)
|
||||||
# fail, ambiguous match
|
# fail, ambiguous match
|
||||||
printf "${0##*/}: $(gettext "option '%s' is ambiguous; possibilities:")" "--$1"
|
printf "${0##*/}: $(gettext "option '%s' is ambiguous; possibilities:")" "--$1"
|
||||||
printf " '%s'" "${longmatch[@]%:}"
|
printf " '%s'" "${longmatch[@]%[:?]}"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
return 254 ;;
|
return 254 ;;
|
||||||
esac >&2
|
esac >&2
|
||||||
|
@ -87,23 +95,16 @@ parseopts() {
|
||||||
for (( i = 1; i < ${#1}; i++ )); do
|
for (( i = 1; i < ${#1}; i++ )); do
|
||||||
opt=${1:i:1}
|
opt=${1:i:1}
|
||||||
|
|
||||||
# option doesn't exist
|
case $shortopts in
|
||||||
if [[ $shortopts != *$opt* ]]; then
|
|
||||||
printf "${0##*/}: $(gettext "invalid option") -- '%s'\n" "$opt" >&2
|
|
||||||
OPTRET=(--)
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
OPTRET+=("-$opt")
|
|
||||||
# option requires optarg
|
# option requires optarg
|
||||||
if [[ $shortopts = *$opt:* ]]; then
|
*$opt:*)
|
||||||
# if we're not at the end of the option chunk, the rest is the optarg
|
# if we're not at the end of the option chunk, the rest is the optarg
|
||||||
if (( i < ${#1} - 1 )); then
|
if (( i < ${#1} - 1 )); then
|
||||||
OPTRET+=("${1:i+1}")
|
OPTRET+=("-$opt" "${1:i+1}")
|
||||||
break
|
break
|
||||||
# if we're at the end, grab the the next positional, if it exists
|
# if we're at the end, grab the the next positional, if it exists
|
||||||
elif (( i == ${#1} - 1 )) && [[ $2 ]]; then
|
elif (( i == ${#1} - 1 )) && [[ $2 ]]; then
|
||||||
OPTRET+=("$2")
|
OPTRET+=("-$opt" "$2")
|
||||||
shift
|
shift
|
||||||
break
|
break
|
||||||
# parse failure
|
# parse failure
|
||||||
|
@ -112,7 +113,29 @@ parseopts() {
|
||||||
OPTRET=(--)
|
OPTRET=(--)
|
||||||
return 1
|
return 1
|
||||||
fi
|
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
|
fi
|
||||||
|
;;
|
||||||
|
# option has no optarg
|
||||||
|
*$opt*)
|
||||||
|
OPTRET+=("-$opt")
|
||||||
|
;;
|
||||||
|
# option doesn't exist
|
||||||
|
*)
|
||||||
|
printf "${0##*/}: $(gettext "invalid option") -- '%s'\n" "$opt" >&2
|
||||||
|
OPTRET=(--)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
;;
|
;;
|
||||||
--?*=*|--?*) # long option
|
--?*=*|--?*) # long option
|
||||||
|
@ -145,6 +168,15 @@ parseopts() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
2)
|
||||||
|
# --longopt=optarg
|
||||||
|
if [[ $1 = *=* ]]; then
|
||||||
|
OPTRET+=("--$opt=$optarg")
|
||||||
|
# --longopt
|
||||||
|
else
|
||||||
|
OPTRET+=("--$opt")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
254)
|
254)
|
||||||
# ambiguous option -- error was reported for us by longoptmatch()
|
# ambiguous option -- error was reported for us by longoptmatch()
|
||||||
OPTRET=(--)
|
OPTRET=(--)
|
||||||
|
|
|
@ -16,12 +16,12 @@ if ! type -t parseopts &>/dev/null; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# borrow opts from makepkg
|
# borrow opts from makepkg
|
||||||
OPT_SHORT="AcdefFghiLmop:rRsV"
|
OPT_SHORT="AcdefFghiLmop:rRsVb?"
|
||||||
OPT_LONG=('allsource' 'asroot' 'ignorearch' 'check' 'clean:' 'cleanall' 'nodeps'
|
OPT_LONG=('allsource' 'asroot' 'ignorearch' 'check' 'clean:' 'cleanall' 'nodeps'
|
||||||
'noextract' 'force' 'forcever:' 'geninteg' 'help' 'holdver'
|
'noextract' 'force' 'forcever:' 'geninteg' 'help' 'holdver'
|
||||||
'install' 'key:' 'log' 'nocolor' 'nobuild' 'nocheck' 'noprepare' 'nosign' 'pkg:' 'rmdeps'
|
'install' 'key:' 'log' 'nocolor' 'nobuild' 'nocheck' 'noprepare' 'nosign' 'pkg:' 'rmdeps'
|
||||||
'repackage' 'skipinteg' 'sign' 'source' 'syncdeps' 'version' 'config:'
|
'repackage' 'skipinteg' 'sign' 'source' 'syncdeps' 'version' 'config:'
|
||||||
'noconfirm' 'noprogressbar')
|
'noconfirm' 'noprogressbar' 'opt?')
|
||||||
|
|
||||||
tap_parse() {
|
tap_parse() {
|
||||||
local result=$1 tokencount=$2; shift 2
|
local result=$1 tokencount=$2; shift 2
|
||||||
|
@ -31,7 +31,7 @@ tap_parse() {
|
||||||
unset OPTRET
|
unset OPTRET
|
||||||
}
|
}
|
||||||
|
|
||||||
tap_plan 50
|
tap_plan 54
|
||||||
|
|
||||||
# usage: tap_parse <expected result> <token count> test-params...
|
# usage: tap_parse <expected result> <token count> test-params...
|
||||||
# a failed tap_parse will match only the end of options marker '--'
|
# 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)
|
# exact match on possible stem (opt has optarg)
|
||||||
tap_parse '--clean foo --' 3 --clean=foo
|
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
|
tap_finish
|
||||||
|
|
Loading…
Add table
Reference in a new issue