1
0
Fork 0
mirror of https://github.com/notwa/rc synced 2024-11-12 15:49:03 -08:00
rc/sh/feud

209 lines
5.9 KiB
Bash
Executable file

#!/usr/bin/env sh
# YES_ZSH YES_BASH YES_DASH YES_ASH
### @feud - parse command-line arguments, mapping short-flags to variable names.
### **NOTE:** the API is still experimental and will undergo major changes.
__feud_invalid() {
#v(){ case "$1" in (*[!_A-Za-z0-9]*) :;;([_A-Za-z]*) [ ];esac;}
case "$1" in (*[!_A-Za-z0-9]*) true;; ([_A-Za-z]*) false;; (*) true;; esac
}
feud_new_program() {
if [ $# != 2 ]; then
printf >&2 %s\\n "usage: feud_new_program {program name} {callback name}"
return 64
elif __feud_invalid "$2"; then
printf >&2 %s\\n "feud_new_program: callback name must be a valid identifier"
return 64
fi
__feud_program="$1" __feud_callback="$2"
__feud_switches= __feud_options= __feud_matches=
}
feud_add_option() {
if [ $# != 2 ] || [ "${#1}" != 1 ] || [ "${#2}" = 0 ]; then
# TODO: improve error message, especially when running from __feud_dispatch!
[ "${#1}" = 1 ] || printf >&2 %s\\n "feud_add_option: invalid argument: \"$1\""
[ "${#2}" != 0 ] || printf >&2 %s\\n "feud_add_option: invalid argument: \"$2\""
printf >&2 %s\\n "usage: feud_add_option {single character} {variable name}"
return 64
elif __feud_invalid "$2"; then
printf >&2 %s\\n "feud_add_option: invalid argument: \"$2\""
printf >&2 %s\\n "feud_add_option: variable name must be a valid identifier"
return 64
fi
__feud_options="$__feud_options$1"
__feud_matches="$__feud_matches ($1) $2=\"\$2\";;"
}
feud_add_switch() {
if { [ $# != 2 ] && [ $# != 3 ] ;} || [ "${#1}" != 1 ] || [ "${#2}" = 0 ]; then
# TODO: improve error message, especially when running from __feud_dispatch!
[ "${#1}" = 1 ] || printf >&2 %s\\n "feud_add_switch: invalid argument: \"$1\""
[ "${#2}" != 0 ] || printf >&2 %s\\n "feud_add_switch: invalid argument: \"$2\""
printf >&2 %s\\n "usage: feud_add_switch {single character} {variable name} [value]"
return 64
elif __feud_invalid "$2"; then
printf >&2 %s\\n "feud_add_switch: invalid argument: \"$2\""
printf >&2 %s\\n "feud_add_switch: variable name must be a valid identifier"
return 64
elif [ $# = 3 ] && [ -z "${3##*[!0-9]*}" ]; then
# TODO: lift this restriction?
printf >&2 %s\\n "feud_add_switch: value must be a non-negative integer"
return 64
fi
__feud_switches="$__feud_switches$1"
if [ $# = 2 ]; then
eval "$2=0"
__feud_matches="$__feud_matches ($1) : \$(($2+=1));;"
else
__feud_matches="$__feud_matches ($1) $2=$3;;"
fi
}
__feud_dispatch() {
# NOTE: temporarily using 2>/dev/null instead of 2>&- until OSH bug gets fixed.
PATH=: setopt local_options sh_word_split 2>/dev/null
case $- in (*f*) set -- $1${1:+,};; (*) set -f; set -- $1${1:+,}; set +f;; esac
for arg; do
set -- "${arg##*-}" "${arg%-*}"
case "$arg" in
(?*=?*-?*)
"$__feud_c" "$1" "${2%%=*}" "${2#*=}";;
(?*-?*)
"$__feud_c" "$1" "$2";;
# TODO: handle (*=*) to give a contextual error message.
(*) # TODO: catch this case as well: "c=-d"
printf >&2 %s\\n "feud_configure: expected two hyphen-separated parameters: \"$arg\""
__feud_error=64;;
esac || {
__feud_error=$?
printf >&2 %s\\n "feud_configure: the above error is due to this substring: \"$arg\""
}
done
}
__feud_dispatch_both() {
# FIXME? on shells that don't support locals (ksh), IFS never gets reset.
__feud_c=feud_add_option IFS=, __feud_dispatch "$1"
__feud_c=feud_add_switch IFS=, __feud_dispatch "$2"
return $__feud_error
}
feud_configure() {
if [ $# != 1 ]; then
printf >&2 %s\\n 'usage: feud_configure {string of options:switches}'
printf >&2 %s\\n 'example: `feud_configure input-i,output-o:always-a,yes-y`'
return 64
fi
case "$1" in
(*:*:*)
printf >&2 %s\\n 'feud_configure: too many colons, expected one'
printf >&2 %s\\n 'feud_configure: (hint) "switch,switch:option,option"'
return 64;;
(*:*) :;; # no-op
(*)
printf >&2 %s\\n 'feud_configure: expected one colon'
return 64;;
esac
__feud_error=0 \
__feud_old_options="$__feud_options" \
__feud_old_switches="$__feud_switches" \
__feud_old_matches="$__feud_matches" \
__feud_dispatch_both "${1%:*}" "${1#*:}"
}
feud_match_flag() {
false
}
__feud_help() {
"${__feud_callback}_help"
}
feud() {
# locals: __feud_n, __feud_f
if [ -z "$__feud_callback" ]; then
printf >&2 %s\\n "feud: missing initialization -- call feud_new_program first!"
return 64
fi
eval "feud_match_flag() { case \"\$1\" in $__feud_matches (*) false; esac ;}"
__feud_n=$#
while [ $((__feud_n-=1)) -ge 0 ]; do
__feud_f="$1"
case "$__feud_f" in
# TODO: don't check for -h here if the user configured their own.
(/\?|-h|-help|--help)
__feud_help
return 0;;
(--)
shift
while [ $((__feud_n-=1)) -ge 0 ]; do
set -- "$@" "$1"
shift
done
break;;
(-*)
shift
case "${__feud_f#-}" in
(["$__feud_switches"])
feud_match_flag "${__feud_f#-}" || printf >&2 '%s: %s\n' feud "internal error"
:;;
(["$__feud_options"])
if [ $__feud_n = 0 ]; then
printf >&2 '%s: %s\n' "$__feud_program" "missing argument for $__feud_f"
__feud_help >&2
return 64
fi
: $((__feud_n-=1))
feud_match_flag "${__feud_f#-}" "$1" || printf '%s: %s\n' feud "internal error"
shift;;
(-*|?)
printf >&2 '%s: %s\n' "$__feud_program" "unknown flag: $__feud_f"
__feud_help >&2
return 64;;
(*)
if case "$__feud_f" in
(-["$__feud_options"]*) true;;
(*) false;;
esac; then
set -- "${__feud_f%"${__feud_f#-?}"}" "${__feud_f#-?}" "$@"
else
set -- "${__feud_f%"${__feud_f#-?}"}" "-${__feud_f#-?}" "$@"
fi
: $((__feud_n+=2))
continue;;
esac;;
(*)
shift
set -- "$@" "$__feud_f";;
esac
done
"$__feud_callback" "$@"
}
feud_cleanup() { # optional
unset \
__feud_program __feud_callback __feud_switches __feud_options __feud_matches \
__feud_c __feud_f __feud_n __feud_error \
__feud_old_options __feud_old_switches __feud_old_matches
feud_match_flag() { false; }
}
[ -n "${preload+-}" ] || feud "$@"
# cursed modeline:
# vim:ft=bash ts=3 sw=3 noet sts=3