diff --git a/active_help.go b/active_help.go index b3efa4875..4809a69c3 100644 --- a/active_help.go +++ b/active_help.go @@ -26,6 +26,18 @@ func AppendActiveHelp(compArray []string, activeHelpStr string) []string { return append(compArray, fmt.Sprintf("%s%s", activeHelpMarker, activeHelpStr)) } +// GetActiveHelpConfig reads the ActiveHelp environment variable +// of the program and returns its value. +// It will always return "0" if the global environment variable COBRA_ACTIVE_HELP +// is set to "0". +func GetActiveHelpConfig(cmd *Command) string { + activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar) + if activeHelpCfg != activeHelpGlobalDisable { + activeHelpCfg = os.Getenv(activeHelpEnvVar(cmd.Root().Name())) + } + return activeHelpCfg +} + // activeHelpEnvVar returns the name of the program-specific ActiveHelp environment // variable. It has the format _ACTIVE_HELP where is the name of the // root command in upper case, with all - replaced by _. @@ -34,19 +46,3 @@ func activeHelpEnvVar(name string) string { activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) return strings.ReplaceAll(activeHelpEnvVar, "-", "_") } - -// setActiveHelpConfig first checks the global environment variable -// of ActiveHelp to see if it is disabling active help, and if it is not, -// it then looks to the program-specific variable. -// It then sets the ActiveHelpConfig value to make it available when -// calling the completion function. We also set it on the root, -// just in case users try to access it from there. -func setActiveHelpConfig(cmd *Command) { - activeHelpCfg := os.Getenv(activeHelpGlobalEnvVar) - if activeHelpCfg != activeHelpGlobalDisable { - activeHelpCfg = os.Getenv(activeHelpEnvVar(cmd.Root().Name())) - } - - cmd.ActiveHelpConfig = activeHelpCfg - cmd.Root().ActiveHelpConfig = cmd.ActiveHelpConfig -} diff --git a/active_help.md b/active_help.md index fb2d19074..4b48b437c 100644 --- a/active_help.md +++ b/active_help.md @@ -86,26 +86,23 @@ bash-5.1$ bin/helm install myrelease bitnami/solr --version 2.0.[tab][tab] You may want to allow your users to disable Active Help or choose between different levels of Active Help. It is entirely up to the program to define the type of configurability of Active Help that it wants to offer. -### Configuration using an environment variable - -One way to configure Active Help is to use the program's Active Help environment +The way to configure Active Help is to use the program's Active Help environment variable. That variable is named `_ACTIVE_HELP` where `` is the name of your -program in uppercase with any `-` replaced by an `_`. You can find that variable in the generated -completion scripts of your program. The variable should be set by the user to whatever Active Help -configuration values are supported by the program. +program in uppercase with any `-` replaced by an `_`. The variable should be set by the user to whatever +Active Help configuration values are supported by the program. -For example, say `helm` supports three levels for Active Help: `on`, `off`, `local`. Then a user +For example, say `helm` has chosen to support three levels for Active Help: `on`, `off`, `local`. Then a user would set the desired behavior to `local` by doing `export HELM_ACTIVE_HELP=local` in their shell. -When in `cmd.ValidArgsFunction(...)` or a flag's completion function, the program should read the -Active Help configuration from the `cmd.ActiveHelpConfig` field and select what Active Help messages -should or should not be added. +For simplicity, when in `cmd.ValidArgsFunction(...)` or a flag's completion function, the program should read the +Active Help configuration using the `cobra.GetActiveHelpConfig(cmd)` function and select what Active Help messages +should or should not be added (instead of reading the environment variable directly). For example: ```go ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - activeHelpLevel := cmd.ActiveHelpConfig - + activeHelpLevel := cobra.GetActiveHelpConfig(cmd) + var comps []string if len(args) == 0 { if activeHelpLevel != "off" { @@ -123,47 +120,22 @@ ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([ return comps, cobra.ShellCompDirectiveNoFileComp }, ``` -**Note 1**: If the string "0" is used for `cmd.Root().ActiveHelpConfig`, it will automatically be handled by Cobra and will completely disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help. - -**Note 2**: Cobra transparently passes the `cmd.ActiveHelpConfig` string you specified back to your program when completion is invoked. You can therefore define any scheme you choose for your program; you are not limited to using integer levels for the configuration of Active Help. **However, the reserved "0" value can also be sent to you program and you should be prepared for it.** +**Note 1**: If the `_ACTIVE_HELP` environment variable is set to the string "0", Cobra will automatically disable all Active Help output (even if some output was specified by the program using the `cobra.AppendActiveHelp(...)` function). Using "0" can simplify your code in situations where you want to blindly disable Active Help without having to call `cobra.GetActiveHelpConfig(cmd)` explicitly. -**Note 3**: If a user wants to disable Active Help for every single program based on Cobra, the global environment variable `COBRA_ACTIVE_HELP` can be used as follows: +**Note 2**: If a user wants to disable Active Help for every single program based on Cobra, the global environment variable `COBRA_ACTIVE_HELP` can be used as follows: ``` export COBRA_ACTIVE_HELP=0 ``` - -### Configuration using a flag - -Another approach for a user to configure Active Help is for the program to add a flag to the command that generates -the completion script. Using the flag, the user specifies the Active Help configuration that is -desired. Then the program should specify that configuration by setting the `rootCmd.ActiveHelpConfig` string -before calling the Cobra API that generates the shell completion script. -The ActiveHelp configuration would then be read in `cmd.ValidArgsFunction(...)` or a flag's completion function, in the same -fashion as explained above, using the same `cmd.ActiveHelpConfig` field. - -For example, a program that uses a `completion` command to generate the shell completion script can add a flag `--activehelp-level` to that command. The user would then use that flag to choose an Active Help level: -``` -bash-5.1$ source <(helm completion bash --activehelp-level 1) -``` -The code to pass that information to Cobra would look something like: -```go -cmd.Root().ActiveHelpConfig = strconv.Itoa(activeHelpLevel) -return cmd.Root().GenBashCompletionV2(out, true) -``` - -The advantage of using a flag is that it becomes self-documenting through Cobra's `help` command. Also, it allows you to -use Cobra's flag parsing to handle the configuration value, instead of having to deal with a environment variable natively. - ## Active Help with Cobra's default completion command Cobra provides a default `completion` command for programs that wish to use it. -When using the default `completion` command, Active Help is configurable using the -environment variable approach described above. You may wish to document this in more +When using the default `completion` command, Active Help is configurable in the same +fashion as described above using environment variables. You may wish to document this in more details for your users. ## Debugging Active Help -Debugging your Active Help code is done in the same way as debugging the dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details. +Debugging your Active Help code is done in the same way as debugging your dynamic completion code, which is with Cobra's hidden `__complete` command. Please refer to [debugging shell completion](shell_completions.md#debugging) for details. When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable (as you can find in the generated completion scripts). That variable is named `_ACTIVE_HELP` where any `-` is replaced by an `_`. For example, we can test deactivating some Active Help as shown below: ``` diff --git a/active_help_test.go b/active_help_test.go index 28fc3b36c..524be884b 100644 --- a/active_help_test.go +++ b/active_help_test.go @@ -263,17 +263,13 @@ func TestConfigActiveHelp(t *testing.T) { rootCmd.AddCommand(childCmd) activeHelpCfg := "someconfig,anotherconfig" - // Set the variable that the completions scripts will be setting + // Set the variable that the user would be setting os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg) childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { - // The activeHelpConfig variable should be set on the command - if cmd.ActiveHelpConfig != activeHelpCfg { - t.Errorf("expected activeHelpConfig on command: %q, but got: %q", activeHelpCfg, cmd.ActiveHelpConfig) - } - // The activeHelpConfig variable should also be set on the root - if cmd.Root().ActiveHelpConfig != activeHelpCfg { - t.Errorf("expected activeHelpConfig on root: %q, but got: %q", activeHelpCfg, cmd.Root().ActiveHelpConfig) + receivedActiveHelpCfg := GetActiveHelpConfig(cmd) + if receivedActiveHelpCfg != activeHelpCfg { + t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg) } return nil, ShellCompDirectiveDefault } @@ -293,13 +289,9 @@ func TestConfigActiveHelp(t *testing.T) { // Test that multiple activeHelp message can be added _ = childCmd.RegisterFlagCompletionFunc(flagname, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { - // The activeHelpConfig variable should be set on the command - if cmd.ActiveHelpConfig != activeHelpCfg { - t.Errorf("expected activeHelpConfig on command: %q, but got: %q", activeHelpCfg, cmd.ActiveHelpConfig) - } - // The activeHelpConfig variable should also be set on the root - if cmd.Root().ActiveHelpConfig != activeHelpCfg { - t.Errorf("expected activeHelpConfig on root: %q, but got: %q", activeHelpCfg, cmd.Root().ActiveHelpConfig) + receivedActiveHelpCfg := GetActiveHelpConfig(cmd) + if receivedActiveHelpCfg != activeHelpCfg { + t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg) } return nil, ShellCompDirectiveDefault }) @@ -380,13 +372,9 @@ func TestDisableActiveHelp(t *testing.T) { os.Setenv(activeHelpEnvVar(rootCmd.Name()), activeHelpCfg) childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { - // The activeHelpConfig variable should be set on the command - if cmd.ActiveHelpConfig != activeHelpCfg { - t.Errorf("expected activeHelpConfig on command: %q, but got: %q", activeHelpCfg, cmd.ActiveHelpConfig) - } - // The activeHelpConfig variable should also be set on the root - if cmd.Root().ActiveHelpConfig != activeHelpCfg { - t.Errorf("expected activeHelpConfig on root: %q, but got: %q", activeHelpCfg, cmd.Root().ActiveHelpConfig) + receivedActiveHelpCfg := GetActiveHelpConfig(cmd) + if receivedActiveHelpCfg != activeHelpCfg { + t.Errorf("expected activeHelpConfig: %q, but got: %q", activeHelpCfg, receivedActiveHelpCfg) } return nil, ShellCompDirectiveDefault } diff --git a/bash_completionsV2.go b/bash_completionsV2.go index 3e9bb612b..20b2c58c9 100644 --- a/bash_completionsV2.go +++ b/bash_completionsV2.go @@ -9,12 +9,12 @@ import ( func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) - genBashComp(buf, c, includeDesc) + genBashComp(buf, c.Name(), includeDesc) _, err := buf.WriteTo(w) return err } -func genBashComp(buf io.StringWriter, cmd *Command, includeDesc bool) { +func genBashComp(buf io.StringWriter, name string, includeDesc bool) { compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd @@ -45,7 +45,7 @@ __%[1]s_get_completion_results() { # Prepare the command to request completions for the program. # Calling ${words[0]} instead of directly %[1]s allows to handle aliases args=("${words[@]:1}") - requestComp="%[9]s=${%[9]s-%[10]s} ${words[0]} %[2]s ${args[*]}" + requestComp="${words[0]} %[2]s ${args[*]}" lastParam=${words[$((${#words[@]}-1))]} lastChar=${lastParam:$((${#lastParam}-1)):1} @@ -350,10 +350,10 @@ else fi # ex: ts=4 sw=4 et filetype=sh -`, cmd.Name(), compCmd, +`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - activeHelpMarker, activeHelpEnvVar(cmd.Name()), cmd.Root().ActiveHelpConfig)) + activeHelpMarker)) } // GenBashCompletionFileV2 generates Bash completion version 2. diff --git a/command.go b/command.go index e2c4a98b1..2cc18891d 100644 --- a/command.go +++ b/command.go @@ -222,23 +222,6 @@ type Command struct { // SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions. // Must be > 0. SuggestionsMinimumDistance int - - // ActiveHelpConfig is a string that can be used to communicate the level of activeHelp - // the user is interested in receiving. - // The program can set this string before generating the completion scripts. - // When setting this string, it MUST be set on the Root command. - // - // The program should read this string from within ValidArgsFunction or the flag value - // completion functions to make decisions on whether or not to append activeHelp messages. - // This string can be read directly from the command passed to the completion functions, - // or from the Root command. - // - // If the value 0 is used, it will automatically be handled by Cobra and - // will completely disable activeHelp output, even if some output was specified by - // the program. - // Any other value will not be interpreted by Cobra but only provided back - // to the program when ValidArgsFunction is called. - ActiveHelpConfig string } // Context returns underlying command context. If command wasn't diff --git a/completions.go b/completions.go index e725ab876..3a6a0777f 100644 --- a/completions.go +++ b/completions.go @@ -170,7 +170,7 @@ func (c *Command) initCompleteCmd(args []string) { noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) for _, comp := range completions { - if finalCmd.ActiveHelpConfig == activeHelpGlobalDisable { + if GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable { // Remove all activeHelp entries in this case if strings.HasPrefix(comp, activeHelpMarker) { continue @@ -435,9 +435,6 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // Go custom completion defined for this flag or command. // Call the registered completion function to get the completions. var comps []string - - setActiveHelpConfig(finalCmd) - comps, directive = completionFn(finalCmd, finalArgs, toComplete) completions = append(completions, comps...) } diff --git a/zsh_completions.go b/zsh_completions.go index f3f30070f..5878d0991 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -65,12 +65,12 @@ func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) - genZshComp(buf, c, includeDesc) + genZshComp(buf, c.Name(), includeDesc) _, err := buf.WriteTo(w) return err } -func genZshComp(buf io.StringWriter, cmd *Command, includeDesc bool) { +func genZshComp(buf io.StringWriter, name string, includeDesc bool) { compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd @@ -121,7 +121,7 @@ _%[1]s() fi # Prepare the command to obtain completions - requestComp="%[9]s=${%[9]s-%[10]s} ${words[1]} %[2]s ${words[2,-1]}" + requestComp="${words[1]} %[2]s ${words[2,-1]}" if [ "${lastChar}" = "" ]; then # If the last parameter is complete (there is a space following it) # We add an extra empty parameter so we can indicate this to the go completion code. @@ -280,8 +280,8 @@ _%[1]s() if [ "$funcstack[1]" = "_%[1]s" ]; then _%[1]s fi -`, cmd.Name(), compCmd, +`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, - activeHelpMarker, activeHelpEnvVar(cmd.Name()), cmd.ActiveHelpConfig)) + activeHelpMarker)) }