blob: d6705e6338e721f9356776492267eb18ea1517f4 [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001// Package cmdline provides a data-driven framework to simplify writing
2// command-line programs. It includes built-in support for formatted help.
3//
4// Commands may be linked together to form a command tree. Since commands may
5// be arbitrarily nested within other commands, it's easy to create wrapper
6// programs that invoke existing commands.
7//
8// The syntax for each command-line program is:
9//
10// command [flags] [subcommand [flags]]* [args]
11//
12// Each sequence of flags on the command-line is associated with the command
13// that immediately precedes them. Global flags registered with the standard
14// flags package are allowed anywhere a command-specific flag is allowed.
15package cmdline
16
17import (
18 "errors"
19 "flag"
20 "fmt"
21 "io"
22 "os"
23 "strings"
24)
25
26// ErrUsage is returned to indicate an error in command usage; e.g. unknown
27// flags, subcommands or args.
28var ErrUsage = errors.New("usage error")
29
30// Command represents a single command in a command-line program. A program
31// with subcommands is represented as a root Command with children representing
32// each subcommand. The command graph must be a tree; each command may either
33// have exactly one parent (a sub-command), or no parent (the root), and cycles
34// are not allowed. This makes it easier to display the usage for subcommands.
35type Command struct {
36 Name string // Name of the command.
37 Short string // Short description, shown in help called on parent.
38 Long string // Long description, shown in help called on itself.
39 Flags flag.FlagSet // Flags for the command.
40 ArgsName string // Name of the args, shown in usage line.
41 ArgsLong string // Long description of the args, shown in help.
42
43 // Children of the command. The framework will match args[0] against each
44 // child's name, and call Run on the first matching child.
45 Children []*Command
46
47 // Run is a function that runs cmd with args. If both Children and Run are
48 // specified, Run will only be called if none of the children match. It is an
49 // error if neither is specified.
50 Run func(cmd *Command, args []string) error
51
52 // parent holds the parent of this Command, or nil if this is the root.
53 parent *Command
54
55 // Stdout and stderr are set through Init.
56 stdout, stderr io.Writer
57
58 // parseFlags holds the merged flags used for parsing. Each command starts
59 // with its own Flags, and we merge in all global flags. If the same flag is
60 // specified in both sets, the command's own flag wins.
61 parseFlags *flag.FlagSet
62
63 // TODO(toddw): If necessary we can add alias support, e.g. for abbreviations.
64 // Alias map[string]string
65}
66
67// style describes the formatting style for usage descriptions.
68type style int
69
70const (
71 styleText style = iota // Default style, good for cmdline output.
72 styleGoDoc // Style good for godoc processing.
73)
74
75// String returns the human-readable representation of the style.
76func (s *style) String() string {
77 switch *s {
78 case styleText:
79 return "text"
80 case styleGoDoc:
81 return "godoc"
82 default:
83 panic(fmt.Errorf("Unhandled style %d", *s))
84 }
85}
86
87// Set implements the flag.Value interface method.
88func (s *style) Set(value string) error {
89 switch value {
90 case "text":
91 *s = styleText
92 case "godoc":
93 *s = styleGoDoc
94 default:
95 return fmt.Errorf("Unknown style %q", value)
96 }
97 return nil
98}
99
100// Stdout is where output goes. Typically os.Stdout.
101func (cmd *Command) Stdout() io.Writer {
102 return cmd.stdout
103}
104
105// Stderr is where error messages go. Typically os.Stderr
106func (cmd *Command) Stderr() io.Writer {
107 return cmd.stderr
108}
109
110// Errorf should be called to signal an invalid usage of the command.
111func (cmd *Command) Errorf(format string, v ...interface{}) error {
112 fmt.Fprint(cmd.stderr, "ERROR: ")
113 fmt.Fprintf(cmd.stderr, format, v...)
114 fmt.Fprint(cmd.stderr, "\n\n")
115 cmd.usage(cmd.stderr, styleText, true)
116 return ErrUsage
117}
118
119// usage prints the usage of cmd to the writer, with the given style. The
120// firstCall boolean is set to false when printing usage for multiple commands,
121// and is used to avoid printing redundant information (e.g. section headers,
122// global flags).
123func (cmd *Command) usage(w io.Writer, style style, firstCall bool) {
124 var names []string
125 for c := cmd; c != nil; c = c.parent {
126 names = append([]string{c.Name}, names...)
127 }
128 namestr := strings.Join(names, " ")
129 if !firstCall && style == styleGoDoc {
130 // Title-case names so that godoc recognizes it as a section header.
131 fmt.Fprintf(w, "%s\n\n", strings.Title(namestr))
132 }
133 // Long description.
134 fmt.Fprint(w, strings.Trim(cmd.Long, "\n"))
135 fmt.Fprintln(w)
136 // Usage line.
137 hasFlags := false
138 cmd.Flags.VisitAll(func(*flag.Flag) {
139 hasFlags = true
140 })
141 fmt.Fprintf(w, "\nUsage:\n")
142 nameflags := " " + namestr
143 if hasFlags {
144 nameflags += " [flags]"
145 }
146 if len(cmd.Children) > 0 {
147 fmt.Fprintf(w, "%s <command>\n", nameflags)
148 }
149 if cmd.Run != nil {
150 if cmd.ArgsName != "" {
151 fmt.Fprintf(w, "%s %s\n", nameflags, cmd.ArgsName)
152 } else {
153 fmt.Fprintf(w, "%s\n", nameflags)
154 }
155 }
156 if len(cmd.Children) == 0 && cmd.Run == nil {
157 // This is a specification error.
158 fmt.Fprintf(w, "%s [ERROR: neither Children nor Run is specified]\n", nameflags)
159 }
160 // Commands.
161 if len(cmd.Children) > 0 {
162 fmt.Fprintf(w, "\nThe %s commands are:\n", cmd.Name)
163 for _, child := range cmd.Children {
164 fmt.Fprintf(w, " %-11s %s\n", child.Name, child.Short)
165 }
166 }
167 // Args.
168 if cmd.Run != nil && cmd.ArgsLong != "" {
169 fmt.Fprintf(w, "\n")
170 fmt.Fprint(w, strings.Trim(cmd.ArgsLong, "\n"))
171 fmt.Fprintf(w, "\n")
172 }
173 // Flags.
174 if hasFlags {
175 fmt.Fprintf(w, "\nThe %s flags are:\n", cmd.Name)
176 cmd.Flags.VisitAll(func(f *flag.Flag) {
177 fmt.Fprintf(w, " -%s=%s: %s\n", f.Name, f.DefValue, f.Usage)
178 })
179 }
180 // Global flags.
181 hasGlobalFlags := false
182 flag.VisitAll(func(*flag.Flag) {
183 hasGlobalFlags = true
184 })
185 if firstCall && hasGlobalFlags {
186 fmt.Fprintf(w, "\nThe global flags are:\n")
187 flag.VisitAll(func(f *flag.Flag) {
188 fmt.Fprintf(w, " -%s=%s: %s\n", f.Name, f.DefValue, f.Usage)
189 })
190 }
191}
192
193// newDefaultHelp creates a new default help command. We need to create new
194// instances since the parent for each help command is different.
195func newDefaultHelp() *Command {
196 helpStyle := styleText
197 help := &Command{
198 Name: helpName,
199 Short: "Display help for commands",
200 Long: `
201Help displays usage descriptions for this command, or usage descriptions for
202sub-commands.
203`,
204 ArgsName: "<command>",
205 ArgsLong: `
206<command> is an optional sequence of commands to display detailed per-command
207usage. The special-case "help ..." recursively displays help for this command
208and all sub-commands.
209`,
210 Run: func(cmd *Command, args []string) error {
211 // Help applies to its parent - e.g. "foo help" applies to the foo command.
212 return runHelp(cmd.parent, args, helpStyle)
213 },
214 }
215 help.Flags.Var(&helpStyle, "style", `The formatting style for help output, either "text" or "godoc".`)
216 return help
217}
218
219const helpName = "help"
220
221// runHelp runs the "help" command.
222func runHelp(cmd *Command, args []string, style style) error {
223 if len(args) == 0 {
224 cmd.usage(cmd.stdout, style, true)
225 return nil
226 }
227 if args[0] == "..." {
228 recursiveHelp(cmd, style, true)
229 return nil
230 }
231 // Find the subcommand to display help.
232 subName := args[0]
233 subArgs := args[1:]
234 for _, child := range cmd.Children {
235 if child.Name == subName {
236 return runHelp(child, subArgs, style)
237 }
238 }
239 return cmd.Errorf("%s: unknown command %q", cmd.Name, subName)
240}
241
242// recursiveHelp prints help recursively via DFS from this cmd onward.
243func recursiveHelp(cmd *Command, style style, firstCall bool) {
244 cmd.usage(cmd.stdout, style, firstCall)
245 switch style {
246 case styleText:
247 fmt.Fprintln(cmd.stdout, strings.Repeat("=", 80))
248 case styleGoDoc:
249 fmt.Fprintln(cmd.stdout)
250 }
251 for _, child := range cmd.Children {
252 recursiveHelp(child, style, false)
253 }
254}
255
256// prefixErrorWriter simply wraps a regular io.Writer and adds an "ERROR: "
257// prefix if Write is ever called. It's used to ensure errors are clearly
258// marked when flag.FlagSet.Parse encounters errors.
259type prefixErrorWriter struct {
260 writer io.Writer
261 prefixWritten bool
262}
263
264func (p *prefixErrorWriter) Write(b []byte) (int, error) {
265 if !p.prefixWritten {
266 io.WriteString(p.writer, "ERROR: ")
267 p.prefixWritten = true
268 }
269 return p.writer.Write(b)
270}
271
272// Init initializes all nodes in the command tree rooted at cmd. Init must be
273// called before Execute.
274func (cmd *Command) Init(parent *Command, stdout, stderr io.Writer) {
275 cmd.parent = parent
276 cmd.stdout = stdout
277 cmd.stderr = stderr
278 // Add help command, if it doesn't already exist.
279 hasHelp := false
280 for _, child := range cmd.Children {
281 if child.Name == helpName {
282 hasHelp = true
283 break
284 }
285 }
286 if !hasHelp && cmd.Name != helpName && len(cmd.Children) > 0 {
287 cmd.Children = append(cmd.Children, newDefaultHelp())
288 }
289 // Merge command-specific and global flags into parseFlags.
290 cmd.parseFlags = flag.NewFlagSet(cmd.Name, flag.ContinueOnError)
291 cmd.parseFlags.SetOutput(&prefixErrorWriter{writer: stderr})
292 cmd.parseFlags.Usage = func() {
293 cmd.usage(stderr, styleText, true)
294 }
295 flagMerger := func(f *flag.Flag) {
296 if cmd.parseFlags.Lookup(f.Name) == nil {
297 cmd.parseFlags.Var(f.Value, f.Name, f.Usage)
298 }
299 }
300 cmd.Flags.VisitAll(flagMerger)
301 flag.VisitAll(flagMerger)
302 // Call children recursively.
303 for _, child := range cmd.Children {
304 child.Init(cmd, stdout, stderr)
305 }
306}
307
308// Execute the command with the given args. The returned error is ErrUsage if
309// there are usage errors, otherwise it is whatever the leaf command returns
310// from its Run function.
311func (cmd *Command) Execute(args []string) error {
312 // Parse the merged flags.
313 if err := cmd.parseFlags.Parse(args); err != nil {
314 return ErrUsage
315 }
316 args = cmd.parseFlags.Args()
317 // Look for matching children.
318 if len(args) > 0 {
319 subName := args[0]
320 subArgs := args[1:]
321 for _, child := range cmd.Children {
322 if child.Name == subName {
323 return child.Execute(subArgs)
324 }
325 }
326 }
327 // No matching children, try Run.
328 if cmd.Run != nil {
329 if cmd.ArgsName == "" && len(args) > 0 {
330 if len(cmd.Children) > 0 {
331 return cmd.Errorf("%s: unknown command %q", cmd.Name, args[0])
332 } else {
333 return cmd.Errorf("%s doesn't take any arguments", cmd.Name)
334 }
335 }
336 return cmd.Run(cmd, args)
337 }
338 switch {
339 case len(cmd.Children) == 0:
340 return cmd.Errorf("%s: neither Children nor Run is specified", cmd.Name)
341 case len(args) > 0:
342 return cmd.Errorf("%s: unknown command %q", cmd.Name, args[0])
343 default:
344 return cmd.Errorf("%s: no command specified", cmd.Name)
345 }
346}
347
348// Main executes the command tree rooted at cmd, writing output to os.Stdout,
349// writing errors to os.Stderr, and getting args from os.Args. We'll call
350// os.Exit with a non-zero exit code on errors. It's meant as a simple
351// one-liner for the main function of command-line tools.
352func (cmd *Command) Main() {
353 cmd.Init(nil, os.Stdout, os.Stderr)
354 if err := cmd.Execute(os.Args[1:]); err != nil {
355 if err == ErrUsage {
356 os.Exit(1)
357 } else {
358 fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
359 os.Exit(2)
360 }
361 }
362}