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