bash_completions.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  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 *bytes.Buffer, name string) {
  19. buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
  20. buf.WriteString(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 *bytes.Buffer, name string) {
  339. name = strings.Replace(name, ":", "__", -1)
  340. buf.WriteString(fmt.Sprintf("__start_%s()\n", name))
  341. buf.WriteString(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. buf.WriteString(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. buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n")
  372. }
  373. func writeCommands(buf *bytes.Buffer, cmd *Command) {
  374. buf.WriteString(" commands=()\n")
  375. for _, c := range cmd.Commands() {
  376. if !c.IsAvailableCommand() && c != cmd.helpCommand {
  377. continue
  378. }
  379. buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
  380. writeCmdAliases(buf, c)
  381. }
  382. buf.WriteString("\n")
  383. }
  384. func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
  385. for key, value := range annotations {
  386. switch key {
  387. case BashCompFilenameExt:
  388. buf.WriteString(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. buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
  396. case BashCompCustom:
  397. buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
  398. if len(value) > 0 {
  399. handlers := strings.Join(value, "; ")
  400. buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers))
  401. } else {
  402. buf.WriteString(" flags_completion+=(:)\n")
  403. }
  404. case BashCompSubdirsInDir:
  405. buf.WriteString(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. buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
  413. }
  414. }
  415. }
  416. func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
  417. name := flag.Shorthand
  418. format := " "
  419. if len(flag.NoOptDefVal) == 0 {
  420. format += "two_word_"
  421. }
  422. format += "flags+=(\"-%s\")\n"
  423. buf.WriteString(fmt.Sprintf(format, name))
  424. writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
  425. }
  426. func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
  427. name := flag.Name
  428. format := " flags+=(\"--%s"
  429. if len(flag.NoOptDefVal) == 0 {
  430. format += "="
  431. }
  432. format += "\")\n"
  433. buf.WriteString(fmt.Sprintf(format, name))
  434. if len(flag.NoOptDefVal) == 0 {
  435. format = " two_word_flags+=(\"--%s\")\n"
  436. buf.WriteString(fmt.Sprintf(format, name))
  437. }
  438. writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
  439. }
  440. func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
  441. name := flag.Name
  442. format := " local_nonpersistent_flags+=(\"--%[1]s\")\n"
  443. if len(flag.NoOptDefVal) == 0 {
  444. format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n"
  445. }
  446. buf.WriteString(fmt.Sprintf(format, name))
  447. if len(flag.Shorthand) > 0 {
  448. buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand))
  449. }
  450. }
  451. // Setup annotations for go completions for registered flags
  452. func prepareCustomAnnotationsForFlags(cmd *Command) {
  453. for flag := range flagCompletionFunctions {
  454. // Make sure the completion script calls the __*_go_custom_completion function for
  455. // every registered flag. We need to do this here (and not when the flag was registered
  456. // for completion) so that we can know the root command name for the prefix
  457. // of __<prefix>_go_custom_completion
  458. if flag.Annotations == nil {
  459. flag.Annotations = map[string][]string{}
  460. }
  461. flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())}
  462. }
  463. }
  464. func writeFlags(buf *bytes.Buffer, cmd *Command) {
  465. prepareCustomAnnotationsForFlags(cmd)
  466. buf.WriteString(` flags=()
  467. two_word_flags=()
  468. local_nonpersistent_flags=()
  469. flags_with_completion=()
  470. flags_completion=()
  471. `)
  472. localNonPersistentFlags := cmd.LocalNonPersistentFlags()
  473. cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  474. if nonCompletableFlag(flag) {
  475. return
  476. }
  477. writeFlag(buf, flag, cmd)
  478. if len(flag.Shorthand) > 0 {
  479. writeShortFlag(buf, flag, cmd)
  480. }
  481. // localNonPersistentFlags are used to stop the completion of subcommands when one is set
  482. // if TraverseChildren is true we should allow to complete subcommands
  483. if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren {
  484. writeLocalNonPersistentFlag(buf, flag)
  485. }
  486. })
  487. cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
  488. if nonCompletableFlag(flag) {
  489. return
  490. }
  491. writeFlag(buf, flag, cmd)
  492. if len(flag.Shorthand) > 0 {
  493. writeShortFlag(buf, flag, cmd)
  494. }
  495. })
  496. buf.WriteString("\n")
  497. }
  498. func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) {
  499. buf.WriteString(" must_have_one_flag=()\n")
  500. flags := cmd.NonInheritedFlags()
  501. flags.VisitAll(func(flag *pflag.Flag) {
  502. if nonCompletableFlag(flag) {
  503. return
  504. }
  505. for key := range flag.Annotations {
  506. switch key {
  507. case BashCompOneRequiredFlag:
  508. format := " must_have_one_flag+=(\"--%s"
  509. if flag.Value.Type() != "bool" {
  510. format += "="
  511. }
  512. format += "\")\n"
  513. buf.WriteString(fmt.Sprintf(format, flag.Name))
  514. if len(flag.Shorthand) > 0 {
  515. buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand))
  516. }
  517. }
  518. }
  519. })
  520. }
  521. func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
  522. buf.WriteString(" must_have_one_noun=()\n")
  523. sort.Sort(sort.StringSlice(cmd.ValidArgs))
  524. for _, value := range cmd.ValidArgs {
  525. // Remove any description that may be included following a tab character.
  526. // Descriptions are not supported by bash completion.
  527. value = strings.Split(value, "\t")[0]
  528. buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
  529. }
  530. if cmd.ValidArgsFunction != nil {
  531. buf.WriteString(" has_completion_function=1\n")
  532. }
  533. }
  534. func writeCmdAliases(buf *bytes.Buffer, cmd *Command) {
  535. if len(cmd.Aliases) == 0 {
  536. return
  537. }
  538. sort.Sort(sort.StringSlice(cmd.Aliases))
  539. buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n"))
  540. for _, value := range cmd.Aliases {
  541. buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value))
  542. buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name()))
  543. }
  544. buf.WriteString(` fi`)
  545. buf.WriteString("\n")
  546. }
  547. func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
  548. buf.WriteString(" noun_aliases=()\n")
  549. sort.Sort(sort.StringSlice(cmd.ArgAliases))
  550. for _, value := range cmd.ArgAliases {
  551. buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value))
  552. }
  553. }
  554. func gen(buf *bytes.Buffer, cmd *Command) {
  555. for _, c := range cmd.Commands() {
  556. if !c.IsAvailableCommand() && c != cmd.helpCommand {
  557. continue
  558. }
  559. gen(buf, c)
  560. }
  561. commandName := cmd.CommandPath()
  562. commandName = strings.Replace(commandName, " ", "_", -1)
  563. commandName = strings.Replace(commandName, ":", "__", -1)
  564. if cmd.Root() == cmd {
  565. buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
  566. } else {
  567. buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
  568. }
  569. buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
  570. buf.WriteString("\n")
  571. buf.WriteString(" command_aliases=()\n")
  572. buf.WriteString("\n")
  573. writeCommands(buf, cmd)
  574. writeFlags(buf, cmd)
  575. writeRequiredFlag(buf, cmd)
  576. writeRequiredNouns(buf, cmd)
  577. writeArgAliases(buf, cmd)
  578. buf.WriteString("}\n\n")
  579. }
  580. // GenBashCompletion generates bash completion file and writes to the passed writer.
  581. func (c *Command) GenBashCompletion(w io.Writer) error {
  582. buf := new(bytes.Buffer)
  583. writePreamble(buf, c.Name())
  584. if len(c.BashCompletionFunction) > 0 {
  585. buf.WriteString(c.BashCompletionFunction + "\n")
  586. }
  587. gen(buf, c)
  588. writePostscript(buf, c.Name())
  589. _, err := buf.WriteTo(w)
  590. return err
  591. }
  592. func nonCompletableFlag(flag *pflag.Flag) bool {
  593. return flag.Hidden || len(flag.Deprecated) > 0
  594. }
  595. // GenBashCompletionFile generates bash completion file.
  596. func (c *Command) GenBashCompletionFile(filename string) error {
  597. outFile, err := os.Create(filename)
  598. if err != nil {
  599. return err
  600. }
  601. defer outFile.Close()
  602. return c.GenBashCompletion(outFile)
  603. }