From daf3ced8286e9bfa79a26c624737375979885a88 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 11 Feb 2019 07:10:59 +0100 Subject: [PATCH 1/7] Extending redirection to stdout, stderr, stdin --- command.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/command.go b/command.go index b257f91b6..a00367d36 100644 --- a/command.go +++ b/command.go @@ -177,8 +177,6 @@ type Command struct { // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName - // output is an output writer defined by user. - output io.Writer // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. @@ -195,6 +193,13 @@ type Command struct { helpCommand *Command // versionTemplate is the version template defined by user. versionTemplate string + + // inReader is a reader defined by the user that replaces stdin + inReader io.Reader + // outWriter is a writer defined by the user that replaces stdout + outWriter io.Writer + // errWriter is a writer defined by the user that replaces stderr + errWriter io.Writer } // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden @@ -206,7 +211,25 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. func (c *Command) SetOutput(output io.Writer) { - c.output = output + c.outWriter = output +} + +// SetOut sets the destination for usage messages. +// If newOut is nil, os.Stdout is used. +func (c *Command) SetOut(newOut io.Writer) { + c.outWriter = newOut +} + +// SetErr sets the destination for error messages. +// If newErr is nil, os.Stderr is used. +func (c *Command) SetErr(newErr io.Writer) { + c.errWriter = newErr +} + +// SetOut sets the source for input data +// If newIn is nil, os.Stdin is used. +func (c *Command) SetIn(newIn io.Reader) { + c.inReader = newIn } // SetUsageFunc sets usage function. Usage can be defined by application. @@ -267,9 +290,19 @@ func (c *Command) OutOrStderr() io.Writer { return c.getOut(os.Stderr) } +// ErrOrStderr returns output to stderr +func (c *Command) ErrOrStderr() io.Writer { + return c.getErr(os.Stderr) +} + +// ErrOrStderr returns output to stderr +func (c *Command) InOrStdin() io.Reader { + return c.getIn(os.Stdin) +} + func (c *Command) getOut(def io.Writer) io.Writer { - if c.output != nil { - return c.output + if c.outWriter != nil { + return c.outWriter } if c.HasParent() { return c.parent.getOut(def) @@ -277,6 +310,26 @@ func (c *Command) getOut(def io.Writer) io.Writer { return def } +func (c *Command) getErr(def io.Writer) io.Writer { + if c.errWriter != nil { + return c.errWriter + } + if c.HasParent() { + return c.parent.getErr(def) + } + return def +} + +func (c *Command) getIn(def io.Reader) io.Reader { + if c.inReader != nil { + return c.inReader + } + if c.HasParent() { + return c.parent.getIn(def) + } + return def +} + // UsageFunc returns either the function set by SetUsageFunc for this command // or a parent, or it returns a default usage function. func (c *Command) UsageFunc() (f func(*Command) error) { @@ -331,11 +384,11 @@ func (c *Command) Help() error { // UsageString return usage string. func (c *Command) UsageString() string { - tmpOutput := c.output + tmpOutput := c.outWriter bb := new(bytes.Buffer) - c.SetOutput(bb) + c.outWriter = bb c.Usage() - c.output = tmpOutput + c.outWriter = tmpOutput return bb.String() } From d4fdebeba208e0114985f0e0a097c93fd267ec2e Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 11 Feb 2019 08:22:54 +0100 Subject: [PATCH 2/7] Fixed linter issues --- command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index a00367d36..58cc87322 100644 --- a/command.go +++ b/command.go @@ -217,12 +217,12 @@ func (c *Command) SetOutput(output io.Writer) { // SetOut sets the destination for usage messages. // If newOut is nil, os.Stdout is used. func (c *Command) SetOut(newOut io.Writer) { - c.outWriter = newOut + c.outWriter = newOut } // SetErr sets the destination for error messages. // If newErr is nil, os.Stderr is used. -func (c *Command) SetErr(newErr io.Writer) { +func (c *Command) SetErr(newErr io.Writer) { c.errWriter = newErr } From 3b830e63629a7ddbbb9a449eaa6c7cf479650c6b Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 11 Feb 2019 16:06:55 +0100 Subject: [PATCH 3/7] Allow for explicit output to err/stderr --- command.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/command.go b/command.go index 58cc87322..3e97687a2 100644 --- a/command.go +++ b/command.go @@ -1121,6 +1121,21 @@ func (c *Command) Printf(format string, i ...interface{}) { c.Print(fmt.Sprintf(format, i...)) } +// PrintErr is a convenience method to Print to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErr(i ...interface{}) { + fmt.Fprint(c.ErrOrStderr(), i...) +} + +// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrln(i ...interface{}) { + c.Print(fmt.Sprintln(i...)) +} + +// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrf(format string, i ...interface{}) { + c.Print(fmt.Sprintf(format, i...)) +} + // CommandPath returns the full path to this command. func (c *Command) CommandPath() string { if c.HasParent() { From 0bc69e72916c8841976f4954b45e73e36a0be934 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 13 Feb 2019 06:32:16 +0100 Subject: [PATCH 4/7] Deprecate and maintain backwards compatibility --- command.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/command.go b/command.go index 3e97687a2..6dbb3e8c8 100644 --- a/command.go +++ b/command.go @@ -210,8 +210,10 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. +// Deprecated: Use SetOut and/or SetErr instead func (c *Command) SetOutput(output io.Writer) { c.outWriter = output + c.errWriter = output } // SetOut sets the destination for usage messages. From 2bd35aa49d5ae32382592ebbad54a00300f2fe14 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Tue, 30 Apr 2019 18:41:56 +0100 Subject: [PATCH 5/7] Add tests --- command_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/command_test.go b/command_test.go index 6e483a3ec..4f1d36907 100644 --- a/command_test.go +++ b/command_test.go @@ -1381,6 +1381,30 @@ func TestSetOutput(t *testing.T) { } } +func TestSetOut(t *testing.T) { + c := &Command{} + c.SetOut(nil) + if out := c.OutOrStdout(); out != os.Stdout { + t.Errorf("Expected setting output to nil to revert back to stdout") + } +} + +func TestSetErr(t *testing.T) { + c := &Command{} + c.SetErr(nil) + if out := c.ErrOrStderr(); out != os.Stderr { + t.Errorf("Expected setting error to nil to revert back to stderr") + } +} + +func TestSetIn(t *testing.T) { + c := &Command{} + c.SetIn(nil) + if out := c.InOrStdin(); out != os.Stdin { + t.Errorf("Expected setting input to nil to revert back to stdin") + } +} + func TestFlagErrorFunc(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} From 4d52f3f7182e5b71c85e1621c6343a3df395478f Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 15 May 2019 18:49:16 +0200 Subject: [PATCH 6/7] considering stderr in UsageString --- command.go | 11 ++++++++++- command_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 6dbb3e8c8..c7e898303 100644 --- a/command.go +++ b/command.go @@ -384,13 +384,22 @@ func (c *Command) Help() error { return nil } -// UsageString return usage string. +// UsageString returns usage string. func (c *Command) UsageString() string { + // Storing normal writers tmpOutput := c.outWriter + tmpErr := c.errWriter + bb := new(bytes.Buffer) c.outWriter = bb + c.errWriter = bb + c.Usage() + + // Setting things back to normal c.outWriter = tmpOutput + c.errWriter = tmpErr + return bb.String() } diff --git a/command_test.go b/command_test.go index 4f1d36907..258f20a25 100644 --- a/command_test.go +++ b/command_test.go @@ -1405,6 +1405,22 @@ func TestSetIn(t *testing.T) { } } +func TestUsageStringRedirected(t *testing.T) { + c := &Command{} + + c.usageFunc = func(cmd *Command) error { + cmd.Print("[stdout1]") + cmd.PrintErr("[stderr2]") + cmd.Print("[stdout3]") + return nil; + } + + expected := "[stdout1][stderr2][stdout3]" + if got := c.UsageString(); got != expected { + t.Errorf("Expected usage string to consider both stdout and stderr") + } +} + func TestFlagErrorFunc(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} From 558b01866a1ed3abd3fb6106edeef8f1b012f0f8 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 15 May 2019 18:53:39 +0200 Subject: [PATCH 7/7] fixing linter issues --- command_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command_test.go b/command_test.go index 258f20a25..2fa2003cb 100644 --- a/command_test.go +++ b/command_test.go @@ -1412,11 +1412,11 @@ func TestUsageStringRedirected(t *testing.T) { cmd.Print("[stdout1]") cmd.PrintErr("[stderr2]") cmd.Print("[stdout3]") - return nil; + return nil } expected := "[stdout1][stderr2][stdout3]" - if got := c.UsageString(); got != expected { + if got := c.UsageString(); got != expected { t.Errorf("Expected usage string to consider both stdout and stderr") } }