2015-10-31 20:32:20 -05:00
|
|
|
package arg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2016-01-18 10:24:21 -06:00
|
|
|
// the width of the left column
|
|
|
|
const colWidth = 25
|
|
|
|
|
2019-05-03 17:49:44 -05:00
|
|
|
// to allow monkey patching in tests
|
|
|
|
var stderr = os.Stderr
|
|
|
|
|
2015-11-09 03:27:15 -06:00
|
|
|
// Fail prints usage information to stderr and exits with non-zero status
|
2015-11-01 01:13:23 -06:00
|
|
|
func (p *Parser) Fail(msg string) {
|
2019-05-03 17:49:44 -05:00
|
|
|
p.failWithCommand(msg, p.cmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
// failWithCommand prints usage information for the given subcommand to stderr and exits with non-zero status
|
|
|
|
func (p *Parser) failWithCommand(msg string, cmd *command) {
|
|
|
|
p.writeUsageForCommand(stderr, cmd)
|
|
|
|
fmt.Fprintln(stderr, "error:", msg)
|
|
|
|
osExit(-1)
|
2015-10-31 20:32:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteUsage writes usage information to the given writer
|
2015-11-01 01:13:23 -06:00
|
|
|
func (p *Parser) WriteUsage(w io.Writer) {
|
2020-01-23 23:09:21 -06:00
|
|
|
cmd := p.cmd
|
|
|
|
if p.lastCmd != nil {
|
|
|
|
cmd = p.lastCmd
|
|
|
|
}
|
|
|
|
p.writeUsageForCommand(w, cmd)
|
2019-05-03 17:49:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// writeUsageForCommand writes usage information for the given subcommand
|
|
|
|
func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
|
2020-12-19 17:54:03 -06:00
|
|
|
var positionals, longOptions, shortOptions []*spec
|
2019-05-03 17:49:44 -05:00
|
|
|
for _, spec := range cmd.specs {
|
2020-12-19 17:54:03 -06:00
|
|
|
switch {
|
|
|
|
case spec.positional:
|
2015-10-31 20:32:20 -05:00
|
|
|
positionals = append(positionals, spec)
|
2020-12-19 17:54:03 -06:00
|
|
|
case spec.long != "":
|
|
|
|
longOptions = append(longOptions, spec)
|
|
|
|
case spec.short != "":
|
|
|
|
shortOptions = append(shortOptions, spec)
|
2015-10-31 20:32:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-08 23:18:19 -05:00
|
|
|
if p.version != "" {
|
|
|
|
fmt.Fprintln(w, p.version)
|
|
|
|
}
|
|
|
|
|
2019-05-03 17:49:44 -05:00
|
|
|
// make a list of ancestor commands so that we print with full context
|
|
|
|
var ancestors []string
|
|
|
|
ancestor := cmd
|
|
|
|
for ancestor != nil {
|
|
|
|
ancestors = append(ancestors, ancestor.name)
|
|
|
|
ancestor = ancestor.parent
|
|
|
|
}
|
|
|
|
|
|
|
|
// print the beginning of the usage string
|
2019-05-03 17:50:41 -05:00
|
|
|
fmt.Fprint(w, "Usage:")
|
2019-05-03 17:49:44 -05:00
|
|
|
for i := len(ancestors) - 1; i >= 0; i-- {
|
|
|
|
fmt.Fprint(w, " "+ancestors[i])
|
|
|
|
}
|
2015-10-31 20:32:20 -05:00
|
|
|
|
2015-11-01 15:53:51 -06:00
|
|
|
// write the option component of the usage message
|
2020-12-19 17:54:03 -06:00
|
|
|
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 {
|
2015-11-07 08:39:23 -06:00
|
|
|
// prefix with a space
|
|
|
|
fmt.Fprint(w, " ")
|
2015-10-31 20:32:20 -05:00
|
|
|
if !spec.required {
|
|
|
|
fmt.Fprint(w, "[")
|
|
|
|
}
|
|
|
|
fmt.Fprint(w, synopsis(spec, "--"+spec.long))
|
|
|
|
if !spec.required {
|
|
|
|
fmt.Fprint(w, "]")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-01 15:53:51 -06:00
|
|
|
// write the positional component of the usage message
|
2015-10-31 20:32:20 -05:00
|
|
|
for _, spec := range positionals {
|
2015-11-07 08:39:23 -06:00
|
|
|
// prefix with a space
|
|
|
|
fmt.Fprint(w, " ")
|
2021-04-19 15:21:04 -05:00
|
|
|
if spec.cardinality == multiple {
|
2017-03-30 13:32:39 -05:00
|
|
|
if !spec.required {
|
|
|
|
fmt.Fprint(w, "[")
|
|
|
|
}
|
2019-11-29 15:22:21 -06:00
|
|
|
fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder)
|
2017-03-30 13:32:39 -05:00
|
|
|
if !spec.required {
|
|
|
|
fmt.Fprint(w, "]")
|
|
|
|
}
|
2015-10-31 20:32:20 -05:00
|
|
|
} else {
|
2019-11-29 15:22:21 -06:00
|
|
|
fmt.Fprint(w, spec.placeholder)
|
2015-10-31 20:32:20 -05:00
|
|
|
}
|
|
|
|
}
|
2020-01-19 12:40:53 -06:00
|
|
|
|
2020-01-23 09:35:45 -06:00
|
|
|
// if the program supports subcommands, give a hint to the user about their existence
|
|
|
|
if len(cmd.subcommands) > 0 {
|
2020-01-19 12:40:53 -06:00
|
|
|
fmt.Fprint(w, " <command> [<args>]")
|
|
|
|
}
|
|
|
|
|
2015-10-31 20:32:20 -05:00
|
|
|
fmt.Fprint(w, "\n")
|
2015-11-01 15:53:51 -06:00
|
|
|
}
|
|
|
|
|
2020-06-03 03:05:20 -05:00
|
|
|
func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) {
|
2019-05-03 17:02:10 -05:00
|
|
|
lhs := " " + left
|
|
|
|
fmt.Fprint(w, lhs)
|
|
|
|
if help != "" {
|
|
|
|
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)
|
|
|
|
}
|
2020-06-03 03:05:20 -05:00
|
|
|
|
|
|
|
bracketsContent := []string{}
|
|
|
|
|
2019-10-20 01:23:32 -05:00
|
|
|
if defaultVal != "" {
|
2020-06-03 03:05:20 -05:00
|
|
|
bracketsContent = append(bracketsContent,
|
|
|
|
fmt.Sprintf("default: %s", defaultVal),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if envVal != "" {
|
|
|
|
bracketsContent = append(bracketsContent,
|
|
|
|
fmt.Sprintf("env: %s", envVal),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(bracketsContent) > 0 {
|
|
|
|
fmt.Fprintf(w, " [%s]", strings.Join(bracketsContent, ", "))
|
2019-05-03 17:02:10 -05:00
|
|
|
}
|
|
|
|
fmt.Fprint(w, "\n")
|
|
|
|
}
|
|
|
|
|
2015-11-01 15:53:51 -06:00
|
|
|
// WriteHelp writes the usage string followed by the full help string for each option
|
|
|
|
func (p *Parser) WriteHelp(w io.Writer) {
|
2020-01-23 23:09:21 -06:00
|
|
|
cmd := p.cmd
|
|
|
|
if p.lastCmd != nil {
|
|
|
|
cmd = p.lastCmd
|
|
|
|
}
|
|
|
|
p.writeHelpForCommand(w, cmd)
|
2019-05-03 17:49:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// writeHelp writes the usage string for the given subcommand
|
|
|
|
func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
2021-01-31 21:15:49 -06:00
|
|
|
var positionals, longOptions, shortOptions []*spec
|
2019-05-03 17:49:44 -05:00
|
|
|
for _, spec := range cmd.specs {
|
2021-01-31 21:15:49 -06:00
|
|
|
switch {
|
|
|
|
case spec.positional:
|
2015-11-01 15:53:51 -06:00
|
|
|
positionals = append(positionals, spec)
|
2021-01-31 21:15:49 -06:00
|
|
|
case spec.long != "":
|
|
|
|
longOptions = append(longOptions, spec)
|
|
|
|
case spec.short != "":
|
|
|
|
shortOptions = append(shortOptions, spec)
|
2015-11-01 15:53:51 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-08 10:41:07 -06:00
|
|
|
if p.description != "" {
|
|
|
|
fmt.Fprintln(w, p.description)
|
|
|
|
}
|
2019-05-03 17:49:44 -05:00
|
|
|
p.writeUsageForCommand(w, cmd)
|
2015-10-31 20:32:20 -05:00
|
|
|
|
|
|
|
// write the list of positionals
|
|
|
|
if len(positionals) > 0 {
|
2017-03-08 13:44:01 -06:00
|
|
|
fmt.Fprint(w, "\nPositional arguments:\n")
|
2015-10-31 20:32:20 -05:00
|
|
|
for _, spec := range positionals {
|
2020-06-03 03:05:20 -05:00
|
|
|
printTwoCols(w, spec.placeholder, spec.help, "", "")
|
2015-10-31 20:32:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 21:15:49 -06:00
|
|
|
// 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 {
|
2020-01-23 23:08:18 -06:00
|
|
|
fmt.Fprint(w, "\nOptions:\n")
|
2021-01-31 21:15:49 -06:00
|
|
|
for _, spec := range shortOptions {
|
|
|
|
p.printOption(w, spec)
|
|
|
|
}
|
|
|
|
for _, spec := range longOptions {
|
2020-01-23 23:08:18 -06:00
|
|
|
p.printOption(w, spec)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// obtain a flattened list of options from all ancestors
|
|
|
|
var globals []*spec
|
|
|
|
ancestor := cmd.parent
|
|
|
|
for ancestor != nil {
|
2020-01-23 23:36:24 -06:00
|
|
|
globals = append(globals, ancestor.specs...)
|
2020-01-23 23:08:18 -06:00
|
|
|
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)
|
|
|
|
}
|
2015-11-11 03:15:57 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// write the list of built in options
|
2019-04-30 13:16:01 -05:00
|
|
|
p.printOption(w, &spec{
|
2021-04-19 15:21:04 -05:00
|
|
|
cardinality: zero,
|
|
|
|
long: "help",
|
|
|
|
short: "h",
|
|
|
|
help: "display this help and exit",
|
2019-04-30 13:16:01 -05:00
|
|
|
})
|
2016-09-08 23:18:19 -05:00
|
|
|
if p.version != "" {
|
2019-04-30 13:16:01 -05:00
|
|
|
p.printOption(w, &spec{
|
2021-04-19 15:21:04 -05:00
|
|
|
cardinality: zero,
|
|
|
|
long: "version",
|
|
|
|
help: "display version and exit",
|
2019-04-30 13:16:01 -05:00
|
|
|
})
|
2016-09-08 23:18:19 -05:00
|
|
|
}
|
2019-05-03 17:02:10 -05:00
|
|
|
|
|
|
|
// write the list of subcommands
|
2019-05-03 17:49:44 -05:00
|
|
|
if len(cmd.subcommands) > 0 {
|
2019-05-03 17:02:10 -05:00
|
|
|
fmt.Fprint(w, "\nCommands:\n")
|
2019-05-03 17:49:44 -05:00
|
|
|
for _, subcmd := range cmd.subcommands {
|
2020-06-03 03:05:20 -05:00
|
|
|
printTwoCols(w, subcmd.name, subcmd.help, "", "")
|
2019-05-03 17:02:10 -05:00
|
|
|
}
|
|
|
|
}
|
2015-11-11 03:15:57 -06:00
|
|
|
}
|
|
|
|
|
2019-04-14 21:50:17 -05:00
|
|
|
func (p *Parser) printOption(w io.Writer, spec *spec) {
|
2021-01-31 21:15:49 -06:00
|
|
|
ways := make([]string, 0, 2)
|
2020-12-19 17:54:03 -06:00
|
|
|
if spec.long != "" {
|
2021-01-31 21:15:49 -06:00
|
|
|
ways = append(ways, synopsis(spec, "--"+spec.long))
|
2020-12-19 17:54:03 -06:00
|
|
|
}
|
2015-11-11 03:15:57 -06:00
|
|
|
if spec.short != "" {
|
2021-01-31 21:15:49 -06:00
|
|
|
ways = append(ways, synopsis(spec, "-"+spec.short))
|
|
|
|
}
|
|
|
|
if len(ways) > 0 {
|
|
|
|
printTwoCols(w, strings.Join(ways, ", "), spec.help, spec.defaultVal, spec.env)
|
2015-11-11 03:15:57 -06:00
|
|
|
}
|
2015-10-31 20:32:20 -05:00
|
|
|
}
|
2015-11-01 01:57:26 -05:00
|
|
|
|
|
|
|
func synopsis(spec *spec, form string) string {
|
2021-04-19 15:21:04 -05:00
|
|
|
if spec.cardinality == zero {
|
2015-11-01 01:57:26 -05:00
|
|
|
return form
|
|
|
|
}
|
2019-11-29 15:22:21 -06:00
|
|
|
return form + " " + spec.placeholder
|
2015-11-01 01:57:26 -05:00
|
|
|
}
|