Fix --version help and output for plugins (#2180)

* Fix --version help with CommandDisplayNameAnnotation

When setting Command.Version, a --version option is added. The help
message for the --version command did not consider the command display
name:

    Flags:
      -h, --help      help for kubectl plugin
      -v, --version   version for kubectl-plugin

With this change the help test is consistent with other flags:

    Flags:
      -h, --help      help for kubectl plugin
      -v, --version   version for kubectl plugin

* Make command DisplayName() public

This allows using the display name in templates or other code that want
to use the same value.

* Use display name in version template

The version template used `{{.Name}}` but for plugins you want to use
`{{.DisplayName}}` to be consistent with other help output.

With this change will show:

    $ kubectl plugin --version
    kubectl plugin version 1.0.0
This commit is contained in:
Nir Soffer 2024-10-12 19:08:27 +03:00 committed by GitHub
parent ff7c561cf7
commit 5bef9d8d87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 19 deletions

View File

@ -607,7 +607,7 @@ func (c *Command) VersionTemplate() string {
if c.HasParent() { if c.HasParent() {
return c.parent.VersionTemplate() return c.parent.VersionTemplate()
} }
return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}} return `{{with .DisplayName}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}}
` `
} }
@ -1190,7 +1190,7 @@ func (c *Command) InitDefaultHelpFlag() {
c.mergePersistentFlags() c.mergePersistentFlags()
if c.Flags().Lookup("help") == nil { if c.Flags().Lookup("help") == nil {
usage := "help for " usage := "help for "
name := c.displayName() name := c.DisplayName()
if name == "" { if name == "" {
usage += "this command" usage += "this command"
} else { } else {
@ -1216,7 +1216,7 @@ func (c *Command) InitDefaultVersionFlag() {
if c.Name() == "" { if c.Name() == "" {
usage += "this command" usage += "this command"
} else { } else {
usage += c.Name() usage += c.DisplayName()
} }
if c.Flags().ShorthandLookup("v") == nil { if c.Flags().ShorthandLookup("v") == nil {
c.Flags().BoolP("version", "v", false, usage) c.Flags().BoolP("version", "v", false, usage)
@ -1240,7 +1240,7 @@ func (c *Command) InitDefaultHelpCmd() {
Use: "help [command]", Use: "help [command]",
Short: "Help about any command", Short: "Help about any command",
Long: `Help provides help for any command in the application. Long: `Help provides help for any command in the application.
Simply type ` + c.displayName() + ` help [path to command] for full details.`, Simply type ` + c.DisplayName() + ` help [path to command] for full details.`,
ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) { ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
var completions []string var completions []string
cmd, _, e := c.Root().Find(args) cmd, _, e := c.Root().Find(args)
@ -1431,10 +1431,12 @@ func (c *Command) CommandPath() string {
if c.HasParent() { if c.HasParent() {
return c.Parent().CommandPath() + " " + c.Name() return c.Parent().CommandPath() + " " + c.Name()
} }
return c.displayName() return c.DisplayName()
} }
func (c *Command) displayName() string { // DisplayName returns the name to display in help text. Returns command Name()
// If CommandDisplayNameAnnoation is not set
func (c *Command) DisplayName() string {
if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok { if displayName, ok := c.Annotations[CommandDisplayNameAnnotation]; ok {
return displayName return displayName
} }
@ -1444,7 +1446,7 @@ func (c *Command) displayName() string {
// UseLine puts out the full usage for a given command (including parents). // UseLine puts out the full usage for a given command (including parents).
func (c *Command) UseLine() string { func (c *Command) UseLine() string {
var useline string var useline string
use := strings.Replace(c.Use, c.Name(), c.displayName(), 1) use := strings.Replace(c.Use, c.Name(), c.DisplayName(), 1)
if c.HasParent() { if c.HasParent() {
useline = c.parent.CommandPath() + " " + use useline = c.parent.CommandPath() + " " + use
} else { } else {
@ -1650,7 +1652,7 @@ func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) f
// to this command (local and persistent declared here and by all parents). // to this command (local and persistent declared here and by all parents).
func (c *Command) Flags() *flag.FlagSet { func (c *Command) Flags() *flag.FlagSet {
if c.flags == nil { if c.flags == nil {
c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.flags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil { if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
} }
@ -1665,7 +1667,7 @@ func (c *Command) Flags() *flag.FlagSet {
func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
persistentFlags := c.PersistentFlags() persistentFlags := c.PersistentFlags()
out := flag.NewFlagSet(c.displayName(), flag.ContinueOnError) out := flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
c.LocalFlags().VisitAll(func(f *flag.Flag) { c.LocalFlags().VisitAll(func(f *flag.Flag) {
if persistentFlags.Lookup(f.Name) == nil { if persistentFlags.Lookup(f.Name) == nil {
out.AddFlag(f) out.AddFlag(f)
@ -1680,7 +1682,7 @@ func (c *Command) LocalFlags() *flag.FlagSet {
c.mergePersistentFlags() c.mergePersistentFlags()
if c.lflags == nil { if c.lflags == nil {
c.lflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.lflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil { if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
} }
@ -1708,7 +1710,7 @@ func (c *Command) InheritedFlags() *flag.FlagSet {
c.mergePersistentFlags() c.mergePersistentFlags()
if c.iflags == nil { if c.iflags == nil {
c.iflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.iflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil { if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
} }
@ -1737,7 +1739,7 @@ func (c *Command) NonInheritedFlags() *flag.FlagSet {
// PersistentFlags returns the persistent FlagSet specifically set in the current command. // PersistentFlags returns the persistent FlagSet specifically set in the current command.
func (c *Command) PersistentFlags() *flag.FlagSet { func (c *Command) PersistentFlags() *flag.FlagSet {
if c.pflags == nil { if c.pflags == nil {
c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.pflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
if c.flagErrorBuf == nil { if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
} }
@ -1750,9 +1752,9 @@ func (c *Command) PersistentFlags() *flag.FlagSet {
func (c *Command) ResetFlags() { func (c *Command) ResetFlags() {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
c.flagErrorBuf.Reset() c.flagErrorBuf.Reset()
c.flags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.flags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
c.flags.SetOutput(c.flagErrorBuf) c.flags.SetOutput(c.flagErrorBuf)
c.pflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.pflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
c.pflags.SetOutput(c.flagErrorBuf) c.pflags.SetOutput(c.flagErrorBuf)
c.lflags = nil c.lflags = nil
@ -1869,7 +1871,7 @@ func (c *Command) mergePersistentFlags() {
// If c.parentsPflags == nil, it makes new. // If c.parentsPflags == nil, it makes new.
func (c *Command) updateParentsPflags() { func (c *Command) updateParentsPflags() {
if c.parentsPflags == nil { if c.parentsPflags == nil {
c.parentsPflags = flag.NewFlagSet(c.displayName(), flag.ContinueOnError) c.parentsPflags = flag.NewFlagSet(c.DisplayName(), flag.ContinueOnError)
c.parentsPflags.SetOutput(c.flagErrorBuf) c.parentsPflags.SetOutput(c.flagErrorBuf)
c.parentsPflags.SortFlags = false c.parentsPflags.SortFlags = false
} }

View File

@ -372,6 +372,7 @@ func TestAliasPrefixMatching(t *testing.T) {
func TestPlugin(t *testing.T) { func TestPlugin(t *testing.T) {
cmd := &Command{ cmd := &Command{
Use: "kubectl-plugin", Use: "kubectl-plugin",
Version: "1.0.0",
Args: NoArgs, Args: NoArgs,
Annotations: map[string]string{ Annotations: map[string]string{
CommandDisplayNameAnnotation: "kubectl plugin", CommandDisplayNameAnnotation: "kubectl plugin",
@ -386,12 +387,14 @@ func TestPlugin(t *testing.T) {
checkStringContains(t, cmdHelp, "kubectl plugin [flags]") checkStringContains(t, cmdHelp, "kubectl plugin [flags]")
checkStringContains(t, cmdHelp, "help for kubectl plugin") checkStringContains(t, cmdHelp, "help for kubectl plugin")
checkStringContains(t, cmdHelp, "version for kubectl plugin")
} }
// TestPlugin checks usage as plugin with sub commands. // TestPlugin checks usage as plugin with sub commands.
func TestPluginWithSubCommands(t *testing.T) { func TestPluginWithSubCommands(t *testing.T) {
rootCmd := &Command{ rootCmd := &Command{
Use: "kubectl-plugin", Use: "kubectl-plugin",
Version: "1.0.0",
Args: NoArgs, Args: NoArgs,
Annotations: map[string]string{ Annotations: map[string]string{
CommandDisplayNameAnnotation: "kubectl plugin", CommandDisplayNameAnnotation: "kubectl plugin",
@ -408,6 +411,7 @@ func TestPluginWithSubCommands(t *testing.T) {
checkStringContains(t, rootHelp, "kubectl plugin [command]") checkStringContains(t, rootHelp, "kubectl plugin [command]")
checkStringContains(t, rootHelp, "help for kubectl plugin") checkStringContains(t, rootHelp, "help for kubectl plugin")
checkStringContains(t, rootHelp, "version for kubectl plugin")
checkStringContains(t, rootHelp, "kubectl plugin [command] --help") checkStringContains(t, rootHelp, "kubectl plugin [command] --help")
childHelp, err := executeCommand(rootCmd, "sub", "-h") childHelp, err := executeCommand(rootCmd, "sub", "-h")
@ -1090,6 +1094,24 @@ func TestVersionFlagExecuted(t *testing.T) {
checkStringContains(t, output, "root version 1.0.0") checkStringContains(t, output, "root version 1.0.0")
} }
func TestVersionFlagExecutedDiplayName(t *testing.T) {
rootCmd := &Command{
Use: "kubectl-plugin",
Version: "1.0.0",
Annotations: map[string]string{
CommandDisplayNameAnnotation: "kubectl plugin",
},
Run: emptyRun,
}
output, err := executeCommand(rootCmd, "--version", "arg1")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
checkStringContains(t, output, "kubectl plugin version 1.0.0")
}
func TestVersionFlagExecutedWithNoName(t *testing.T) { func TestVersionFlagExecutedWithNoName(t *testing.T) {
rootCmd := &Command{Version: "1.0.0", Run: emptyRun} rootCmd := &Command{Version: "1.0.0", Run: emptyRun}