package arg import ( "fmt" "io" "strings" ) // the width of the left column const colWidth = 25 // Fail prints usage information to p.Config.Out and exits with status code 2. func (p *Parser) Fail(msg string) { p.FailSubcommand(msg) } // 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 { err := p.WriteUsageForSubcommand(p.config.Out, subcommand...) if err != nil { return err } fmt.Fprintln(p.config.Out, "error:", msg) p.config.Exit(2) return nil } // WriteUsage writes usage information to the given writer func (p *Parser) WriteUsage(w io.Writer) { p.WriteUsageForSubcommand(w, p.subcommand...) } // WriteUsageForSubcommand writes the usage information for a specified // subcommand. 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) WriteUsageForSubcommand(w io.Writer, subcommand ...string) error { cmd, err := p.lookupCommand(subcommand...) if err != nil { return err } var positionals, longOptions, shortOptions []*spec for _, spec := range cmd.specs { switch { case spec.positional: positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) case spec.short != "": shortOptions = append(shortOptions, spec) } } // print the beginning of the usage string fmt.Fprintf(w, "Usage: %s", p.cmd.name) for _, s := range subcommand { fmt.Fprint(w, " "+s) } // write the option component of the usage message for _, spec := range shortOptions { // prefix with a space fmt.Fprint(w, " ") if !spec.required { fmt.Fprint(w, "[") } fmt.Fprint(w, synopsis(spec, "-"+spec.short)) if !spec.required { fmt.Fprint(w, "]") } } for _, spec := range longOptions { // prefix with a space fmt.Fprint(w, " ") if !spec.required { fmt.Fprint(w, "[") } fmt.Fprint(w, synopsis(spec, "--"+spec.long)) if !spec.required { fmt.Fprint(w, "]") } } // When we parse positionals, we check that: // 1. required positionals come before non-required positionals // 2. there is at most one multiple-value positional // 3. if there is a multiple-value positional then it comes after all other positionals // Here we merely print the usage string, so we do not explicitly re-enforce those rules // write the positionals in following form: // REQUIRED1 REQUIRED2 // REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]] // REQUIRED1 REQUIRED2 REPEATED [REPEATED ...] // REQUIRED1 REQUIRED2 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]] // REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]] var closeBrackets int for _, spec := range positionals { fmt.Fprint(w, " ") if !spec.required { fmt.Fprint(w, "[") closeBrackets += 1 } if spec.cardinality == multiple { fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder) } else { fmt.Fprint(w, spec.placeholder) } } fmt.Fprint(w, strings.Repeat("]", closeBrackets)) // if the program supports subcommands, give a hint to the user about their existence if len(cmd.subcommands) > 0 { fmt.Fprint(w, " []") } fmt.Fprint(w, "\n") return nil } // 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 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, description) } var brack string for _, s := range bracketed { if s != "" { if brack != "" { brack += ", " } brack += s } } if brack != "" { fmt.Fprintf(w, " [%s]", brack) } fmt.Fprint(w, "\n") } func withDefault(s string) string { if s == "" { return "" } return "default: " + s } func withEnv(env string) string { if env == "" { return "" } return "env: " + env } // WriteHelp writes the usage string followed by the full help string for each option func (p *Parser) WriteHelp(w io.Writer) { p.WriteHelpForSubcommand(w, p.subcommand...) } // WriteHelpForSubcommand writes the usage string followed by the full help // string for a specified subcommand. To write help for a top-level subcommand, // provide just the name of that subcommand. To write help 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) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error { cmd, err := p.lookupCommand(subcommand...) if err != nil { return err } var positionals, longOptions, shortOptions, envOnlyOptions []*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) case spec.short == "" && spec.long == "": envOnlyOptions = append(envOnlyOptions, spec) } } // 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) } if !hasVersionOption && p.version != "" { fmt.Fprintln(w, p.version) } p.WriteUsageForSubcommand(w, subcommand...) // write the list of positionals if len(positionals) > 0 { fmt.Fprint(w, "\nPositional arguments:\n") for _, spec := range positionals { print(w, spec.placeholder, spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } // write the list of options with the short-only ones first to match the usage string if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil { fmt.Fprint(w, "\nOptions:\n") for _, spec := range shortOptions { p.printOption(w, spec) } for _, spec := range longOptions { p.printOption(w, spec) } } // write the list of global options if len(globals) > 0 { fmt.Fprint(w, "\nGlobal options:\n") for _, spec := range globals { p.printOption(w, spec) } } // write the list of built in options p.printOption(w, &spec{ cardinality: zero, long: "help", short: "h", help: "display this help and exit", }) if !hasVersionOption && p.version != "" { p.printOption(w, &spec{ cardinality: zero, long: "version", help: "display version and exit", }) } // write the list of environment only variables if len(envOnlyOptions) > 0 { fmt.Fprint(w, "\nEnvironment variables:\n") for _, spec := range envOnlyOptions { p.printEnvOnlyVar(w, spec) } } // write the list of subcommands if len(cmd.subcommands) > 0 { fmt.Fprint(w, "\nCommands:\n") for _, subcmd := range cmd.subcommands { 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) { ways := make([]string, 0, 2) if spec.long != "" { ways = append(ways, synopsis(spec, "--"+spec.long)) } if spec.short != "" { ways = append(ways, synopsis(spec, "-"+spec.short)) } if len(ways) > 0 { print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) { ways := make([]string, 0, 2) if spec.required { ways = append(ways, "Required.") } else { ways = append(ways, "Optional.") } if spec.help != "" { ways = append(ways, spec.help) } print(w, spec.env, strings.Join(ways, " "), withDefault(spec.defaultString)) } func synopsis(spec *spec, form string) string { // 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 }