| // Package cmdline provides a data-driven framework to simplify writing |
| // command-line programs. It includes built-in support for formatted help. |
| // |
| // Commands may be linked together to form a command tree. Since commands may |
| // be arbitrarily nested within other commands, it's easy to create wrapper |
| // programs that invoke existing commands. |
| // |
| // The syntax for each command-line program is: |
| // |
| // command [flags] [subcommand [flags]]* [args] |
| // |
| // Each sequence of flags on the command-line is associated with the command |
| // that immediately precedes them. Global flags registered with the standard |
| // flags package are allowed anywhere a command-specific flag is allowed. |
| package cmdline |
| |
| import ( |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "os" |
| "strings" |
| ) |
| |
| // ErrUsage is returned to indicate an error in command usage; e.g. unknown |
| // flags, subcommands or args. |
| var ErrUsage = errors.New("usage error") |
| |
| // Command represents a single command in a command-line program. A program |
| // with subcommands is represented as a root Command with children representing |
| // each subcommand. The command graph must be a tree; each command may either |
| // have exactly one parent (a sub-command), or no parent (the root), and cycles |
| // are not allowed. This makes it easier to display the usage for subcommands. |
| type Command struct { |
| Name string // Name of the command. |
| Short string // Short description, shown in help called on parent. |
| Long string // Long description, shown in help called on itself. |
| Flags flag.FlagSet // Flags for the command. |
| ArgsName string // Name of the args, shown in usage line. |
| ArgsLong string // Long description of the args, shown in help. |
| |
| // Children of the command. The framework will match args[0] against each |
| // child's name, and call Run on the first matching child. |
| Children []*Command |
| |
| // Run is a function that runs cmd with args. If both Children and Run are |
| // specified, Run will only be called if none of the children match. It is an |
| // error if neither is specified. |
| Run func(cmd *Command, args []string) error |
| |
| // parent holds the parent of this Command, or nil if this is the root. |
| parent *Command |
| |
| // Stdout and stderr are set through Init. |
| stdout, stderr io.Writer |
| |
| // parseFlags holds the merged flags used for parsing. Each command starts |
| // with its own Flags, and we merge in all global flags. If the same flag is |
| // specified in both sets, the command's own flag wins. |
| parseFlags *flag.FlagSet |
| |
| // TODO(toddw): If necessary we can add alias support, e.g. for abbreviations. |
| // Alias map[string]string |
| } |
| |
| // style describes the formatting style for usage descriptions. |
| type style int |
| |
| const ( |
| styleText style = iota // Default style, good for cmdline output. |
| styleGoDoc // Style good for godoc processing. |
| ) |
| |
| // String returns the human-readable representation of the style. |
| func (s *style) String() string { |
| switch *s { |
| case styleText: |
| return "text" |
| case styleGoDoc: |
| return "godoc" |
| default: |
| panic(fmt.Errorf("Unhandled style %d", *s)) |
| } |
| } |
| |
| // Set implements the flag.Value interface method. |
| func (s *style) Set(value string) error { |
| switch value { |
| case "text": |
| *s = styleText |
| case "godoc": |
| *s = styleGoDoc |
| default: |
| return fmt.Errorf("Unknown style %q", value) |
| } |
| return nil |
| } |
| |
| // Stdout is where output goes. Typically os.Stdout. |
| func (cmd *Command) Stdout() io.Writer { |
| return cmd.stdout |
| } |
| |
| // Stderr is where error messages go. Typically os.Stderr |
| func (cmd *Command) Stderr() io.Writer { |
| return cmd.stderr |
| } |
| |
| // Errorf should be called to signal an invalid usage of the command. |
| func (cmd *Command) Errorf(format string, v ...interface{}) error { |
| fmt.Fprint(cmd.stderr, "ERROR: ") |
| fmt.Fprintf(cmd.stderr, format, v...) |
| fmt.Fprint(cmd.stderr, "\n\n") |
| cmd.usage(cmd.stderr, styleText, true) |
| return ErrUsage |
| } |
| |
| // usage prints the usage of cmd to the writer, with the given style. The |
| // firstCall boolean is set to false when printing usage for multiple commands, |
| // and is used to avoid printing redundant information (e.g. section headers, |
| // global flags). |
| func (cmd *Command) usage(w io.Writer, style style, firstCall bool) { |
| var names []string |
| for c := cmd; c != nil; c = c.parent { |
| names = append([]string{c.Name}, names...) |
| } |
| namestr := strings.Join(names, " ") |
| if !firstCall && style == styleGoDoc { |
| // Title-case names so that godoc recognizes it as a section header. |
| fmt.Fprintf(w, "%s\n\n", strings.Title(namestr)) |
| } |
| // Long description. |
| fmt.Fprint(w, strings.Trim(cmd.Long, "\n")) |
| fmt.Fprintln(w) |
| // Usage line. |
| hasFlags := false |
| cmd.Flags.VisitAll(func(*flag.Flag) { |
| hasFlags = true |
| }) |
| fmt.Fprintf(w, "\nUsage:\n") |
| nameflags := " " + namestr |
| if hasFlags { |
| nameflags += " [flags]" |
| } |
| if len(cmd.Children) > 0 { |
| fmt.Fprintf(w, "%s <command>\n", nameflags) |
| } |
| if cmd.Run != nil { |
| if cmd.ArgsName != "" { |
| fmt.Fprintf(w, "%s %s\n", nameflags, cmd.ArgsName) |
| } else { |
| fmt.Fprintf(w, "%s\n", nameflags) |
| } |
| } |
| if len(cmd.Children) == 0 && cmd.Run == nil { |
| // This is a specification error. |
| fmt.Fprintf(w, "%s [ERROR: neither Children nor Run is specified]\n", nameflags) |
| } |
| // Commands. |
| if len(cmd.Children) > 0 { |
| fmt.Fprintf(w, "\nThe %s commands are:\n", cmd.Name) |
| for _, child := range cmd.Children { |
| fmt.Fprintf(w, " %-11s %s\n", child.Name, child.Short) |
| } |
| } |
| // Args. |
| if cmd.Run != nil && cmd.ArgsLong != "" { |
| fmt.Fprintf(w, "\n") |
| fmt.Fprint(w, strings.Trim(cmd.ArgsLong, "\n")) |
| fmt.Fprintf(w, "\n") |
| } |
| // Flags. |
| if hasFlags { |
| fmt.Fprintf(w, "\nThe %s flags are:\n", cmd.Name) |
| cmd.Flags.VisitAll(func(f *flag.Flag) { |
| fmt.Fprintf(w, " -%s=%s: %s\n", f.Name, f.DefValue, f.Usage) |
| }) |
| } |
| // Global flags. |
| hasGlobalFlags := false |
| flag.VisitAll(func(*flag.Flag) { |
| hasGlobalFlags = true |
| }) |
| if firstCall && hasGlobalFlags { |
| fmt.Fprintf(w, "\nThe global flags are:\n") |
| flag.VisitAll(func(f *flag.Flag) { |
| fmt.Fprintf(w, " -%s=%s: %s\n", f.Name, f.DefValue, f.Usage) |
| }) |
| } |
| } |
| |
| // newDefaultHelp creates a new default help command. We need to create new |
| // instances since the parent for each help command is different. |
| func newDefaultHelp() *Command { |
| helpStyle := styleText |
| help := &Command{ |
| Name: helpName, |
| Short: "Display help for commands", |
| Long: ` |
| Help displays usage descriptions for this command, or usage descriptions for |
| sub-commands. |
| `, |
| ArgsName: "<command>", |
| ArgsLong: ` |
| <command> is an optional sequence of commands to display detailed per-command |
| usage. The special-case "help ..." recursively displays help for this command |
| and all sub-commands. |
| `, |
| Run: func(cmd *Command, args []string) error { |
| // Help applies to its parent - e.g. "foo help" applies to the foo command. |
| return runHelp(cmd.parent, args, helpStyle) |
| }, |
| } |
| help.Flags.Var(&helpStyle, "style", `The formatting style for help output, either "text" or "godoc".`) |
| return help |
| } |
| |
| const helpName = "help" |
| |
| // runHelp runs the "help" command. |
| func runHelp(cmd *Command, args []string, style style) error { |
| if len(args) == 0 { |
| cmd.usage(cmd.stdout, style, true) |
| return nil |
| } |
| if args[0] == "..." { |
| recursiveHelp(cmd, style, true) |
| return nil |
| } |
| // Find the subcommand to display help. |
| subName := args[0] |
| subArgs := args[1:] |
| for _, child := range cmd.Children { |
| if child.Name == subName { |
| return runHelp(child, subArgs, style) |
| } |
| } |
| return cmd.Errorf("%s: unknown command %q", cmd.Name, subName) |
| } |
| |
| // recursiveHelp prints help recursively via DFS from this cmd onward. |
| func recursiveHelp(cmd *Command, style style, firstCall bool) { |
| cmd.usage(cmd.stdout, style, firstCall) |
| switch style { |
| case styleText: |
| fmt.Fprintln(cmd.stdout, strings.Repeat("=", 80)) |
| case styleGoDoc: |
| fmt.Fprintln(cmd.stdout) |
| } |
| for _, child := range cmd.Children { |
| recursiveHelp(child, style, false) |
| } |
| } |
| |
| // prefixErrorWriter simply wraps a regular io.Writer and adds an "ERROR: " |
| // prefix if Write is ever called. It's used to ensure errors are clearly |
| // marked when flag.FlagSet.Parse encounters errors. |
| type prefixErrorWriter struct { |
| writer io.Writer |
| prefixWritten bool |
| } |
| |
| func (p *prefixErrorWriter) Write(b []byte) (int, error) { |
| if !p.prefixWritten { |
| io.WriteString(p.writer, "ERROR: ") |
| p.prefixWritten = true |
| } |
| return p.writer.Write(b) |
| } |
| |
| // Init initializes all nodes in the command tree rooted at cmd. Init must be |
| // called before Execute. |
| func (cmd *Command) Init(parent *Command, stdout, stderr io.Writer) { |
| cmd.parent = parent |
| cmd.stdout = stdout |
| cmd.stderr = stderr |
| // Add help command, if it doesn't already exist. |
| hasHelp := false |
| for _, child := range cmd.Children { |
| if child.Name == helpName { |
| hasHelp = true |
| break |
| } |
| } |
| if !hasHelp && cmd.Name != helpName && len(cmd.Children) > 0 { |
| cmd.Children = append(cmd.Children, newDefaultHelp()) |
| } |
| // Merge command-specific and global flags into parseFlags. |
| cmd.parseFlags = flag.NewFlagSet(cmd.Name, flag.ContinueOnError) |
| cmd.parseFlags.SetOutput(&prefixErrorWriter{writer: stderr}) |
| cmd.parseFlags.Usage = func() { |
| cmd.usage(stderr, styleText, true) |
| } |
| flagMerger := func(f *flag.Flag) { |
| if cmd.parseFlags.Lookup(f.Name) == nil { |
| cmd.parseFlags.Var(f.Value, f.Name, f.Usage) |
| } |
| } |
| cmd.Flags.VisitAll(flagMerger) |
| flag.VisitAll(flagMerger) |
| // Call children recursively. |
| for _, child := range cmd.Children { |
| child.Init(cmd, stdout, stderr) |
| } |
| } |
| |
| // Execute the command with the given args. The returned error is ErrUsage if |
| // there are usage errors, otherwise it is whatever the leaf command returns |
| // from its Run function. |
| func (cmd *Command) Execute(args []string) error { |
| // Parse the merged flags. |
| if err := cmd.parseFlags.Parse(args); err != nil { |
| return ErrUsage |
| } |
| args = cmd.parseFlags.Args() |
| // Look for matching children. |
| if len(args) > 0 { |
| subName := args[0] |
| subArgs := args[1:] |
| for _, child := range cmd.Children { |
| if child.Name == subName { |
| return child.Execute(subArgs) |
| } |
| } |
| } |
| // No matching children, try Run. |
| if cmd.Run != nil { |
| if cmd.ArgsName == "" && len(args) > 0 { |
| if len(cmd.Children) > 0 { |
| return cmd.Errorf("%s: unknown command %q", cmd.Name, args[0]) |
| } else { |
| return cmd.Errorf("%s doesn't take any arguments", cmd.Name) |
| } |
| } |
| return cmd.Run(cmd, args) |
| } |
| switch { |
| case len(cmd.Children) == 0: |
| return cmd.Errorf("%s: neither Children nor Run is specified", cmd.Name) |
| case len(args) > 0: |
| return cmd.Errorf("%s: unknown command %q", cmd.Name, args[0]) |
| default: |
| return cmd.Errorf("%s: no command specified", cmd.Name) |
| } |
| } |
| |
| // Main executes the command tree rooted at cmd, writing output to os.Stdout, |
| // writing errors to os.Stderr, and getting args from os.Args. We'll call |
| // os.Exit with a non-zero exit code on errors. It's meant as a simple |
| // one-liner for the main function of command-line tools. |
| func (cmd *Command) Main() { |
| cmd.Init(nil, os.Stdout, os.Stderr) |
| if err := cmd.Execute(os.Args[1:]); err != nil { |
| if err == ErrUsage { |
| os.Exit(1) |
| } else { |
| fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) |
| os.Exit(2) |
| } |
| } |
| } |