Silence some usage and error logging in Execute

`RunE` was introduced on `Command` to support centralized error handling.

The errors returned from `RunE` is, however, still logged by Cobra, and there is no easy way to toogle the usage logging on a case-by-case basis (other than the brutal `os.Exit(-1)`.

This commit introduces two small interfaces that enables end users to signal the behavior they want from Cobra in this area.
This commit is contained in:
Bjørn Erik Pedersen 2015-10-17 19:22:43 +02:00
parent 8b2293c741
commit f03a2c5110
2 changed files with 102 additions and 4 deletions

View file

@ -2,15 +2,15 @@ package cobra
import (
"bytes"
"errors"
"fmt"
"github.com/spf13/pflag"
"os"
"reflect"
"runtime"
"strings"
"testing"
"text/template"
"github.com/spf13/pflag"
)
var _ = fmt.Println
@ -25,6 +25,22 @@ var globalFlag1 bool
var flagEcho, rootcalled bool
var versionUsed int
type silentErr int
func (silentErr) Error() string { return "<nil>" }
func (silentErr) ShouldLogError() bool { return false }
func (silentErr) ShouldLogUsage() bool { return false }
type noErrorLogging int
func (noErrorLogging) Error() string { return "<nil>" }
func (noErrorLogging) ShouldLogError() bool { return false }
type noUsageLogging int
func (noUsageLogging) Error() string { return "<nil>" }
func (noUsageLogging) ShouldLogUsage() bool { return false }
const strtwoParentHelp = "help message for parent flag strtwo"
const strtwoChildHelp = "help message for child flag strtwo"
@ -582,6 +598,64 @@ func TestSubcommandArgEvaluation(t *testing.T) {
}
}
func TestSilentError(t *testing.T) {
rootCmd := initializeWithRootCmd()
tests := []struct {
cmd *Command
logError bool
logUsage bool
}{
{
&Command{
Use: "silent",
RunE: func(cmd *Command, args []string) error {
return silentErr(1)
}}, false, false},
{
&Command{
Use: "nousage",
RunE: func(cmd *Command, args []string) error {
return noUsageLogging(2)
}}, true, false},
{
&Command{
Use: "noerror",
RunE: func(cmd *Command, args []string) error {
return noErrorLogging(3)
}}, false, true},
{
&Command{
Use: "regular",
RunE: func(cmd *Command, args []string) error {
return errors.New("Failed")
}}, true, true},
}
for _, tc := range tests {
rootCmd.AddCommand(tc.cmd)
}
for i, tc := range tests {
result := fullTester(rootCmd, tc.cmd.Use)
if result.Error == nil {
t.Fatalf("[%d] Err was nil", i)
}
hasError := strings.Contains(result.Output, "Error:")
hasUsage := strings.Contains(result.Output, "Usage:")
if hasError != tc.logError {
t.Errorf("[%d] Error was logged: %s", i, result.Output)
}
if hasUsage != tc.logUsage {
t.Errorf("[%d] Usage was logged: %s", i, result.Output)
}
}
}
func TestPersistentFlags(t *testing.T) {
fullSetupTest("echo -s something -p more here")

View file

@ -115,6 +115,16 @@ type Command struct {
SuggestionsMinimumDistance int
}
// LogError can be implemented to toggle error logging when error is returned from RunE.
type LogError interface {
ShouldLogError() bool
}
// LogUsage can be implemented to toggle usage logging when error is returned from RunE.
type LogUsage interface {
ShouldLogUsage() bool
}
// os.Args[1:] by default, if desired, can be overridden
// particularly useful when testing.
func (c *Command) SetArgs(a []string) {
@ -637,8 +647,22 @@ func (c *Command) Execute() (err error) {
cmd.HelpFunc()(cmd, args)
return nil
}
c.Println(cmd.UsageString())
c.Println("Error:", err.Error())
logError := true
logUsage := true
if le, ok := err.(LogError); ok {
logError = le.ShouldLogError()
}
if lu, ok := err.(LogUsage); ok {
logUsage = lu.ShouldLogUsage()
}
if logUsage {
c.Println(cmd.UsageString())
}
if logError {
c.Println("Error:", err.Error())
}
}
return