twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

dashdash.bash_completion.in (14407B)


      1 #!/bin/bash
      2 #
      3 # Bash completion generated for '{{name}}' at {{date}}.
      4 #
      5 # The original template lives here:
      6 # https://github.com/trentm/node-dashdash/blob/master/etc/dashdash.bash_completion.in
      7 #
      8 
      9 #
     10 # Copyright 2016 Trent Mick
     11 # Copyright 2016 Joyent, Inc.
     12 #
     13 #
     14 # A generic Bash completion driver script.
     15 #
     16 # This is meant to provide a re-usable chunk of Bash to use for
     17 # "etc/bash_completion.d/" files for individual tools. Only the "Configuration"
     18 # section with tool-specific info need differ. Features:
     19 #
     20 # - support for short and long opts
     21 # - support for knowing which options take arguments
     22 # - support for subcommands (e.g. 'git log <TAB>' to show just options for the
     23 #   log subcommand)
     24 # - does the right thing with "--" to stop options
     25 # - custom optarg and arg types for custom completions
     26 # - (TODO) support for shells other than Bash (tcsh, zsh, fish?, etc.)
     27 #
     28 #
     29 # Examples/design:
     30 #
     31 # 1. Bash "default" completion. By default Bash's 'complete -o default' is
     32 #    enabled. That means when there are no completions (e.g. if no opts match
     33 #    the current word), then you'll get Bash's default completion. Most notably
     34 #    that means you get filename completion. E.g.:
     35 #       $ tool ./<TAB>
     36 #       $ tool READ<TAB>
     37 #
     38 # 2. all opts and subcmds:
     39 #       $ tool <TAB>
     40 #       $ tool -v <TAB>     # assuming '-v' doesn't take an arg
     41 #       $ tool -<TAB>       # matching opts
     42 #       $ git lo<TAB>       # matching subcmds
     43 #
     44 #    Long opt completions are given *without* the '=', i.e. we prefer space
     45 #    separated because that's easier for good completions.
     46 #
     47 # 3. long opt arg with '='
     48 #       $ tool --file=<TAB>
     49 #       $ tool --file=./d<TAB>
     50 #    We maintain the "--file=" prefix. Limitation: With the attached prefix
     51 #    the 'complete -o filenames' doesn't know to do dirname '/' suffixing. Meh.
     52 #
     53 # 4. envvars:
     54 #       $ tool $<TAB>
     55 #       $ tool $P<TAB>
     56 #    Limitation: Currently only getting exported vars, so we miss "PS1" and
     57 #    others.
     58 #
     59 # 5. Defer to other completion in a subshell:
     60 #       $ tool --file $(cat ./<TAB>
     61 #    We get this from 'complete -o default ...'.
     62 #
     63 # 6. Custom completion types from a provided bash function.
     64 #       $ tool --profile <TAB>        # complete available "profiles"
     65 #
     66 #
     67 # Dev Notes:
     68 # - compgen notes, from http://unix.stackexchange.com/questions/151118/understand-compgen-builtin-command
     69 # - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html
     70 #
     71 
     72 
     73 # Debugging this completion:
     74 #   1. Uncomment the "_{{name}}_log_file=..." line.
     75 #   2. 'tail -f /var/tmp/dashdash-completion.log' in one terminal.
     76 #   3. Re-source this bash completion file.
     77 #_{{name}}_log=/var/tmp/dashdash-completion.log
     78 
     79 function _{{name}}_completer {
     80 
     81     # ---- cmd definition
     82 
     83     {{spec}}
     84 
     85 
     86     # ---- locals
     87 
     88     declare -a argv
     89 
     90 
     91     # ---- support functions
     92 
     93     function trace {
     94         [[ -n "$_{{name}}_log" ]] && echo "$*" >&2
     95     }
     96 
     97     function _dashdash_complete {
     98         local idx context
     99         idx=$1
    100         context=$2
    101 
    102         local shortopts longopts optargs subcmds allsubcmds argtypes
    103         shortopts="$(eval "echo \${cmd${context}_shortopts}")"
    104         longopts="$(eval "echo \${cmd${context}_longopts}")"
    105         optargs="$(eval "echo \${cmd${context}_optargs}")"
    106         subcmds="$(eval "echo \${cmd${context}_subcmds}")"
    107         allsubcmds="$(eval "echo \${cmd${context}_allsubcmds}")"
    108         IFS=', ' read -r -a argtypes <<< "$(eval "echo \${cmd${context}_argtypes}")"
    109 
    110         trace ""
    111         trace "_dashdash_complete(idx=$idx, context=$context)"
    112         trace "  shortopts: $shortopts"
    113         trace "  longopts: $longopts"
    114         trace "  optargs: $optargs"
    115         trace "  subcmds: $subcmds"
    116         trace "  allsubcmds: $allsubcmds"
    117 
    118         # Get 'state' of option parsing at this COMP_POINT.
    119         # Copying "dashdash.js#parse()" behaviour here.
    120         local state=
    121         local nargs=0
    122         local i=$idx
    123         local argtype
    124         local optname
    125         local prefix
    126         local word
    127         local dashdashseen=
    128         while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do
    129             argtype=
    130             optname=
    131             prefix=
    132             word=
    133 
    134             arg=${argv[$i]}
    135             trace "  consider argv[$i]: '$arg'"
    136 
    137             if [[ "$arg" == "--" && $i -lt $COMP_CWORD ]]; then
    138                 trace "    dashdash seen"
    139                 dashdashseen=yes
    140                 state=arg
    141                 word=$arg
    142             elif [[ -z "$dashdashseen" && "${arg:0:2}" == "--" ]]; then
    143                 arg=${arg:2}
    144                 if [[ "$arg" == *"="* ]]; then
    145                     optname=${arg%%=*}
    146                     val=${arg##*=}
    147                     trace "    long opt: optname='$optname' val='$val'"
    148                     state=arg
    149                     argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
    150                     word=$val
    151                     prefix="--$optname="
    152                 else
    153                     optname=$arg
    154                     val=
    155                     trace "    long opt: optname='$optname'"
    156                     state=longopt
    157                     word=--$optname
    158 
    159                     if [[ "$optargs" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then
    160                         i=$(( $i + 1 ))
    161                         state=arg
    162                         argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
    163                         word=${argv[$i]}
    164                         trace "    takes arg (consume argv[$i], word='$word')"
    165                     fi
    166                 fi
    167             elif [[ -z "$dashdashseen" && "${arg:0:1}" == "-" ]]; then
    168                 trace "    short opt group"
    169                 state=shortopt
    170                 word=$arg
    171 
    172                 local j=1
    173                 while [[ $j -lt ${#arg} ]]; do
    174                     optname=${arg:$j:1}
    175                     trace "    consider index $j: optname '$optname'"
    176 
    177                     if [[ "$optargs" == *"-$optname="* ]]; then
    178                         argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
    179                         if [[ $(( $j + 1 )) -lt ${#arg} ]]; then
    180                             state=arg
    181                             word=${arg:$(( $j + 1 ))}
    182                             trace "      takes arg (rest of this arg, word='$word', argtype='$argtype')"
    183                         elif [[ $i -lt $COMP_CWORD ]]; then
    184                             state=arg
    185                             i=$(( $i + 1 ))
    186                             word=${argv[$i]}
    187                             trace "    takes arg (word='$word', argtype='$argtype')"
    188                         fi
    189                         break
    190                     fi
    191 
    192                     j=$(( $j + 1 ))
    193                 done
    194             elif [[ $i -lt $COMP_CWORD && -n "$arg" ]] && $(echo "$allsubcmds" | grep -w "$arg" >/dev/null); then
    195                 trace "    complete subcmd: recurse _dashdash_complete"
    196                 _dashdash_complete $(( $i + 1 )) "${context}__${arg/-/_}"
    197                 return
    198             else
    199                 trace "    not an opt or a complete subcmd"
    200                 state=arg
    201                 word=$arg
    202                 nargs=$(( $nargs + 1 ))
    203                 if [[ ${#argtypes[@]} -gt 0 ]]; then
    204                     argtype="${argtypes[$(( $nargs - 1 ))]}"
    205                     if [[ -z "$argtype" ]]; then
    206                         # If we have more args than argtypes, we use the
    207                         # last type.
    208                         argtype="${argtypes[@]: -1:1}"
    209                     fi
    210                 fi
    211             fi
    212 
    213             trace "    state=$state prefix='$prefix' word='$word'"
    214             i=$(( $i + 1 ))
    215         done
    216 
    217         trace "  parsed: state=$state optname='$optname' argtype='$argtype' prefix='$prefix' word='$word' dashdashseen=$dashdashseen"
    218         local compgen_opts=
    219         if [[ -n "$prefix" ]]; then
    220             compgen_opts="$compgen_opts -P $prefix"
    221         fi
    222 
    223         case $state in
    224         shortopt)
    225             compgen $compgen_opts -W "$shortopts $longopts" -- "$word"
    226             ;;
    227         longopt)
    228             compgen $compgen_opts -W "$longopts" -- "$word"
    229             ;;
    230         arg)
    231             # If we don't know what completion to do, then emit nothing. We
    232             # expect that we are running with:
    233             #       complete -o default ...
    234             # where "default" means: "Use Readline's default completion if
    235             # the compspec generates no matches." This gives us the good filename
    236             # completion, completion in subshells/backticks.
    237             #
    238             # We cannot support an argtype="directory" because
    239             #       compgen -S '/' -A directory -- "$word"
    240             # doesn't give a satisfying result. It doesn't stop at the trailing '/'
    241             # so you cannot descend into dirs.
    242             if [[ "${word:0:1}" == '$' ]]; then
    243                 # By default, Bash will complete '$<TAB>' to all envvars. Apparently
    244                 # 'complete -o default' does *not* give us that. The following
    245                 # gets *close* to the same completions: '-A export' misses envvars
    246                 # like "PS1".
    247                 trace "  completing envvars"
    248                 compgen $compgen_opts -P '$' -A export -- "${word:1}"
    249             elif [[ -z "$argtype" ]]; then
    250                 # Only include opts in completions if $word is not empty.
    251                 # This is to avoid completing the leading '-', which foils
    252                 # using 'default' completion.
    253                 if [[ -n "$dashdashseen" ]]; then
    254                     trace "  completing subcmds, if any (no argtype, dashdash seen)"
    255                     compgen $compgen_opts -W "$subcmds" -- "$word"
    256                 elif [[ -z "$word" ]]; then
    257                     trace "  completing subcmds, if any (no argtype, empty word)"
    258                     compgen $compgen_opts -W "$subcmds" -- "$word"
    259                 else
    260                     trace "  completing opts & subcmds (no argtype)"
    261                     compgen $compgen_opts -W "$shortopts $longopts $subcmds" -- "$word"
    262                 fi
    263             elif [[ $argtype == "none" ]]; then
    264                 # We want *no* completions, i.e. some way to get the active
    265                 # 'complete -o default' to not do filename completion.
    266                 trace "  completing 'none' (hack to imply no completions)"
    267                 echo "##-no-completion- -results-##"
    268             elif [[ $argtype == "file" ]]; then
    269                 # 'complete -o default' gives the best filename completion, at least
    270                 # on Mac.
    271                 trace "  completing 'file' (let 'complete -o default' handle it)"
    272                 echo ""
    273             elif ! type complete_$argtype 2>/dev/null >/dev/null; then
    274                 trace "  completing '$argtype' (fallback to default b/c complete_$argtype is unknown)"
    275                 echo ""
    276             else
    277                 trace "  completing custom '$argtype'"
    278                 completions=$(complete_$argtype "$word")
    279                 if [[ -z "$completions" ]]; then
    280                     trace "  no custom '$argtype' completions"
    281                     # These are in ascii and "dictionary" order so they sort
    282                     # correctly.
    283                     echo "##-no-completion- -results-##"
    284                 else
    285                     echo $completions
    286                 fi
    287             fi
    288             ;;
    289         *)
    290             trace "  unknown state: $state"
    291             ;;
    292         esac
    293     }
    294 
    295 
    296     trace ""
    297     trace "-- $(date)"
    298     #trace "\$IFS: '$IFS'"
    299     #trace "\$@: '$@'"
    300     #trace "COMP_WORDBREAKS: '$COMP_WORDBREAKS'"
    301     trace "COMP_CWORD: '$COMP_CWORD'"
    302     trace "COMP_LINE: '$COMP_LINE'"
    303     trace "COMP_POINT: $COMP_POINT"
    304 
    305     # Guard against negative COMP_CWORD. This is a Bash bug at least on
    306     # Mac 10.10.4's bash. See
    307     # <https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00125.html>.
    308     if [[ $COMP_CWORD -lt 0 ]]; then
    309         trace "abort on negative COMP_CWORD"
    310         exit 1;
    311     fi
    312 
    313     # I don't know how to do array manip on argv vars,
    314     # so copy over to argv array to work on them.
    315     shift   # the leading '--'
    316     i=0
    317     len=$#
    318     while [[ $# -gt 0 ]]; do
    319         argv[$i]=$1
    320         shift;
    321         i=$(( $i + 1 ))
    322     done
    323     trace "argv: '${argv[@]}'"
    324     trace "argv[COMP_CWORD-1]: '${argv[$(( $COMP_CWORD - 1 ))]}'"
    325     trace "argv[COMP_CWORD]: '${argv[$COMP_CWORD]}'"
    326     trace "argv len: '$len'"
    327 
    328     _dashdash_complete 1 ""
    329 }
    330 
    331 
    332 # ---- mainline
    333 
    334 # Note: This if-block to help work with 'compdef' and 'compctl' is
    335 # adapted from 'npm completion'.
    336 if type complete &>/dev/null; then
    337     function _{{name}}_completion {
    338         local _log_file=/dev/null
    339         [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
    340         COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
    341             COMP_LINE="$COMP_LINE" \
    342             COMP_POINT="$COMP_POINT" \
    343             _{{name}}_completer -- "${COMP_WORDS[@]}" \
    344             2>$_log_file)) || return $?
    345     }
    346     complete -o default -F _{{name}}_completion {{name}}
    347 elif type compdef &>/dev/null; then
    348     function _{{name}}_completion {
    349         local _log_file=/dev/null
    350         [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
    351         compadd -- $(COMP_CWORD=$((CURRENT-1)) \
    352             COMP_LINE=$BUFFER \
    353             COMP_POINT=0 \
    354             _{{name}}_completer -- "${words[@]}" \
    355             2>$_log_file)
    356     }
    357     compdef _{{name}}_completion {{name}}
    358 elif type compctl &>/dev/null; then
    359     function _{{name}}_completion {
    360         local cword line point words si
    361         read -Ac words
    362         read -cn cword
    363         let cword-=1
    364         read -l line
    365         read -ln point
    366         local _log_file=/dev/null
    367         [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
    368         reply=($(COMP_CWORD="$cword" \
    369             COMP_LINE="$line" \
    370             COMP_POINT="$point" \
    371             _{{name}}_completer -- "${words[@]}" \
    372             2>$_log_file)) || return $?
    373     }
    374     compctl -K _{{name}}_completion {{name}}
    375 fi
    376 
    377 
    378 ##
    379 ## This is a Bash completion file for the '{{name}}' command. You can install
    380 ## with either:
    381 ##
    382 ##     cp FILE /usr/local/etc/bash_completion.d/{{name}}   # Mac
    383 ##     cp FILE /etc/bash_completion.d/{{name}}             # Linux
    384 ##
    385 ## or:
    386 ##
    387 ##     cp FILE > ~/.{{name}}.completion
    388 ##     echo "source ~/.{{name}}.completion" >> ~/.bashrc
    389 ##