-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcmd.go
216 lines (192 loc) · 5.47 KB
/
cmd.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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package cmndr
import (
"flag"
"fmt"
"os"
"sort"
"text/tabwriter"
)
// RunFunc defines the arity and return signatures of a function that a Cmd
// will run.
type RunFunc func(cmd *Cmd, args []string) error
// Cmd defines the structure of a command that can be run.
type Cmd struct {
// The name of the command.
Name string
// A brief, single line description of the command.
Description string
// A *flag.FlagSet for registering command-line flags for the command.
Flags *flag.FlagSet
// Will be nil unless subcommands are registered with the AddCmd()
// method.
Commands map[string]*Cmd
// The function to run.
Run RunFunc
}
func newUsage(c *Cmd) func() {
return func() {
if c.Flags == nil {
c.Flags = flag.NewFlagSet(c.Name, flag.ExitOnError)
}
fmt.Fprintf(os.Stderr, "%s - %s\n", c.Name, c.Description)
printSubcommands(c)
fmt.Fprintln(os.Stderr, "\nFlags")
c.Flags.PrintDefaults()
}
}
// newHelpCmd is called by New() to add a "help" subcommand to parent.
func newHelpCmd(parent *Cmd) *Cmd {
descr := fmt.Sprintf("Print the help message for %s or a subcommand", parent.Name)
return &Cmd{
Name: "help",
Description: descr,
Run: func(cmd *Cmd, args []string) error {
// The command to print the help message for.
var pp *Cmd
if len(args) == 0 || parent.Commands == nil {
// In this situation, the user has just run
//
// $ cmd help
//
// or, they have run
//
// $ cmd help foo
//
// but "cmd" has no registered subcommands.
pp = parent
} else if sub, ok := parent.Commands[args[0]]; ok {
// The user has run
//
// $ cmd help foo
//
// and the "foo" subcommand has been
// registered.
pp = sub
}
// If pp hasn't been set, this means that the user has
// intended to print the help message of a subcommand,
// but that subcommand does not exist.
if pp == nil {
return fmt.Errorf("no such command: %q", args[0])
}
if pp.Flags == nil {
fmt.Fprintf(os.Stderr, "%s - %s\n", pp.Name, pp.Description)
printSubcommands(pp)
} else {
pp.Flags.Usage()
}
return nil
},
}
}
// printSubcommands is a helper function, used when calling a "help"
// subcommand; it prints all of the registered subcommands of c, if any.
func printSubcommands(c *Cmd) {
if c.Commands == nil {
return
}
fmt.Fprintln(os.Stderr, "\nCommands")
// Gather a list of all subcommand names, and sort them (for
// consistent output).
var subNames []string
for name := range c.Commands {
subNames = append(subNames, name)
}
sort.Strings(subNames)
tw := tabwriter.NewWriter(os.Stderr, 0, 4, 1, ' ', 0)
defer tw.Flush()
for _, name := range subNames {
fmt.Fprintf(tw, "\t\t%s\t%s\n", name, c.Commands[name].Description)
}
}
// New is a convenience function for creating and returning a new *Cmd.
//
// New will automatically add a "help" subcommand that, when called with no
// arguments, will print the help message for its parent command. If any
// arguments are provided to the "help" subcommand, only the first argument
// will be consulted, and it will print the help message for the specified
// subcommand.
//
// Note, that the following two command-line calls are effectively the same:
//
// $ my-command help <subcommand>
// $ my-command <subcommand> help
func New(name string, run RunFunc) *Cmd {
c := &Cmd{
Name: name,
Flags: flag.NewFlagSet(name, flag.ExitOnError),
Run: run,
}
c.Flags.Usage = newUsage(c)
c.AddCmd(newHelpCmd(c))
return c
}
// AddCmd registers a subcommand.
//
// AddCmd will panic if the given cmd's Name field is an empty string.
// If there is a subcommand already registered with the same name, it will be
// replaced.
func (c *Cmd) AddCmd(cmd *Cmd) {
if c.Commands == nil {
c.Commands = make(map[string]*Cmd)
}
if cmd.Name == "" {
panic("cannot add nameless subcommand")
}
c.Commands[cmd.Name] = cmd
}
// Exec parses the arguments provided on the command line. This is the
// method that should be called from the outer-most command (e.g. the
// "root" command).
//
// It is essentially a short-hand invocation of
//
// c.ExecArgs(os.Args[1:])
func (c *Cmd) Exec() {
c.ExecArgs(os.Args[1:])
}
// ExecArgs executes c.Run with the given arguments. If c.Run == nil,
// and no subcommand was provided as a positional argument, this method will
// print a usage message, and exit.
//
// To customize the usage message that is printed, set c.Flags.Usage (refer to
// the documentation for flag.FlagSet).
func (c *Cmd) ExecArgs(args []string) {
// Make sure there is a non-nil flag set.
if c.Flags == nil {
c.Flags = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
}
// Parse the given arguments.
if err := c.Flags.Parse(args); err != nil {
fmt.Fprintln(os.Stderr, "error parsing arguments:", err)
os.Exit(2)
}
// If we have some registered subcommands, and the first positional
// argument matches the name of one of the registered subcommands,
// execute it.
if c.Commands != nil && c.Flags.Arg(0) != "" {
if sub, ok := c.Commands[c.Flags.Arg(0)]; ok {
// Our first positional argument refers to a registered
// subcommand.
//
// Run that subcommand.
if c.Flags.NArg() > 1 {
sub.ExecArgs(c.Flags.Args()[1:])
} else {
sub.ExecArgs(nil)
}
return
}
}
// No subcommand was provided, and our main RunFunc is nil. Print a
// usage message, and exit.
if c.Run == nil {
c.Flags.Usage()
os.Exit(1)
}
// Call our RunFunc.
if err := c.Run(c, c.Flags.Args()); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}