From e355693d8e3a694071379e4458cfb182e0c9d8dd Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Tue, 25 Aug 2015 23:28:35 -0700 Subject: [PATCH] an early start at bash completion (up to and including the subcommand) --- etc/triton.completion | 237 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 etc/triton.completion diff --git a/etc/triton.completion b/etc/triton.completion new file mode 100644 index 0000000..92c847b --- /dev/null +++ b/etc/triton.completion @@ -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 + # . + 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