package bonfire import ( "context" "io" "slices" "strings" "charm.land/fang/v2" "github.com/charmbracelet/colorprofile" "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, or ExitUsage for usage errors. 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) { if cw, ok := w.(*colorprofile.Writer); ok { // RenderError applies its own color profile. w = cw.Forward } RenderError(w, name, err, 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. func Fail(w io.Writer, name string, err error, g Globals) int { if err == nil { return ExitOK } RenderError(w, name, err, g) return ExitFailure } func exitCode(err error) int { switch { case err == nil: return ExitOK 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 }