1package cli 2 3import ( 4 "fmt" 5 "io" 6 "os" 7 "strings" 8) 9 10// OsExiter is the function used when the app exits. If not set defaults to os.Exit. 11var OsExiter = os.Exit 12 13// ErrWriter is used to write errors to the user. This can be anything 14// implementing the io.Writer interface and defaults to os.Stderr. 15var ErrWriter io.Writer = os.Stderr 16 17// MultiError is an error that wraps multiple errors. 18type MultiError interface { 19 error 20 // Errors returns a copy of the errors slice 21 Errors() []error 22} 23 24// NewMultiError creates a new MultiError. Pass in one or more errors. 25func newMultiError(err ...error) MultiError { 26 ret := multiError(err) 27 return &ret 28} 29 30type multiError []error 31 32// Error implements the error interface. 33func (m *multiError) Error() string { 34 errs := make([]string, len(*m)) 35 for i, err := range *m { 36 errs[i] = err.Error() 37 } 38 39 return strings.Join(errs, "\n") 40} 41 42// Errors returns a copy of the errors slice 43func (m *multiError) Errors() []error { 44 errs := make([]error, len(*m)) 45 for _, err := range *m { 46 errs = append(errs, err) 47 } 48 return errs 49} 50 51// ErrorFormatter is the interface that will suitably format the error output 52type ErrorFormatter interface { 53 Format(s fmt.State, verb rune) 54} 55 56// ExitCoder is the interface checked by `App` and `Command` for a custom exit 57// code 58type ExitCoder interface { 59 error 60 ExitCode() int 61} 62 63type exitError struct { 64 exitCode int 65 message interface{} 66} 67 68// NewExitError makes a new *exitError 69func NewExitError(message interface{}, exitCode int) ExitCoder { 70 return Exit(message, exitCode) 71} 72 73// Exit wraps a message and exit code into an ExitCoder suitable for handling by 74// HandleExitCoder 75func Exit(message interface{}, exitCode int) ExitCoder { 76 return &exitError{ 77 message: message, 78 exitCode: exitCode, 79 } 80} 81 82func (ee *exitError) Error() string { 83 return fmt.Sprintf("%v", ee.message) 84} 85 86func (ee *exitError) ExitCode() int { 87 return ee.exitCode 88} 89 90// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if 91// so prints the error to stderr (if it is non-empty) and calls OsExiter with the 92// given exit code. If the given error is a MultiError, then this func is 93// called on all members of the Errors slice and calls OsExiter with the last exit code. 94func HandleExitCoder(err error) { 95 if err == nil { 96 return 97 } 98 99 if exitErr, ok := err.(ExitCoder); ok { 100 if err.Error() != "" { 101 if _, ok := exitErr.(ErrorFormatter); ok { 102 _, _ = fmt.Fprintf(ErrWriter, "%+v\n", err) 103 } else { 104 _, _ = fmt.Fprintln(ErrWriter, err) 105 } 106 } 107 OsExiter(exitErr.ExitCode()) 108 return 109 } 110 111 if multiErr, ok := err.(MultiError); ok { 112 code := handleMultiError(multiErr) 113 OsExiter(code) 114 return 115 } 116} 117 118func handleMultiError(multiErr MultiError) int { 119 code := 1 120 for _, merr := range multiErr.Errors() { 121 if multiErr2, ok := merr.(MultiError); ok { 122 code = handleMultiError(multiErr2) 123 } else if merr != nil { 124 fmt.Fprintln(ErrWriter, merr) 125 if exitErr, ok := merr.(ExitCoder); ok { 126 code = exitErr.ExitCode() 127 } 128 } 129 } 130 return code 131} 132