Docs around exit error behavior, + handling MultiError in HandleExitCoder

This commit is contained in:
Dan Buch 2016-04-27 09:54:08 -04:00
parent b2ac0616d1
commit d48e22a9dd
No known key found for this signature in database
GPG Key ID: FAEF12936DD3E3EC
2 changed files with 50 additions and 20 deletions

View File

@ -140,7 +140,7 @@ You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go ``` go
... ...
app.Action = func(c *cli.Context) error { app.Action = func(c *cli.Context) error {
println("Hello", c.Args()[0]) fmt.Println("Hello", c.Args()[0])
return nil return nil
} }
... ...
@ -165,9 +165,9 @@ app.Action = func(c *cli.Context) error {
name = c.Args()[0] name = c.Args()[0]
} }
if c.String("lang") == "spanish" { if c.String("lang") == "spanish" {
println("Hola", name) fmt.Println("Hola", name)
} else { } else {
println("Hello", name) fmt.Println("Hello", name)
} }
return nil return nil
} }
@ -193,9 +193,9 @@ app.Action = func(c *cli.Context) error {
name = c.Args()[0] name = c.Args()[0]
} }
if language == "spanish" { if language == "spanish" {
println("Hola", name) fmt.Println("Hola", name)
} else { } else {
println("Hello", name) fmt.Println("Hello", name)
} }
return nil return nil
} }
@ -323,7 +323,7 @@ app.Commands = []cli.Command{
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "add a task to the list", Usage: "add a task to the list",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("added task: ", c.Args().First()) fmt.Println("added task: ", c.Args().First())
}, },
}, },
{ {
@ -331,7 +331,7 @@ app.Commands = []cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "complete a task on the list", Usage: "complete a task on the list",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("completed task: ", c.Args().First()) fmt.Println("completed task: ", c.Args().First())
}, },
}, },
{ {
@ -343,14 +343,14 @@ app.Commands = []cli.Command{
Name: "add", Name: "add",
Usage: "add a new template", Usage: "add a new template",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("new task template: ", c.Args().First()) fmt.Println("new task template: ", c.Args().First())
}, },
}, },
{ {
Name: "remove", Name: "remove",
Usage: "remove an existing template", Usage: "remove an existing template",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First()) fmt.Println("removed task template: ", c.Args().First())
}, },
}, },
}, },
@ -401,20 +401,35 @@ COMMANDS:
### Exit code ### Exit code
Calling `App.Run` will not automatically call `os.Exit`, which means that by Calling `App.Run` will not automatically call `os.Exit`, which means that by
default the exit code will "fall through" to being `0`. Proper exit code default the exit code will "fall through" to being `0`. An explicit exit code
propagation is the responsibility of the code that calls `App.Run`, e.g.: may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:
``` go ``` go
package main package main
import ( import (
"os" "os"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
) )
func main() { func main() {
exitCode, _ := cli.NewApp().Run(os.Args) app := cli.NewApp()
os.Exit(exitCode) app.Flags = []cli.Flag{
cli.BoolTFlag{
Name: "ginger-crouton",
Usage: "is it in the soup?",
},
}
app.Action = func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") {
return cli.NewExitError("it is not in the soup", 86)
}
return nil
}
app.Run(os.Args)
} }
``` ```
@ -436,7 +451,7 @@ app.Commands = []cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "complete a task on the list", Usage: "complete a task on the list",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("completed task: ", c.Args().First()) fmt.Println("completed task: ", c.Args().First())
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
// This will complete if no args are passed // This will complete if no args are passed

View File

@ -23,15 +23,19 @@ func (m MultiError) Error() string {
return strings.Join(errs, "\n") return strings.Join(errs, "\n")
} }
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
// code
type ExitCoder interface { type ExitCoder interface {
ExitCode() int ExitCode() int
} }
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
type ExitError struct { type ExitError struct {
exitCode int exitCode int
message string message string
} }
// NewExitError makes a new *ExitError
func NewExitError(message string, exitCode int) *ExitError { func NewExitError(message string, exitCode int) *ExitError {
return &ExitError{ return &ExitError{
exitCode: exitCode, exitCode: exitCode,
@ -39,22 +43,33 @@ func NewExitError(message string, exitCode int) *ExitError {
} }
} }
// Error returns the string message, fulfilling the interface required by
// `error`
func (ee *ExitError) Error() string { func (ee *ExitError) Error() string {
return ee.message return ee.message
} }
func (ee *ExitError) String() string { // ExitCode returns the exit code, fulfilling the interface required by
return fmt.Sprintf("%s exitcode=%v", ee.message, ee.exitCode) // `ExitCoder`
}
func (ee *ExitError) ExitCode() int { func (ee *ExitError) ExitCode() int {
return ee.exitCode return ee.exitCode
} }
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
// so calls os.Exit with the given exit code. // so prints the error to stderr (if it is non-empty) and calls os.Exit with the
// given exit code. If the given error is a MultiError, then this func is
// called on all members of the Errors slice.
func HandleExitCoder(err error) { func HandleExitCoder(err error) {
if exitErr, ok := err.(ExitCoder); ok { if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" {
fmt.Fprintln(os.Stderr, err)
}
os.Exit(exitErr.ExitCode()) os.Exit(exitErr.ExitCode())
} }
if multiErr, ok := err.(MultiError); ok {
for _, merr := range multiErr.Errors {
HandleExitCoder(merr)
}
}
} }