This commit is contained in:
Jun Nishimura 2025-01-24 00:07:24 +09:00 committed by GitHub
commit 72f699a553
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 105 additions and 3 deletions

View file

@ -32,6 +32,7 @@ import (
const ( const (
FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra" FlagSetByCobraAnnotation = "cobra_annotation_flag_set_by_cobra"
CommandDisplayNameAnnotation = "cobra_annotation_command_display_name" CommandDisplayNameAnnotation = "cobra_annotation_command_display_name"
trueString = "true"
) )
// FParseErrWhitelist configures Flag parse errors to be ignored // FParseErrWhitelist configures Flag parse errors to be ignored
@ -1161,7 +1162,7 @@ func (c *Command) ValidateRequiredFlags() error {
if !found { if !found {
return return
} }
if (requiredAnnotation[0] == "true") && !pflag.Changed { if (requiredAnnotation[0] == trueString) && !pflag.Changed {
missingFlagNames = append(missingFlagNames, pflag.Name) missingFlagNames = append(missingFlagNames, pflag.Name)
} }
}) })
@ -1199,7 +1200,7 @@ func (c *Command) InitDefaultHelpFlag() {
usage += name usage += name
} }
c.Flags().BoolP("help", "h", false, usage) c.Flags().BoolP("help", "h", false, usage)
_ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{"true"}) _ = c.Flags().SetAnnotation("help", FlagSetByCobraAnnotation, []string{trueString})
} }
} }
@ -1225,7 +1226,7 @@ func (c *Command) InitDefaultVersionFlag() {
} else { } else {
c.Flags().Bool("version", false, usage) c.Flags().Bool("version", false, usage)
} }
_ = c.Flags().SetAnnotation("version", FlagSetByCobraAnnotation, []string{"true"}) _ = c.Flags().SetAnnotation("version", FlagSetByCobraAnnotation, []string{trueString})
} }
} }
@ -1262,6 +1263,23 @@ Simply type ` + c.DisplayName() + ` help [path to command] for full details.`,
} }
return completions, ShellCompDirectiveNoFileComp return completions, ShellCompDirectiveNoFileComp
}, },
PersistentPreRunE: func(cmd *Command, args []string) error {
cmd.Flags().VisitAll(func(pflag *flag.Flag) {
requiredAnnotation, found := pflag.Annotations[BashCompOneRequiredFlag]
if found && requiredAnnotation[0] == trueString {
// Disable any persistent required flags for the help command
pflag.Annotations[BashCompOneRequiredFlag] = []string{"false"}
}
})
// Adding PersistentPreRun on sub-commands prevents root's PersistentPreRun from being called.
// So it is intentionally called here.
if cmd.Root().PersistentPreRunE != nil {
return cmd.Root().PersistentPreRunE(cmd, args)
} else if cmd.Root().PersistentPreRun != nil {
cmd.Root().PersistentPreRun(cmd, args)
}
return nil
},
Run: func(c *Command, args []string) { Run: func(c *Command, args []string) {
cmd, _, e := c.Root().Find(args) cmd, _, e := c.Root().Find(args)
if cmd == nil || e != nil { if cmd == nil || e != nil {

View file

@ -950,6 +950,18 @@ func TestHelpCommandExecuted(t *testing.T) {
checkStringContains(t, output, rootCmd.Long) checkStringContains(t, output, rootCmd.Long)
} }
func TestHelpCommandExecutedWithPersistentRequiredFlags(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
rootCmd.PersistentFlags().Bool("foo", false, "")
childCmd := &Command{Use: "child", Run: emptyRun}
rootCmd.AddCommand(childCmd)
assertNoErr(t, rootCmd.MarkPersistentFlagRequired("foo"))
if _, err := executeCommand(rootCmd, "help"); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func TestHelpCommandExecutedOnChild(t *testing.T) { func TestHelpCommandExecutedOnChild(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Long: "Long description", Run: emptyRun} childCmd := &Command{Use: "child", Long: "Long description", Run: emptyRun}
@ -1703,6 +1715,25 @@ func testPersistentHooks(t *testing.T, expectedHookRunOrder []string) {
} }
} }
func TestPersistentPreRunHooksForHelpCommand(t *testing.T) {
executed := false
rootCmd := &Command{
Use: "root",
PersistentPreRun: func(*Command, []string) { executed = true },
Run: emptyRun,
}
childCmd := &Command{Use: "child", Run: emptyRun}
rootCmd.AddCommand(childCmd)
if _, err := executeCommand(rootCmd, "help"); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !executed {
t.Error("Root PersistentPreRun should have been executed")
}
}
// Related to https://github.com/spf13/cobra/issues/521. // Related to https://github.com/spf13/cobra/issues/521.
func TestGlobalNormFuncPropagation(t *testing.T) { func TestGlobalNormFuncPropagation(t *testing.T) {
normFunc := func(f *pflag.FlagSet, name string) pflag.NormalizedName { normFunc := func(f *pflag.FlagSet, name string) pflag.NormalizedName {

View file

@ -735,6 +735,23 @@ See each sub-command's help for details on how to use the generated script.
ValidArgsFunction: NoFileCompletions, ValidArgsFunction: NoFileCompletions,
Hidden: c.CompletionOptions.HiddenDefaultCmd, Hidden: c.CompletionOptions.HiddenDefaultCmd,
GroupID: c.completionCommandGroupID, GroupID: c.completionCommandGroupID,
PersistentPreRunE: func(cmd *Command, args []string) error {
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
requiredAnnotation, found := flag.Annotations[BashCompOneRequiredFlag]
if found && requiredAnnotation[0] == "true" {
// Disable any persistent required flags for the completion command
flag.Annotations[BashCompOneRequiredFlag] = []string{"false"}
}
})
// Adding PersistentPreRun on sub-commands prevents root's PersistentPreRun from being called.
// So it is intentionally called here.
if cmd.Root().PersistentPreRunE != nil {
return cmd.Root().PersistentPreRunE(cmd, args)
} else if cmd.Root().PersistentPreRun != nil {
cmd.Root().PersistentPreRun(cmd, args)
}
return nil
},
} }
c.AddCommand(completionCmd) c.AddCommand(completionCmd)

View file

@ -2591,6 +2591,42 @@ func TestDefaultCompletionCmd(t *testing.T) {
rootCmd.CompletionOptions.HiddenDefaultCmd = false rootCmd.CompletionOptions.HiddenDefaultCmd = false
// Remove completion command for the next test // Remove completion command for the next test
removeCompCmd(rootCmd) removeCompCmd(rootCmd)
// Test that required flag will be ignored
rootCmd.PersistentFlags().Bool("foo", false, "")
assertNoErr(t, rootCmd.MarkPersistentFlagRequired("foo"))
for _, shell := range []string{"bash", "fish", "powershell", "zsh"} {
if _, err = executeCommand(rootCmd, compCmdName, shell); err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
// Remove completion command for the next test
removeCompCmd(rootCmd)
}
func TestPersistentPreRunHooksForCompletionCommand(t *testing.T) {
executed := false
rootCmd := &Command{
Use: "root",
PersistentPreRun: func(*Command, []string) { executed = true },
Run: emptyRun,
}
subCmd := &Command{
Use: "sub",
Run: emptyRun,
}
rootCmd.AddCommand(subCmd)
for _, shell := range []string{"bash", "fish", "powershell", "zsh"} {
if _, err := executeCommand(rootCmd, compCmdName, shell); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !executed {
t.Error("Root PersistentPreRun should have been executed")
}
executed = false
}
} }
func TestCompleteCompletion(t *testing.T) { func TestCompleteCompletion(t *testing.T) {