Skip to content

Commit 87ee10b

Browse files
If a subcommand does not set a version, use the root command's version (#176)
1 parent aa45774 commit 87ee10b

3 files changed

Lines changed: 82 additions & 15 deletions

File tree

command.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import (
1717
)
1818

1919
const (
20-
helpBufferSize = 1024 // helpBufferSize is sufficient to hold most command --help text.
21-
versionBufferSize = 256 // versionBufferSize is sufficient to hold all the --version text.
20+
helpBufferSize = 1024 // helpBufferSize is sufficient to hold most command --help text.
21+
versionBufferSize = 256 // versionBufferSize is sufficient to hold all the --version text.
22+
defaultVersion = "dev" // defaultVersion is the version shown in --version when the user has not provided one.
23+
defaultShort = "A placeholder for something cool" // defaultShort is the default value for cli.Short.
2224
)
2325

2426
// Builder is a function that constructs and returns a [Command], it makes constructing
@@ -51,8 +53,8 @@ func New(name string, options ...Option) (*Command, error) {
5153
stderr: os.Stderr,
5254
args: os.Args[1:],
5355
name: name,
54-
version: "dev",
55-
short: "A placeholder for something cool",
56+
version: defaultVersion,
57+
short: defaultShort,
5658
argValidator: AnyArgs(),
5759
}
5860

@@ -218,7 +220,7 @@ func (cmd *Command) Execute() error {
218220
}
219221

