3 # Bash completion generated for '{{name}}' at {{date}}.
5 # The original template lives here:
6 # https://github.com/trentm/node-dashdash/blob/master/etc/dashdash.bash_completion.in
10 # Copyright 2016 Trent Mick
11 # Copyright 2016 Joyent, Inc.
14 # A generic Bash completion driver script.
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:
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
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.)
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.:
38 # 2. all opts and subcmds:
40 # $ tool -v <TAB> # assuming '-v' doesn't take an arg
41 # $ tool -<TAB> # matching opts
42 # $ git lo<TAB> # matching subcmds
44 # Long opt completions are given *without* the '=', i.e. we prefer space
45 # separated because that's easier for good completions.
47 # 3. long opt arg with '='
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.
56 # Limitation: Currently only getting exported vars, so we miss "PS1" and
59 # 5. Defer to other completion in a subshell:
60 # $ tool --file $(cat ./<TAB>
61 # We get this from 'complete -o default ...'.
63 # 6. Custom completion types from a provided bash function.
64 # $ tool --profile <TAB> # complete available "profiles"
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
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
79 function _{{name}}_completer {
91 # ---- support functions
94 [[ -n "$_{{name}}_log" ]] && echo "$*" >&2
97 function _dashdash_complete {
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}")"
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"
118 # Get 'state' of option parsing at this COMP_POINT.
119 # Copying "dashdash.js#parse()" behaviour here.
128 while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do
135 trace " consider argv[$i]: '$arg'"
137 if [[ "$arg" == "--" && $i -lt $COMP_CWORD ]]; then
138 trace " dashdash seen"
142 elif [[ -z "$dashdashseen" && "${arg:0:2}" == "--" ]]; then
144 if [[ "$arg" == *"="* ]]; then
147 trace " long opt: optname='$optname' val='$val'"
149 argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
155 trace " long opt: optname='$optname'"
159 if [[ "$optargs" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then
162 argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
164 trace " takes arg (consume argv[$i], word='$word')"
167 elif [[ -z "$dashdashseen" && "${arg:0:1}" == "-" ]]; then
168 trace " short opt group"
173 while [[ $j -lt ${#arg} ]]; do
175 trace " consider index $j: optname '$optname'"
177 if [[ "$optargs" == *"-$optname="* ]]; then
178 argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
179 if [[ $(( $j + 1 )) -lt ${#arg} ]]; then
181 word=${arg:$(( $j + 1 ))}
182 trace " takes arg (rest of this arg, word='$word', argtype='$argtype')"
183 elif [[ $i -lt $COMP_CWORD ]]; then
187 trace " takes arg (word='$word', argtype='$argtype')"
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/-/_}"
199 trace " not an opt or a complete subcmd"
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
208 argtype="${argtypes[@]: -1:1}"
213 trace " state=$state prefix='$prefix' word='$word'"
217 trace " parsed: state=$state optname='$optname' argtype='$argtype' prefix='$prefix' word='$word' dashdashseen=$dashdashseen"
219 if [[ -n "$prefix" ]]; then
220 compgen_opts="$compgen_opts -P $prefix"
225 compgen $compgen_opts -W "$shortopts $longopts" -- "$word"
228 compgen $compgen_opts -W "$longopts" -- "$word"
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.
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
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"
260 trace " completing opts & subcmds (no argtype)"
261 compgen $compgen_opts -W "$shortopts $longopts $subcmds" -- "$word"
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 completions-##"
268 elif [[ $argtype == "file" ]]; then
269 # 'complete -o default' gives the best filename completion, at least
271 trace " completing 'file' (let 'complete -o default' handle it)"
273 elif ! type complete_$argtype 2>/dev/null >/dev/null; then
274 trace " completing '$argtype' (fallback to default b/c complete_$argtype is unknown)"
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 alpha order so they show up correctly.
282 echo "##-no -$argtype- completions-##"
289 trace " unknown state: $state"
297 #trace "\$IFS: '$IFS'"
299 #trace "COMP_WORDBREAKS: '$COMP_WORDBREAKS'"
300 trace "COMP_CWORD: '$COMP_CWORD'"
301 trace "COMP_LINE: '$COMP_LINE'"
302 trace "COMP_POINT: $COMP_POINT"
304 # Guard against negative COMP_CWORD. This is a Bash bug at least on
305 # Mac 10.10.4's bash. See
306 # <https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00125.html>.
307 if [[ $COMP_CWORD -lt 0 ]]; then
308 trace "abort on negative COMP_CWORD"
312 # I don't know how to do array manip on argv vars,
313 # so copy over to argv array to work on them.
314 shift # the leading '--'
317 while [[ $# -gt 0 ]]; do
322 trace "argv: '${argv[@]}'"
323 trace "argv[COMP_CWORD-1]: '${argv[$(( $COMP_CWORD - 1 ))]}'"
324 trace "argv[COMP_CWORD]: '${argv[$COMP_CWORD]}'"
325 trace "argv len: '$len'"
327 _dashdash_complete 1 ""
333 # Note: This if-block to help work with 'compdef' and 'compctl' is
334 # adapted from 'npm completion'.
335 if type complete &>/dev/null; then
336 function _{{name}}_completion {
337 local _log_file=/dev/null
338 [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
339 COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
340 COMP_LINE="$COMP_LINE" \
341 COMP_POINT="$COMP_POINT" \
342 _{{name}}_completer -- "${COMP_WORDS[@]}" \
343 2>$_log_file)) || return $?
345 complete -o default -F _{{name}}_completion {{name}}
346 elif type compdef &>/dev/null; then
347 function _{{name}}_completion {
348 local _log_file=/dev/null
349 [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
350 compadd -- $(COMP_CWORD=$((CURRENT-1)) \
353 _{{name}}_completer -- "${words[@]}" \
356 compdef _{{name}}_completion {{name}}
357 elif type compctl &>/dev/null; then
358 function _{{name}}_completion {
359 local cword line point words si
365 local _log_file=/dev/null
366 [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
367 reply=($(COMP_CWORD="$cword" \
369 COMP_POINT="$point" \
370 _{{name}}_completer -- "${words[@]}" \
371 2>$_log_file)) || return $?
373 compctl -K _{{name}}_completion {{name}}
378 ## This is a Bash completion file for the '{{name}}' command. You can install
381 ## cp FILE /usr/local/etc/bash_completion.d/{{name}} # Mac
382 ## cp FILE /etc/bash_completion.d/{{name}} # Linux
386 ## cp FILE > ~/.{{name}}.completion
387 ## echo "source ~/.{{name}}.completion" >> ~/.bashrc