Merge branch 'main' into completion

This commit is contained in:
maxlandon 2023-11-02 14:22:13 +01:00
commit 13d042d545
No known key found for this signature in database
GPG key ID: 2DE5C14975A86900
12 changed files with 314 additions and 93 deletions

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- run: >- - run: >-
docker run docker run
@ -39,15 +39,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v4
with: with:
go-version: '^1.21' go-version: '^1.21'
check-latest: true check-latest: true
cache: true cache: true
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: golangci/golangci-lint-action@v3.7.0 - uses: golangci/golangci-lint-action@v3.7.0
with: with:
@ -72,9 +72,9 @@ jobs:
runs-on: ${{ matrix.platform }}-latest runs-on: ${{ matrix.platform }}-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v4
with: with:
go-version: 1.${{ matrix.go }}.x go-version: 1.${{ matrix.go }}.x
cache: true cache: true
@ -108,7 +108,7 @@ jobs:
unzip unzip
mingw-w64-x86_64-go mingw-w64-x86_64-go
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/cache@v3 - uses: actions/cache@v3
with: with:

View file

@ -17,6 +17,7 @@ package cobra
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
) )
@ -29,6 +30,8 @@ const (
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.
@ -42,7 +45,7 @@ func AppendActiveHelp(compArray []string, activeHelpStr string) []string {
// GetActiveHelpConfig returns the value of the ActiveHelp environment variable // GetActiveHelpConfig returns the value of the ActiveHelp environment variable
// <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the root command in upper // <PROGRAM>_ACTIVE_HELP where <PROGRAM> is the name of the root command in upper
// case, with all - replaced by _. // case, with all non-ASCII-alphanumeric characters replaced by `_`.
// It will always return "0" if the global environment variable COBRA_ACTIVE_HELP // It will always return "0" if the global environment variable COBRA_ACTIVE_HELP
// is set to "0". // is set to "0".
func GetActiveHelpConfig(cmd *Command) string { func GetActiveHelpConfig(cmd *Command) string {
@ -55,9 +58,10 @@ func GetActiveHelpConfig(cmd *Command) string {
// activeHelpEnvVar returns the name of the program-specific ActiveHelp environment // activeHelpEnvVar returns the name of the program-specific ActiveHelp environment
// 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 - 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. // This format should not be changed: users will be using it explicitly.
activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix)) activeHelpEnvVar := strings.ToUpper(fmt.Sprintf("%s%s", name, activeHelpEnvVarSuffix))
return strings.ReplaceAll(activeHelpEnvVar, "-", "_") activeHelpEnvVar = activeHelpEnvVarPrefixSubstRegexp.ReplaceAllString(activeHelpEnvVar, "_")
return activeHelpEnvVar
} }

View file

@ -46,6 +46,7 @@ const (
defaultPrefixMatching = false defaultPrefixMatching = false
defaultCommandSorting = true defaultCommandSorting = true
defaultCaseInsensitive = false defaultCaseInsensitive = false
defaultTraverseRunHooks = false
) )
// EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing // EnablePrefixMatching allows setting automatic prefix matching. Automatic prefix matching can be a dangerous thing
@ -60,6 +61,10 @@ var EnableCommandSorting = defaultCommandSorting
// EnableCaseInsensitive allows case-insensitive commands names. (case sensitive by default) // EnableCaseInsensitive allows case-insensitive commands names. (case sensitive by default)
var EnableCaseInsensitive = defaultCaseInsensitive var EnableCaseInsensitive = defaultCaseInsensitive
// EnableTraverseRunHooks executes persistent pre-run and post-run hooks from all parents.
// By default this is disabled, which means only the first run hook to be found is executed.
var EnableTraverseRunHooks = defaultTraverseRunHooks
// MousetrapHelpText enables an information splash screen on Windows // MousetrapHelpText enables an information splash screen on Windows
// if the CLI is started from explorer.exe. // if the CLI is started from explorer.exe.
// To disable the mousetrap, just set this variable to blank string (""). // To disable the mousetrap, just set this variable to blank string ("").

View file

