summaryrefslogtreecommitdiff
path: root/usage.go
diff options
context:
space:
mode:
Diffstat (limited to 'usage.go')
-rw-r--r--usage.go191
1 files changed, 94 insertions, 97 deletions
diff --git a/usage.go b/usage.go
index a9f9844..66a5be9 100644
--- a/usage.go
+++ b/usage.go
@@ -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