summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Flint <[email protected]>2019-05-03 15:02:10 -0700
committerAlex Flint <[email protected]>2019-05-03 15:02:10 -0700
commit15bf383f1d5db9bf362029529f3c83f092e2d00f (patch)
tree8cb7448834eacb0039546a5b10d896af11437400
parentedd1af466781355875c44cd9213ce1e398a4c84d (diff)
add subcommands to usage string
-rw-r--r--example_test.go37
-rw-r--r--parse.go3
-rw-r--r--usage.go63
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
+}