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 struct {
19 Errors []error
20}
21
22// NewMultiError creates a new MultiError. Pass in one or more errors.
23func NewMultiError(err ...error) MultiError {
24 return MultiError{Errors: err}
25}
26
27// Error implements the error interface.
28func (m MultiError) Error() string {
29 errs := make([]string, len(m.Errors))
30 for i, err := range m.Errors {
31 errs[i] = err.Error()
32 }
33
34 return strings.Join(errs, "\n")
35}
36
37type ErrorFormatter interface {
38 Format(s fmt.State, verb rune)
39}
40
41// ExitCoder is the interface checked by `App` and `Command` for a custom exit
42// code
43type ExitCoder interface {
44 error
45 ExitCode() int
46}
47
48// ExitError fulfills both the builtin `error` interface and `ExitCoder`
49type ExitError struct {
50 exitCode int
51 message interface{}
52}
53
54// NewExitError makes a new *ExitError
55func NewExitError(message interface{}, exitCode int) *ExitError {
56 return &ExitError{
57 exitCode: exitCode,
58 message: message,
59 }
60}
61
62// Error returns the string message, fulfilling the interface required by
63// `error`
64func (ee *ExitError) Error() string {
65 return fmt.Sprintf("%v", ee.message)
66}
67
68// ExitCode returns the exit code, fulfilling the interface required by
69// `ExitCoder`
70func (ee *ExitError) ExitCode() int {
71 return ee.exitCode
72}
73
74// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
75// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
76// given exit code. If the given error is a MultiError, then this func is
77// called on all members of the Errors slice and calls OsExiter with the last exit code.
78func HandleExitCoder(err error) {
79 if err == nil {
80 return
81 }
82
83 if exitErr, ok := err.(ExitCoder); ok {
84 if err.Error() != "" {
85 if _, ok := exitErr.(ErrorFormatter); ok {
86 fmt.Fprintf(ErrWriter, "%+v\n", err)
87 } else {
88 fmt.Fprintln(ErrWriter, err)
89 }
90 }
91 OsExiter(exitErr.ExitCode())
92 return
93 }
94
95 if multiErr, ok := err.(MultiError); ok {
96 code := handleMultiError(multiErr)
97 OsExiter(code)
98 return
99 }
100}
101
102func handleMultiError(multiErr MultiError) int {
103 code := 1
104 for _, merr := range multiErr.Errors {
105 if multiErr2, ok := merr.(MultiError); ok {
106 code = handleMultiError(multiErr2)
107 } else {
108 fmt.Fprintln(ErrWriter, merr)
109 if exitErr, ok := merr.(ExitCoder); ok {
110 code = exitErr.ExitCode()
111 }
112 }
113 }
114 return code
115}