Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (
)

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

// Builder is a function that constructs and returns a [Command], it makes constructing
Expand Down Expand Up @@ -51,8 +53,8 @@ func New(name string, options ...Option) (*Command, error) {
stderr: os.Stderr,
args: os.Args[1:],
name: name,
version: "dev",
short: "A placeholder for something cool",
version: defaultVersion,
short: defaultShort,
argValidator: AnyArgs(),
}

Expand Down Expand Up @@ -218,7 +220,7 @@ func (cmd *Command) Execute() error {
}

if helpCalled {
if err := defaultHelp(cmd); err != nil {
if err := showHelp(cmd); err != nil {
return fmt.Errorf("help function returned an error: %w", err)
}

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

if versionCalled {
if err := defaultVersion(cmd); err != nil {
return fmt.Errorf("version function returned an error: %w", err)
if err := showVersion(cmd); err != nil {
return fmt.Errorf("could not show version: %w", err)
}

return nil
Expand Down Expand Up @@ -279,7 +281,7 @@ func (cmd *Command) Execute() error {

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

Expand Down Expand Up @@ -487,8 +489,8 @@ func stripFlags(cmd *Command, args []string) []string {
return argsWithoutFlags
}

// defaultHelp is the default for a command's helpFunc.
func defaultHelp(cmd *Command) error {
// showHelp is the default for a command's helpFunc.
func showHelp(cmd *Command) error {
if cmd == nil {
return errors.New("defaultHelp called on a nil Command")
}
Expand Down Expand Up @@ -580,6 +582,8 @@ func defaultHelp(cmd *Command) error {
writeFooter(cmd, s)
}

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

return nil
Expand Down Expand Up @@ -700,15 +704,22 @@ func writeFooter(cmd *Command, s *strings.Builder) {
s.WriteByte('\n')
}

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

name := cmd.name // Incase we need to show the subcommand name

if cmd.version == defaultVersion {
// User has not set a version for this command, so we show the root version info
cmd = cmd.root()
}

s := &strings.Builder{}
s.Grow(versionBufferSize)
s.WriteString(style.Title.Text(cmd.name))
s.WriteString(style.Title.Text(name))
s.WriteString("\n\n")
s.WriteString(style.Bold.Text("Version:"))
s.WriteString(" ")
Expand All @@ -729,7 +740,9 @@ func defaultVersion(cmd *Command) error {
s.WriteString("\n")
}

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

return nil
}
55 changes: 54 additions & 1 deletion command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,31 @@ func TestHelp(t *testing.T) {
}

func TestVersion(t *testing.T) {
sub1 := func() (*cli.Command, error) {
return cli.New(
"sub1",
cli.Short("Do one thing"),
// No version set on sub1
cli.Run(func(cmd *cli.Command, _ []string) error {
fmt.Fprintln(cmd.Stdout(), "Hello from sub1")

return nil
}))
}

sub2 := func() (*cli.Command, error) {
return cli.New(
"sub2",
cli.Short("Do another thing"),
cli.Version("sub2 version text"),
cli.Run(func(cmd *cli.Command, _ []string) error {
fmt.Fprintln(cmd.Stdout(), "Hello from sub2")

return nil
}),
)
}

tests := []struct {
name string // Name of the test case
stderr string // Expected output to stderr
Expand Down Expand Up @@ -719,6 +744,34 @@ func TestVersion(t *testing.T) {
stderr: "version-test\n\nVersion: v8.17.6\nCommit: b9aaafd\nBuildDate: 2024-08-17T10:37:30Z\n",
wantErr: false,
},
{
name: "call on subcommand with no version",
options: []cli.Option{
cli.OverrideArgs([]string{"sub1", "--version"}),
cli.Version("v8.17.6"),
cli.Commit("b9aaafd"),
cli.BuildDate("2024-08-17T10:37:30Z"),
cli.SubCommands(sub1, sub2),
cli.Run(func(cmd *cli.Command, args []string) error { return nil }),
},
// Should show the root commands version info
stderr: "sub1\n\nVersion: v8.17.6\nCommit: b9aaafd\nBuildDate: 2024-08-17T10:37:30Z\n",
wantErr: false,
},
{
name: "call on subcommand with version",
options: []cli.Option{
cli.OverrideArgs([]string{"sub2", "--version"}),
cli.Version("v8.17.6"),
cli.Commit("b9aaafd"),
cli.BuildDate("2024-08-17T10:37:30Z"),
cli.SubCommands(sub1, sub2),
cli.Run(func(cmd *cli.Command, args []string) error { return nil }),
},
// Should show sub2's version text
stderr: "sub2\n\nVersion: sub2 version text\n",
wantErr: false,
},
}

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

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

err = cmd.Execute()
Expand Down
1 change: 1 addition & 0 deletions examples/subcommands/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func buildDoCommand() (*cli.Command, error) {
cli.Example("Do something", "demo do something --fast"),
cli.Example("Do it 3 times", "demo do something --count 3"),
cli.Example("Do it for a specific duration", "demo do something --duration 1m30s"),
cli.Version("do version"),
cli.Allow(cli.ExactArgs(1)), // Only allowed to do one thing
cli.Flag(&options.count, "count", 'c', 1, "Number of times to do the thing"),
cli.Flag(&options.fast, "fast", 'f', false, "Do the thing quickly"),
Expand Down
Loading