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