Fix generated bash completion for Bash 3 (OSX) (#520)

* Make preamble functions unique to command

Prior to this commit the functions in the preamble had names that didn't
vary based on the command for which the bash completion was generated.

This meant that if you had two bash completions with differences in the
preamble functions then only the last loaded function would be
available.

This commit prefixes all of these functions with the name of the command
so that multiple cobra generated completion files won't clash.

Signed-off-by: John McCabe <john@johnmccabe.net>

* Fix function names in writeFlagHandler

The references to the `__handle_filename_extension_flag` and
`__handle_subdirs_in_dir_flag` functions in `writeFlagHandler` hadn't
been updated correctly in the previous commits.

Signed-off-by: John McCabe <john@johnmccabe.net>

* Pass cmd into writeFlagHandler

This commit passes the cmd pointer into the writeFlagHandler so that the
`__handle_filename_extension_flag` and `__handle_subdirs_in_dir_flag`
functions can have the `cmd.Name()` prefixed.

* Update Bash completion tests

Prefixes the tested `__handle_filename_extension_flag` and
`__handle_subdirs_in_dir_flag` functions with the command name.
This commit is contained in:
John McCabe 2018-02-08 21:34:46 +00:00 committed by Eric Paris
parent 93959269ad
commit fd32f09af1
2 changed files with 52 additions and 51 deletions

View File

@ -21,8 +21,8 @@ const (
func writePreamble(buf *bytes.Buffer, name string) { func writePreamble(buf *bytes.Buffer, name string) {
buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
buf.WriteString(` buf.WriteString(fmt.Sprintf(`
__debug() __%[1]s_debug()
{ {
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}" echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
@ -31,13 +31,13 @@ __debug()
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include # Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function. # _init_completion. This is a very minimal version of that function.
__my_init_completion() __%[1]s_init_completion()
{ {
COMPREPLY=() COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword _get_comp_words_by_ref "$@" cur prev words cword
} }
__index_of_word() __%[1]s_index_of_word()
{ {
local w word=$1 local w word=$1
shift shift
@ -49,7 +49,7 @@ __index_of_word()
index=-1 index=-1
} }
__contains_word() __%[1]s_contains_word()
{ {
local w word=$1; shift local w word=$1; shift
for w in "$@"; do for w in "$@"; do
@ -58,9 +58,9 @@ __contains_word()
return 1 return 1
} }
__handle_reply() __%[1]s_handle_reply()
{ {
__debug "${FUNCNAME[0]}" __%[1]s_debug "${FUNCNAME[0]}"
case $cur in case $cur in
-*) -*)
if [[ $(type -t compopt) = "builtin" ]]; then if [[ $(type -t compopt) = "builtin" ]]; then
@ -85,7 +85,7 @@ __handle_reply()
local index flag local index flag
flag="${cur%%=*}" flag="${cur%%=*}"
__index_of_word "${flag}" "${flags_with_completion[@]}" __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
COMPREPLY=() COMPREPLY=()
if [[ ${index} -ge 0 ]]; then if [[ ${index} -ge 0 ]]; then
PREFIX="" PREFIX=""
@ -103,7 +103,7 @@ __handle_reply()
# check if we are handling a flag with special work handling # check if we are handling a flag with special work handling
local index local index
__index_of_word "${prev}" "${flags_with_completion[@]}" __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
if [[ ${index} -ge 0 ]]; then if [[ ${index} -ge 0 ]]; then
${flags_completion[${index}]} ${flags_completion[${index}]}
return return
@ -139,21 +139,21 @@ __handle_reply()
} }
# The arguments should be in the form "ext1|ext2|extn" # The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag() __%[1]s_handle_filename_extension_flag()
{ {
local ext="$1" local ext="$1"
_filedir "@(${ext})" _filedir "@(${ext})"
} }
__handle_subdirs_in_dir_flag() __%[1]s_handle_subdirs_in_dir_flag()
{ {
local dir="$1" local dir="$1"
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
} }
__handle_flag() __%[1]s_handle_flag()
{ {
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
# if a command required a flag, and we found it, unset must_have_one_flag() # if a command required a flag, and we found it, unset must_have_one_flag()
local flagname=${words[c]} local flagname=${words[c]}
@ -164,13 +164,13 @@ __handle_flag()
flagname=${flagname%%=*} # strip everything after the = flagname=${flagname%%=*} # strip everything after the =
flagname="${flagname}=" # but put the = back flagname="${flagname}=" # but put the = back
fi fi
__debug "${FUNCNAME[0]}: looking for ${flagname}" __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
must_have_one_flag=() must_have_one_flag=()
fi fi
# if you set a flag which only applies to this command, don't show subcommands # if you set a flag which only applies to this command, don't show subcommands
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
commands=() commands=()
fi fi
@ -187,7 +187,7 @@ __handle_flag()
fi fi
# skip the argument to a two word flag # skip the argument to a two word flag
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
c=$((c+1)) c=$((c+1))
# if we are looking for a flags value, don't show commands # if we are looking for a flags value, don't show commands
if [[ $c -eq $cword ]]; then if [[ $c -eq $cword ]]; then
@ -199,13 +199,13 @@ __handle_flag()
} }
__handle_noun() __%[1]s_handle_noun()
{ {
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
must_have_one_noun=() must_have_one_noun=()
elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
must_have_one_noun=() must_have_one_noun=()
fi fi
@ -213,9 +213,9 @@ __handle_noun()
c=$((c+1)) c=$((c+1))
} }
__handle_command() __%[1]s_handle_command()
{ {
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local next_command local next_command
if [[ -n ${last_command} ]]; then if [[ -n ${last_command} ]]; then
@ -228,30 +228,30 @@ __handle_command()
fi fi
fi fi
c=$((c+1)) c=$((c+1))
__debug "${FUNCNAME[0]}: looking for ${next_command}" __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
declare -F "$next_command" >/dev/null && $next_command declare -F "$next_command" >/dev/null && $next_command
} }
__handle_word() __%[1]s_handle_word()
{ {
if [[ $c -ge $cword ]]; then if [[ $c -ge $cword ]]; then
__handle_reply __%[1]s_handle_reply
return return
fi fi
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if [[ "${words[c]}" == -* ]]; then if [[ "${words[c]}" == -* ]]; then
__handle_flag __%[1]s_handle_flag
elif __contains_word "${words[c]}" "${commands[@]}"; then elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
__handle_command __%[1]s_handle_command
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then elif [[ $c -eq 0 ]] && __%[1]s_contains_word "$(basename "${words[c]}")" "${commands[@]}"; then
__handle_command __%[1]s_handle_command
else else
__handle_noun __%[1]s_handle_noun
fi fi
__handle_word __%[1]s_handle_word
} }
`) `, name))
} }
func writePostscript(buf *bytes.Buffer, name string) { func writePostscript(buf *bytes.Buffer, name string) {
@ -263,7 +263,7 @@ func writePostscript(buf *bytes.Buffer, name string) {
if declare -F _init_completion >/dev/null 2>&1; then if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -s || return _init_completion -s || return
else else
__my_init_completion -n "=" || return __%[1]s_init_completion -n "=" || return
fi fi
local c=0 local c=0
@ -272,13 +272,13 @@ func writePostscript(buf *bytes.Buffer, name string) {
local local_nonpersistent_flags=() local local_nonpersistent_flags=()
local flags_with_completion=() local flags_with_completion=()
local flags_completion=() local flags_completion=()
local commands=("%s") local commands=("%[1]s")
local must_have_one_flag=() local must_have_one_flag=()
local must_have_one_noun=() local must_have_one_noun=()
local last_command local last_command
local nouns=() local nouns=()
__handle_word __%[1]s_handle_word
} }
`, name)) `, name))
@ -303,7 +303,7 @@ func writeCommands(buf *bytes.Buffer, cmd *Command) {
buf.WriteString("\n") buf.WriteString("\n")
} }
func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string) { func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
for key, value := range annotations { for key, value := range annotations {
switch key { switch key {
case BashCompFilenameExt: case BashCompFilenameExt:
@ -311,7 +311,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
var ext string var ext string
if len(value) > 0 { if len(value) > 0 {
ext = "__handle_filename_extension_flag " + strings.Join(value, "|") ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Name()) + strings.Join(value, "|")
} else { } else {
ext = "_filedir" ext = "_filedir"
} }
@ -329,7 +329,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
var ext string var ext string
if len(value) == 1 { if len(value) == 1 {
ext = "__handle_subdirs_in_dir_flag " + value[0] ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Name()) + value[0]
} else { } else {
ext = "_filedir -d" ext = "_filedir -d"
} }
@ -338,7 +338,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
} }
} }
func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) { func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Shorthand name := flag.Shorthand
format := " " format := " "
if len(flag.NoOptDefVal) == 0 { if len(flag.NoOptDefVal) == 0 {
@ -346,10 +346,10 @@ func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) {
} }
format += "flags+=(\"-%s\")\n" format += "flags+=(\"-%s\")\n"
buf.WriteString(fmt.Sprintf(format, name)) buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "-"+name, flag.Annotations) writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
} }
func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) { func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Name name := flag.Name
format := " flags+=(\"--%s" format := " flags+=(\"--%s"
if len(flag.NoOptDefVal) == 0 { if len(flag.NoOptDefVal) == 0 {
@ -357,7 +357,7 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) {
} }
format += "\")\n" format += "\")\n"
buf.WriteString(fmt.Sprintf(format, name)) buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "--"+name, flag.Annotations) writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
} }
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
@ -383,9 +383,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if nonCompletableFlag(flag) { if nonCompletableFlag(flag) {
return return
} }
writeFlag(buf, flag) writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag) writeShortFlag(buf, flag, cmd)
} }
if localNonPersistentFlags.Lookup(flag.Name) != nil { if localNonPersistentFlags.Lookup(flag.Name) != nil {
writeLocalNonPersistentFlag(buf, flag) writeLocalNonPersistentFlag(buf, flag)
@ -395,9 +395,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if nonCompletableFlag(flag) { if nonCompletableFlag(flag) {
return return
} }
writeFlag(buf, flag) writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 { if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag) writeShortFlag(buf, flag, cmd)
} }
}) })

View File

@ -2,6 +2,7 @@ package cobra
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -146,11 +147,11 @@ func TestBashCompletions(t *testing.T) {
// check for filename extension flags // check for filename extension flags
check(t, output, `must_have_one_noun+=("three")`) check(t, output, `must_have_one_noun+=("three")`)
// check for filename extension flags // check for filename extension flags
check(t, output, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_filename_extension_flag json|yaml|yml")`, rootCmd.Name()))
// check for custom flags // check for custom flags
check(t, output, `flags_completion+=("__complete_custom")`) check(t, output, `flags_completion+=("__complete_custom")`)
// check for subdirs_in_dir flags // check for subdirs_in_dir flags
check(t, output, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`) check(t, output, fmt.Sprintf(`flags_completion+=("__%s_handle_subdirs_in_dir_flag themes")`, rootCmd.Name()))
checkOmit(t, output, deprecatedCmd.Name()) checkOmit(t, output, deprecatedCmd.Name())