diff options
Diffstat (limited to 'usage.go')
| -rw-r--r-- | usage.go | 191 |
1 files changed, 94 insertions, 97 deletions
@@ -9,40 +9,31 @@ import ( // the width of the left column const colWidth = 25 -// Fail prints usage information to stderr and exits with non-zero status +// Fail prints usage information to p.Config.Out and exits with status code 2. func (p *Parser) Fail(msg string) { - p.failWithSubcommand(msg, p.cmd) + p.FailSubcommand(msg) } -// FailSubcommand prints usage information for a specified subcommand to stderr, -// then exits with non-zero status. To write usage information for a top-level +// FailSubcommand prints usage information for a specified subcommand to p.Config.Out, +// then exits with status code 2. To write usage information for a top-level // subcommand, provide just the name of that subcommand. To write usage // information for a subcommand that is nested under another subcommand, provide // a sequence of subcommand names starting with the top-level subcommand and so // on down the tree. func (p *Parser) FailSubcommand(msg string, subcommand ...string) error { - cmd, err := p.lookupCommand(subcommand...) + err := p.WriteUsageForSubcommand(p.config.Out, subcommand...) if err != nil { return err } - p.failWithSubcommand(msg, cmd) - return nil -} -// failWithSubcommand prints usage information for the given subcommand to stderr and exits with non-zero status -func (p *Parser) failWithSubcommand(msg string, cmd *command) { - p.writeUsageForSubcommand(p.config.Out, cmd) fmt.Fprintln(p.config.Out, "error:", msg) - p.config.Exit(-1) + p.config.Exit(2) + return nil } // WriteUsage writes usage information to the given writer func (p *Parser) WriteUsage(w io.Writer) { - cmd := p.cmd - if p.lastCmd != nil { - cmd = p.lastCmd - } - p.writeUsageForSubcommand(w, cmd) + p.WriteUsageForSubcommand(w, p.subcommand...) } // WriteUsageForSubcommand writes the usage information for a specified @@ -55,40 +46,45 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro if err != nil { return err } - p.writeUsageForSubcommand(w, cmd) - return nil -} -// writeUsageForSubcommand writes usage information for the given subcommand -func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { var positionals, longOptions, shortOptions []*spec + var hasVersionOption bool for _, spec := range cmd.specs { switch { case spec.positional: positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) + if spec.long == "version" { + hasVersionOption = true + } case spec.short != "": shortOptions = append(shortOptions, spec) } } - if p.version != "" { - fmt.Fprintln(w, p.version) - } - // make a list of ancestor commands so that we print with full context + // also determine if any ancestor has a version option spec var ancestors []string ancestor := cmd for ancestor != nil { + for _, spec := range ancestor.specs { + if spec.long == "version" { + hasVersionOption = true + } + } ancestors = append(ancestors, ancestor.name) ancestor = ancestor.parent } + if !hasVersionOption && p.version != "" { + fmt.Fprintln(w, p.version) + } + // print the beginning of the usage string - fmt.Fprint(w, "Usage:") - for i := len(ancestors) - 1; i >= 0; i-- { - fmt.Fprint(w, " "+ancestors[i]) + fmt.Fprintf(w, "Usage: %s", p.cmd.name) + for _, s := range subcommand { + fmt.Fprint(w, " "+s) } // write the option component of the usage message @@ -149,47 +145,66 @@ func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) { } fmt.Fprint(w, "\n") + return nil } -func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) { - lhs := " " + left +// print prints a line like this: +// +// --option FOO A description of the option [default: 123] +// +// If the text on the left is longer than a certain threshold, the description is moved to the next line: +// +// --verylongoptionoption VERY_LONG_VARIABLE +// A description of the option [default: 123] +// +// If multiple "extras" are provided then they are put inside a single set of square brackets: +// +// --option FOO A description of the option [default: 123, env: FOO] +func print(w io.Writer, item, description string, bracketed ...string) { + lhs := " " + item fmt.Fprint(w, lhs) - if help != "" { + if description != "" { if len(lhs)+2 < colWidth { fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs))) } else { fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) } - fmt.Fprint(w, help) + fmt.Fprint(w, description) } - bracketsContent := []string{} + var brack string + for _, s := range bracketed { + if s != "" { + if brack != "" { + brack += ", " + } + brack += s + } + } - if defaultVal != "" { - bracketsContent = append(bracketsContent, - fmt.Sprintf("default: %s", defaultVal), - ) + if brack != "" { + fmt.Fprintf(w, " [%s]", brack) } + fmt.Fprint(w, "\n") +} - if envVal != "" { - bracketsContent = append(bracketsContent, - fmt.Sprintf("env: %s", envVal), - ) +func withDefault(s string) string { + if s == "" { + return "" } + return "default: " + s +} - if len(bracketsContent) > 0 { - fmt.Fprintf(w, " [%s]", strings.Join(bracketsContent, ", ")) +func withEnv(env string) string { + if env == "" { + return "" } - fmt.Fprint(w, "\n") + return "env: " + env } // WriteHelp writes the usage string followed by the full help string for each option func (p *Parser) WriteHelp(w io.Writer) { - cmd := p.cmd - if p.lastCmd != nil { - cmd = p.lastCmd - } - p.writeHelpForSubcommand(w, cmd) + p.WriteHelpForSubcommand(w, p.subcommand...) } // WriteHelpForSubcommand writes the usage string followed by the full help @@ -202,12 +217,7 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error if err != nil { return err } - p.writeHelpForSubcommand(w, cmd) - return nil -} -// writeHelp writes the usage string for the given subcommand -func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { var positionals, longOptions, shortOptions, envOnlyOptions []*spec var hasVersionOption bool for _, spec := range cmd.specs { @@ -216,6 +226,9 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) + if spec.long == "version" { + hasVersionOption = true + } case spec.short != "": shortOptions = append(shortOptions, spec) case spec.short == "" && spec.long == "": @@ -223,16 +236,31 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { } } + // obtain a flattened list of options from all ancestors + // also determine if any ancestor has a version option spec + var globals []*spec + ancestor := cmd.parent + for ancestor != nil { + for _, spec := range ancestor.specs { + if spec.long == "version" { + hasVersionOption = true + break + } + } + globals = append(globals, ancestor.specs...) + ancestor = ancestor.parent + } + if p.description != "" { fmt.Fprintln(w, p.description) } - p.writeUsageForSubcommand(w, cmd) + p.WriteUsageForSubcommand(w, subcommand...) // write the list of positionals if len(positionals) > 0 { fmt.Fprint(w, "\nPositional arguments:\n") for _, spec := range positionals { - printTwoCols(w, spec.placeholder, spec.help, "", "") + print(w, spec.placeholder, spec.help) } } @@ -244,28 +272,14 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { } for _, spec := range longOptions { p.printOption(w, spec) - if spec.long == "version" { - hasVersionOption = true - } } } - // obtain a flattened list of options from all ancestors - var globals []*spec - ancestor := cmd.parent - for ancestor != nil { - globals = append(globals, ancestor.specs...) - ancestor = ancestor.parent - } - // write the list of global options if len(globals) > 0 { fmt.Fprint(w, "\nGlobal options:\n") for _, spec := range globals { p.printOption(w, spec) - if spec.long == "version" { - hasVersionOption = true - } } } @@ -296,13 +310,15 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) { if len(cmd.subcommands) > 0 { fmt.Fprint(w, "\nCommands:\n") for _, subcmd := range cmd.subcommands { - printTwoCols(w, subcmd.name, subcmd.help, "", "") + names := append([]string{subcmd.name}, subcmd.aliases...) + print(w, strings.Join(names, ", "), subcmd.help) } } if p.epilogue != "" { fmt.Fprintln(w, "\n"+p.epilogue) } + return nil } func (p *Parser) printOption(w io.Writer, spec *spec) { @@ -314,7 +330,7 @@ func (p *Parser) printOption(w io.Writer, spec *spec) { ways = append(ways, synopsis(spec, "-"+spec.short)) } if len(ways) > 0 { - printTwoCols(w, strings.Join(ways, ", "), spec.help, spec.defaultString, spec.env) + print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } @@ -330,33 +346,14 @@ func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) { ways = append(ways, spec.help) } - printTwoCols(w, spec.env, strings.Join(ways, " "), spec.defaultString, "") -} - -// lookupCommand finds a subcommand based on a sequence of subcommand names. The -// first string should be a top-level subcommand, the next should be a child -// subcommand of that subcommand, and so on. If no strings are given then the -// root command is returned. If no such subcommand exists then an error is -// returned. -func (p *Parser) lookupCommand(path ...string) (*command, error) { - cmd := p.cmd - for _, name := range path { - var found *command - for _, child := range cmd.subcommands { - if child.name == name { - found = child - } - } - if found == nil { - return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name) - } - cmd = found - } - return cmd, nil + print(w, spec.env, strings.Join(ways, " "), withDefault(spec.defaultString)) } func synopsis(spec *spec, form string) string { - if spec.cardinality == zero { + // if the user omits the placeholder tag then we pick one automatically, + // but if the user explicitly specifies an empty placeholder then we + // leave out the placeholder in the help message + if spec.cardinality == zero || spec.placeholder == "" { return form } return form + " " + spec.placeholder |
