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