From 28a0ece8675abf91d9b4d9ab02aa1716bb777b90 Mon Sep 17 00:00:00 2001 From: Nathan Potter Date: Mon, 19 Aug 2019 21:45:10 +0000 Subject: [PATCH 1/7] Support POSIX flags --- .gitignore | 1 + README.md | 12 +++--- command.go | 7 ++-- examples/simple/main.go | 8 ++-- examples/subcommands/main.go | 6 +-- go.mod | 2 + go.sum | 2 + help.go | 28 +++++-------- run.go | 79 +++++++++++++++++++++++++----------- 9 files changed, 88 insertions(+), 57 deletions(-) create mode 100644 .gitignore create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/README.md b/README.md index fa9c9c5..e034366 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ A minimal, command-oriented CLI package. ## Features - Very small, simple API. -- Uses standard `flag` package as much as possible. -- No external dependencies. +- Support for POSIX flags. +- Only external dependency is [spf13/pflag](https://github.com/spf13/pflag). - Subcommands. - Auto-generated help. @@ -37,7 +37,7 @@ type cmd struct { verbose bool } -func (c *cmd) Run(fl *flag.FlagSet) { +func (c *cmd) Run(fl *pflag.FlagSet) { if c.verbose { fmt.Println("verbose enabled") } @@ -52,7 +52,7 @@ func (c *cmd) Spec() cli.CommandSpec { } } -func (c *cmd) RegisterFlags(fl *flag.FlagSet) { +func (c *cmd) RegisterFlags(fl *pflag.FlagSet) { fl.BoolVar(&c.verbose, "v", false, "sets verbose mode") } @@ -87,7 +87,7 @@ import ( type subcmd struct { } -func (c *subcmd) Run(fl *flag.FlagSet) { +func (c *subcmd) Run(fl *pflag.FlagSet) { fmt.Println("subcommand invoked") } @@ -102,7 +102,7 @@ func (c *subcmd) Spec() cli.CommandSpec { type cmd struct { } -func (c *cmd) Run(fl *flag.FlagSet) { +func (c *cmd) Run(fl *pflag.FlagSet) { // This root command has no default action, so print the help. fl.Usage() } diff --git a/command.go b/command.go index d99a365..e829dff 100644 --- a/command.go +++ b/command.go @@ -1,8 +1,9 @@ package cli import ( - "flag" "strings" + + "github.com/spf13/pflag" ) // CommandSpec describes a Command's usage. @@ -38,7 +39,7 @@ type Command interface { // Spec returns metadata about the command. Spec() CommandSpec // Run invokes the command's main routine with parsed flags. - Run(fl *flag.FlagSet) + Run(fl *pflag.FlagSet) } // ParentCommand is an optional interface for commands that have subcommands. @@ -52,5 +53,5 @@ type ParentCommand interface { // FlaggedCommand is an optional interface for commands that have flags. type FlaggedCommand interface { // RegisterFlags lets the command register flags which be sent to Handle. - RegisterFlags(fl *flag.FlagSet) + RegisterFlags(fl *pflag.FlagSet) } diff --git a/examples/simple/main.go b/examples/simple/main.go index c5d1afa..e8c1b7a 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -1,9 +1,9 @@ package main import ( - "flag" "fmt" + "github.com/spf13/pflag" "go.coder.com/cli" ) @@ -11,7 +11,7 @@ type cmd struct { verbose bool } -func (c *cmd) Run(fl *flag.FlagSet) { +func (c *cmd) Run(fl *pflag.FlagSet) { if c.verbose { fmt.Println("verbose enabled") } @@ -26,8 +26,8 @@ func (c *cmd) Spec() cli.CommandSpec { } } -func (c *cmd) RegisterFlags(fl *flag.FlagSet) { - fl.BoolVar(&c.verbose, "v", false, "sets verbose mode") +func (c *cmd) RegisterFlags(fl *pflag.FlagSet) { + fl.BoolVarP(&c.verbose, "verbose", "v", false, "sets verbose mode") } func main() { diff --git a/examples/subcommands/main.go b/examples/subcommands/main.go index a8157cd..3d39bea 100644 --- a/examples/subcommands/main.go +++ b/examples/subcommands/main.go @@ -1,16 +1,16 @@ package main import ( - "flag" "fmt" + "github.com/spf13/pflag" "go.coder.com/cli" ) type subcmd struct { } -func (c *subcmd) Run(fl *flag.FlagSet) { +func (c *subcmd) Run(fl *pflag.FlagSet) { fmt.Println("subcommand invoked") } @@ -25,7 +25,7 @@ func (c *subcmd) Spec() cli.CommandSpec { type cmd struct { } -func (c *cmd) Run(fl *flag.FlagSet) { +func (c *cmd) Run(fl *pflag.FlagSet) { // This root command has no default action, so print the help. fl.Usage() } diff --git a/go.mod b/go.mod index 8c80661..870002d 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module go.coder.com/cli go 1.12 + +require github.com/spf13/pflag v1.0.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..edd0bcf --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= diff --git a/help.go b/help.go index 520bb01..3f679c8 100644 --- a/help.go +++ b/help.go @@ -1,13 +1,14 @@ package cli import ( - "flag" "fmt" "io" "strings" "text/tabwriter" "unicode" "unicode/utf8" + + "github.com/spf13/pflag" ) func flagDashes(name string) string { @@ -36,27 +37,18 @@ func fmtDefValue(value interface{}) string { // \xFF is used to escape a \t so tabwriter ignores it. const tabEscape = "\xFF" -func renderFlagHelp(fl *flag.FlagSet, w io.Writer) { - var count int - fl.VisitAll(func(f *flag.Flag) { - if count == 0 { - fmt.Fprintf(w, "\n%v flags:\n", fl.Name()) - } - - count++ - if f.DefValue == "" { - fmt.Fprintf(w, tabEscape+"\t%v%v\t%v\n", flagDashes(f.Name), f.Name, f.Usage) - } else { - fmt.Fprintf(w, tabEscape+"\t%v%v=%v\t%v\n", flagDashes(f.Name), f.Name, fmtDefValue(f.DefValue), f.Usage) - } - }) +func renderFlagHelp(fullName string, fl *pflag.FlagSet, w io.Writer) { + if fl.HasFlags() { + fmt.Fprintf(w, "\n%s flags:\n", fullName) + fmt.Fprint(w, fl.FlagUsages()) + } } // renderHelp generates a command's help page. -func renderHelp(cmd Command, fl *flag.FlagSet, w io.Writer) { +func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) { // Render usage and description. fmt.Fprintf(w, "Usage: %v %v\n\n", - fl.Name(), cmd.Spec().Usage, + fullName, cmd.Spec().Usage, ) fmt.Fprintf(w, "%v\n", cmd.Spec().Desc) @@ -64,7 +56,7 @@ func renderHelp(cmd Command, fl *flag.FlagSet, w io.Writer) { defer tw.Flush() // Render flag help. - renderFlagHelp(fl, tw) + renderFlagHelp(fullName, fl, tw) // Render subcommand summaries. pc, ok := cmd.(ParentCommand) diff --git a/run.go b/run.go index c136531..e3a1ddb 100644 --- a/run.go +++ b/run.go @@ -1,17 +1,36 @@ package cli import ( - "flag" "os" + "strings" + + "github.com/spf13/pflag" ) func appendParent(parent string, add string) string { - if parent == "" { - return add + " " - } return parent + add + " " } +// splitArgs tries to split the args between the parent command's flags/args, and the subcommand's +// flags/args. If a subcommand is found, the parent args, subcommand args, and the subcommand will +// be returned. If a subcommand isn't found, the args will be returned as is, the subcommand args +// will be empty, and the subcommand will be nil. +func splitArgs(subCmds []Command, args []string) (cmdArgs, subArgs []string, subCmd Command) { + for i, arg := range args { + if strings.HasPrefix(arg, "-") { + continue + } + + for _, subCommand := range subCmds { + if subCommand.Spec().Name == arg { + return args[:i], args[i+1:], subCommand + } + } + } + + return args, []string{}, nil +} + // Run sets up flags, helps, and executes the command with the provided // arguments. // @@ -20,37 +39,51 @@ func appendParent(parent string, add string) string { // // Use RunRoot if this package is managing the entire CLI. func Run(cmd Command, args []string, parent string) { - fl := flag.NewFlagSet(parent+""+cmd.Spec().Name, flag.ExitOnError) + name := parent + cmd.Spec().Name + fl := pflag.NewFlagSet(name, pflag.ContinueOnError) + // Ensure pflag library doesn't print usage for us automatically, + // we'll override this below. + fl.Usage = func() {} if fc, ok := cmd.(FlaggedCommand); ok { fc.RegisterFlags(fl) } - fl.Usage = func() { - renderHelp(cmd, fl, os.Stderr) - } - if cmd.Spec().RawArgs { // Use `--` to return immediately when parsing the flags. args = append([]string{"--"}, args...) } - _ = fl.Parse(args) - subcommandArg := fl.Arg(0) + var ( + cmdArgs, subArgs []string + subCmd Command + ) + pc, isParentCmd := cmd.(ParentCommand) + if isParentCmd { + cmdArgs, subArgs, subCmd = splitArgs(pc.Subcommands(), args) + if subCmd != nil { + args = cmdArgs + } + } - // Route to subcommand. - if pc, ok := cmd.(ParentCommand); ok && subcommandArg != "" { - for _, subcommand := range pc.Subcommands() { - if subcommand.Spec().Name != subcommandArg { - continue - } + err := fl.Parse(args) + // 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) + } + if err != nil { + fl.Usage() + os.Exit(2) + } - Run( - subcommand, fl.Args()[1:], - appendParent(parent, cmd.Spec().Name), - ) - return - } + // Route to subcommand. + if isParentCmd && subCmd != nil { + Run( + subCmd, subArgs, + appendParent(parent, cmd.Spec().Name), + ) + return } cmd.Run(fl) From f5f7e47213388823f3d3048c24346018d4d861c5 Mon Sep 17 00:00:00 2001 From: Steven Edwards Date: Wed, 1 Jul 2020 10:20:01 -0400 Subject: [PATCH 2/7] Replace flag imports with spf13/pflag in README. The README examples imported flag, but used pflag instead. This just reflects the change in imports. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e034366..0798e22 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ See `examples/` for more. package main import ( - "flag" "fmt" + "github.com/spf13/pflag" "go.coder.com/cli" ) @@ -78,9 +78,9 @@ simple-example flags: package main import ( - "flag" "fmt" + "github.com/spf13/pflag" "go.coder.com/cli" ) @@ -135,4 +135,4 @@ This is a simple example of subcommands. Commands: sub This is a simple subcommand. -``` \ No newline at end of file +``` From b4ff21d21ab28ae6ade9b4c213e245cfd2f9e5c4 Mon Sep 17 00:00:00 2001 From: Faris Huskovic Date: Mon, 20 Jul 2020 08:55:19 -0500 Subject: [PATCH 3/7] Support subcommand aliases --- README.md | 3 +- command.go | 8 +- command_test.go | 105 +++++++++++++++++++++ go.mod | 6 +- go.sum | 237 ++++++++++++++++++++++++++++++++++++++++++++++++ help.go | 26 ++++-- run.go | 30 +++++- 7 files changed, 401 insertions(+), 14 deletions(-) create mode 100644 command_test.go diff --git a/README.md b/README.md index 0798e22..d361e86 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ func (c *subcmd) Spec() cli.CommandSpec { return cli.CommandSpec{ Name: "sub", Usage: "", + Aliases: []string{"s"}, Desc: `This is a simple subcommand.`, } } @@ -134,5 +135,5 @@ Usage: subcommand [flags] This is a simple example of subcommands. Commands: - sub This is a simple subcommand. + s,sub - This is a simple subcommand. ``` diff --git a/command.go b/command.go index e829dff..957c52c 100644 --- a/command.go +++ b/command.go @@ -23,10 +23,11 @@ type CommandSpec struct { // RawArgs indicates that flags should not be parsed, and they should be deferred // to the command. RawArgs bool - // Hidden indicates that this command should not show up in it's parent's // subcommand help. Hidden bool + // Aliases contains a list of alternative names that can be used for a particular command. + Aliases []string } // ShortDesc returns the first line of Desc. @@ -34,6 +35,11 @@ func (c CommandSpec) ShortDesc() string { return strings.Split(c.Desc, "\n")[0] } +// HasAliases evaluates whether particular command has any alternative names. +func (c CommandSpec) HasAliases() bool { + return len(c.Aliases) > 0 +} + // Command describes a command or subcommand. type Command interface { // Spec returns metadata about the command. diff --git a/command_test.go b/command_test.go new file mode 100644 index 0000000..0640898 --- /dev/null +++ b/command_test.go @@ -0,0 +1,105 @@ +package cli + +import ( + "bytes" + "os" + "testing" + + "cdr.dev/slog/sloggers/slogtest/assert" + "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)} + +type ( + mockParentCmd struct{} + mockSubCmd struct{ buf *bytes.Buffer } +) + +func (c *mockParentCmd) Run(fl *pflag.FlagSet) {} + +func (c *mockParentCmd) Subcommands() []Command { return []Command{subCmd} } + +func (c *mockParentCmd) Spec() CommandSpec { + return CommandSpec{ + Name: "mockParentCmd", + Usage: "Mock parent command usage.", + Desc: "Mock parent command description.", + } +} + +func (c *mockSubCmd) Run(fl *pflag.FlagSet) { + c.buf = new(bytes.Buffer) + if _, err := c.Write([]byte(success)); err != nil { + fl.Usage() + } +} + +func (c *mockSubCmd) Write(b []byte) (int, error) { return c.buf.Write(b) } + +func (c *mockSubCmd) Spec() CommandSpec { + return CommandSpec{ + Name: "mockSubCmd", + Usage: "Test a subcommand.", + Aliases: []string{"s", "sc", "sub"}, + Desc: "A simple mock subcommand with aliases.", + } +} + +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. + got := string(subCmd.buf.Bytes()) + assert.Equal(t, test.name, test.expected, 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) +} diff --git a/go.mod b/go.mod index 870002d..1a34c97 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module go.coder.com/cli go 1.12 -require github.com/spf13/pflag v1.0.3 +require ( + cdr.dev/slog v1.3.0 + github.com/spf13/pflag v1.0.3 + github.com/stretchr/testify v1.6.1 // indirect +) diff --git a/go.sum b/go.sum index edd0bcf..83e625b 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,239 @@ +cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ= +cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw= +github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e h1:4WfjkTUTsO6siF8ghDQQk6t7x/FPsv3w6MXkc47do7Q= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/help.go b/help.go index 3f679c8..c04e2e5 100644 --- a/help.go +++ b/help.go @@ -46,11 +46,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) { + 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) + } // Render usage and description. - fmt.Fprintf(w, "Usage: %v %v\n\n", - fullName, cmd.Spec().Usage, - ) - fmt.Fprintf(w, "%v\n", cmd.Spec().Desc) + usageAndDesc := fmt.Sprintf("%s%s\n", b.String(), spec.Desc) + fmt.Fprint(w, usageAndDesc) tw := tabwriter.NewWriter(w, 0, 4, 2, ' ', tabwriter.StripEscape) defer tw.Flush() @@ -68,14 +74,18 @@ func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) { } for _, cmd := range pc.Subcommands() { - if cmd.Spec().Hidden { + spec := cmd.Spec() + + if spec.Hidden { continue } + allNames := strings.Join(append(spec.Aliases, spec.Name), ",") + fmt.Fprintf(tw, - tabEscape+"\t"+tabEscape+"%v\t%v\n", - cmd.Spec().Name, - cmd.Spec().ShortDesc(), + tabEscape+"\t"+tabEscape+"%v\t- %v\n", + allNames, + spec.ShortDesc(), ) } } diff --git a/run.go b/run.go index e3a1ddb..34341a9 100644 --- a/run.go +++ b/run.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/pflag" ) +type aliasMap map[Command][]string + func appendParent(parent string, add string) string { return parent + add + " " } @@ -15,14 +17,24 @@ func appendParent(parent string, add string) string { // flags/args. If a subcommand is found, the parent args, subcommand args, and the subcommand will // be returned. If a subcommand isn't found, the args will be returned as is, the subcommand args // will be empty, and the subcommand will be nil. -func splitArgs(subCmds []Command, args []string) (cmdArgs, subArgs []string, subCmd Command) { +func splitArgs(subCmds []Command, args []string, aliases aliasMap) (cmdArgs, subArgs []string, subCmd Command) { for i, arg := range args { if strings.HasPrefix(arg, "-") { continue } for _, subCommand := range subCmds { - if subCommand.Spec().Name == arg { + spec := subCommand.Spec() + + if spec.HasAliases() { + for _, alias := range spec.Aliases { + if arg == alias { + return args[:i], args[i+1:], subCommand + } + } + } + + if spec.Name == arg { return args[:i], args[i+1:], subCommand } } @@ -58,9 +70,21 @@ func Run(cmd Command, args []string, parent string) { cmdArgs, subArgs []string subCmd Command ) + pc, isParentCmd := cmd.(ParentCommand) if isParentCmd { - cmdArgs, subArgs, subCmd = splitArgs(pc.Subcommands(), args) + aliases := make(aliasMap) + subCmds := pc.Subcommands() + + for _, sc := range subCmds { + spec := sc.Spec() + + if spec.HasAliases() { + aliases[sc] = spec.Aliases + } + } + + cmdArgs, subArgs, subCmd = splitArgs(subCmds, args, aliases) if subCmd != nil { args = cmdArgs } From 0a4c36a80a1863ca8775750e04b97d53c18e580e Mon Sep 17 00:00:00 2001 From: Faris Huskovic Date: Thu, 30 Jul 2020 02:14:33 +0000 Subject: [PATCH 4/7] 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 5/7] 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 6/7] 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 7/7] 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) }) }