-
Notifications
You must be signed in to change notification settings - Fork 0
/
command.go
176 lines (141 loc) · 3.94 KB
/
command.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package command
import (
"context"
"flag"
"fmt"
"os"
"strings"
)
// Handler represents a command function called by [Command.Execute].
// The command flags can be accessed from the FlagSet parameter using [Lookup] or [flag.Lookup].
type Handler func(context.Context, *flag.FlagSet, []string) int
// Command represents any command or subcommand of the application.
type Command interface {
// SubCommand adds a new subcommand to an existing command.
SubCommand(string) Command
// Action sets the action to execute when calling the command.
Action(Handler) Command
// Execute runs the command using [os.Args]. It should normally be called on the root command.
Execute(context.Context)
// Help sets the help message of a command.
Help(string) Command
// Flags is used to declare the flags of a command.
Flags(func(*flag.FlagSet)) Command
}
type command struct {
name string
help string
handler Handler
subCommands map[string]*command
flagSet *flag.FlagSet
parent *command
}
// Root creates a new root command.
func Root() Command {
command := command{
name: os.Args[0],
subCommands: map[string]*command{},
flagSet: flag.CommandLine,
}
flag.CommandLine.Usage = command.usage
return &command
}
func (c *command) SubCommand(name string) Command {
c.subCommands[name] = &command{
name: name,
subCommands: map[string]*command{},
flagSet: flag.NewFlagSet(name, flag.ExitOnError),
parent: c,
}
c.subCommands[name].flagSet.Usage = c.subCommands[name].usage
return c.subCommands[name]
}
func (c *command) Action(handler Handler) Command {
c.handler = handler
return c
}
func (c *command) Execute(ctx context.Context) {
command, args := c, os.Args[1:]
for {
if err := command.flagSet.Parse(args); err != nil {
// This should never occur because the flag sets use flag.ExitOnError
os.Exit(2) // Use 2 to mimick the behavior of flag.ExitOnError
}
args = command.flagSet.Args()
if len(args) == 0 {
break
}
subCommand, ok := command.subCommands[args[0]]
if !ok {
break
}
command.flagSet.VisitAll(func(f *flag.Flag) {
subCommand.flagSet.Var(f.Value, f.Name, f.Usage)
})
command = subCommand
args = args[1:]
}
if command.handler == nil {
if len(args) > 0 {
command.flagSet.SetOutput(os.Stderr)
fmt.Fprintf(command.flagSet.Output(), "command provided but not defined: %s\n", args[0])
command.usage()
os.Exit(2) // Use 2 to mimick the behavior of flag.ExitOnError
}
command.usage()
os.Exit(0)
}
os.Exit(command.handler(ctx, command.flagSet, args))
}
func (c *command) Help(help string) Command {
c.help = help
return c
}
func (c *command) Flags(flags func(*flag.FlagSet)) Command {
flags(c.flagSet)
return c
}
func (c *command) usage() {
var builder strings.Builder
output := c.flagSet.Output()
c.flagSet.SetOutput(&builder)
fullCommand := []string{c.name}
for command := c.parent; command != nil; command = command.parent {
fullCommand = append([]string{command.name}, fullCommand...)
}
optionsHint := " [OPTIONS]"
subCommandHint := ""
if len(c.subCommands) > 0 {
subCommandHint = " [COMMAND]"
if c.handler == nil {
subCommandHint = " COMMAND"
}
}
builder.WriteString("Usage: ")
builder.WriteString(strings.Join(fullCommand, " "))
builder.WriteString(optionsHint)
builder.WriteString(subCommandHint)
builder.WriteString("\n")
if c.help != "" {
builder.WriteString("\n")
builder.WriteString(c.help)
builder.WriteString("\n")
}
// TODO: check if there are options to print
builder.WriteString("\n")
builder.WriteString("Options:\n")
c.flagSet.PrintDefaults()
if len(c.subCommands) > 0 {
builder.WriteString("\n")
builder.WriteString("Subcommands:")
for name, subCommand := range c.subCommands {
builder.WriteString("\n ")
builder.WriteString(name)
if subCommand.help != "" {
builder.WriteString("\n\t")
builder.WriteString(subCommand.help)
}
}
}
fmt.Fprintln(output, builder.String())
}