arg/usage.go

257 lines
5.7 KiB
Go
Raw Normal View History

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
// to allow monkey patching in tests
var stderr = os.Stderr
// Fail prints usage information to stderr and exits with non-zero status
func (p *Parser) Fail(msg string) {
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
func (p *Parser) WriteUsage(w io.Writer) {
cmd := p.cmd
if p.lastCmd != nil {
cmd = p.lastCmd
}
p.writeUsageForCommand(w, cmd)
}
// 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
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)
}
// 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:")
for i := len(ancestors) - 1; i >= 0; i-- {
fmt.Fprint(w, " "+ancestors[i])
}
2015-10-31 20:32:20 -05: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 {
// 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, "]")
}
}
// write the positional component of the usage message
2015-10-31 20:32:20 -05:00
for _, spec := range positionals {
// prefix with a space
fmt.Fprint(w, " ")
2015-10-31 20:32:20 -05:00
if spec.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-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 {
fmt.Fprint(w, " <command> [<args>]")
}
2015-10-31 20:32:20 -05:00
fmt.Fprint(w, "\n")
}
func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) {
2020-12-19 18:51:33 -06:00
if left == "" {
return
}
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)
}
bracketsContent := []string{}
2019-10-20 01:23:32 -05:00
if defaultVal != "" {
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")
}
// 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.writeHelpForCommand(w, cmd)
}
// writeHelp writes the usage string for the given subcommand
func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
var positionals, options []*spec
for _, spec := range cmd.specs {
if spec.positional {
positionals = append(positionals, spec)
} else {
options = append(options, spec)
}
}
if p.description != "" {
fmt.Fprintln(w, p.description)
}
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 {
printTwoCols(w, spec.placeholder, spec.help, "", "")
2015-10-31 20:32:20 -05:00
}
}
2015-11-21 17:58:27 -06:00
// write the list of options
if len(options) > 0 || cmd.parent == nil {
fmt.Fprint(w, "\nOptions:\n")
for _, spec := range options {
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...)
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)
}
}
// write the list of built in options
2019-04-30 13:16:01 -05:00
p.printOption(w, &spec{
boolean: true,
long: "help",
short: "h",
help: "display this help and exit",
})
2016-09-08 23:18:19 -05:00
if p.version != "" {
2019-04-30 13:16:01 -05:00
p.printOption(w, &spec{
boolean: true,
long: "version",
help: "display version and exit",
})
2016-09-08 23:18:19 -05:00
}
2019-05-03 17:02:10 -05:00
// write the list of subcommands
if len(cmd.subcommands) > 0 {
2019-05-03 17:02:10 -05:00
fmt.Fprint(w, "\nCommands:\n")
for _, subcmd := range cmd.subcommands {
printTwoCols(w, subcmd.name, subcmd.help, "", "")
2019-05-03 17:02:10 -05:00
}
}
}
2019-04-14 21:50:17 -05:00
func (p *Parser) printOption(w io.Writer, spec *spec) {
2020-12-19 17:54:03 -06:00
text := make([]string, 0, 2)
if spec.long != "" {
text = append(text, synopsis(spec, "--"+spec.long))
}
if spec.short != "" {
2020-12-19 17:54:03 -06:00
text = append(text, synopsis(spec, "-"+spec.short))
}
2020-12-19 17:54:03 -06:00
left := strings.Join(text, ", ")
printTwoCols(w, left, spec.help, spec.defaultVal, spec.env)
2015-10-31 20:32:20 -05:00
}
2015-11-01 01:57:26 -05:00
func synopsis(spec *spec, form string) string {
if spec.boolean {
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
}
2019-05-03 17:02:10 -05:00
func ptrTo(s string) *string {
return &s
}