1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package cobra
16
17 import (
18 "bytes"
19 "fmt"
20 "io"
21 "os"
22 "sort"
23 "strings"
24
25 "github.com/spf13/pflag"
26 )
27
28
29 const (
30 BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
31 BashCompCustom = "cobra_annotation_bash_completion_custom"
32 BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
33 BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
34 )
35
36 func writePreamble(buf io.StringWriter, name string) {
37 WriteStringAndCheck(buf, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
38 WriteStringAndCheck(buf, fmt.Sprintf(`
39 __%[1]s_debug()
40 {
41 if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then
42 echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
43 fi
44 }
45
46 # Homebrew on Macs have version 1.3 of bash-completion which doesn't include
47 # _init_completion. This is a very minimal version of that function.
48 __%[1]s_init_completion()
49 {
50 COMPREPLY=()
51 _get_comp_words_by_ref "$@" cur prev words cword
52 }
53
54 __%[1]s_index_of_word()
55 {
56 local w word=$1
57 shift
58 index=0
59 for w in "$@"; do
60 [[ $w = "$word" ]] && return
61 index=$((index+1))
62 done
63 index=-1
64 }
65
66 __%[1]s_contains_word()
67 {
68 local w word=$1; shift
69 for w in "$@"; do
70 [[ $w = "$word" ]] && return
71 done
72 return 1
73 }
74
75 __%[1]s_handle_go_custom_completion()
76 {
77 __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}"
78
79 local shellCompDirectiveError=%[3]d
80 local shellCompDirectiveNoSpace=%[4]d
81 local shellCompDirectiveNoFileComp=%[5]d
82 local shellCompDirectiveFilterFileExt=%[6]d
83 local shellCompDirectiveFilterDirs=%[7]d
84
85 local out requestComp lastParam lastChar comp directive args
86
87 # Prepare the command to request completions for the program.
88 # Calling ${words[0]} instead of directly %[1]s allows handling aliases
89 args=("${words[@]:1}")
90 # Disable ActiveHelp which is not supported for bash completion v1
91 requestComp="%[8]s=0 ${words[0]} %[2]s ${args[*]}"
92
93 lastParam=${words[$((${#words[@]}-1))]}
94 lastChar=${lastParam:$((${#lastParam}-1)):1}
95 __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}"
96
97 if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then
98 # If the last parameter is complete (there is a space following it)
99 # We add an extra empty parameter so we can indicate this to the go method.
100 __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter"
101 requestComp="${requestComp} \"\""
102 fi
103
104 __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}"
105 # Use eval to handle any environment variables and such
106 out=$(eval "${requestComp}" 2>/dev/null)
107
108 # Extract the directive integer at the very end of the output following a colon (:)
109 directive=${out##*:}
110 # Remove the directive
111 out=${out%%:*}
112 if [ "${directive}" = "${out}" ]; then
113 # There is not directive specified
114 directive=0
115 fi
116 __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}"
117 __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out}"
118
119 if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
120 # Error code. No completion.
121 __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code"
122 return
123 else
124 if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
125 if [[ $(type -t compopt) = "builtin" ]]; then
126 __%[1]s_debug "${FUNCNAME[0]}: activating no space"
127 compopt -o nospace
128 fi
129 fi
130 if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
131 if [[ $(type -t compopt) = "builtin" ]]; then
132 __%[1]s_debug "${FUNCNAME[0]}: activating no file completion"
133 compopt +o default
134 fi
135 fi
136 fi
137
138 if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
139 # File extension filtering
140 local fullFilter filter filteringCmd
141 # Do not use quotes around the $out variable or else newline
142 # characters will be kept.
143 for filter in ${out}; do
144 fullFilter+="$filter|"
145 done
146
147 filteringCmd="_filedir $fullFilter"
148 __%[1]s_debug "File filtering command: $filteringCmd"
149 $filteringCmd
150 elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
151 # File completion for directories only
152 local subdir
153 # Use printf to strip any trailing newline
154 subdir=$(printf "%%s" "${out}")
155 if [ -n "$subdir" ]; then
156 __%[1]s_debug "Listing directories in $subdir"
157 __%[1]s_handle_subdirs_in_dir_flag "$subdir"
158 else
159 __%[1]s_debug "Listing directories in ."
160 _filedir -d
161 fi
162 else
163 while IFS='' read -r comp; do
164 COMPREPLY+=("$comp")
165 done < <(compgen -W "${out}" -- "$cur")
166 fi
167 }
168
169 __%[1]s_handle_reply()
170 {
171 __%[1]s_debug "${FUNCNAME[0]}"
172 local comp
173 case $cur in
174 -*)
175 if [[ $(type -t compopt) = "builtin" ]]; then
176 compopt -o nospace
177 fi
178 local allflags
179 if [ ${#must_have_one_flag[@]} -ne 0 ]; then
180 allflags=("${must_have_one_flag[@]}")
181 else
182 allflags=("${flags[*]} ${two_word_flags[*]}")
183 fi
184 while IFS='' read -r comp; do
185 COMPREPLY+=("$comp")
186 done < <(compgen -W "${allflags[*]}" -- "$cur")
187 if [[ $(type -t compopt) = "builtin" ]]; then
188 [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace
189 fi
190
191 # complete after --flag=abc
192 if [[ $cur == *=* ]]; then
193 if [[ $(type -t compopt) = "builtin" ]]; then
194 compopt +o nospace
195 fi
196
197 local index flag
198 flag="${cur%%=*}"
199 __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
200 COMPREPLY=()
201 if [[ ${index} -ge 0 ]]; then
202 PREFIX=""
203 cur="${cur#*=}"
204 ${flags_completion[${index}]}
205 if [ -n "${ZSH_VERSION:-}" ]; then
206 # zsh completion needs --flag= prefix
207 eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
208 fi
209 fi
210 fi
211
212 if [[ -z "${flag_parsing_disabled}" ]]; then
213 # If flag parsing is enabled, we have completed the flags and can return.
214 # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough
215 # to possibly call handle_go_custom_completion.
216 return 0;
217 fi
218 ;;
219 esac
220
221 # check if we are handling a flag with special work handling
222 local index
223 __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
224 if [[ ${index} -ge 0 ]]; then
225 ${flags_completion[${index}]}
226 return
227 fi
228
229 # we are parsing a flag and don't have a special handler, no completion
230 if [[ ${cur} != "${words[cword]}" ]]; then
231 return
232 fi
233
234 local completions
235 completions=("${commands[@]}")
236 if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
237 completions+=("${must_have_one_noun[@]}")
238 elif [[ -n "${has_completion_function}" ]]; then
239 # if a go completion function is provided, defer to that function
240 __%[1]s_handle_go_custom_completion
241 fi
242 if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
243 completions+=("${must_have_one_flag[@]}")
244 fi
245 while IFS='' read -r comp; do
246 COMPREPLY+=("$comp")
247 done < <(compgen -W "${completions[*]}" -- "$cur")
248
249 if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then
250 while IFS='' read -r comp; do
251 COMPREPLY+=("$comp")
252 done < <(compgen -W "${noun_aliases[*]}" -- "$cur")
253 fi
254
255 if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
256 if declare -F __%[1]s_custom_func >/dev/null; then
257 # try command name qualified custom func
258 __%[1]s_custom_func
259 else
260 # otherwise fall back to unqualified for compatibility
261 declare -F __custom_func >/dev/null && __custom_func
262 fi
263 fi
264
265 # available in bash-completion >= 2, not always present on macOS
266 if declare -F __ltrim_colon_completions >/dev/null; then
267 __ltrim_colon_completions "$cur"
268 fi
269
270 # If there is only 1 completion and it is a flag with an = it will be completed
271 # but we don't want a space after the =
272 if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
273 compopt -o nospace
274 fi
275 }
276
277 # The arguments should be in the form "ext1|ext2|extn"
278 __%[1]s_handle_filename_extension_flag()
279 {
280 local ext="$1"
281 _filedir "@(${ext})"
282 }
283
284 __%[1]s_handle_subdirs_in_dir_flag()
285 {
286 local dir="$1"
287 pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
288 }
289
290 __%[1]s_handle_flag()
291 {
292 __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
293
294 # if a command required a flag, and we found it, unset must_have_one_flag()
295 local flagname=${words[c]}
296 local flagvalue=""
297 # if the word contained an =
298 if [[ ${words[c]} == *"="* ]]; then
299 flagvalue=${flagname#*=} # take in as flagvalue after the =
300 flagname=${flagname%%=*} # strip everything after the =
301 flagname="${flagname}=" # but put the = back
302 fi
303 __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
304 if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
305 must_have_one_flag=()
306 fi
307
308 # if you set a flag which only applies to this command, don't show subcommands
309 if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
310 commands=()
311 fi
312
313 # keep flag value with flagname as flaghash
314 # flaghash variable is an associative array which is only supported in bash > 3.
315 if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
316 if [ -n "${flagvalue}" ] ; then
317 flaghash[${flagname}]=${flagvalue}
318 elif [ -n "${words[ $((c+1)) ]}" ] ; then
319 flaghash[${flagname}]=${words[ $((c+1)) ]}
320 else
321 flaghash[${flagname}]="true" # pad "true" for bool flag
322 fi
323 fi
324
325 # skip the argument to a two word flag
326 if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
327 __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument"
328 c=$((c+1))
329 # if we are looking for a flags value, don't show commands
330 if [[ $c -eq $cword ]]; then
331 commands=()
332 fi
333 fi
334
335 c=$((c+1))
336
337 }
338
339 __%[1]s_handle_noun()
340 {
341 __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
342
343 if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
344 must_have_one_noun=()
345 elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
346 must_have_one_noun=()
347 fi
348
349 nouns+=("${words[c]}")
350 c=$((c+1))
351 }
352
353 __%[1]s_handle_command()
354 {
355 __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
356
357 local next_command
358 if [[ -n ${last_command} ]]; then
359 next_command="_${last_command}_${words[c]//:/__}"
360 else
361 if [[ $c -eq 0 ]]; then
362 next_command="_%[1]s_root_command"
363 else
364 next_command="_${words[c]//:/__}"
365 fi
366 fi
367 c=$((c+1))
368 __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
369 declare -F "$next_command" >/dev/null && $next_command
370 }
371
372 __%[1]s_handle_word()
373 {
374 if [[ $c -ge $cword ]]; then
375 __%[1]s_handle_reply
376 return
377 fi
378 __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
379 if [[ "${words[c]}" == -* ]]; then
380 __%[1]s_handle_flag
381 elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
382 __%[1]s_handle_command
383 elif [[ $c -eq 0 ]]; then
384 __%[1]s_handle_command
385 elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then
386 # aliashash variable is an associative array which is only supported in bash > 3.
387 if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then
388 words[c]=${aliashash[${words[c]}]}
389 __%[1]s_handle_command
390 else
391 __%[1]s_handle_noun
392 fi
393 else
394 __%[1]s_handle_noun
395 fi
396 __%[1]s_handle_word
397 }
398
399 `, name, ShellCompNoDescRequestCmd,
400 ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
401 ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
402 }
403
404 func writePostscript(buf io.StringWriter, name string) {
405 name = strings.ReplaceAll(name, ":", "__")
406 WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name))
407 WriteStringAndCheck(buf, fmt.Sprintf(`{
408 local cur prev words cword split
409 declare -A flaghash 2>/dev/null || :
410 declare -A aliashash 2>/dev/null || :
411 if declare -F _init_completion >/dev/null 2>&1; then
412 _init_completion -s || return
413 else
414 __%[1]s_init_completion -n "=" || return
415 fi
416
417 local c=0
418 local flag_parsing_disabled=
419 local flags=()
420 local two_word_flags=()
421 local local_nonpersistent_flags=()
422 local flags_with_completion=()
423 local flags_completion=()
424 local commands=("%[1]s")
425 local command_aliases=()
426 local must_have_one_flag=()
427 local must_have_one_noun=()
428 local has_completion_function=""
429 local last_command=""
430 local nouns=()
431 local noun_aliases=()
432
433 __%[1]s_handle_word
434 }
435
436 `, name))
437 WriteStringAndCheck(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
438 complete -o default -F __start_%s %s
439 else
440 complete -o default -o nospace -F __start_%s %s
441 fi
442
443 `, name, name, name, name))
444 WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n")
445 }
446
447 func writeCommands(buf io.StringWriter, cmd *Command) {
448 WriteStringAndCheck(buf, " commands=()\n")
449 for _, c := range cmd.Commands() {
450 if !c.IsAvailableCommand() && c != cmd.helpCommand {
451 continue
452 }
453 WriteStringAndCheck(buf, fmt.Sprintf(" commands+=(%q)\n", c.Name()))
454 writeCmdAliases(buf, c)
455 }
456 WriteStringAndCheck(buf, "\n")
457 }
458
459 func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *Command) {
460 for key, value := range annotations {
461 switch key {
462 case BashCompFilenameExt:
463 WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
464
465 var ext string
466 if len(value) > 0 {
467 ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
468 } else {
469 ext = "_filedir"
470 }
471 WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext))
472 case BashCompCustom:
473 WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
474
475 if len(value) > 0 {
476 handlers := strings.Join(value, "; ")
477 WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", handlers))
478 } else {
479 WriteStringAndCheck(buf, " flags_completion+=(:)\n")
480 }
481 case BashCompSubdirsInDir:
482 WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
483
484 var ext string
485 if len(value) == 1 {
486 ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
487 } else {
488 ext = "_filedir -d"
489 }
490 WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext))
491 }
492 }
493 }
494
495 const cbn = "\")\n"
496
497 func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
498 name := flag.Shorthand
499 format := " "
500 if len(flag.NoOptDefVal) == 0 {
501 format += "two_word_"
502 }
503 format += "flags+=(\"-%s" + cbn
504 WriteStringAndCheck(buf, fmt.Sprintf(format, name))
505 writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
506 }
507
508 func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) {
509 name := flag.Name
510 format := " flags+=(\"--%s"
511 if len(flag.NoOptDefVal) == 0 {
512 format += "="
513 }
514 format += cbn
515 WriteStringAndCheck(buf, fmt.Sprintf(format, name))
516 if len(flag.NoOptDefVal) == 0 {
517 format = " two_word_flags+=(\"--%s" + cbn
518 WriteStringAndCheck(buf, fmt.Sprintf(format, name))
519 }
520 writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
521 }
522
523 func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) {
524 name := flag.Name
525 format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn
526 if len(flag.NoOptDefVal) == 0 {
527 format += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn
528 }
529 WriteStringAndCheck(buf, fmt.Sprintf(format, name))
530 if len(flag.Shorthand) > 0 {
531 WriteStringAndCheck(buf, fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
532 }
533 }
534
535
536 func prepareCustomAnnotationsForFlags(cmd *Command) {
537 flagCompletionMutex.RLock()
538 defer flagCompletionMutex.RUnlock()
539 for flag := range flagCompletionFunctions {
540
541
542
543
544 if flag.Annotations == nil {
545 flag.Annotations = map[string][]string{}
546 }
547 flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
548 }
549 }
550
551 func writeFlags(buf io.StringWriter, cmd *Command) {
552 prepareCustomAnnotationsForFlags(cmd)
553 WriteStringAndCheck(buf, ` flags=()
554 two_word_flags=()
555 local_nonpersistent_flags=()
556 flags_with_completion=()
557 flags_completion=()
558
559 `)
560
561 if cmd.DisableFlagParsing {
562 WriteStringAndCheck(buf, " flag_parsing_disabled=1\n")
563 }
564
565 localNonPersistentFlags := cmd.LocalNonPersistentFlags()
566 cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
567 if nonCompletableFlag(flag) {
568 return
569 }
570 writeFlag(buf, flag, cmd)
571 if len(flag.Shorthand) > 0 {
572 writeShortFlag(buf, flag, cmd)
573 }
574
575
576 if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
577 writeLocalNonPersistentFlag(buf, flag)
578 }
579 })
580 cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
581 if nonCompletableFlag(flag) {
582 return
583 }
584 writeFlag(buf, flag, cmd)
585 if len(flag.Shorthand) > 0 {
586 writeShortFlag(buf, flag, cmd)
587 }
588 })
589
590 WriteStringAndCheck(buf, "\n")
591 }
592
593 func writeRequiredFlag(buf io.StringWriter, cmd *Command) {
594 WriteStringAndCheck(buf, " must_have_one_flag=()\n")
595 flags := cmd.NonInheritedFlags()
596 flags.VisitAll(func(flag *pflag.Flag) {
597 if nonCompletableFlag(flag) {
598 return
599 }
600 for key := range flag.Annotations {
601 switch key {
602 case BashCompOneRequiredFlag:
603 format := " must_have_one_flag+=(\"--%s"
604 if flag.Value.Type() != "bool" {
605 format += "="
606 }
607 format += cbn
608 WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name))
609
610 if len(flag.Shorthand) > 0 {
611 WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand))
612 }
613 }
614 }
615 })
616 }
617
618 func writeRequiredNouns(buf io.StringWriter, cmd *Command) {
619 WriteStringAndCheck(buf, " must_have_one_noun=()\n")
620 sort.Strings(cmd.ValidArgs)
621 for _, value := range cmd.ValidArgs {
622
623
624 value = strings.Split(value, "\t")[0]
625 WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
626 }
627 if cmd.ValidArgsFunction != nil {
628 WriteStringAndCheck(buf, " has_completion_function=1\n")
629 }
630 }
631
632 func writeCmdAliases(buf io.StringWriter, cmd *Command) {
633 if len(cmd.Aliases) == 0 {
634 return
635 }
636
637 sort.Strings(cmd.Aliases)
638
639 WriteStringAndCheck(buf, fmt.Sprint(` if [[ -z "${BASH_VERSION:-}" || "${BASH_VERSINFO[0]:-}" -gt 3 ]]; then`, "\n"))
640 for _, value := range cmd.Aliases {
641 WriteStringAndCheck(buf, fmt.Sprintf(" command_aliases+=(%q)\n", value))
642 WriteStringAndCheck(buf, fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name()))
643 }
644 WriteStringAndCheck(buf, ` fi`)
645 WriteStringAndCheck(buf, "\n")
646 }
647 func writeArgAliases(buf io.StringWriter, cmd *Command) {
648 WriteStringAndCheck(buf, " noun_aliases=()\n")
649 sort.Strings(cmd.ArgAliases)
650 for _, value := range cmd.ArgAliases {
651 WriteStringAndCheck(buf, fmt.Sprintf(" noun_aliases+=(%q)\n", value))
652 }
653 }
654
655 func gen(buf io.StringWriter, cmd *Command) {
656 for _, c := range cmd.Commands() {
657 if !c.IsAvailableCommand() && c != cmd.helpCommand {
658 continue
659 }
660 gen(buf, c)
661 }
662 commandName := cmd.CommandPath()
663 commandName = strings.ReplaceAll(commandName, " ", "_")
664 commandName = strings.ReplaceAll(commandName, ":", "__")
665
666 if cmd.Root() == cmd {
667 WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName))
668 } else {
669 WriteStringAndCheck(buf, fmt.Sprintf("_%s()\n{\n", commandName))
670 }
671
672 WriteStringAndCheck(buf, fmt.Sprintf(" last_command=%q\n", commandName))
673 WriteStringAndCheck(buf, "\n")
674 WriteStringAndCheck(buf, " command_aliases=()\n")
675 WriteStringAndCheck(buf, "\n")
676
677 writeCommands(buf, cmd)
678 writeFlags(buf, cmd)
679 writeRequiredFlag(buf, cmd)
680 writeRequiredNouns(buf, cmd)
681 writeArgAliases(buf, cmd)
682 WriteStringAndCheck(buf, "}\n\n")
683 }
684
685
686 func (c *Command) GenBashCompletion(w io.Writer) error {
687 buf := new(bytes.Buffer)
688 writePreamble(buf, c.Name())
689 if len(c.BashCompletionFunction) > 0 {
690 buf.WriteString(c.BashCompletionFunction + "\n")
691 }
692 gen(buf, c)
693 writePostscript(buf, c.Name())
694
695 _, err := buf.WriteTo(w)
696 return err
697 }
698
699 func nonCompletableFlag(flag *pflag.Flag) bool {
700 return flag.Hidden || len(flag.Deprecated) > 0
701 }
702
703
704 func (c *Command) GenBashCompletionFile(filename string) error {
705 outFile, err := os.Create(filename)
706 if err != nil {
707 return err
708 }
709 defer outFile.Close()
710
711 return c.GenBashCompletion(outFile)
712 }
713
View as plain text