diff --git a/command.go b/command.go index c05fed45a..9ff6290e1 100644 --- a/command.go +++ b/command.go @@ -225,6 +225,9 @@ type Command struct { commandsMaxUseLen int commandsMaxCommandPathLen int commandsMaxNameLen int + namePadding int + usagePadding int + commandPathPadding int // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool @@ -377,6 +380,24 @@ func (c *Command) SetErrPrefix(s string) { c.errPrefix = s } +// SetNamePadding sets the padding width used when the command lists its subcommands. +// A padding value of zero clears the override and reverts to the automatic calculation. +func (c *Command) SetNamePadding(padding int) { + c.namePadding = padding +} + +// SetUsagePadding sets the padding width used for aligning the usage column. +// A padding value of zero clears the override and reverts to the automatic calculation. +func (c *Command) SetUsagePadding(padding int) { + c.usagePadding = padding +} + +// SetCommandPathPadding sets the padding width used for aligning command paths. +// A padding value of zero clears the override and reverts to the automatic calculation. +func (c *Command) SetCommandPathPadding(padding int) { + c.commandPathPadding = padding +} + // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. // The user should not have a cyclic dependency on commands. func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { @@ -561,9 +582,21 @@ const minUsagePadding = 25 // UsagePadding return padding for the usage. func (c *Command) UsagePadding() int { - if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen { + if c.parent == nil { + if c.usagePadding > 0 { + return c.usagePadding + } return minUsagePadding } + + if c.parent.usagePadding > 0 { + return c.parent.usagePadding + } + + if minUsagePadding > c.parent.commandsMaxUseLen { + return minUsagePadding + } + return c.parent.commandsMaxUseLen } @@ -571,9 +604,21 @@ const minCommandPathPadding = 11 // CommandPathPadding return padding for the command path. func (c *Command) CommandPathPadding() int { - if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen { + if c.parent == nil { + if c.commandPathPadding > 0 { + return c.commandPathPadding + } return minCommandPathPadding } + + if c.parent.commandPathPadding > 0 { + return c.parent.commandPathPadding + } + + if minCommandPathPadding > c.parent.commandsMaxCommandPathLen { + return minCommandPathPadding + } + return c.parent.commandsMaxCommandPathLen } @@ -581,9 +626,21 @@ const minNamePadding = 11 // NamePadding returns padding for the name. func (c *Command) NamePadding() int { - if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen { + if c.parent == nil { + if c.namePadding > 0 { + return c.namePadding + } return minNamePadding } + + if c.parent.namePadding > 0 { + return c.parent.namePadding + } + + if minNamePadding > c.parent.commandsMaxNameLen { + return minNamePadding + } + return c.parent.commandsMaxNameLen } diff --git a/command_test.go b/command_test.go index a86e57f0a..1edfb19fb 100644 --- a/command_test.go +++ b/command_test.go @@ -1165,6 +1165,117 @@ func TestSetUsageTemplate(t *testing.T) { } } +func TestSetNamePadding(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + shortCmd := &Command{Use: "short", Run: emptyRun} + longCmd := &Command{Use: "a-very-long-command", Run: emptyRun} + rootCmd.AddCommand(shortCmd, longCmd) + + defaultPadding := shortCmd.NamePadding() + expectedDefault := len(longCmd.Name()) + if defaultPadding != expectedDefault { + t.Fatalf("expected default padding %d, got %d", expectedDefault, defaultPadding) + } + + rootCmd.SetNamePadding(42) + if got := shortCmd.NamePadding(); got != 42 { + t.Fatalf("expected override padding 42, got %d", got) + } + + if got := rootCmd.NamePadding(); got != 42 { + t.Fatalf("expected root NamePadding to honor override, got %d", got) + } + + rootCmd.SetNamePadding(0) + if got := shortCmd.NamePadding(); got != expectedDefault { + t.Fatalf("expected clearing override to restore default %d, got %d", expectedDefault, got) + } + + nestedParent := &Command{Use: "nested-parent", Run: emptyRun} + child := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(nestedParent) + nestedParent.AddCommand(child) + + nestedParent.SetNamePadding(25) + if got := child.NamePadding(); got != 25 { + t.Fatalf("expected nested override 25, got %d", got) + } +} + +func TestSetUsagePadding(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + shortCmd := &Command{Use: "short", Run: emptyRun} + longCmd := &Command{Use: "this-command-has-a-long-use-line", Run: emptyRun} + rootCmd.AddCommand(shortCmd, longCmd) + + defaultPadding := shortCmd.UsagePadding() + expectedDefault := len(longCmd.Use) + if defaultPadding != expectedDefault { + t.Fatalf("expected default usage padding %d, got %d", expectedDefault, defaultPadding) + } + + rootCmd.SetUsagePadding(64) + if got := shortCmd.UsagePadding(); got != 64 { + t.Fatalf("expected override usage padding 64, got %d", got) + } + + if got := rootCmd.UsagePadding(); got != 64 { + t.Fatalf("expected root usage padding to honor override, got %d", got) + } + + rootCmd.SetUsagePadding(0) + if got := shortCmd.UsagePadding(); got != expectedDefault { + t.Fatalf("expected clearing usage padding override to restore %d, got %d", expectedDefault, got) + } + + nestedParent := &Command{Use: "nested-parent", Run: emptyRun} + child := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(nestedParent) + nestedParent.AddCommand(child) + + nestedParent.SetUsagePadding(33) + if got := child.UsagePadding(); got != 33 { + t.Fatalf("expected nested usage padding override 33, got %d", got) + } +} + +func TestSetCommandPathPadding(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + shortCmd := &Command{Use: "short", Run: emptyRun} + longCmd := &Command{Use: "long-child-command", Run: emptyRun} + rootCmd.AddCommand(shortCmd, longCmd) + + defaultPadding := shortCmd.CommandPathPadding() + expectedDefault := len(longCmd.CommandPath()) + if defaultPadding != expectedDefault { + t.Fatalf("expected default command path padding %d, got %d", expectedDefault, defaultPadding) + } + + rootCmd.SetCommandPathPadding(58) + if got := shortCmd.CommandPathPadding(); got != 58 { + t.Fatalf("expected override command path padding 58, got %d", got) + } + + if got := rootCmd.CommandPathPadding(); got != 58 { + t.Fatalf("expected root command path padding to honor override, got %d", got) + } + + rootCmd.SetCommandPathPadding(0) + if got := shortCmd.CommandPathPadding(); got != expectedDefault { + t.Fatalf("expected clearing command path padding override to restore %d, got %d", expectedDefault, got) + } + + nestedParent := &Command{Use: "nested-parent", Run: emptyRun} + child := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(nestedParent) + nestedParent.AddCommand(child) + + nestedParent.SetCommandPathPadding(29) + if got := child.CommandPathPadding(); got != 29 { + t.Fatalf("expected nested command path padding override 29, got %d", got) + } +} + func TestVersionFlagExecuted(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} diff --git a/site/content/user_guide.md b/site/content/user_guide.md index d339185c0..ef2e373c7 100644 --- a/site/content/user_guide.md +++ b/site/content/user_guide.md @@ -617,6 +617,9 @@ with the following functions: cmd.SetHelpCommand(cmd *Command) cmd.SetHelpFunc(f func(*Command, []string)) cmd.SetHelpTemplate(s string) +cmd.SetNamePadding(padding int) +cmd.SetUsagePadding(padding int) +cmd.SetCommandPathPadding(padding int) ``` The latter two will also apply to any children commands. @@ -624,6 +627,10 @@ The latter two will also apply to any children commands. Note that templates specified with `SetHelpTemplate` are evaluated using `text/template` which can increase the size of the compiled executable. +The `SetNamePadding` helper lets you explicitly control the alignment of the command names shown under "Available Commands". Call it on the parent command to override the automatically calculated padding width. Pass `0` to clear your override and return to the default behavior. + +Similarly, `SetUsagePadding` and `SetCommandPathPadding` allow you to pin the widths used for the usage column and command path listings (such as those shown under "Additional help topics"). Set these on the parent command to cascade the override to its children, or pass `0` to fall back to Cobra's automatic sizing. + ## Usage Message When the user provides an invalid flag or invalid command, Cobra responds by