-
-
Notifications
You must be signed in to change notification settings - Fork 366
/
cli.go
137 lines (122 loc) · 3.04 KB
/
cli.go
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package cli is a package to build CLI programs.
//
// eg:
// func main() {
// opts := struct {
// Foo string `env:"FOO" help:"A simple string."`
// Bar int `env:"BAR" help:"A simple integer."`
// }{
// Foo: "foo",
// Bar: 42,
// }
//
// ctx, cancel := cli.ContextWithSignals(context.Background(),
// os.Interrupt,
// syscall.SIGTERM,
// )
// defer cancel()
//
// cli.Register().
// Help("A simple command").
// Options(&opts)
//
// cli.Register("hello").
// Help("A sub command").
// Options(&opts)
//
// cli.Register("world").
// Help("Another sub command").
// Options(&opts)
//
// switch cli.Load() {
// case "hello":
// helloCmd(ctx, opts)
//
// case "world":
// worldCmd(ctx, opts)
//
// default:
// defaultCmd(ctx, opts)
// }
// }
//
package cli
import (
"context"
"errors"
"flag"
"os"
"os/signal"
)
var (
defaultManager = commandManager{out: os.Stderr}
currentUsage func()
exitOnError = true
programArgs = os.Args[1:]
)
// Command is the interface that describes a command.
type Command interface {
// Sets the command help description.
Help(string) Command
// Sets the command options with the given receiver. The receiver must be a
// pointer to a struct.
Options(interface{}) Command
}
// Register registers and returns the named command.
func Register(cmd ...string) Command {
return defaultManager.register(cmd...)
}
// Load loads the registered command that matches the program args. If defined,
// environment variables and flags are loaded in the command options.
//
// It prints the command usage and exits the program with code -1 when an error
// occurs.
func Load() (cmd string) {
cmd, usage, err := defaultManager.parse(programArgs...)
currentUsage = usage
if err != nil {
if errors.Is(err, errNoRootCmd) && err != flag.ErrHelp {
printError(defaultManager.out, err)
}
if usage != nil {
usage()
}
if exitOnError {
os.Exit(-1)
}
panic(err)
}
return cmd
}
// Usage prints the loaded command usage. It panics when called before the Load
// function.
func Usage() {
if currentUsage == nil {
panic("usage func is called before load func")
}
currentUsage()
}
// Error prints the given error and exit the program with code -1.
func Error(err error) {
printError(defaultManager.out, err)
if exitOnError {
os.Exit(-1)
}
}
// ContextWithSignals returns a copy of the parent context that gets canceled
// when one of the specified signals is emitted. If no signals are provided, all
// incoming signals will cancel the context.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func ContextWithSignals(parent context.Context, sig ...os.Signal) (ctx context.Context, cancel func()) {
ctx, cancel = context.WithCancel(parent)
c := make(chan os.Signal, 1)
signal.Notify(c, sig...)
go func() {
<-c
close(c)
cancel()
}()
return ctx, cancel
}