aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/cmd.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cli/cmd.go')
-rw-r--r--internal/cli/cmd.go73
1 files changed, 73 insertions, 0 deletions
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
+}