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}
116