mcfg: modify WithCLITail to allow modifying the Usage description

This commit is contained in:
Brian Picciano 2019-04-06 15:07:37 -04:00
parent 99387d89ac
commit 507c359997
2 changed files with 61 additions and 16 deletions

View File

@ -16,10 +16,15 @@ import (
type cliKey int
const (
cliKeyTailPtr cliKey = iota
cliKeyTail cliKey = iota
cliKeySubCmdM
)
type cliTail struct {
dst *[]string
descr string
}
// WithCLITail returns a Context which modifies the behavior of SourceCLI's
// Parse, if SourceCLI is used with that Context at all. Normally when SourceCLI
// encounters an unexpected Arg it will immediately return an error. This
@ -27,21 +32,33 @@ const (
// and all subsequent Args (i.e. the tail) should be set to the returned
// []string value.
//
// If multiple WithCLITail calls are used then only the latest returned pointer
// will be filled.
func WithCLITail(ctx context.Context) (context.Context, *[]string) {
// The descr (optional) will be appended to the "Usage" line which is printed
// with the help document when "-h" is passed in.
func WithCLITail(ctx context.Context, descr string) (context.Context, *[]string) {
if ctx.Value(cliKeyTail) != nil {
panic("WithCLITail already called in this Context")
}
tailPtr := new([]string)
return context.WithValue(ctx, cliKeyTailPtr, tailPtr), tailPtr
ctx = context.WithValue(ctx, cliKeyTail, cliTail{
dst: tailPtr,
descr: descr,
})
return ctx, tailPtr
}
func populateCLITail(ctx context.Context, tail []string) bool {
tailPtr, ok := ctx.Value(cliKeyTailPtr).(*[]string)
ct, ok := ctx.Value(cliKeyTail).(cliTail)
if ok {
*tailPtr = tail
*ct.dst = tail
}
return ok
}
func getCLITailDescr(ctx context.Context) string {
ct, _ := ctx.Value(cliKeyTail).(cliTail)
return ct.descr
}
type subCmd struct {
name, descr string
flag *bool
@ -133,10 +150,9 @@ func (cli *SourceCLI) parse(
if err != nil {
return nil, nil, err
}
subCmdM, _ := ctx.Value(cliKeySubCmdM).(map[string]subCmd)
printHelpAndExit := func() {
cli.printHelp(os.Stderr, subCmdPrefix, subCmdM, pM)
cli.printHelp(ctx, os.Stderr, subCmdPrefix, pM)
os.Stderr.Sync()
os.Exit(1)
}
@ -145,6 +161,7 @@ func (cli *SourceCLI) parse(
// of them should have been given, in which case send the Context through
// the callback to obtain a new one (which presumably has further config
// options the previous didn't) and call parse again.
subCmdM, _ := ctx.Value(cliKeySubCmdM).(map[string]subCmd)
if len(subCmdM) > 0 {
subCmd, args, ok := cli.getSubCmd(subCmdM, args)
if !ok {
@ -256,9 +273,9 @@ func (cli *SourceCLI) cliParams(params []Param) (map[string]Param, error) {
}
func (cli *SourceCLI) printHelp(
ctx context.Context,
w io.Writer,
subCmdPrefix []string,
subCmdM map[string]subCmd,
pM map[string]Param,
) {
type pEntry struct {
@ -297,6 +314,7 @@ func (cli *SourceCLI) printHelp(
subCmd
}
subCmdM, _ := ctx.Value(cliKeySubCmdM).(map[string]subCmd)
subCmdA := make([]subCmdEntry, 0, len(subCmdM))
for name, subCmd := range subCmdM {
subCmdA = append(subCmdA, subCmdEntry{name: name, subCmd: subCmd})
@ -316,6 +334,9 @@ func (cli *SourceCLI) printHelp(
if len(pA) > 0 {
fmt.Fprint(w, " [options]")
}
if descr := getCLITailDescr(ctx); descr != "" {
fmt.Fprintf(w, " %s", descr)
}
fmt.Fprint(w, "\n\n")
if len(subCmdA) > 0 {

View File

@ -22,8 +22,7 @@ func TestSourceCLIHelp(t *T) {
src := &SourceCLI{}
pM, err := src.cliParams(CollectParams(ctx))
require.NoError(t, err)
subCmdM, _ := ctx.Value(cliKeySubCmdM).(map[string]subCmd)
src.printHelp(buf, subCmdPrefix, subCmdM, pM)
src.printHelp(ctx, buf, subCmdPrefix, pM)
out := buf.String()
ok := regexp.MustCompile(exp).MatchString(out)
@ -106,6 +105,31 @@ Options:
--foo \(Default: 5\)
Test int param.
$`)
ctx, _ = WithCLITail(ctx, "[arg...]")
assertHelp(ctx, nil, `^Usage: \S+ <sub-command> \[options\] \[arg\.\.\.\]
Sub-commands:
first First sub-command
second Second sub-command
Options:
--baz2 \(Required\)
--baz3 \(Required\)
--bar \(Flag\)
Test bool param.
--baz \(Default: "baz"\)
Test string param.
--foo \(Default: 5\)
Test int param.
$`)
}
@ -204,7 +228,7 @@ func TestWithCLITail(t *T) {
}
for _, tc := range cases {
ctx, tail := WithCLITail(ctx)
ctx, tail := WithCLITail(ctx, "foo")
_, err := Populate(ctx, &SourceCLI{Args: tc.args})
massert.Require(t, massert.Comment(massert.All(
massert.Nil(err),
@ -216,15 +240,15 @@ func TestWithCLITail(t *T) {
func ExampleWithCLITail() {
ctx := context.Background()
ctx, foo := WithInt(ctx, "foo", 1, "Description of foo.")
ctx, tail := WithCLITail(ctx)
ctx, tail := WithCLITail(ctx, "[arg...]")
ctx, bar := WithString(ctx, "bar", "defaultVal", "Description of bar.")
_, err := Populate(ctx, &SourceCLI{
Args: []string{"--foo=100", "BADARG", "--bar", "BAR"},
Args: []string{"--foo=100", "arg1", "arg2", "arg3"},
})
fmt.Printf("err:%v foo:%v bar:%v tail:%#v\n", err, *foo, *bar, *tail)
// Output: err:<nil> foo:100 bar:defaultVal tail:[]string{"BADARG", "--bar", "BAR"}
// Output: err:<nil> foo:100 bar:defaultVal tail:[]string{"arg1", "arg2", "arg3"}
}
func TestWithCLISubCommand(t *T) {