@ -31,7 +31,10 @@ import (
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
const FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra" const (
FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra"
CommandDisplayNameAnnotation = "cobra_annotation_command_display_name"
)
// FParseErrWhitelist configures Flag parse errors to be ignored // FParseErrWhitelist configures Flag parse errors to be ignored
type FParseErrWhitelist flag.ParseErrorsWhitelist type FParseErrWhitelist flag.ParseErrorsWhitelist
@ -100,7 +103,7 @@ type Command struct {
Deprecated string Deprecated string
// Annotations are key/value pairs that can be used by applications to identify or // Annotations are key/value pairs that can be used by applications to identify or
// group commands. // group commands or set special options.
Annotations map[string]string Annotations map[string]string
// Version defines the version for this command. If this value is non-empty and the command does not // Version defines the version for this command. If this value is non-empty and the command does not
@ -116,6 +119,8 @@ type Command struct {
// * PostRun() // * PostRun()
// * PersistentPostRun() // * PersistentPostRun()
// All functions get the same args, the arguments after the command name. // All functions get the same args, the arguments after the command name.
// The *PreRun and *PostRun functions will only be executed if the Run function of the current
// command has been declared.
// //
// PersistentPreRun: children of this command will inherit and execute. // PersistentPreRun: children of this command will inherit and execute.
PersistentPreRun func(cmd *Command, args []string) PersistentPreRun func(cmd *Command, args []string)
@ -940,17 +945,33 @@ func (c *Command) execute(a []string) (err error) {
return err return err
} }
parents := make([]*Command, 0, 5)
for p := c; p != nil; p = p.Parent() { for p := c; p != nil; p = p.Parent() {
if EnableTraverseRunHooks {
// When EnableTraverseRunHooks is set:
// - Execute all persistent pre-runs from the root parent till this command.
// - Execute all persistent post-runs from this command till the root parent.
parents = append([]*Command{p}, parents...)
} else {
// Otherwise, execute only the first found persistent hook.
parents = append(parents, p)
}
}
for _, p := range parents {
if p.PersistentPreRunE != nil { if p.PersistentPreRunE != nil {
if err := p.PersistentPreRunE(c, argWoFlags); err != nil { if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
return err return err
} }
if !EnableTraverseRunHooks {
break break
}
} else if p.PersistentPreRun != nil { } else if p.PersistentPreRun != nil {
p.PersistentPreRun(c, argWoFlags) p.PersistentPreRun(c, argWoFlags)
if !EnableTraverseRunHooks {
break break
} }
} }
}
if c.PreRunE != nil { if c.PreRunE != nil {
if err := c.PreRunE(c, argWoFlags); err != nil { if err := c.PreRunE(c, argWoFlags); err != nil {
return err return err
@ -985,12 +1006,16 @@ func (c *Command) execute(a []string) (err error) {
if err := p.PersistentPostRunE(c, argWoFlags); err != nil { if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
return err return err
} }
if !EnableTraverseRunHooks {
break break
}
} else if p.PersistentPostRun != nil { } else if p.PersistentPostRun != nil {
p.PersistentPostRun(c, argWoFlags) p.PersistentPostRun(c, argWoFlags)
if !EnableTraverseRunHooks {
break break
} }
} }
}
return nil return nil
} }
@ -1410,6 +1435,9 @@ func (c *Command) CommandPath() string {
if c.HasParent() { if c.HasParent() {
return c.Parent().CommandPath() + " " + c.Name() return c.Parent().CommandPath() + " " + c.Name()
} }
if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok {
return displayName
}
return c.Name() return c.Name()
} }
@ -1432,6 +1460,7 @@ func (c *Command) UseLine() string {
// DebugFlags used to determine which flags have been assigned to which commands // DebugFlags used to determine which flags have been assigned to which commands
// and which persist. // and which persist.
// nolint:goconst
func (c *Command) DebugFlags() { func (c *Command) DebugFlags() {
c.Println("DebugFlags called on", c.Name()) c.Println("DebugFlags called on", c.Name())
var debugflags func(*Command) var debugflags func(*Command)

View file

@ -366,6 +366,36 @@ func TestAliasPrefixMatching(t *testing.T) {
EnablePrefixMatching = defaultPrefixMatching EnablePrefixMatching = defaultPrefixMatching
} }
// TestPlugin checks usage as plugin for another command such as kubectl. The
// executable is `kubectl-plugin`, but we run it as `kubectl plugin`. The help
// text should reflect the way we run the command.
func TestPlugin(t *testing.T) {
rootCmd := &Command{
Use: "plugin",
Args: NoArgs,
Annotations: map[string]string{
CommandDisplayNameAnnotation: "kubectl plugin",
},
}
subCmd := &Command{Use: "sub [flags]", Args: NoArgs, Run: emptyRun}
rootCmd.AddCommand(subCmd)
rootHelp, err := executeCommand(rootCmd, "-h")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
checkStringContains(t, rootHelp, "kubectl plugin [command]")
childHelp, err := executeCommand(rootCmd, "sub", "-h")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
checkStringContains(t, childHelp, "kubectl plugin sub [flags]")
}
// TestChildSameName checks the correct behaviour of cobra in cases, // TestChildSameName checks the correct behaviour of cobra in cases,
// when an application with name "foo" and with subcommand "foo" // when an application with name "foo" and with subcommand "foo"
// is executed with args "foo foo". // is executed with args "foo foo".
@ -1530,57 +1560,73 @@ func TestHooks(t *testing.T) {
} }
func TestPersistentHooks(t *testing.T) { func TestPersistentHooks(t *testing.T) {
var ( EnableTraverseRunHooks = true
parentPersPreArgs string testPersistentHooks(t, []string{
parentPreArgs string "parent PersistentPreRun",
parentRunArgs string "child PersistentPreRun",
parentPostArgs string "child PreRun",
parentPersPostArgs string "child Run",
) "child PostRun",
"child PersistentPostRun",
"parent PersistentPostRun",
})
var ( EnableTraverseRunHooks = false
childPersPreArgs string testPersistentHooks(t, []string{
childPreArgs string "child PersistentPreRun",
childRunArgs string "child PreRun",
childPostArgs string "child Run",
childPersPostArgs string "child PostRun",
) "child PersistentPostRun",
})
}
func testPersistentHooks(t *testing.T, expectedHookRunOrder []string) {
var hookRunOrder []string
validateHook := func(args []string, hookName string) {
hookRunOrder = append(hookRunOrder, hookName)
got := strings.Join(args, " ")
if onetwo != got {
t.Errorf("Expected %s %q, got %q", hookName, onetwo, got)
}
}
parentCmd := &Command{ parentCmd := &Command{
Use: "parent", Use: "parent",
PersistentPreRun: func(_ *Command, args []string) { PersistentPreRun: func(_ *Command, args []string) {
parentPersPreArgs = strings.Join(args, " ") validateHook(args, "parent PersistentPreRun")
}, },
PreRun: func(_ *Command, args []string) { PreRun: func(_ *Command, args []string) {
parentPreArgs = strings.Join(args, " ") validateHook(args, "parent PreRun")
}, },
Run: func(_ *Command, args []string) { Run: func(_ *Command, args []string) {
parentRunArgs = strings.Join(args, " ") validateHook(args, "parent Run")
}, },
PostRun: func(_ *Command, args []string) { PostRun: func(_ *Command, args []string) {
parentPostArgs = strings.Join(args, " ") validateHook(args, "parent PostRun")
}, },
PersistentPostRun: func(_ *Command, args []string) { PersistentPostRun: func(_ *Command, args []string) {
parentPersPostArgs = strings.Join(args, " ") validateHook(args, "parent PersistentPostRun")
}, },
} }
childCmd := &Command{ childCmd := &Command{
Use: "child", Use: "child",
PersistentPreRun: func(_ *Command, args []string) { PersistentPreRun: func(_ *Command, args []string) {
childPersPreArgs = strings.Join(args, " ") validateHook(args, "child PersistentPreRun")
}, },
PreRun: func(_ *Command, args []string) { PreRun: func(_ *Command, args []string) {
childPreArgs = strings.Join(args, " ") validateHook(args, "child PreRun")
}, },
Run: func(_ *Command, args []string) { Run: func(_ *Command, args []string) {
childRunArgs = strings.Join(args, " ") validateHook(args, "child Run")
}, },
PostRun: func(_ *Command, args []string) { PostRun: func(_ *Command, args []string) {
childPostArgs = strings.Join(args, " ") validateHook(args, "child PostRun")
}, },
PersistentPostRun: func(_ *Command, args []string) { PersistentPostRun: func(_ *Command, args []string) {
childPersPostArgs = strings.Join(args, " ") validateHook(args, "child PersistentPostRun")
}, },
} }
parentCmd.AddCommand(childCmd) parentCmd.AddCommand(childCmd)
@ -1593,41 +1639,13 @@ func TestPersistentHooks(t *testing.T) {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
for _, v := range []struct { for idx, exp := range expectedHookRunOrder {
name string if len(hookRunOrder) > idx {
got string if act := hookRunOrder[idx]; act != exp {
}{ t.Errorf("Expected %q at %d, got %q", exp, idx, act)
// TODO: currently PersistentPreRun* defined in parent does not
// run if the matching child subcommand has PersistentPreRun.
// If the behavior changes (https://github.com/spf13/cobra/issues/252)
// this test must be fixed.
{"parentPersPreArgs", parentPersPreArgs},
{"parentPreArgs", parentPreArgs},
{"parentRunArgs", parentRunArgs},
{"parentPostArgs", parentPostArgs},
// TODO: currently PersistentPostRun* defined in parent does not
// run if the matching child subcommand has PersistentPostRun.
// If the behavior changes (https://github.com/spf13/cobra/issues/252)
// this test must be fixed.
{"parentPersPostArgs", parentPersPostArgs},
} {
if v.got != "" {
t.Errorf("Expected blank %s, got %q", v.name, v.got)
} }
} } else {
t.Errorf("Expected %q at %d, got nothing", exp, idx)
for _, v := range []struct {
name string
got string
}{
{"childPersPreArgs", childPersPreArgs},
{"childPreArgs", childPreArgs},
{"childRunArgs", childRunArgs},
{"childPostArgs", childPostArgs},
{"childPersPostArgs", childPersPostArgs},
} {
if v.got != onetwo {
t.Errorf("Expected %s %q, got %q", v.name, onetwo, v.got)
} }
} }
} }

View file

@ -335,9 +335,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// These flags are normally added when `execute()` is called on `finalCmd`, // These flags are normally added when `execute()` is called on `finalCmd`,
// however, when doing completion, we don't call `finalCmd.execute()`. // however, when doing completion, we don't call `finalCmd.execute()`.
// Let's add the --help and --version flag ourselves. // Let's add the --help and --version flag ourselves but only if the finalCmd
// has not disabled flag parsing; if flag parsing is disabled, it is up to the
// finalCmd itself to handle the completion of *all* flags.
if !finalCmd.DisableFlagParsing {
finalCmd.InitDefaultHelpFlag() finalCmd.InitDefaultHelpFlag()
finalCmd.InitDefaultVersionFlag() finalCmd.InitDefaultVersionFlag()
}
// Check if we are doing flag value completion before parsing the flags. // Check if we are doing flag value completion before parsing the flags.
// This is important because if we are completing a flag value, we need to also // This is important because if we are completing a flag value, we need to also
@ -441,6 +445,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
doCompleteFlags(flag) doCompleteFlags(flag)
}) })
// Try to complete non-inherited flags even if DisableFlagParsing==true.
// This allows programs to tell Cobra about flags for completion even
// if the actual parsing of flags is not done by Cobra.
// For instance, Helm uses this to provide flag name completion for
// some of its plugins.
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
doCompleteFlags(flag) doCompleteFlags(flag)
}) })

