- bonfire.WithExitCode (additive) makes an error carry the exit code bonfire.Execute returns; a code outside 1..255 gives 1 - runAppCmd, factored out of runAppBinary, returns Reported(WithExitCode(err, code)) when the binary exited with a code, so summer prints no second error block; a binary killed by a signal keeps summer's block and exit 1 - README, console introduction and upgrading notes say so (CR-02)
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
package bonfire
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
|
|
"charm.land/fang/v2"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// Exit codes returned by Execute and Fail.
|
|
const (
|
|
// ExitOK means the command succeeded.
|
|
ExitOK = 0
|
|
// ExitFailure means the command failed.
|
|
ExitFailure = 1
|
|
// ExitUsage means the command was called wrongly: an unknown command or
|
|
// flag, or a wrong number of arguments.
|
|
ExitUsage = 2
|
|
)
|
|
|
|
// Execute runs root with args and returns the process exit code: ExitOK,
|
|
// ExitFailure, ExitUsage for usage errors, or the code an error carries
|
|
// through WithExitCode. With no command word in args (only global options,
|
|
// -h or --help) it prints the command list. Errors are rendered on the
|
|
// root's error stream by RenderError through fang's error handler; fang is
|
|
// used without its version flag, man page and completion commands.
|
|
func Execute(ctx context.Context, root *cobra.Command, args []string) int {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if args == nil {
|
|
// cobra reads os.Args when the args are nil.
|
|
args = []string{}
|
|
}
|
|
g := PeekGlobals(args)
|
|
if !hasCommandWord(args) {
|
|
args = slices.DeleteFunc(slices.Clone(args), func(a string) bool {
|
|
return a == "-h" || a == "--help"
|
|
})
|
|
}
|
|
resetFlags(root)
|
|
root.SetArgs(args)
|
|
name := root.Name()
|
|
err := fang.Execute(ctx, root,
|
|
fang.WithoutVersion(),
|
|
fang.WithoutManpage(),
|
|
fang.WithoutCompletions(),
|
|
fang.WithErrorHandler(func(w io.Writer, _ fang.Styles, err error) {
|
|
// fang hands over a colorprofile writer built from the
|
|
// environment only; RenderError unwraps it and applies
|
|
// --ansi and --no-ansi itself.
|
|
RenderError(w, name, wrongDirectory(name, err, args, ""), g)
|
|
}),
|
|
)
|
|
return exitCode(err)
|
|
}
|
|
|
|
// Fail renders err like Execute does and returns ExitFailure (ExitOK for a
|
|
// nil error). The generated main uses it for errors that happen before the
|
|
// command root exists, such as a configuration that does not load. Like
|
|
// Execute, it adds the wrong-directory hint for an application binary run
|
|
// outside its application root.
|
|
func Fail(w io.Writer, name string, err error, g Globals) int {
|
|
if err == nil {
|
|
return ExitOK
|
|
}
|
|
RenderError(w, name, wrongDirectory(name, err, os.Args[1:], ""), g)
|
|
return ExitFailure
|
|
}
|
|
|
|
func exitCode(err error) int {
|
|
var carrier *exitCodeError
|
|
switch {
|
|
case err == nil:
|
|
return ExitOK
|
|
case errors.As(err, &carrier):
|
|
if carrier.code < 1 || carrier.code > 255 {
|
|
return ExitFailure
|
|
}
|
|
return carrier.code
|
|
case isUsage(err):
|
|
return ExitUsage
|
|
default:
|
|
return ExitFailure
|
|
}
|
|
}
|
|
|
|
// hasCommandWord reports whether args, before "--", hold a token that is
|
|
// neither a flag nor the value of --env.
|
|
func hasCommandWord(args []string) bool {
|
|
for i := 0; i < len(args); i++ {
|
|
a := args[i]
|
|
switch {
|
|
case a == "--":
|
|
return false
|
|
case a == "--"+flagEnv:
|
|
i++
|
|
case strings.HasPrefix(a, "-"):
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|