You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
2.6 KiB

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