View file

@ -17,7 +17,9 @@ package cobra
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"strings" "strings"
"sync"
"testing" "testing"
) )
@ -2040,6 +2042,114 @@ func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) {
} }
} }
func TestFlagCompletionForPersistentFlagsCalledFromSubCmd(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
rootCmd.PersistentFlags().String("string", "", "test string flag")
_ = rootCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"myval"}, ShellCompDirectiveDefault
})
childCmd := &Command{
Use: "child",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"--validarg", "test"}, ShellCompDirectiveDefault
},
}
childCmd.Flags().Bool("bool", false, "test bool flag")
rootCmd.AddCommand(childCmd)
// Test that persistent flag completion works for the subcmd
output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
"myval",
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
// This test tries to register flag completion concurrently to make sure the
// code handles concurrency properly.
// This was reported as a problem when tests are run concurrently:
// https://github.com/spf13/cobra/issues/1320
//
// NOTE: this test can sometimes pass even if the code were to not handle
// concurrency properly. This is not great but the important part is that
// it should never fail. Therefore, if the tests fails sometimes, we will
// still be able to know there is a problem.
func TestFlagCompletionConcurrentRegistration(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
const maxFlags = 50
for i := 1; i < maxFlags; i += 2 {
flagName := fmt.Sprintf("flag%d", i)
rootCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on root", flagName))
}
childCmd := &Command{
Use: "child",
Run: emptyRun,
}
for i := 2; i <= maxFlags; i += 2 {
flagName := fmt.Sprintf("flag%d", i)
childCmd.Flags().String(flagName, "", fmt.Sprintf("test %s flag on child", flagName))
}
rootCmd.AddCommand(childCmd)
// Register completion in different threads to test concurrency.
var wg sync.WaitGroup
for i := 1; i <= maxFlags; i++ {
index := i
flagName := fmt.Sprintf("flag%d", i)
wg.Add(1)
go func() {
defer wg.Done()
cmd := rootCmd
if index%2 == 0 {
cmd = childCmd
}
_ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{fmt.Sprintf("flag%d", index)}, ShellCompDirectiveDefault
})
}()
}
wg.Wait()
// Test that flag completion works for each flag
for i := 1; i <= 6; i++ {
var output string
var err error
flagName := fmt.Sprintf("flag%d", i)
if i%2 == 1 {
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--"+flagName, "")
} else {
output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--"+flagName, "")
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
expected := strings.Join([]string{
flagName,
":0",
"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}
}
}
func TestFlagCompletionInGoWithDesc(t *testing.T) { func TestFlagCompletionInGoWithDesc(t *testing.T) {
rootCmd := &Command{ rootCmd := &Command{
Use: "root", Use: "root",
@ -2622,8 +2732,6 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) {
expected := strings.Join([]string{ expected := strings.Join([]string{
"--persistent", "--persistent",
"-p", "-p",
"--help",
"-h",
"--nonPersistent", "--nonPersistent",
"-n", "-n",
"--flag", "--flag",
@ -3053,8 +3161,26 @@ func TestCompletionCobraFlags(t *testing.T) {
return []string{"extra3"}, ShellCompDirectiveNoFileComp return []string{"extra3"}, ShellCompDirectiveNoFileComp
}, },
} }
childCmd4 := &Command{
Use: "child4",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra4"}, ShellCompDirectiveNoFileComp
},
DisableFlagParsing: true,
}
childCmd5 := &Command{
Use: "child5",
Version: "1.1.1",
Run: emptyRun,
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
return []string{"extra5"}, ShellCompDirectiveNoFileComp
},
DisableFlagParsing: true,
}
rootCmd.AddCommand(childCmd, childCmd2, childCmd3) rootCmd.AddCommand(childCmd, childCmd2, childCmd3, childCmd4, childCmd5)
_ = childCmd.Flags().Bool("bool", false, "A bool flag") _ = childCmd.Flags().Bool("bool", false, "A bool flag")
_ = childCmd.MarkFlagRequired("bool") _ = childCmd.MarkFlagRequired("bool")
@ -3066,6 +3192,10 @@ func TestCompletionCobraFlags(t *testing.T) {
// Have a command that only adds its own -v flag // Have a command that only adds its own -v flag
_ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag") _ = childCmd3.Flags().BoolP("verbose", "v", false, "Not a version flag")
// Have a command that DisablesFlagParsing but that also adds its own help and version flags
_ = childCmd5.Flags().BoolP("help", "h", false, "My own help")
_ = childCmd5.Flags().BoolP("version", "v", false, "My own version")
return rootCmd return rootCmd
} }
@ -3196,6 +3326,26 @@ func TestCompletionCobraFlags(t *testing.T) {
":4", ":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"), "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
}, },
{
desc: "no completion for --help/-h and --version/-v flags when DisableFlagParsing=true",
args: []string{"child4", "-"},
expectedOutput: strings.Join([]string{
"extra4",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
{
desc: "completions for program-defined --help/-h and --version/-v flags even when DisableFlagParsing=true",
args: []string{"child5", "-"},
expectedOutput: strings.Join([]string{
"--help",
"-h",
"--version",
"-v",
"extra5",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n"),
},
} }
for _, tc := range testcases { for _, tc := range testcases {

View file

@ -27,6 +27,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const markdownExtension = ".md"
func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error {
flags := cmd.NonInheritedFlags() flags := cmd.NonInheritedFlags()
flags.SetOutput(buf) flags.SetOutput(buf)
@ -83,7 +85,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
if cmd.HasParent() { if cmd.HasParent() {
parent := cmd.Parent() parent := cmd.Parent()
pname := parent.CommandPath() pname := parent.CommandPath()
link := pname + ".md" link := pname + markdownExtension
link = strings.ReplaceAll(link, " ", "_") link = strings.ReplaceAll(link, " ", "_")
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short)) buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short))
cmd.VisitParents(func(c *cobra.Command) { cmd.VisitParents(func(c *cobra.Command) {
@ -101,7 +103,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
continue continue
} }
cname := name + " " + child.Name() cname := name + " " + child.Name()
link := cname + ".md" link := cname + markdownExtension
link = strings.ReplaceAll(link, " ", "_") link = strings.ReplaceAll(link, " ", "_")
buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short)) buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short))
} }
@ -138,7 +140,7 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa
} }
} }
basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + ".md" basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + markdownExtension
filename := filepath.Join(dir, basename) filename := filepath.Join(dir, basename)
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {

2
go.mod
View file

@ -3,7 +3,7 @@ module github.com/spf13/cobra
go 1.15 go 1.15
require ( require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 github.com/cpuguy83/go-md2man/v2 v2.0.3
github.com/inconshreveable/mousetrap v1.1.0 github.com/inconshreveable/mousetrap v1.1.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1

4
go.sum
View file

@ -1,5 +1,5 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=

View file

@ -92,7 +92,7 @@ Allowing to configure Active Help is entirely optional; you can use Active Help
The 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 `<PROGRAM>_ACTIVE_HELP` where `<PROGRAM>` is the name of your variable. That variable is named `<PROGRAM>_ACTIVE_HELP` where `<PROGRAM>` is the name of your
program in uppercase with any `-` replaced by an `_`. The variable should be set by the user to whatever program in uppercase with any non-ASCII-alphanumeric characters replaced by an `_`. The variable should be set by the user to whatever
Active Help configuration values are supported by the program. Active Help configuration values are supported by the program.
For example, say `helm` has chosen to support 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
@ -140,7 +140,7 @@ details for your users.
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. 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. That variable is named `<PROGRAM>_ACTIVE_HELP` where any `-` is replaced by an `_`. For example, we can test deactivating some Active Help as shown below: When debugging with the `__complete` command, if you want to specify different Active Help configurations, you should use the active help environment variable. That variable is named `<PROGRAM>_ACTIVE_HELP` where any non-ASCII-alphanumeric characters are replaced by an `_`. For example, we can test deactivating some Active Help as shown below:
``` ```
$ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h<ENTER> $ HELM_ACTIVE_HELP=1 bin/helm __complete install wordpress bitnami/h<ENTER>
bitnami/haproxy bitnami/haproxy

View file

@ -604,7 +604,7 @@ The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` fu
## PreRun and PostRun Hooks ## PreRun and PostRun Hooks
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. The `*PreRun` and `*PostRun` functions will only be executed if the `Run` function of the current command has been declared. These functions are run in the following order:
- `PersistentPreRun` - `PersistentPreRun`
- `PreRun` - `PreRun`
@ -687,6 +687,10 @@ Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2] Inside subCmd PersistentPostRun with args: [arg1 arg2]
``` ```
By default, only the first persistent hook found in the command chain is executed.
That is why in the above output, the `rootCmd PersistentPostRun` was not called for a child command.
Set `EnableTraverseRunHooks` global variable to `true` if you want to execute all parents' persistent hooks.
## Suggestions when "unknown command" happens ## Suggestions when "unknown command" happens
Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example: Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example: