diff --git a/command_test.go b/command_test.go index 7f0ab6d..4a3b6de 100644 --- a/command_test.go +++ b/command_test.go @@ -12,10 +12,14 @@ import ( var subCmd = new(mockSubCmd) type ( + // Mock for root/parent command. mockParentCmd struct{} - mockSubCmd struct { + // Mock subcommand with aliases and a nested sucommand of its own. + mockSubCmd struct { buf *bytes.Buffer } + // Mock subcommand with aliases and no nested subcommands. + mockSubCmdNoNested struct{} ) func (c *mockParentCmd) Run(fl *pflag.FlagSet) {} @@ -40,6 +44,10 @@ func (c *mockSubCmd) Run(fl *pflag.FlagSet) { } } +func (c *mockSubCmd) Subcommands() []Command { + return []Command{new(mockSubCmd)} +} + func (c *mockSubCmd) WriteString(s string) (int, error) { return c.buf.WriteString(s) } @@ -49,7 +57,18 @@ func (c *mockSubCmd) Spec() CommandSpec { Name: "mockSubCmd", Usage: "Test a subcommand.", Aliases: []string{"s", "sc", "sub"}, - Desc: "A simple mock subcommand with aliases.", + Desc: "A simple mock subcommand with aliases and its own subcommand.", + } +} + +func (c *mockSubCmdNoNested) Run(fl *pflag.FlagSet) {} + +func (c *mockSubCmdNoNested) Spec() CommandSpec { + return CommandSpec{ + Name: "mockSubCmdNoNested", + Usage: "Used for help output tests.", + Aliases: []string{"s", "sc", "sub"}, + Desc: "A simple mock subcommand with aliases and no nested subcommands.", } } @@ -73,10 +92,10 @@ func TestCmdHelpOutput(t *testing.T) { t.Run(t.Name(), func(t *testing.T) { expected := `Usage: mockParentCmd Mock parent command usage. -Mock parent command description. +Description: Mock parent command description. Commands: - s,sc,sub,mockSubCmd - A simple mock subcommand with aliases. + s, sc, sub, mockSubCmd - A simple mock subcommand with aliases and its own subcommand. ` buf := new(bytes.Buffer) cmd := new(mockParentCmd) @@ -84,8 +103,54 @@ Commands: fl := pflag.NewFlagSet(name, pflag.ExitOnError) // If the help output doesn't contain the subcommand and // isn't formatted the way we expect the test will fail. - renderHelp(name, cmd, fl, buf) - got := string(buf.Bytes()) + renderHelp(buf, name, cmd, fl) + got := buf.String() assert.Equal(t, t.Name(), expected, got) }) } + +func TestSubCmdHelpOutput(t *testing.T) { + withNested := `Usage: mockSubCmd Test a subcommand. + +Aliases: s, sc, sub + +Description: A simple mock subcommand with aliases and its own subcommand. + +Commands: + s, sc, sub, mockSubCmd - A simple mock subcommand with aliases and its own subcommand. +` + + noNested := `Usage: mockSubCmdNoNested Used for help output tests. + +Aliases: s, sc, sub + +Description: A simple mock subcommand with aliases and no nested subcommands. +` + + for _, test := range []struct { + name, expected string + cmd Command + }{ + { + name: "subcmd w/nested subcmd.", + expected: withNested, + cmd: new(mockSubCmd), + }, + { + name: "subcmd w/no nested subcmds.", + expected: noNested, + cmd: new(mockSubCmdNoNested), + }, + } { + t.Run(test.name, func(t *testing.T) { + buf := new(bytes.Buffer) + name := test.cmd.Spec().Name + fl := pflag.NewFlagSet(name, pflag.ExitOnError) + // If the help output is not written to the buffer + // in the format we expect then the test will fail. + renderHelp(buf, name, test.cmd, fl) + got := buf.String() + assert.Equal(t, t.Name(), test.expected, got) + }) + } +} diff --git a/help.go b/help.go index c04e2e5..8b90c2f 100644 --- a/help.go +++ b/help.go @@ -45,18 +45,17 @@ func renderFlagHelp(fullName string, fl *pflag.FlagSet, w io.Writer) { } // renderHelp generates a command's help page. -func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) { +func renderHelp(w io.Writer, fullName string, cmd Command, fl *pflag.FlagSet) { var b strings.Builder spec := cmd.Spec() fmt.Fprintf(&b, "Usage: %v %v\n\n", fullName, spec.Usage) // If the command has aliases, add them to the output as a comma-separated list. if spec.HasAliases() { - fmt.Fprintf(&b, "Aliases: %v", spec.Aliases) + fmt.Fprintf(&b, "Aliases: %s\n\n", strings.Join(spec.Aliases, ", ")) } - // Render usage and description. - usageAndDesc := fmt.Sprintf("%s%s\n", b.String(), spec.Desc) - fmt.Fprint(w, usageAndDesc) + // Print usage and description. + fmt.Fprintf(w, "%sDescription: %s\n", b.String(), spec.Desc) tw := tabwriter.NewWriter(w, 0, 4, 2, ' ', tabwriter.StripEscape) defer tw.Flush() @@ -80,7 +79,7 @@ func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) { continue } - allNames := strings.Join(append(spec.Aliases, spec.Name), ",") + allNames := strings.Join(append(spec.Aliases, spec.Name), ", ") fmt.Fprintf(tw, tabEscape+"\t"+tabEscape+"%v\t- %v\n", diff --git a/run.go b/run.go index 34341a9..59d444a 100644 --- a/run.go +++ b/run.go @@ -94,7 +94,7 @@ func Run(cmd Command, args []string, parent string) { // Reassign the usage now that we've parsed the args // so that we can render it manually. fl.Usage = func() { - renderHelp(name, cmd, fl, os.Stderr) + renderHelp(os.Stderr, name, cmd, fl) } if err != nil { fl.Usage()