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 ##