Compare commits

...

5 commits

Author SHA1 Message Date
Tiago Carreira e35bf966fd
fix: allows to capture cmd out/err
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ć <git@mislav.net>
2023-09-11 10:38:47 +01:00
Souma 0c72800b8d
Customizable error message prefix (#2023) 2023-09-08 13:29:06 -04:00
Nuno Adrego c5dacb3ea4
ci: test golang 1.21 (#2024) 2023-09-07 20:30:51 -04:00
Unai Martinez-Corral 285460dca6
command: temporarily disable G602 due to securego/gosec#1005 (#2022) 2023-08-29 10:04:59 -04:00
dependabot[bot] 4955da7c11
build(deps): bump golangci/golangci-lint-action from 3.6.0 to 3.7.0 (#2021) 2023-08-28 18:53:34 +00:00
4 changed files with 111 additions and 7 deletions

View file

@ -43,13 +43,13 @@ jobs:
- uses: actions/setup-go@v3
with:
go-version: '^1.20'
go-version: '^1.21'
check-latest: true
cache: true
- uses: actions/checkout@v3
- uses: golangci/golangci-lint-action@v3.6.0
- uses: golangci/golangci-lint-action@v3.7.0
with:
version: latest
args: --verbose
@ -67,6 +67,7 @@ jobs:
- 18
- 19
- 20
- 21
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
runs-on: ${{ matrix.platform }}-latest
steps:

View file

@ -181,6 +181,9 @@ type Command struct {
// versionTemplate is the version template defined by user.
versionTemplate string
// errPrefix is the error message prefix defined by user.
errPrefix 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
@ -188,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
@ -279,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) {
@ -346,6 +368,11 @@ func (c *Command) SetVersionTemplate(s string) {
c.versionTemplate = s
}
// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix.
func (c *Command) SetErrPrefix(s string) {
c.errPrefix = s
}
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
// The user should not have a cyclic dependency on commands.
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
@ -363,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
@ -382,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)
}
@ -392,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
@ -595,6 +645,18 @@ func (c *Command) VersionTemplate() string {
`
}
// ErrPrefix return error message prefix for the command
func (c *Command) ErrPrefix() string {
if c.errPrefix != "" {
return c.errPrefix
}
if c.HasParent() {
return c.parent.ErrPrefix()
}
return "Error:"
}
func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
flag := fs.Lookup(name)
if flag == nil {
@ -752,7 +814,9 @@ func (c *Command) findNext(next string) *Command {
}
if len(matches) == 1 {
return matches[0]
// Temporarily disable gosec G602, which produces a false positive.
// See https://github.com/securego/gosec/issues/1005.
return matches[0] // #nosec G602
}
return nil
@ -1048,7 +1112,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
c = cmd
}
if !c.SilenceErrors {
c.PrintErrln("Error:", err.Error())
c.PrintErrln(c.ErrPrefix(), err.Error())
c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
}
return c, err
@ -1077,7 +1141,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
// If root command has SilenceErrors flagged,
// all subcommands should respect it
if !cmd.SilenceErrors && !c.SilenceErrors {
c.PrintErrln("Error:", err.Error())
c.PrintErrln(cmd.ErrPrefix(), err.Error())
}
// If root command has SilenceUsage flagged,

View file

@ -1099,6 +1099,39 @@ func TestShorthandVersionTemplate(t *testing.T) {
checkStringContains(t, output, "customized version: 1.0.0")
}
func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
rootCmd.SetErrPrefix("root error prefix:")
rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})
output, err := executeCommand(rootCmd, "sub", "--unknown-flag")
if err == nil {
t.Errorf("Expected error")
}
checkStringContains(t, output, "root error prefix: unknown flag: --unknown-flag")
}
func TestRootAndSubErrPrefix(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
subCmd := &Command{Use: "sub", Run: emptyRun}
rootCmd.AddCommand(subCmd)
rootCmd.SetErrPrefix("root error prefix:")
subCmd.SetErrPrefix("sub error prefix:")
if output, err := executeCommand(rootCmd, "--unknown-root-flag"); err == nil {
t.Errorf("Expected error")
} else {
checkStringContains(t, output, "root error prefix: unknown flag: --unknown-root-flag")
}
if output, err := executeCommand(rootCmd, "sub", "--unknown-sub-flag"); err == nil {
t.Errorf("Expected error")
} else {
checkStringContains(t, output, "sub error prefix: unknown flag: --unknown-sub-flag")
}
}
func TestVersionFlagExecutedOnSubcommand(t *testing.T) {
rootCmd := &Command{Use: "root", Version: "1.0.0"}
rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})

View file

@ -596,6 +596,12 @@ Running an application with the '--version' flag will print the version to stdou
the version template. The template can be customized using the
`cmd.SetVersionTemplate(s string)` function.
## Error Message Prefix
Cobra prints an error message when receiving a non-nil error value.
The default error message is `Error: <error contents>`.
The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` function.
## PreRun and PostRun Hooks
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: