go-arg/usage.go

220 lines
4.9 KiB
Go
Raw Permalink 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) {
2015-10-31 20:32:20 -05:00
var positionals, options []*spec
for _, spec := range cmd.specs {
2015-10-31 20:32:20 -05:00
if spec.positional {
positionals = append(positionals, spec)
} else {
options = append(options, spec)
}
}
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
2015-10-31 20:32:20 -05:00
for _, spec := range options {
// 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")
}
2019-10-20 01:23:32 -05:00
func printTwoCols(w io.Writer, left, help string, defaultVal 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)
}
2019-10-20 01:23:32 -05:00
if defaultVal != "" {
fmt.Fprintf(w, " [default: %s]", defaultVal)
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 {
2019-11-29 15:22:21 -06:00
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 {
2019-10-20 01:23:32 -05:00
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) {
2019-05-03 17:02:10 -05:00
left := synopsis(spec, "--"+spec.long)
if spec.short != "" {
left += ", " + synopsis(spec, "-"+spec.short)
}
2019-10-20 01:23:32 -05:00
printTwoCols(w, left, spec.help, spec.defaultVal)
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
}