From 15bf383f1d5db9bf362029529f3c83f092e2d00f Mon Sep 17 00:00:00 2001 From: Alex Flint Date: Fri, 3 May 2019 15:02:10 -0700 Subject: [PATCH] add subcommands to usage string --- example_test.go | 37 +++++++++++++++++++++++++++++ parse.go | 3 +++ usage.go | 63 +++++++++++++++++++++++++++++-------------------- 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/example_test.go b/example_test.go index 72807a7..eb8d315 100644 --- a/example_test.go +++ b/example_test.go @@ -135,3 +135,40 @@ func Example_usageString() { // optimization level // --help, -h display this help and exit } + +// This example shows the usage string generated by go-arg +func Example_usageStringWithSubcommand() { + // These are the args you would pass in on the command line + os.Args = split("./example --help") + + type getCmd struct { + Item string `arg:"positional" help:"item to fetch"` + } + + type listCmd struct { + Format string `help:"output format"` + Limit int + } + + var args struct { + Verbose bool + Get *getCmd `arg:"subcommand" help:"fetch an item and print it"` + List *listCmd `arg:"subcommand" help:"list available items"` + } + + // This is only necessary when running inside golang's runnable example harness + osExit = func(int) {} + + MustParse(&args) + + // output: + // Usage: example [--verbose] + // + // Options: + // --verbose + // --help, -h display this help and exit + // + // Commands: + // get fetch an item and print it + // list list available items +} diff --git a/parse.go b/parse.go index d06b299..3a48880 100644 --- a/parse.go +++ b/parse.go @@ -59,6 +59,7 @@ type spec struct { // command represents a named subcommand, or the top-level command type command struct { name string + help string dest path specs []*spec subcommands []*command @@ -296,6 +297,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { return false } + subcmd.help = field.Tag.Get("help") + cmd.subcommands = append(cmd.subcommands, subcmd) isSubcommand = true default: diff --git a/usage.go b/usage.go index d73da71..42c564b 100644 --- a/usage.go +++ b/usage.go @@ -69,6 +69,23 @@ func (p *Parser) WriteUsage(w io.Writer) { fmt.Fprint(w, "\n") } +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) { var positionals, options []*spec @@ -89,17 +106,7 @@ func (p *Parser) WriteHelp(w io.Writer) { if len(positionals) > 0 { fmt.Fprint(w, "\nPositional arguments:\n") for _, spec := range positionals { - left := " " + strings.ToUpper(spec.long) - fmt.Fprint(w, left) - if spec.help != "" { - if len(left)+2 < colWidth { - fmt.Fprint(w, strings.Repeat(" ", colWidth-len(left))) - } else { - fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) - } - fmt.Fprint(w, spec.help) - } - fmt.Fprint(w, "\n") + printTwoCols(w, strings.ToUpper(spec.long), spec.help, nil) } } @@ -123,42 +130,44 @@ func (p *Parser) WriteHelp(w io.Writer) { help: "display version and exit", }) } + + // write the list of subcommands + if len(p.cmd.subcommands) > 0 { + fmt.Fprint(w, "\nCommands:\n") + for _, subcmd := range p.cmd.subcommands { + printTwoCols(w, subcmd.name, subcmd.help, nil) + } + } } func (p *Parser) printOption(w io.Writer, spec *spec) { - left := " " + synopsis(spec, "--"+spec.long) + left := synopsis(spec, "--"+spec.long) if spec.short != "" { left += ", " + synopsis(spec, "-"+spec.short) } - fmt.Fprint(w, left) - if spec.help != "" { - if len(left)+2 < colWidth { - fmt.Fprint(w, strings.Repeat(" ", colWidth-len(left))) - } else { - fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) - } - fmt.Fprint(w, spec.help) - } + // If spec.dest is not the zero value then a default value has been added. var v reflect.Value if len(spec.dest.fields) > 0 { v = p.readable(spec.dest) } + + var defaultVal *string 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 { - fmt.Fprintf(w, " [default: error: %v]", err) + defaultVal = ptrTo(fmt.Sprintf("error: %v", err)) } else { - fmt.Fprintf(w, " [default: %v]", string(value)) + defaultVal = ptrTo(fmt.Sprintf("%v", string(value))) } } else { - fmt.Fprintf(w, " [default: %v]", v) + defaultVal = ptrTo(fmt.Sprintf("%v", v)) } } } - fmt.Fprint(w, "\n") + printTwoCols(w, left, spec.help, defaultVal) } func synopsis(spec *spec, form string) string { @@ -167,3 +176,7 @@ func synopsis(spec *spec, form string) string { } return form + " " + strings.ToUpper(spec.long) } + +func ptrTo(s string) *string { + return &s +}