From 6fd8e29b07d8242ebe2888060fede5766e240c25 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 21 Aug 2018 11:12:02 -0500 Subject: [PATCH] Qualify custom bash func name (#730) * Qualify custom bash func name - fixes issue where multiple cobra apps using custom bash completion would have their __custom_func collide - support fallback to plain __custom_func to maintain compatibility #694 * Improve tests for bash completion __custom_func - check for the correct number of occurrences of function name #694 --- bash_completions.go | 8 +++++++- bash_completions.md | 4 ++-- bash_completions_test.go | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 8fa8f486..63274d96 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -129,7 +129,13 @@ __%[1]s_handle_reply() fi if [[ ${#COMPREPLY[@]} -eq 0 ]]; then - declare -F __custom_func >/dev/null && __custom_func + if declare -F __%[1]s_custom_func >/dev/null; then + # try command name qualified custom func + __%[1]s_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F ___custom_func >/dev/null && __custom_func + fi fi # available in bash-completion >= 2, not always present on macOS diff --git a/bash_completions.md b/bash_completions.md index 49d06806..3bd32e0c 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -82,7 +82,7 @@ __kubectl_get_resource() fi } -__custom_func() { +__kubectl_custom_func() { case ${last_command} in kubectl_get | kubectl_describe | kubectl_delete | kubectl_stop) __kubectl_get_resource @@ -109,7 +109,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, } ``` -The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__custom_func()` to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! +The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `___kubectl_custom_func()` (`___custom_func()`) to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__kubectl_customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__kubectl_custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! ## Have the completions code complete your 'nouns' diff --git a/bash_completions_test.go b/bash_completions_test.go index 02a4f15b..94b965da 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -22,6 +22,13 @@ func check(t *testing.T, found, expected string) { } } +func checkNumOccurrences(t *testing.T, found, expected string, expectedOccurrences int) { + numOccurrences := strings.Count(found, expected) + if numOccurrences != expectedOccurrences { + t.Errorf("Expecting to contain %d occurrences of: \n %q\nGot %d:\n %q\n", expectedOccurrences, expected, numOccurrences, found) + } +} + func checkRegex(t *testing.T, found, pattern string) { matched, err := regexp.MatchString(pattern, found) if err != nil { @@ -53,7 +60,7 @@ func runShellCheck(s string) error { } // World worst custom function, just keep telling you to enter hello! -const bashCompletionFunc = `__custom_func() { +const bashCompletionFunc = `__root_custom_func() { COMPREPLY=( "hello" ) } ` @@ -150,7 +157,10 @@ func TestBashCompletions(t *testing.T) { // check for required flags check(t, output, `must_have_one_flag+=("--introot=")`) check(t, output, `must_have_one_flag+=("--persistent-filename=")`) - // check for custom completion function + // check for custom completion function with both qualified and unqualified name + checkNumOccurrences(t, output, `__custom_func`, 2) // 1. check existence, 2. invoke + checkNumOccurrences(t, output, `__root_custom_func`, 3) // 1. check existence, 2. invoke, 3. actual definition + // check for custom completion function body check(t, output, `COMPREPLY=( "hello" )`) // check for required nouns check(t, output, `must_have_one_noun+=("pod")`)