220222
if helpCalled {
221-
if err := defaultHelp(cmd); err != nil {
223+
if err := showHelp(cmd); err != nil {
222224
return fmt.Errorf("help function returned an error: %w", err)
223225
}
224226

@@ -234,8 +236,8 @@ func (cmd *Command) Execute() error {
234236
}
235237

236238
if versionCalled {
237-
if err := defaultVersion(cmd); err != nil {
238-
return fmt.Errorf("version function returned an error: %w", err)
239+
if err := showVersion(cmd); err != nil {
240+
return fmt.Errorf("could not show version: %w", err)
239241
}
240242

241243
return nil
@@ -279,7 +281,7 @@ func (cmd *Command) Execute() error {
279281

280282
// The only way we get here is if the command has subcommands defined but got no arguments given to it
281283
// so just show the usage and error
282-
if err := defaultHelp(cmd); err != nil {
284+
if err := showHelp(cmd); err != nil {
283285
return err
284286
}
285287

@@ -487,8 +489,8 @@ func stripFlags(cmd *Command, args []string) []string {
487489
return argsWithoutFlags
488490
}
489491

490-
// defaultHelp is the default for a command's helpFunc.
491-
func defaultHelp(cmd *Command) error {
492+
// showHelp is the default for a command's helpFunc.
493+
func showHelp(cmd *Command) error {
492494
if cmd == nil {
493495
return errors.New("defaultHelp called on a nil Command")
494496
}
@@ -580,6 +582,8 @@ func defaultHelp(cmd *Command) error {
580582
writeFooter(cmd, s)
581583
}
582584

585+
// Note: It's important to use cmd.Stderr() here over cmd.stderr
586+
// as it resolves to the root's stderr
583587
fmt.Fprint(cmd.Stderr(), s.String())
584588

585589
return nil
@@ -700,15 +704,22 @@ func writeFooter(cmd *Command, s *strings.Builder) {
700704
s.WriteByte('\n')
701705
}
702706

703-
// defaultVersion is the default for a command's versionFunc.
704-
func defaultVersion(cmd *Command) error {
707+
// showVersion is the default implementation of the --version flag.
708+
func showVersion(cmd *Command) error {
705709
if cmd == nil {
706710
return errors.New("defaultVersion called on a nil Command")
707711
}
708712

713+
name := cmd.name // Incase we need to show the subcommand name
714+
715+
if cmd.version == defaultVersion {
716+
// User has not set a version for this command, so we show the root version info
717+
cmd = cmd.root()
718+
}
719+
709720
s := &strings.Builder{}
710721
s.Grow(versionBufferSize)
711-
s.WriteString(style.Title.Text(cmd.name))
722+
s.WriteString(style.Title.Text(name))
712723
s.WriteString("\n\n")
713724
s.WriteString(style.Bold.Text("Version:"))
714725
s.WriteString(" ")
@@ -729,7 +740,9 @@ func defaultVersion(cmd *Command) error {
729740
s.WriteString("\n")
730741
}
731742

732-
fmt.Fprint(cmd.stderr, s.String())
743+
// Note: It's important to use cmd.Stderr() here over cmd.stderr
744+
// as it resolves to the root's stderr
745+
fmt.Fprint(cmd.Stderr(), s.String())
733746

734747
return nil
735748
}

command_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,31 @@ func TestHelp(t *testing.T) {
642642
}
643643

644644
func TestVersion(t *testing.T) {
645+
sub1 := func() (*cli.Command, error) {
646+
return cli.New(
647+
"sub1",
648+
cli.Short("Do one thing"),
649+
// No version set on sub1
650+
cli.Run(func(cmd *cli.Command, _ []string) error {
651+
fmt.Fprintln(cmd.Stdout(), "Hello from sub1")
652+
653+
return nil
654+
}))
655+
}
656+
657+
sub2 := func() (*cli.Command, error) {
658+
return cli.New(
659+
"sub2",
660+
cli.Short("Do another thing"),
661+
cli.Version("sub2 version text"),
662+
cli.Run(func(cmd *cli.Command, _ []string) error {
663+
fmt.Fprintln(cmd.Stdout(), "Hello from sub2")
664+
665+
return nil
666+
}),
667+
)
668+
}
669+
645670
tests := []struct {
646671
name string // Name of the test case
647672
stderr string // Expected output to stderr
@@ -719,6 +744,34 @@ func TestVersion(t *testing.T) {
719744
stderr: "version-test\n\nVersion: v8.17.6\nCommit: b9aaafd\nBuildDate: 2024-08-17T10:37:30Z\n",
720745
wantErr: false,
721746
},
747+
{
748+
name: "call on subcommand with no version",
749+
options: []cli.Option{
750+
cli.OverrideArgs([]string{"sub1", "--version"}),
751+
cli.Version("v8.17.6"),
752+
cli.Commit("b9aaafd"),
753+
cli.BuildDate("2024-08-17T10:37:30Z"),
754+
cli.SubCommands(sub1, sub2),
755+
cli.Run(func(cmd *cli.Command, args []string) error { return nil }),
756+
},
757+
// Should show the root commands version info
758+
stderr: "sub1\n\nVersion: v8.17.6\nCommit: b9aaafd\nBuildDate: 2024-08-17T10:37:30Z\n",
759+
wantErr: false,
760+
},
761+
{
762+
name: "call on subcommand with version",
763+
options: []cli.Option{
764+
cli.OverrideArgs([]string{"sub2", "--version"}),
765+
cli.Version("v8.17.6"),
766+
cli.Commit("b9aaafd"),
767+
cli.BuildDate("2024-08-17T10:37:30Z"),
768+
cli.SubCommands(sub1, sub2),
769+
cli.Run(func(cmd *cli.Command, args []string) error { return nil }),
770+
},
771+
// Should show sub2's version text
772+
stderr: "sub2\n\nVersion: sub2 version text\n",
773+
wantErr: false,
774+
},
722775
}
723776

724777
for _, tt := range tests {
@@ -733,7 +786,7 @@ func TestVersion(t *testing.T) {
733786
cli.NoColour(true),
734787
}
735788

736-
cmd, err := cli.New("version-test", slices.Concat(tt.options, options)...)
789+
cmd, err := cli.New("version-test", slices.Concat(options, tt.options)...)
737790
test.Ok(t, err)
738791

739792
err = cmd.Execute()

examples/subcommands/cli.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func buildDoCommand() (*cli.Command, error) {
6161
cli.Example("Do something", "demo do something --fast"),
6262
cli.Example("Do it 3 times", "demo do something --count 3"),
6363
cli.Example("Do it for a specific duration", "demo do something --duration 1m30s"),
64+
cli.Version("do version"),
6465
cli.Allow(cli.ExactArgs(1)), // Only allowed to do one thing
6566
cli.Flag(&options.count, "count", 'c', 1, "Number of times to do the thing"),
6667
cli.Flag(&options.fast, "fast", 'f', false, "Do the thing quickly"),

0 commit comments

Comments
 (0)