From 0a4c36a80a1863ca8775750e04b97d53c18e580e Mon Sep 17 00:00:00 2001 From: Faris Huskovic Date: Thu, 30 Jul 2020 02:14:33 +0000 Subject: [PATCH 1/4] Small style adjustments to command tests --- command_test.go | 96 +++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 55 deletions(-) diff --git a/command_test.go b/command_test.go index 0640898..7f0ab6d 100644 --- a/command_test.go +++ b/command_test.go @@ -9,28 +9,20 @@ import ( "github.com/spf13/pflag" ) -const ( - success = "test successful" - - expectedParentCmdHelpOutput = `Usage: mockParentCmd Mock parent command usage. - -Mock parent command description. - -Commands: - s,sc,sub,mockSubCmd - A simple mock subcommand with aliases. -` -) - -var subCmd = &mockSubCmd{buf: new(bytes.Buffer)} +var subCmd = new(mockSubCmd) type ( mockParentCmd struct{} - mockSubCmd struct{ buf *bytes.Buffer } + mockSubCmd struct { + buf *bytes.Buffer + } ) func (c *mockParentCmd) Run(fl *pflag.FlagSet) {} -func (c *mockParentCmd) Subcommands() []Command { return []Command{subCmd} } +func (c *mockParentCmd) Subcommands() []Command { + return []Command{subCmd} +} func (c *mockParentCmd) Spec() CommandSpec { return CommandSpec{ @@ -42,12 +34,15 @@ func (c *mockParentCmd) Spec() CommandSpec { func (c *mockSubCmd) Run(fl *pflag.FlagSet) { c.buf = new(bytes.Buffer) - if _, err := c.Write([]byte(success)); err != nil { + _, err := c.WriteString("success") + if err != nil { fl.Usage() } } -func (c *mockSubCmd) Write(b []byte) (int, error) { return c.buf.Write(b) } +func (c *mockSubCmd) WriteString(s string) (int, error) { + return c.buf.WriteString(s) +} func (c *mockSubCmd) Spec() CommandSpec { return CommandSpec{ @@ -59,47 +54,38 @@ func (c *mockSubCmd) Spec() CommandSpec { } func TestSubCmdAliases(t *testing.T) { - for _, test := range []struct { - name, expected string - }{ - { - name: "s", - expected: success, - }, - { - name: "sc", - expected: success, - }, - { - name: "sub", - expected: success, - }, - } { - t.Run(test.name, func(t *testing.T) { - // Since the alias is the name of the test - // we can just pass it as the alias arg. - os.Args = []string{"mockParentCmd", test.name} - // Based on os.Args, when splitArgs is invoked, - // it should be able to deduce the subcommand we want - // based on the new alias map it's being passed. - RunRoot(&mockParentCmd{}) - // The success const is never written into the buffer - // if the subcommand fails to be invoked by alias. + for _, alias := range []string{"s", "sc", "sub"} { + t.Run(alias, func(t *testing.T) { + // Setup command. + cmd := new(mockParentCmd) + os.Args = []string{cmd.Spec().Name, alias} + // Run command. + RunRoot(cmd) + // If "success" isn't written into the buffer + // then we failed to find the subcommand by alias. got := string(subCmd.buf.Bytes()) - assert.Equal(t, test.name, test.expected, got) + assert.Equal(t, t.Name(), "success", got) }) } } -func TestSubcmdAliasesInParentCmdHelpOutput(t *testing.T) { - buf := new(bytes.Buffer) - cmd := &mockParentCmd{} - name := 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(name, cmd, fl, buf) - got := string(buf.Bytes()) - expected := expectedParentCmdHelpOutput - assert.Equal(t, "display_subcmd_aliases_in_parentcmd_help_output", expected, got) +func TestCmdHelpOutput(t *testing.T) { + t.Run(t.Name(), func(t *testing.T) { + expected := `Usage: mockParentCmd Mock parent command usage. + +Mock parent command description. + +Commands: + s,sc,sub,mockSubCmd - A simple mock subcommand with aliases. +` + 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()) + assert.Equal(t, t.Name(), expected, got) + }) } From 2e2a923f3f88168771f3a51335728718dc691320 Mon Sep 17 00:00:00 2001 From: Faris Huskovic Date: Fri, 31 Jul 2020 09:28:51 -0500 Subject: [PATCH 2/4] Update help output --- command_test.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++--- help.go | 6 ++-- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/command_test.go b/command_test.go index 0640898..4fc3dbe 100644 --- a/command_test.go +++ b/command_test.go @@ -14,18 +14,23 @@ const ( expectedParentCmdHelpOutput = `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. ` ) var subCmd = &mockSubCmd{buf: new(bytes.Buffer)} type ( + // Mock for root/parent command. mockParentCmd struct{} - mockSubCmd struct{ buf *bytes.Buffer } + // 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) {} @@ -47,6 +52,10 @@ func (c *mockSubCmd) Run(fl *pflag.FlagSet) { } } +func (c *mockSubCmd) Subcommands() []Command { + return []Command{new(mockSubCmd)} +} + func (c *mockSubCmd) Write(b []byte) (int, error) { return c.buf.Write(b) } func (c *mockSubCmd) Spec() CommandSpec { @@ -54,7 +63,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.", } } @@ -103,3 +123,49 @@ func TestSubcmdAliasesInParentCmdHelpOutput(t *testing.T) { expected := expectedParentCmdHelpOutput assert.Equal(t, "display_subcmd_aliases_in_parentcmd_help_output", 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 { + cmd Command + name, expected string + }{ + { + 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(name, test.cmd, fl, buf) + got := string(buf.Bytes()) + assert.Equal(t, t.Name(), test.expected, got) + }) + } +} diff --git a/help.go b/help.go index c04e2e5..714a761 100644 --- a/help.go +++ b/help.go @@ -52,10 +52,10 @@ func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) { // 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) + usageAndDesc := fmt.Sprintf("%sDescription: %s\n", b.String(), spec.Desc) fmt.Fprint(w, usageAndDesc) tw := tabwriter.NewWriter(w, 0, 4, 2, ' ', tabwriter.StripEscape) @@ -80,7 +80,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", From 6615750932ecf4ea4a7bd5a702e9a700ed4f4118 Mon Sep 17 00:00:00 2001 From: Faris Huskovic Date: Tue, 4 Aug 2020 15:11:39 +0000 Subject: [PATCH 3/4] Update help output format --- command_test.go | 12 ++++++------ help.go | 7 +++---- run.go | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/command_test.go b/command_test.go index d8af9a1..4a3b6de 100644 --- a/command_test.go +++ b/command_test.go @@ -15,7 +15,7 @@ type ( // Mock for root/parent command. mockParentCmd struct{} // Mock subcommand with aliases and a nested sucommand of its own. - mockSubCmd struct{ + mockSubCmd struct { buf *bytes.Buffer } // Mock subcommand with aliases and no nested subcommands. @@ -103,8 +103,8 @@ 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) }) } @@ -128,8 +128,8 @@ Description: A simple mock subcommand with aliases and no nested subcommands. ` for _, test := range []struct { - cmd Command name, expected string + cmd Command }{ { name: "subcmd w/nested subcmd.", @@ -148,8 +148,8 @@ Description: A simple mock subcommand with aliases and no nested subcommands. 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(name, test.cmd, fl, buf) - got := string(buf.Bytes()) + 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 714a761..8b90c2f 100644 --- a/help.go +++ b/help.go @@ -45,7 +45,7 @@ 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) @@ -54,9 +54,8 @@ func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) { if spec.HasAliases() { fmt.Fprintf(&b, "Aliases: %s\n\n", strings.Join(spec.Aliases, ", ")) } - // Render usage and description. - usageAndDesc := fmt.Sprintf("%sDescription: %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() 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() From 0b0578544d44163a9835ad6f3f6d4f87aa8da55e Mon Sep 17 00:00:00 2001 From: Faris Huskovic Date: Thu, 18 Mar 2021 20:02:54 +0000 Subject: [PATCH 4/4] nits: update readme and test --- README.md | 4 ++-- command_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d361e86..3a075f8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A minimal, command-oriented CLI package. - Very small, simple API. - Support for POSIX flags. - Only external dependency is [spf13/pflag](https://github.com/spf13/pflag). -- Subcommands. +- Subcommands and subcommand aliases. - Auto-generated help. ## Install @@ -135,5 +135,5 @@ Usage: subcommand [flags] This is a simple example of subcommands. Commands: - s,sub - This is a simple subcommand. + s, sub - This is a simple subcommand. ``` diff --git a/command_test.go b/command_test.go index 4a3b6de..ef9f900 100644 --- a/command_test.go +++ b/command_test.go @@ -82,7 +82,7 @@ func TestSubCmdAliases(t *testing.T) { RunRoot(cmd) // If "success" isn't written into the buffer // then we failed to find the subcommand by alias. - got := string(subCmd.buf.Bytes()) + got := subCmd.buf.String() assert.Equal(t, t.Name(), "success", got) }) }