an early start at bash completion (up to and including the subcommand)

This commit is contained in:
Trent Mick 2015-08-25 23:28:35 -07:00
parent 1d6a8178bd
commit e355693d8e

237
etc/triton.completion Normal file
View File

@ -0,0 +1,237 @@
#XXX drop this name
function trace
{
#echo "$*" >&2
echo "$*" >>/var/tmp/triton-completion.log
}
function _dashdash_complete() {
trace ""
trace "-- $(date)"
trace "\$@: '$@'"
trace "COMP_WORDBREAKS: '$COMP_WORDBREAKS'"
trace "COMP_CWORD: '$COMP_CWORD'"
trace "COMP_LINE: '$COMP_LINE'"
trace "COMP_POINT: $COMP_POINT"
# Guard against negative COMP_CWORD. This is a Bash bug at least on
# Mac 10.10.4's bash. See
# <https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00125.html>.
if [[ $COMP_CWORD -lt 0 ]]; then
trace "abort on negative COMP_CWORD"
exit 1;
fi
local interspersed shortOpts longOpts takesArgOpts subCmds
interspersed=$1
shift # $interspersed
shortOpts=$1
shift # $shortOpts
longOpts=$1
shift # $longOpts
takesArgOpts=$1
shift # $takesArgOpts
subCmds=$1
shift # $subCmds or '--'
if [[ -n "$subCmds" ]]; then
shift # '--'
fi
trace "interspersed: '$interspersed'"
trace "shortOpts: '$shortOpts'"
trace "longOpts: '$longOpts'"
trace "takesArgOpts: '$takesArgOpts'"
trace "subCmds: '$subCmds'"
# I don't know how to do array manip on argv vars,
# so copy over to ARGV array to work on them.
declare -a ARGV
i=0
len=$#
while [[ $# -gt 0 ]]; do
ARGV[$i]=$1
shift;
i=$(( $i + 1 ))
done
trace "ARGV: '${ARGV[@]}'"
trace "ARGV[COMP_CWORD-1]: '${ARGV[$(( $COMP_CWORD - 1 ))]}'"
trace "ARGV[COMP_CWORD]: '${ARGV[$COMP_CWORD]}'"
trace "ARGV len: '$len'"
# Get 'state' of option parsing at this COMP_POINT.
# Copying "dashdash.js#parse()" behaviour here.
state=
nargs=0
i=1
while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do
optname=
prefix=
word=
arg=${ARGV[$i]}
trace "consider ARGV[$i]: '$arg'"
if [[ "$arg" == "--" ]]; then
state=longopt
word=--
i=$(( $i + 1 ))
break;
elif [[ "${arg:0:2}" == "--" ]]; then
arg=${arg:2}
if [[ "$arg" == *"="* ]]; then
optname=${arg%%=*}
val=${arg##*=}
trace " long opt: optname='$optname' val='$val'"
state=arg
word=$val
prefix="--$optname="
else
optname=$arg
val=
trace " long opt: optname='$optname'"
state=longopt
word=--$optname
if [[ "$takesArgOpts" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then
i=$(( $i + 1 ))
state=arg
word=${ARGV[$i]}
trace " takes arg (next arg, word='$word')"
fi
fi
elif [[ "${arg:0:1}" == "-" ]]; then
trace " short opt group"
state=shortopt
word=$arg
j=1
while [[ $j -lt ${#arg} ]]; do
optname=${arg:$j:1}
trace " consider index $j: optname '$optname'"
if [[ "$takesArgOpts" == *"-$optname="* ]]; then
if [[ $(( $j + 1 )) -lt ${#arg} ]]; then
state=arg
word=${arg:$(( $j + 1 ))}
trace " takes arg (rest of this arg, word='$word')"
elif [[ $i -lt $COMP_CWORD ]]; then
state=arg
i=$(( $i + 1 ))
word=${ARGV[$i]}
trace " takes arg (word='$word')"
fi
break
fi
j=$(( $j + 1 ))
done
elif [[ "$interspersed" == "true" ]]; then
trace " not an opt"
state=arg #XXX arg type
word=$arg
nargs=$(( $nargs + 1 ))
elif [[ -n "$subCmds" ]]; then
if [[ $i -eq $COMP_CWORD ]]; then
state=subcmds
word=$arg
else
trace "XXX defer on to _triton_${arg}_complete"
#_triton_${arg}_complete XXX a b c XXX
return
fi
else
trace "XXX here else"
XXX
fi
trace " state=$state prefix='$prefix' word='$word'"
i=$(( $i + 1 ))
done
trace "parsed: state=$state prefix='$prefix' word='$word'"
compgen_opts=
if [[ -n "$prefix" ]]; then
compgen_opts="$compgen_opts -P $prefix"
fi
case $state in
shortopt)
compgen $compgen_opts -W "$shortOpts" -- "$word"
;;
longopt)
compgen $compgen_opts -W "$longOpts" -- "$word"
;;
subcmds)
compgen $compgen_opts -W "$subCmds" -- "$word"
;;
arg)
# Figure out arg type, if we can. If this was an *option* arg, then
# 'optname' is set.
if [[ -n "$optname" ]]; then
argtype=$(echo "$takesArgOpts" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
trace "argtype (for opt '$optname'): $argtype"
else
# TODO set argtype from nargs
trace "argtype (for arg $nargs): $argtype"
fi
# If we don't know what completion to do, then emit nothing. We
# expect that we are running with:
# complete -o default ...
# where "default" means: "Use Readline's default filename completion if
# the compspec generates no matches." This gives us the good filename
# completion, completion in subshells/backticks.
#
# The trade-off here is that we cannot explicitly have *no* completion.
if [[ "${word:0:1}" == '$' ]]; then
# "-o default" does *not* give us envvar completion apparently. This
# means that with '-A export' we are missing envvars like "PS1" et al.
compgen $compgen_opts -P '$' -A export -- "${word:1}"
elif [[ $argtype == "none" ]]; then
# TODO: want this hacky way to avoid completions for explicit none?
echo "(no completion)"
elif [[ $argtype == "directory" ]]; then
# XXX damnit... using 'complete -o default' means that no dir hits
# results in filenames matching. Arrrrgggg. Compare to 'rmdir'.
# Does `compopt` work on Linux? If so
# compopt +o filenames -o dirnames
compgen $compgen_opts -S '/' -A directory -- "$word"
elif [[ -z $argtype || $argtype == "file" ]]; then
# 'complete -o default' gives the best filename completion, at least
# on Mac. ... but we're not using 'complete -o default' here.
compgen $compgen_opts -S '/' -A file -- "$word"
else
# Custom completion types.
potentials=$($argtype "" "$word")
compgen $compgen_opts -W "$potentials" -- "$word"
fi
;;
*)
trace "unknown state: $state"
;;
esac
}
function _triton_account_complete() {
trace "XXX _triton_account_complete start"
COMPREPLY=( $(_dashdash_complete \
true \
"-h -j" \
"--help --json" \
"" \
-- "$*") )
}
function _triton_complete() {
COMPREPLY=( $(_dashdash_complete \
false \
"-h -v" \
"--version --help --verbose" \
"" \
"help account info keys create-instance instances instance instance-audit start-instance stop-instance reboot-instance ssh images image packages package" \
-- "${COMP_WORDS[@]}") )
}
complete -F _triton_complete triton