go-arg/usage.go

213 lines
4.9 KiB
Go
Raw Normal View History

2015-10-31 20:32:20 -05:00
package arg
import (
2019-04-14 20:00:40 -05:00
"encoding"
2015-10-31 20:32:20 -05:00
"fmt"
"io"
"os"
2015-11-21 17:58:27 -06:00
"reflect"
2015-10-31 20:32:20 -05:00
"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) {
p.writeUsageForCommand(w, p.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
up := strings.ToUpper(spec.long)
if spec.multiple {
2017-03-30 13:32:39 -05:00
if !spec.required {
fmt.Fprint(w, "[")
}
fmt.Fprintf(w, "%s [%s ...]", up, up)
if !spec.required {
fmt.Fprint(w, "]")
}
2015-10-31 20:32:20 -05:00
} else {
fmt.Fprint(w, up)
}
}
fmt.Fprint(w, "\n")
}
2019-05-03 17:02:10 -05:00
func printTwoCols(w io.Writer, left, help string, defaultVal *string) {
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)
}
if defaultVal != nil {
fmt.Fprintf(w, " [default: %s]", *defaultVal)
}
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) {
p.writeHelpForCommand(w, p.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-05-03 17:02:10 -05:00
printTwoCols(w, strings.ToUpper(spec.long), spec.help, nil)
2015-10-31 20:32:20 -05:00
}
}
2015-11-21 17:58:27 -06:00
// write the list of options
2017-03-08 13:44:01 -06:00
fmt.Fprint(w, "\nOptions:\n")
for _, spec := range options {
2019-04-14 21:50:17 -05:00
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-05-03 17:02:10 -05:00
printTwoCols(w, subcmd.name, subcmd.help, nil)
}
}
}
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-05-03 17:02:10 -05:00
// If spec.dest is not the zero value then a default value has been added.
2019-04-14 21:50:17 -05:00
var v reflect.Value
2019-04-30 15:30:23 -05:00
if len(spec.dest.fields) > 0 {
v = p.val(spec.dest)
2019-04-14 21:50:17 -05:00
}
2019-05-03 17:02:10 -05:00
var defaultVal *string
2015-11-21 17:58:27 -06:00
if v.IsValid() {
z := reflect.Zero(v.Type())
if (v.Type().Comparable() && z.Type().Comparable() && v.Interface() != z.Interface()) || v.Kind() == reflect.Slice && !v.IsNil() {
if scalar, ok := v.Interface().(encoding.TextMarshaler); ok {
if value, err := scalar.MarshalText(); err != nil {
2019-05-03 17:02:10 -05:00
defaultVal = ptrTo(fmt.Sprintf("error: %v", err))
} else {
2019-05-03 17:02:10 -05:00
defaultVal = ptrTo(fmt.Sprintf("%v", string(value)))
}
} else {
2019-05-03 17:02:10 -05:00
defaultVal = ptrTo(fmt.Sprintf("%v", v))
}
2015-11-21 17:58:27 -06:00
}
}
2019-05-03 17:02:10 -05:00
printTwoCols(w, left, spec.help, 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
}
2015-11-11 07:08:28 -06:00
return form + " " + strings.ToUpper(spec.long)
2015-11-01 01:57:26 -05:00
}
2019-05-03 17:02:10 -05:00
func ptrTo(s string) *string {
return &s
}