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/>.
|
||||
#
|
||||
# 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,23 +95,16 @@ 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")
|
||||
case $shortopts in
|
||||
# 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 (( i < ${#1} - 1 )); then
|
||||
OPTRET+=("${1:i+1}")
|
||||
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+=("$2")
|
||||
OPTRET+=("-$opt" "$2")
|
||||
shift
|
||||
break
|
||||
# parse failure
|
||||
|
@ -112,7 +113,29 @@ parseopts() {
|
|||
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
|
||||
;;
|
||||
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=(--)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue