├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── code.go ├── code_test.go ├── go.mod └── go.sum /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | 11 | - name: Set up Go 1.x 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ^1.15 15 | id: go 16 | 17 | - name: Check out code into the Go module directory 18 | uses: actions/checkout@v2 19 | 20 | - name: Get dependencies 21 | run: go mod download 22 | 23 | - name: Test 24 | run: go test -v . 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Carl Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exitcode [![GoDoc](https://godoc.org/github.com/carlmjohnson/exitcode?status.svg)](https://godoc.org/github.com/carlmjohnson/exitcode) [![Go Report Card](https://goreportcard.com/badge/github.com/carlmjohnson/exitcode)](https://goreportcard.com/report/github.com/carlmjohnson/exitcode) 2 | 3 | Go package to convert errors to exit codes 4 | -------------------------------------------------------------------------------- /code.go: -------------------------------------------------------------------------------- 1 | package exitcode 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "os" 7 | ) 8 | 9 | // Coder is an interface to control what value Get returns. 10 | type Coder interface { 11 | error 12 | ExitCode() int 13 | } 14 | 15 | // Get gets the exit code associated with an error. Cases: 16 | // 17 | // nil or flag.ErrHelp => 0 18 | // errors implementing Coder => value returned by ExitCode 19 | // all other errors => 1 20 | func Get(err error) int { 21 | if err == nil { 22 | return 0 23 | } 24 | 25 | if coder := Coder(nil); errors.As(err, &coder) { 26 | return coder.ExitCode() 27 | } 28 | 29 | if errors.Is(err, flag.ErrHelp) { 30 | return 0 31 | } 32 | 33 | return 1 34 | } 35 | 36 | // Set wraps an error in a Coder, setting its error code. 37 | func Set(err error, code int) error { 38 | if err == nil { 39 | return nil 40 | } 41 | return coder{err, code} 42 | } 43 | 44 | var _ Coder = coder{} 45 | 46 | type coder struct { 47 | error 48 | int 49 | } 50 | 51 | func (co coder) ExitCode() int { 52 | return co.int 53 | } 54 | 55 | func (co coder) Unwrap() error { 56 | return co.error 57 | } 58 | 59 | // Exit is a convenience function that calls os.Exit 60 | // with the exit code associated with err. 61 | func Exit(err error) { 62 | os.Exit(Get(err)) 63 | } 64 | -------------------------------------------------------------------------------- /code_test.go: -------------------------------------------------------------------------------- 1 | package exitcode_test 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/carlmjohnson/exitcode" 10 | ) 11 | 12 | func TestGet(t *testing.T) { 13 | base := exitcode.Set(errors.New(""), 4) 14 | wrapped := fmt.Errorf("wrapping: %w", base) 15 | 16 | testCases := map[string]struct { 17 | error 18 | int 19 | }{ 20 | "nil": {nil, 0}, 21 | "default": {errors.New(""), 1}, 22 | "help": {flag.ErrHelp, 0}, 23 | "set": {exitcode.Set(errors.New(""), 3), 3}, 24 | "wrapped": {wrapped, 4}, 25 | } 26 | 27 | for name, tc := range testCases { 28 | t.Run(name, func(t *testing.T) { 29 | err := tc.error 30 | want := tc.int 31 | got := exitcode.Get(err) 32 | if got != want { 33 | t.Errorf("%v: %d != %d", err, got, want) 34 | } 35 | }) 36 | } 37 | } 38 | 39 | func TestSet(t *testing.T) { 40 | t.Run("same-message", func(t *testing.T) { 41 | err := errors.New("hello") 42 | coder := exitcode.Set(err, 2) 43 | got := err.Error() 44 | want := coder.Error() 45 | if got != want { 46 | t.Errorf("error message %q != %q", got, want) 47 | } 48 | }) 49 | t.Run("keep-chain", func(t *testing.T) { 50 | err := errors.New("hello") 51 | coder := exitcode.Set(err, 3) 52 | 53 | if !errors.Is(coder, err) { 54 | t.Errorf("broken chain: %v is not %v", coder, err) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/carlmjohnson/exitcode 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/earthboundkid/exitcode/3d9e153c0cd673fae3bcc403bbcff52db72777b1/go.sum --------------------------------------------------------------------------------