diff --git a/ffcli/command.go b/ffcli/command.go index ed83d19..5254acc 100644 --- a/ffcli/command.go +++ b/ffcli/command.go @@ -231,12 +231,33 @@ func DefaultUsageFunc(c *Command) string { space = "=" } - def := f.DefValue - if def == "" { + // If the help text contains backticks, + // e.g. "foo `bar` baz"`, we'll get: + // + // argname = "bar" + // usage = "foo bar baz" + // + // Otherwise, it's an educated guess for a placeholder, + // or an empty string if one couldn't be determined. + argname, usage := flag.UnquoteUsage(f) + + // For the argument name printed in the help, + // the order of preference is: + // + // 1. the default value + // 2. the back-quoted name from the help text + // 3. the '...' placeholder + var def string + switch { + case f.DefValue != "": + def = f.DefValue + case argname != "": + def = argname + default: def = "..." } - fmt.Fprintf(tw, " -%s%s%s\t%s\n", f.Name, space, def, f.Usage) + fmt.Fprintf(tw, " -%s%s%s\t%s\n", f.Name, space, def, usage) }) tw.Flush() fmt.Fprintf(&b, "\n") diff --git a/ffcli/command_test.go b/ffcli/command_test.go index 7656a85..9d9aa90 100644 --- a/ffcli/command_test.go +++ b/ffcli/command_test.go @@ -439,6 +439,62 @@ func TestIssue57(t *testing.T) { } } +func TestDefaultUsageFuncFlagHelp(t *testing.T) { + t.Parallel() + + for _, testcase := range []struct { + name string // name of test case + def string // default value, if any + help string // help text for flag + want string // expected usage text + }{ + { + name: "plain text", + help: "does stuff", + want: "-x string does stuff", + }, + { + name: "placeholder", + help: "reads from `file` instead of stdout", + want: "-x file reads from file instead of stdout", + }, + { + name: "default", + def: "www", + help: "path to output directory", + want: "-x www path to output directory", + }, + { + name: "default with placeholder", + def: "www", + help: "path to output `directory`", + want: "-x www path to output directory", + }, + } { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + + fset := flag.NewFlagSet(t.Name(), flag.ContinueOnError) + fset.String("x", testcase.def, testcase.help) + + usage := ffcli.DefaultUsageFunc(&ffcli.Command{ + FlagSet: fset, + }) + + // Discard everything before the FLAGS section. + _, flagUsage, ok := strings.Cut(usage, "\nFLAGS\n") + if !ok { + t.Fatalf("FLAGS section not found in:\n%s", usage) + } + + assertMultilineString(t, + strings.TrimSpace(testcase.want), + strings.TrimSpace(flagUsage)) + }) + } +} + func ExampleCommand_Parse_then_Run() { // Assume our CLI will use some client that requires a token. type FooClient struct { @@ -543,10 +599,10 @@ USAGE Some long help. FLAGS - -b=false bool - -d 0s time.Duration - -f 0 float64 - -i 0 int - -s ... string - -x ... collection of strings (repeatable) + -b=false bool + -d 0s time.Duration + -f 0 float64 + -i 0 int + -s string string + -x ... collection of strings (repeatable) `) + "\n\n"