mirror of
https://github.com/spf13/cobra
synced 2024-11-24 14:47:12 +00:00
Add env variable to suppress completion descriptions on create (#1938)
COBRA_COMPLETION_DESCRIPTIONS=0 or <PROGRAM>_COMPLETION_DESCRIPTIONS=0 can now be used to disable shell completion descriptions.
This commit is contained in:
parent
236f3c0418
commit
e63925d321
4 changed files with 237 additions and 11 deletions
|
@ -17,21 +17,17 @@ package cobra
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
activeHelpMarker = "_activeHelp_ "
|
activeHelpMarker = "_activeHelp_ "
|
||||||
// The below values should not be changed: programs will be using them explicitly
|
// The below values should not be changed: programs will be using them explicitly
|
||||||
// in their user documentation, and users will be using them explicitly.
|
// in their user documentation, and users will be using them explicitly.
|
||||||
activeHelpEnvVarSuffix = "_ACTIVE_HELP"
|
activeHelpEnvVarSuffix = "ACTIVE_HELP"
|
||||||
activeHelpGlobalEnvVar = "COBRA_ACTIVE_HELP"
|
activeHelpGlobalEnvVar = configEnvVarGlobalPrefix + "_" + activeHelpEnvVarSuffix
|
||||||
activeHelpGlobalDisable = "0"
|
activeHelpGlobalDisable = "0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var activeHelpEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
|
|
||||||
|
|
||||||
// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp.
|
// AppendActiveHelp adds the specified string to the specified array to be used as ActiveHelp.
|
||||||
// Such strings will be processed by the completion script and will be shown as ActiveHelp
|
// Such strings will be processed by the completion script and will be shown as ActiveHelp
|
||||||
// to the user.
|
// to the user.
|
||||||
|
@ -60,8 +56,5 @@ func GetActiveHelpConfig(cmd *Command) string {
|
||||||
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
|
// variable. It has the format <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the
|
||||||
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
func activeHelpEnvVar(name string) string {
|
func activeHelpEnvVar(name string) string {
|
||||||
// This format should not be changed: users will be using it explicitly.
|
return configEnvVar(name, activeHelpEnvVarSuffix)
|
||||||
activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix))
|
|
||||||
activeHelpEnvVar = activeHelpEnvVarPrefixSubstRegexp.ReplaceAllString(activeHelpEnvVar, "_")
|
|
||||||
return activeHelpEnvVar
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ package cobra
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -211,7 +213,12 @@ func (c *Command) initCompleteCmd(args []string) {
|
||||||
// 2- Even without completions, we need to print the directive
|
// 2- Even without completions, we need to print the directive
|
||||||
}
|
}
|
||||||
|
|
||||||
noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
|
noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd
|
||||||
|
if !noDescriptions {
|
||||||
|
if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil {
|
||||||
|
noDescriptions = !doDescriptions
|
||||||
|
}
|
||||||
|
}
|
||||||
noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable
|
noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable
|
||||||
out := finalCmd.OutOrStdout()
|
out := finalCmd.OutOrStdout()
|
||||||
for _, comp := range completions {
|
for _, comp := range completions {
|
||||||
|
@ -899,3 +906,34 @@ func CompError(msg string) {
|
||||||
func CompErrorln(msg string) {
|
func CompErrorln(msg string) {
|
||||||
CompError(fmt.Sprintf("%s\n", msg))
|
CompError(fmt.Sprintf("%s\n", msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These values should not be changed: users will be using them explicitly.
|
||||||
|
const (
|
||||||
|
configEnvVarGlobalPrefix = "COBRA"
|
||||||
|
configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
|
||||||
|
|
||||||
|
// configEnvVar returns the name of the program-specific configuration environment
|
||||||
|
// variable. It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the
|
||||||
|
// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
func configEnvVar(name, suffix string) string {
|
||||||
|
// This format should not be changed: users will be using it explicitly.
|
||||||
|
v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix))
|
||||||
|
v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnvConfig returns the value of the configuration environment variable
|
||||||
|
// <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper
|
||||||
|
// case, with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
// If the value is empty or not set, the value of the environment variable
|
||||||
|
// COBRA_<SUFFIX> is returned instead.
|
||||||
|
func getEnvConfig(cmd *Command, suffix string) string {
|
||||||
|
v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix))
|
||||||
|
if v == "" {
|
||||||
|
v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -3517,3 +3518,194 @@ func TestGetFlagCompletion(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetEnvConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
use string
|
||||||
|
suffix string
|
||||||
|
cmdVar string
|
||||||
|
globalVar string
|
||||||
|
cmdVal string
|
||||||
|
globalVal string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Command envvar overrides global",
|
||||||
|
use: "root",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "ROOT_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "cmd",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "cmd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Missing/empty command envvar falls back to global",
|
||||||
|
use: "root",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "ROOT_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "global",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Missing/empty command and global envvars fall back to empty",
|
||||||
|
use: "root",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "ROOT_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "",
|
||||||
|
globalVal: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Periods in command use transform to underscores in env var name",
|
||||||
|
use: "foo.bar",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "FOO_BAR_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "cmd",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "cmd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Dashes in command use transform to underscores in env var name",
|
||||||
|
use: "quux-BAZ",
|
||||||
|
suffix: "test",
|
||||||
|
cmdVar: "QUUX_BAZ_TEST",
|
||||||
|
globalVar: "COBRA_TEST",
|
||||||
|
cmdVal: "cmd",
|
||||||
|
globalVal: "global",
|
||||||
|
expected: "cmd",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
// Could make env handling cleaner with t.Setenv with Go >= 1.17
|
||||||
|
err := os.Setenv(tc.cmdVar, tc.cmdVal)
|
||||||
|
defer func() {
|
||||||
|
assertNoErr(t, os.Unsetenv(tc.cmdVar))
|
||||||
|
}()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
err = os.Setenv(tc.globalVar, tc.globalVal)
|
||||||
|
defer func() {
|
||||||
|
assertNoErr(t, os.Unsetenv(tc.globalVar))
|
||||||
|
}()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
cmd := &Command{Use: tc.use}
|
||||||
|
got := getEnvConfig(cmd, tc.suffix)
|
||||||
|
if got != tc.expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", tc.expected, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisableDescriptions(t *testing.T) {
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
childCmd := &Command{
|
||||||
|
Use: "thechild",
|
||||||
|
Short: "The child command",
|
||||||
|
Run: emptyRun,
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(childCmd)
|
||||||
|
|
||||||
|
specificDescriptionsEnvVar := configEnvVar(rootCmd.Name(), configEnvVarSuffixDescriptions)
|
||||||
|
globalDescriptionsEnvVar := configEnvVar(configEnvVarGlobalPrefix, configEnvVarSuffixDescriptions)
|
||||||
|
|
||||||
|
const (
|
||||||
|
descLineWithDescription = "first\tdescription"
|
||||||
|
descLineWithoutDescription = "first"
|
||||||
|
)
|
||||||
|
childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
comps := []string{descLineWithDescription}
|
||||||
|
return comps, ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
globalEnvValue string
|
||||||
|
specificEnvValue string
|
||||||
|
expectedLine string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"No env variables set",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
descLineWithDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Global value false",
|
||||||
|
"false",
|
||||||
|
"",
|
||||||
|
descLineWithoutDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Specific value false",
|
||||||
|
"",
|
||||||
|
"false",
|
||||||
|
descLineWithoutDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Both values false",
|
||||||
|
"false",
|
||||||
|
"false",
|
||||||
|
descLineWithoutDescription,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Both values true",
|
||||||
|
"true",
|
||||||
|
"true",
|
||||||
|
descLineWithDescription,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
if err := os.Setenv(specificDescriptionsEnvVar, tc.specificEnvValue); err != nil {
|
||||||
|
t.Errorf("Unexpected error setting %s: %v", specificDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
if err := os.Setenv(globalDescriptionsEnvVar, tc.globalEnvValue); err != nil {
|
||||||
|
t.Errorf("Unexpected error setting %s: %v", globalDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var run = func() {
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "thechild", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
tc.expectedLine,
|
||||||
|
":0",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
|
|
||||||
|
// For empty cases, test also unset state
|
||||||
|
if tc.specificEnvValue == "" {
|
||||||
|
if err := os.Unsetenv(specificDescriptionsEnvVar); err != nil {
|
||||||
|
t.Errorf("Unexpected error unsetting %s: %v", specificDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
if tc.globalEnvValue == "" {
|
||||||
|
if err := os.Unsetenv(globalDescriptionsEnvVar); err != nil {
|
||||||
|
t.Errorf("Unexpected error unsetting %s: %v", globalDescriptionsEnvVar, err)
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -393,6 +393,9 @@ $ source <(helm completion bash --no-descriptions)
|
||||||
$ helm completion [tab][tab]
|
$ helm completion [tab][tab]
|
||||||
bash fish powershell zsh
|
bash fish powershell zsh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Setting the `<PROGRAM>_COMPLETION_DESCRIPTIONS` environment variable (falling back to `COBRA_COMPLETION_DESCRIPTIONS` if empty or not set) to a [falsey value](https://pkg.go.dev/strconv#ParseBool) achieves the same. `<PROGRAM>` is the name of your program with all non-ASCII-alphanumeric characters replaced by `_`.
|
||||||
|
|
||||||
## Bash completions
|
## Bash completions
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
Loading…
Reference in a new issue