From 5a0bfd38d5bf725668ea88c5ec9b0cd004849a4e Mon Sep 17 00:00:00 2001 From: Tiago Carreira Date: Sat, 22 Jul 2023 22:59:47 +0100 Subject: [PATCH 1/4] fix: allows to capture cmd out/err MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OutOrStderr() introduces an inconsistent behavior. This commit adds a feature to get the right behavior, without breaking changes closes: https://github.com/spf13/cobra/issues/1708 Co-authored-by: Mislav Marohnić --- command.go | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 6866f7d..379d7a1 100644 --- a/command.go +++ b/command.go @@ -191,6 +191,11 @@ type Command struct { // errWriter is a writer defined by the user that replaces stderr errWriter io.Writer + // outFallbackWriter is a writer defined by the user that is used if outWriter is nil + outFallbackWriter io.Writer + // errFallbackWriter is a writer defined by the user that is used if errWriter is nil + errFallbackWriter io.Writer + // FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist @@ -282,16 +287,30 @@ func (c *Command) SetOutput(output io.Writer) { // SetOut sets the destination for usage messages. // If newOut is nil, os.Stdout is used. +// Deprecated: Use SetOutFallback and/or SetErrFallback instead (see https://github.com/spf13/cobra/issues/1708) 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. +// Deprecated: Use SetOutFallback and/or SetErrFallback instead (see https://github.com/spf13/cobra/issues/1708) func (c *Command) SetErr(newErr io.Writer) { c.errWriter = newErr } +// SetOutFallback sets the destination for usage messages when SetOut() was not used. +// If newOut is nil, os.Stdout is used. +func (c *Command) SetOutFallback(newOut io.Writer) { + c.outFallbackWriter = newOut +} + +// SetErrFallback sets the destination for error messages when SetErr() was not used. +// If newErr is nil, os.Stderr is used. +func (c *Command) SetErrFallback(newErr io.Writer) { + c.errFallbackWriter = newErr +} + // SetIn sets the source for input data // If newIn is nil, os.Stdin is used. func (c *Command) SetIn(newIn io.Reader) { @@ -371,9 +390,10 @@ func (c *Command) OutOrStdout() io.Writer { return c.getOut(os.Stdout) } -// OutOrStderr returns output to stderr +// OutOrStderr returns output to stderr. +// Deprecated: Use OutOrStdout or ErrOrStderr instead func (c *Command) OutOrStderr() io.Writer { - return c.getOut(os.Stderr) + return c.getOutFallbackToErr(os.Stderr) } // ErrOrStderr returns output to stderr @@ -390,6 +410,9 @@ func (c *Command) getOut(def io.Writer) io.Writer { if c.outWriter != nil { return c.outWriter } + if c.outFallbackWriter != nil { + return c.outFallbackWriter + } if c.HasParent() { return c.parent.getOut(def) } @@ -400,12 +423,31 @@ func (c *Command) getErr(def io.Writer) io.Writer { if c.errWriter != nil { return c.errWriter } + if c.errFallbackWriter != nil { + return c.errFallbackWriter + } if c.HasParent() { return c.parent.getErr(def) } return def } +// getOutFallbackToErr should only be used inside OutOrStderr. +// Deprecated: this function exists to allow for backwards compatibility only +// (see https://github.com/spf13/cobra/issues/1708) +func (c *Command) getOutFallbackToErr(def io.Writer) io.Writer { + if c.outWriter != nil { + return c.outWriter + } + if c.errFallbackWriter != nil { + return c.errFallbackWriter + } + if c.HasParent() { + return c.parent.getOutFallbackToErr(def) + } + return def +} + func (c *Command) getIn(def io.Reader) io.Reader { if c.inReader != nil { return c.inReader From 2f70b7caa359e4cba78cc4dd42c5db940cac3ddb Mon Sep 17 00:00:00 2001 From: Tiago Carreira Date: Mon, 2 Oct 2023 01:37:31 +0100 Subject: [PATCH 2/4] refactor: rename out/err stream vars/funcs --- command.go | 76 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/command.go b/command.go index 379d7a1..159cd25 100644 --- a/command.go +++ b/command.go @@ -186,15 +186,17 @@ type Command struct { // 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 + // legacyOutWriter is a writer defined by the user that replaces stdout. + // Deprecated: use outStreamWriter instead (see https://github.com/spf13/cobra/issues/1708) + legacyOutWriter io.Writer + // legacyErrWriter is a writer defined by the user that replaces stderr. + // Deprecated: use errStreamWriter instead (see https://github.com/spf13/cobra/issues/1708) + legacyErrWriter io.Writer - // outFallbackWriter is a writer defined by the user that is used if outWriter is nil - outFallbackWriter io.Writer - // errFallbackWriter is a writer defined by the user that is used if errWriter is nil - errFallbackWriter io.Writer + // outStreamWriter is a writer defined by the user that replaces stdout + outStreamWriter io.Writer + // errStreamWriter is a writer defined by the user that replaces stderr + errStreamWriter io.Writer // FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist @@ -281,34 +283,36 @@ func (c *Command) SetArgs(a []string) { // 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 + c.legacyOutWriter = output + c.legacyErrWriter = output } // SetOut sets the destination for usage messages. // If newOut is nil, os.Stdout is used. // Deprecated: Use SetOutFallback and/or SetErrFallback instead (see https://github.com/spf13/cobra/issues/1708) func (c *Command) SetOut(newOut io.Writer) { - c.outWriter = newOut + c.legacyOutWriter = newOut } // SetErr sets the destination for error messages. // If newErr is nil, os.Stderr is used. // Deprecated: Use SetOutFallback and/or SetErrFallback instead (see https://github.com/spf13/cobra/issues/1708) func (c *Command) SetErr(newErr io.Writer) { - c.errWriter = newErr + c.legacyErrWriter = newErr } -// SetOutFallback sets the destination for usage messages when SetOut() was not used. +// SetOutStream sets the destination for usage messages. +// It includes (at least): --help, --version, completion. // If newOut is nil, os.Stdout is used. -func (c *Command) SetOutFallback(newOut io.Writer) { - c.outFallbackWriter = newOut +func (c *Command) SetOutStream(newOut io.Writer) { + c.outStreamWriter = newOut } -// SetErrFallback sets the destination for error messages when SetErr() was not used. +// SetErrStream sets the destination for error messages. +// It includes (at least): errors, usage, deprecations msgs, unknowns cmds/flags/topics msgs, DebugFlags. // If newErr is nil, os.Stderr is used. -func (c *Command) SetErrFallback(newErr io.Writer) { - c.errFallbackWriter = newErr +func (c *Command) SetErrStream(newErr io.Writer) { + c.errStreamWriter = newErr } // SetIn sets the source for input data @@ -407,11 +411,11 @@ func (c *Command) InOrStdin() io.Reader { } func (c *Command) getOut(def io.Writer) io.Writer { - if c.outWriter != nil { - return c.outWriter + if c.legacyOutWriter != nil { + return c.legacyOutWriter } - if c.outFallbackWriter != nil { - return c.outFallbackWriter + if c.outStreamWriter != nil { + return c.outStreamWriter } if c.HasParent() { return c.parent.getOut(def) @@ -420,11 +424,11 @@ func (c *Command) getOut(def io.Writer) io.Writer { } func (c *Command) getErr(def io.Writer) io.Writer { - if c.errWriter != nil { - return c.errWriter + if c.legacyErrWriter != nil { + return c.legacyErrWriter } - if c.errFallbackWriter != nil { - return c.errFallbackWriter + if c.errStreamWriter != nil { + return c.errStreamWriter } if c.HasParent() { return c.parent.getErr(def) @@ -436,11 +440,11 @@ func (c *Command) getErr(def io.Writer) io.Writer { // Deprecated: this function exists to allow for backwards compatibility only // (see https://github.com/spf13/cobra/issues/1708) func (c *Command) getOutFallbackToErr(def io.Writer) io.Writer { - if c.outWriter != nil { - return c.outWriter + if c.legacyOutWriter != nil { + return c.legacyOutWriter } - if c.errFallbackWriter != nil { - return c.errFallbackWriter + if c.errStreamWriter != nil { + return c.errStreamWriter } if c.HasParent() { return c.parent.getOutFallbackToErr(def) @@ -515,18 +519,18 @@ func (c *Command) Help() error { // UsageString returns usage string. func (c *Command) UsageString() string { // Storing normal writers - tmpOutput := c.outWriter - tmpErr := c.errWriter + tmpOutput := c.legacyOutWriter + tmpErr := c.legacyErrWriter bb := new(bytes.Buffer) - c.outWriter = bb - c.errWriter = bb + c.legacyOutWriter = bb + c.legacyErrWriter = bb CheckErr(c.Usage()) // Setting things back to normal - c.outWriter = tmpOutput - c.errWriter = tmpErr + c.legacyOutWriter = tmpOutput + c.legacyErrWriter = tmpErr return bb.String() } From e80bdb9a8cd504fc310cdb5cb1f763e11b87b396 Mon Sep 17 00:00:00 2001 From: Tiago Carreira Date: Mon, 2 Oct 2023 01:45:00 +0100 Subject: [PATCH 3/4] fix: give priority to new API for when getOut() --- command.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/command.go b/command.go index 159cd25..5d030cb 100644 --- a/command.go +++ b/command.go @@ -397,7 +397,7 @@ func (c *Command) OutOrStdout() io.Writer { // OutOrStderr returns output to stderr. // Deprecated: Use OutOrStdout or ErrOrStderr instead func (c *Command) OutOrStderr() io.Writer { - return c.getOutFallbackToErr(os.Stderr) + return c.getErrOrLegacyOutFallbackToStderr() } // ErrOrStderr returns output to stderr @@ -411,12 +411,12 @@ func (c *Command) InOrStdin() io.Reader { } func (c *Command) getOut(def io.Writer) io.Writer { - if c.legacyOutWriter != nil { - return c.legacyOutWriter - } if c.outStreamWriter != nil { return c.outStreamWriter } + if c.legacyOutWriter != nil { + return c.legacyOutWriter + } if c.HasParent() { return c.parent.getOut(def) } @@ -436,20 +436,20 @@ func (c *Command) getErr(def io.Writer) io.Writer { return def } -// getOutFallbackToErr should only be used inside OutOrStderr. +// getErrOrLegacyOutFallbackToStderr should only be used inside OutOrStderr. // Deprecated: this function exists to allow for backwards compatibility only // (see https://github.com/spf13/cobra/issues/1708) -func (c *Command) getOutFallbackToErr(def io.Writer) io.Writer { - if c.legacyOutWriter != nil { - return c.legacyOutWriter - } +func (c *Command) getErrOrLegacyOutFallbackToStderr() io.Writer { if c.errStreamWriter != nil { return c.errStreamWriter } - if c.HasParent() { - return c.parent.getOutFallbackToErr(def) + if c.legacyOutWriter != nil { + return c.legacyOutWriter } - return def + if c.HasParent() { + return c.parent.getErrOrLegacyOutFallbackToStderr() + } + return os.Stderr } func (c *Command) getIn(def io.Reader) io.Reader { From 13748259606e0509f5eb8aab73f5b688ef41cab5 Mon Sep 17 00:00:00 2001 From: Tiago Carreira Date: Mon, 2 Oct 2023 01:46:07 +0100 Subject: [PATCH 4/4] feat: create PrintOut() - deprecate Print() --- command.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/command.go b/command.go index 5d030cb..865ebf7 100644 --- a/command.go +++ b/command.go @@ -1414,20 +1414,38 @@ main: } // Print is a convenience method to Print to the defined output, fallback to Stderr if not set. +// Deprecated: Use PrintOut or PrintErr instead. func (c *Command) Print(i ...interface{}) { fmt.Fprint(c.OutOrStderr(), i...) } // Println is a convenience method to Println to the defined output, fallback to Stderr if not set. +// Deprecated: Use PrintOutln or PrintErrln instead. func (c *Command) Println(i ...interface{}) { c.Print(fmt.Sprintln(i...)) } // Printf is a convenience method to Printf to the defined output, fallback to Stderr if not set. +// Deprecated: Use PrintOutf or PrintErrf instead. func (c *Command) Printf(format string, i ...interface{}) { c.Print(fmt.Sprintf(format, i...)) } +// PrintOut is a convenience method to Print to the defined OutStream, fallback to Stdout if not set. +func (c *Command) PrintOut(i ...interface{}) { + fmt.Fprint(c.OutOrStdout(), i...) +} + +// PrintOutln is a convenience method to Println to the defined OutStream, fallback to Stdout if not set. +func (c *Command) PrintOutln(i ...interface{}) { + c.PrintOut(fmt.Sprintln(i...)) +} + +// PrintOutf is a convenience method to Printf to the defined OutStream, fallback to Stdout if not set. +func (c *Command) PrintOutf(format string, i ...interface{}) { + c.PrintOut(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...)