summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am6
-rw-r--r--configure.ac1
-rw-r--r--scripts/Makefile.am1
-rw-r--r--scripts/library/README20
-rw-r--r--scripts/library/parseopts.sh141
-rw-r--r--scripts/po/POTFILES.in1
-rw-r--r--test/scripts/Makefile.am9
-rwxr-xr-xtest/scripts/parseopts_test.sh138
8 files changed, 315 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am
index a024a2e6..e08b809b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = lib/libalpm src/util src/pacman scripts etc test/pacman test/util
+SUBDIRS = lib/libalpm src/util src/pacman scripts etc test/pacman test/util test/scripts
if WANT_DOC
SUBDIRS += doc
endif
@@ -23,7 +23,7 @@ dist_pkgdata_DATA = \
proto/ChangeLog.proto
# run the pactest test suite and vercmp tests
-check-local: test/pacman test/util src/pacman src/util
+check-local: test/pacman test/scripts test/util src/pacman src/util
LC_ALL=C $(PYTHON) $(top_srcdir)/test/pacman/pactest.py --debug=1 \
--test $(top_srcdir)/test/pacman/tests/*.py \
-p $(top_builddir)/src/pacman/pacman
@@ -31,6 +31,8 @@ check-local: test/pacman test/util src/pacman src/util
$(top_builddir)/src/util/pacsort
$(SH) $(top_srcdir)/test/util/vercmptest.sh \
$(top_builddir)/src/util/vercmp
+ $(BASH_SHELL) $(top_srcdir)/test/scripts/parseopts_test.sh \
+ $(top_srcdir)/scripts/library/parseopts.sh
# create the pacman DB and cache directories upon install
install-data-local:
diff --git a/configure.ac b/configure.ac
index 8898f146..d488bfaf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -442,6 +442,7 @@ doc/Makefile
etc/Makefile
test/pacman/Makefile
test/pacman/tests/Makefile
+test/scripts/Makefile
test/util/Makefile
contrib/Makefile
Makefile
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index 328fbff2..7662fba6 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -27,6 +27,7 @@ EXTRA_DIST = \
LIBRARY = \
library/output_format.sh \
+ library/parseopts.sh \
library/parse_options.sh
# Files that should be removed, but which Automake does not know.
diff --git a/scripts/library/README b/scripts/library/README
index 1e9c962b..f43873f3 100644
--- a/scripts/library/README
+++ b/scripts/library/README
@@ -13,3 +13,23 @@ A getopt replacement to avoids portability issues, in particular the
lack of long option name support in the default getopt provided by some
platforms.
Usage: parse_option $SHORT_OPTS $LONG_OPTS "$@"
+
+parseopts.sh:
+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.
+
+Reccommended Usage:
+ OPT_SHORT='fb:z'
+ OPT_LONG=('foo' 'bar:' 'baz')
+ if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
+ exit 1
+ fi
+ set -- "${OPTRET[@]}"
+Returns:
+ 0: parse success
+ 1: parse failure (error message supplied)
diff --git a/scripts/library/parseopts.sh b/scripts/library/parseopts.sh
new file mode 100644
index 00000000..11589ce3
--- /dev/null
+++ b/scripts/library/parseopts.sh
@@ -0,0 +1,141 @@
+# getopt-like parser
+parseopts() {
+ local opt= optarg= i= shortopts=$1
+ local -a longopts=() unused_argv=()
+
+ shift
+ while [[ $1 && $1 != '--' ]]; do
+ longopts+=("$1")
+ shift
+ done
+ shift
+
+ longoptmatch() {
+ local o longmatch=()
+ for o in "${longopts[@]}"; do
+ if [[ ${o%:} = "$1" ]]; then
+ longmatch=("$o")
+ break
+ fi
+ [[ ${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 ;;
+ 0)
+ # fail, no match found
+ return 255 ;;
+ *)
+ # fail, ambiguous match
+ printf "@SCRIPTNAME@: $(gettext "option '%s' is ambiguous; possibilities:")" "--$1"
+ printf " '%s'" "${longmatch[@]%:}"
+ printf '\n'
+ return 254 ;;
+ esac >&2
+ }
+
+ while (( $# )); do
+ case $1 in
+ --) # explicit end of options
+ shift
+ break
+ ;;
+ -[!-]*) # short option
+ for (( i = 1; i < ${#1}; i++ )); do
+ opt=${1:i:1}
+
+ # option doesn't exist
+ if [[ $shortopts != *$opt* ]]; then
+ printf "@SCRIPTNAME@: $(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 "@SCRIPTNAME@: $(gettext "option requires an argument") -- '%s'\n" "$opt" >&2
+ OPTRET=(--)
+ return 1
+ fi
+ fi
+ done
+ ;;
+ --?*=*|--?*) # long option
+ IFS='=' read -r opt optarg <<< "${1#--}"
+ longoptmatch "$opt"
+ case $? in
+ 0)
+ # parse failure
+ if [[ $optarg ]]; then
+ printf "@SCRIPTNAME@: $(gettext "option '%s' does not allow an argument")\n" "--$opt" >&2
+ OPTRET=(--)
+ return 1
+ # --longopt
+ else
+ OPTRET+=("--$opt")
+ shift
+ continue 2
+ fi
+ ;;
+ 1)
+ # --longopt=optarg
+ if [[ $optarg ]]; then
+ OPTRET+=("--$opt" "$optarg")
+ shift
+ # --longopt optarg
+ elif [[ $2 ]]; then
+ OPTRET+=("--$opt" "$2" )
+ shift 2
+ # parse failure
+ else
+ printf "@SCRIPTNAME@: $(gettext "option '%s' requires an argument")\n" "--$opt" >&2
+ OPTRET=(--)
+ return 1
+ fi
+ continue 2
+ ;;
+ 254)
+ # ambiguous option -- error was reported for us by longoptmatch()
+ OPTRET=(--)
+ return 1
+ ;;
+ 255)
+ # parse failure
+ printf "@SCRIPTNAME@: $(gettext "invalid option") '--%s'\n" "$opt" >&2
+ OPTRET=(--)
+ return 1
+ ;;
+ esac
+ ;;
+ *) # non-option arg encountered, add it as a parameter
+ unused_argv+=("$1")
+ ;;
+ esac
+ shift
+ done
+
+ # add end-of-opt terminator and any leftover positional parameters
+ OPTRET+=('--' "${unused_argv[@]}" "$@")
+ unset longoptmatch
+
+ return 0
+}
diff --git a/scripts/po/POTFILES.in b/scripts/po/POTFILES.in
index 007e535f..01cc235f 100644
--- a/scripts/po/POTFILES.in
+++ b/scripts/po/POTFILES.in
@@ -9,3 +9,4 @@ scripts/pkgdelta.sh.in
scripts/repo-add.sh.in
scripts/library/output_format.sh
scripts/library/parse_options.sh
+scripts/library/parseopts.sh
diff --git a/test/scripts/Makefile.am b/test/scripts/Makefile.am
new file mode 100644
index 00000000..b949e880
--- /dev/null
+++ b/test/scripts/Makefile.am
@@ -0,0 +1,9 @@
+check_SCRIPTS = \
+ parseopts_test.sh
+
+noinst_SCRIPTS = $(check_SCRIPTS)
+
+EXTRA_DIST = \
+ $(check_SCRIPTS)
+
+# vim:set ts=2 sw=2 noet:
diff --git a/test/scripts/parseopts_test.sh b/test/scripts/parseopts_test.sh
new file mode 100755
index 00000000..b5c07b5d
--- /dev/null
+++ b/test/scripts/parseopts_test.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+
+declare -i testcount=0 pass=0 fail=0
+
+# source the library function
+if [[ -z $1 || ! -f $1 ]]; then
+ printf "error: path to parseopts library not provided or does not exist\n"
+ exit 1
+fi
+. "$1"
+
+if ! type -t parseopts >/dev/null; then
+ printf 'parseopts function not found\n'
+ exit 1
+fi
+
+# borrow opts from makepkg
+OPT_SHORT="AcdefFghiLmop:rRsV"
+OPT_LONG=('allsource' 'asroot' 'ignorearch' 'check' 'clean:' 'cleanall' 'nodeps'
+ 'noextract' 'force' 'forcever:' 'geninteg' 'help' 'holdver'
+ 'install' 'key:' 'log' 'nocolor' 'nobuild' 'nocheck' 'nosign' 'pkg:' 'rmdeps'
+ 'repackage' 'skipinteg' 'sign' 'source' 'syncdeps' 'version' 'config:'
+ 'noconfirm' 'noprogressbar')
+
+parse() {
+ local result=$1 tokencount=$2; shift 2
+
+ (( ++testcount ))
+ parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@" 2>/dev/null
+ test_result "$result" "$tokencount" "$*" "${OPTRET[@]}"
+ unset OPTRET
+}
+
+test_result() {
+ local result=$1 tokencount=$2 input=$3; shift 3
+
+ if [[ $result = "$*" ]] && (( tokencount == $# )); then
+ (( ++pass ))
+ else
+ printf '[TEST %3s]: FAIL\n' "$testcount"
+ printf ' input: %s\n' "$input"
+ printf ' output: %s (%s tokens)\n' "$*" "$#"
+ printf ' expected: %s (%s tokens)\n' "$result" "$tokencount"
+ echo
+ (( ++fail ))
+ fi
+}
+
+summarize() {
+ if (( !fail )); then
+ printf 'All %s tests successful\n' "$testcount"
+ exit 0
+ else
+ printf '%s of %s tests failed\n' "$fail" "$testcount"
+ exit 1
+ fi
+}
+trap 'summarize' EXIT
+
+printf 'Beginning parseopts tests\n'
+
+# usage: parse <expected result> <token count> test-params...
+# a failed parse will match only the end of options marker '--'
+
+# no options
+parse '--' 1
+
+# short options
+parse '-s -r --' 3 -s -r
+
+# short options, no spaces
+parse '-s -r --' 3 -sr
+
+# short opt missing an opt arg
+parse '--' 1 -s -p
+
+# short opt with an opt arg
+parse '-p PKGBUILD -L --' 4 -p PKGBUILD -L
+
+# short opt with an opt arg, no space
+parse '-p PKGBUILD --' 3 -pPKGBUILD
+
+# valid shortopts as a long opt
+parse '--' 1 --sir
+
+# long opt wiht no optarg
+parse '--log --' 2 --log
+
+# long opt with missing optarg
+parse '--' 1 -sr --pkg
+
+# long opt with optarg
+parse '--pkg foo --' 3 --pkg foo
+
+# long opt with optarg with whitespace
+parse '--pkg foo bar -- baz' 4 --pkg "foo bar" baz
+
+# long opt with optarg with =
+parse '--pkg foo=bar -- baz' 4 --pkg foo=bar baz
+
+# long opt with explicit optarg
+parse '--pkg bar -- foo baz' 5 foo --pkg=bar baz
+
+# long opt with explicit optarg, with whitespace
+parse '--pkg foo bar -- baz' 4 baz --pkg="foo bar"
+
+# long opt with explicit optarg that doesn't take optarg
+parse '--' 1 --force=always -s
+
+# long opt with explicit optarg with =
+parse '--pkg foo=bar --' 3 --pkg=foo=bar
+
+# explicit end of options with options after
+parse '-s -r -- foo bar baz' 6 -s -r -- foo bar baz
+
+# non-option parameters mixed in with options
+parse '-s -r -- foo baz' 5 -s foo baz -r
+
+# optarg with whitespace
+parse '-p foo bar -s --' 4 -p'foo bar' -s
+
+# non-option parameter with whitespace
+parse '-i -- foo bar' 3 -i 'foo bar'
+
+# successful stem match (opt has no arg)
+parse '--nocolor --' 2 --nocol
+
+# successful stem match (opt has arg)
+parse '--config foo --' 3 --conf foo
+
+# ambiguous long opt
+parse '--' 1 '--for'
+
+# exact match on a possible stem (--force & --forcever)
+parse '--force --' 2 --force
+
+# exact match on possible stem (opt has optarg)
+parse '--clean foo --' 3 --clean=foo