2
0

bash_completions.go 21 KB

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