#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