From 2c41acab4c97f584e8e199b31dc456194b8d7e6c Mon Sep 17 00:00:00 2001 From: tsne Date: Mon, 6 Jul 2026 16:52:20 +0200 Subject: initial --- internal/cli/cmd.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 internal/cli/cmd.go (limited to 'internal/cli/cmd.go') diff --git a/internal/cli/cmd.go b/internal/cli/cmd.go new file mode 100644 index 0000000..698cf7c --- /dev/null +++ b/internal/cli/cmd.go @@ -0,0 +1,73 @@ +package cli + +import ( + "context" + "errors" + "io" + "os" + + "github.com/spf13/pflag" + + "tsne.dev/hopper/internal/log" +) + +// Run executes the CLI and returns the process exit code. +// +// Note: Help paths will call exit directly. +func Run(ctx context.Context, args []string) int { + main := newMainCommand() + err := runCommand(ctx, main, args) + if err != nil { + var exit exitError + if errors.As(err, &exit) { + return exit.code + } + + var ev errorVerboser + if errors.As(err, &ev) { + log.Error("%s\n %s", err.Error(), ev.ErrorVerbose()) + } else { + log.Error(err.Error()) + } + return 1 + } + return 0 +} + +type command interface { + Usage() string + Run(ctx context.Context, args []string) error +} + +func parseFlags(cmd command, flags *pflag.FlagSet, args []string) { + err := flags.Parse(args) + if err != nil { + if errors.Is(err, pflag.ErrHelp) { + log.Print(cmd.Usage()) + os.Exit(0) + } else { + log.Error("%s\n\n%s", err.Error(), cmd.Usage()) + os.Exit(2) + } + } +} + +func runCommand(ctx context.Context, cmd command, args []string) error { + err := cmd.Run(ctx, args) + if err != nil { + var usageErr usageError + if errors.As(err, &usageErr) { + log.Error("%s\n\n%s", usageErr.msg, cmd.Usage()) + os.Exit(2) + } + return err + } + return nil +} + +func newFlagset(name string) *pflag.FlagSet { + fs := pflag.NewFlagSet(name, pflag.ContinueOnError) + fs.SetOutput(io.Discard) + fs.Usage = func() {} + return fs +} -- cgit v1.3