[6a3a178] | 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 | ## |
---|