aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/cmd.go
blob: 698cf7cb51cb815ad50896cd8c5dae4735094ae4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
}