| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 | #!/bin/bash## Bash completion generated for '{{name}}' at {{date}}.## The original template lives here:# https://github.com/trentm/node-dashdash/blob/master/etc/dashdash.bash_completion.in### Copyright 2016 Trent Mick# Copyright 2016 Joyent, Inc.### A generic Bash completion driver script.## This is meant to provide a re-usable chunk of Bash to use for# "etc/bash_completion.d/" files for individual tools. Only the "Configuration"# section with tool-specific info need differ. Features:## - support for short and long opts# - support for knowing which options take arguments# - support for subcommands (e.g. 'git log <TAB>' to show just options for the#   log subcommand)# - does the right thing with "--" to stop options# - custom optarg and arg types for custom completions# - (TODO) support for shells other than Bash (tcsh, zsh, fish?, etc.)### Examples/design:## 1. Bash "default" completion. By default Bash's 'complete -o default' is#    enabled. That means when there are no completions (e.g. if no opts match#    the current word), then you'll get Bash's default completion. Most notably#    that means you get filename completion. E.g.:#       $ tool ./<TAB>#       $ tool READ<TAB>## 2. all opts and subcmds:#       $ tool <TAB>#       $ tool -v <TAB>     # assuming '-v' doesn't take an arg#       $ tool -<TAB>       # matching opts#       $ git lo<TAB>       # matching subcmds##    Long opt completions are given *without* the '=', i.e. we prefer space#    separated because that's easier for good completions.## 3. long opt arg with '='#       $ tool --file=<TAB>#       $ tool --file=./d<TAB>#    We maintain the "--file=" prefix. Limitation: With the attached prefix#    the 'complete -o filenames' doesn't know to do dirname '/' suffixing. Meh.## 4. envvars:#       $ tool $<TAB>#       $ tool $P<TAB>#    Limitation: Currently only getting exported vars, so we miss "PS1" and#    others.## 5. Defer to other completion in a subshell:#       $ tool --file $(cat ./<TAB>#    We get this from 'complete -o default ...'.## 6. Custom completion types from a provided bash function.#       $ tool --profile <TAB>        # complete available "profiles"### Dev Notes:# - compgen notes, from http://unix.stackexchange.com/questions/151118/understand-compgen-builtin-command# - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html## Debugging this completion:#   1. Uncomment the "_{{name}}_log_file=..." line.#   2. 'tail -f /var/tmp/dashdash-completion.log' in one terminal.#   3. Re-source this bash completion file.#_{{name}}_log=/var/tmp/dashdash-completion.logfunction _{{name}}_completer {    # ---- cmd definition    {{spec}}    # ---- locals    declare -a argv    # ---- support functions    function trace {        [[ -n "$_{{name}}_log" ]] && echo "$*" >&2    }    function _dashdash_complete {        local idx context        idx=$1        context=$2        local shortopts longopts optargs subcmds allsubcmds argtypes        shortopts="$(eval "echo \${cmd${context}_shortopts}")"        longopts="$(eval "echo \${cmd${context}_longopts}")"        optargs="$(eval "echo \${cmd${context}_optargs}")"        subcmds="$(eval "echo \${cmd${context}_subcmds}")"        allsubcmds="$(eval "echo \${cmd${context}_allsubcmds}")"        IFS=', ' read -r -a argtypes <<< "$(eval "echo \${cmd${context}_argtypes}")"        trace ""        trace "_dashdash_complete(idx=$idx, context=$context)"        trace "  shortopts: $shortopts"        trace "  longopts: $longopts"        trace "  optargs: $optargs"        trace "  subcmds: $subcmds"        trace "  allsubcmds: $allsubcmds"        # Get 'state' of option parsing at this COMP_POINT.        # Copying "dashdash.js#parse()" behaviour here.        local state=        local nargs=0        local i=$idx        local argtype        local optname        local prefix        local word        local dashdashseen=        while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do            argtype=            optname=            prefix=            word=            arg=${argv[$i]}            trace "  consider argv[$i]: '$arg'"            if [[ "$arg" == "--" && $i -lt $COMP_CWORD ]]; then                trace "    dashdash seen"                dashdashseen=yes                state=arg                word=$arg            elif [[ -z "$dashdashseen" && "${arg:0:2}" == "--" ]]; then                arg=${arg:2}                if [[ "$arg" == *"="* ]]; then                    optname=${arg%%=*}                    val=${arg##*=}                    trace "    long opt: optname='$optname' val='$val'"                    state=arg                    argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)                    word=$val                    prefix="--$optname="                else                    optname=$arg                    val=                    trace "    long opt: optname='$optname'"                    state=longopt                    word=--$optname                    if [[ "$optargs" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then                        i=$(( $i + 1 ))                        state=arg                        argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)                        word=${argv[$i]}                        trace "    takes arg (consume argv[$i], word='$word')"                    fi                fi            elif [[ -z "$dashdashseen" && "${arg:0:1}" == "-" ]]; then                trace "    short opt group"                state=shortopt                word=$arg                local j=1                while [[ $j -lt ${#arg} ]]; do                    optname=${arg:$j:1}                    trace "    consider index $j: optname '$optname'"                    if [[ "$optargs" == *"-$optname="* ]]; then                        argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)                        if [[ $(( $j + 1 )) -lt ${#arg} ]]; then                            state=arg                            word=${arg:$(( $j + 1 ))}                            trace "      takes arg (rest of this arg, word='$word', argtype='$argtype')"                        elif [[ $i -lt $COMP_CWORD ]]; then                            state=arg                            i=$(( $i + 1 ))                            word=${argv[$i]}                            trace "    takes arg (word='$word', argtype='$argtype')"                        fi                        break                    fi                    j=$(( $j + 1 ))                done            elif [[ $i -lt $COMP_CWORD && -n "$arg" ]] && $(echo "$allsubcmds" | grep -w "$arg" >/dev/null); then                trace "    complete subcmd: recurse _dashdash_complete"                _dashdash_complete $(( $i + 1 )) "${context}__${arg/-/_}"                return            else                trace "    not an opt or a complete subcmd"                state=arg                word=$arg                nargs=$(( $nargs + 1 ))                if [[ ${#argtypes[@]} -gt 0 ]]; then                    argtype="${argtypes[$(( $nargs - 1 ))]}"                    if [[ -z "$argtype" ]]; then                        # If we have more args than argtypes, we use the                        # last type.                        argtype="${argtypes[@]: -1:1}"                    fi                fi            fi            trace "    state=$state prefix='$prefix' word='$word'"            i=$(( $i + 1 ))        done        trace "  parsed: state=$state optname='$optname' argtype='$argtype' prefix='$prefix' word='$word' dashdashseen=$dashdashseen"        local compgen_opts=        if [[ -n "$prefix" ]]; then            compgen_opts="$compgen_opts -P $prefix"        fi        case $state in        shortopt)            compgen $compgen_opts -W "$shortopts $longopts" -- "$word"            ;;        longopt)            compgen $compgen_opts -W "$longopts" -- "$word"            ;;        arg)            # 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 completion if            # the compspec generates no matches." This gives us the good filename            # completion, completion in subshells/backticks.            #            # We cannot support an argtype="directory" because            #       compgen -S '/' -A directory -- "$word"            # doesn't give a satisfying result. It doesn't stop at the trailing '/'            # so you cannot descend into dirs.            if [[ "${word:0:1}" == '$' ]]; then                # By default, Bash will complete '$<TAB>' to all envvars. Apparently                # 'complete -o default' does *not* give us that. The following                # gets *close* to the same completions: '-A export' misses envvars                # like "PS1".                trace "  completing envvars"                compgen $compgen_opts -P '$' -A export -- "${word:1}"            elif [[ -z "$argtype" ]]; then                # Only include opts in completions if $word is not empty.                # This is to avoid completing the leading '-', which foils                # using 'default' completion.                if [[ -n "$dashdashseen" ]]; then                    trace "  completing subcmds, if any (no argtype, dashdash seen)"                    compgen $compgen_opts -W "$subcmds" -- "$word"                elif [[ -z "$word" ]]; then                    trace "  completing subcmds, if any (no argtype, empty word)"                    compgen $compgen_opts -W "$subcmds" -- "$word"                else                    trace "  completing opts & subcmds (no argtype)"                    compgen $compgen_opts -W "$shortopts $longopts $subcmds" -- "$word"                fi            elif [[ $argtype == "none" ]]; then                # We want *no* completions, i.e. some way to get the active                # 'complete -o default' to not do filename completion.                trace "  completing 'none' (hack to imply no completions)"                echo "##-no-completion- -results-##"            elif [[ $argtype == "file" ]]; then                # 'complete -o default' gives the best filename completion, at least                # on Mac.                trace "  completing 'file' (let 'complete -o default' handle it)"                echo ""            elif ! type complete_$argtype 2>/dev/null >/dev/null; then                trace "  completing '$argtype' (fallback to default b/c complete_$argtype is unknown)"                echo ""            else                trace "  completing custom '$argtype'"                completions=$(complete_$argtype "$word")                if [[ -z "$completions" ]]; then                    trace "  no custom '$argtype' completions"                    # These are in ascii and "dictionary" order so they sort                    # correctly.                    echo "##-no-completion- -results-##"                else                    echo $completions                fi            fi            ;;        *)            trace "  unknown state: $state"            ;;        esac    }    trace ""    trace "-- $(date)"    #trace "\$IFS: '$IFS'"    #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    # I don't know how to do array manip on argv vars,    # so copy over to argv array to work on them.    shift   # the leading '--'    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'"    _dashdash_complete 1 ""}# ---- mainline# Note: This if-block to help work with 'compdef' and 'compctl' is# adapted from 'npm completion'.if type complete &>/dev/null; then    function _{{name}}_completion {        local _log_file=/dev/null        [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"        COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \            COMP_LINE="$COMP_LINE" \            COMP_POINT="$COMP_POINT" \            _{{name}}_completer -- "${COMP_WORDS[@]}" \            2>$_log_file)) || return $?    }    complete -o default -F _{{name}}_completion {{name}}elif type compdef &>/dev/null; then    function _{{name}}_completion {        local _log_file=/dev/null        [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"        compadd -- $(COMP_CWORD=$((CURRENT-1)) \            COMP_LINE=$BUFFER \            COMP_POINT=0 \            _{{name}}_completer -- "${words[@]}" \            2>$_log_file)    }    compdef _{{name}}_completion {{name}}elif type compctl &>/dev/null; then    function _{{name}}_completion {        local cword line point words si        read -Ac words        read -cn cword        let cword-=1        read -l line        read -ln point        local _log_file=/dev/null        [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"        reply=($(COMP_CWORD="$cword" \            COMP_LINE="$line" \            COMP_POINT="$point" \            _{{name}}_completer -- "${words[@]}" \            2>$_log_file)) || return $?    }    compctl -K _{{name}}_completion {{name}}fi#### This is a Bash completion file for the '{{name}}' command. You can install## with either:####     cp FILE /usr/local/etc/bash_completion.d/{{name}}   # Mac##     cp FILE /etc/bash_completion.d/{{name}}             # Linux#### or:####     cp FILE > ~/.{{name}}.completion##     echo "source ~/.{{name}}.completion" >> ~/.bashrc##
 |