Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.

Update help output #17

Merged
merged 3 commits into from
Aug 4, 2020
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
77 changes: 71 additions & 6 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand All @@ -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)
}
Expand All @@ -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.",
}
}

Expand All @@ -73,19 +92,65 @@ 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)
name := cmd.Spec().Name
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)
})
}
}
11 changes: 5 additions & 6 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down