mcfg: implement SourceCLI.TailCallback (though I'm not convinced about the naming or necessity of it)

This commit is contained in:
Brian Picciano 2019-04-02 23:21:16 -04:00
parent f5cea76cb7
commit ea72c49935
2 changed files with 69 additions and 10 deletions

View File

@ -37,6 +37,12 @@ type SourceCLI struct {
Args []string // if nil then os.Args[1:] is used Args []string // if nil then os.Args[1:] is used
DisableHelpPage bool DisableHelpPage bool
// Normally if any unexpected Arg value is encountered Parse will error out.
// If instead TailCallback is set then it will be called whenever the first
// unexpected Arg is encountered, and will not error out. TailCallback will
// be given a slice of Args starting at the first unexpected element.
TailCallback func([]string)
} }
const ( const (
@ -61,12 +67,12 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
var ( var (
key string key string
p Param p Param
pvOk bool pOk bool
pvStrVal string pvStrVal string
pvStrValOk bool pvStrValOk bool
) )
for _, arg := range args { for i, arg := range args {
if pvOk { if pOk {
pvStrVal = arg pvStrVal = arg
pvStrValOk = true pvStrValOk = true
} else if !cli.DisableHelpPage && arg == cliHelpArg { } else if !cli.DisableHelpPage && arg == cliHelpArg {
@ -76,7 +82,7 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
} else { } else {
for key, p = range pM { for key, p = range pM {
if arg == key { if arg == key {
pvOk = true pOk = true
break break
} }
@ -84,24 +90,27 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
if !strings.HasPrefix(arg, prefix) { if !strings.HasPrefix(arg, prefix) {
continue continue
} }
pvOk = true pOk = true
pvStrVal = strings.TrimPrefix(arg, prefix) pvStrVal = strings.TrimPrefix(arg, prefix)
pvStrValOk = true pvStrValOk = true
break break
} }
if !pvOk { if !pOk {
if cli.TailCallback != nil {
cli.TailCallback(args[i:])
return pvs, nil
}
ctx := mctx.Annotate(context.Background(), "param", arg) ctx := mctx.Annotate(context.Background(), "param", arg)
return nil, merr.New("unexpected config parameter", ctx) return nil, merr.New("unexpected config parameter", ctx)
} }
} }
// pvOk is always true at this point, and so pv is filled in // pOk is always true at this point, and so p is filled in
// As a special case for CLI, if a boolean has no value set it means it // As a special case for CLI, if a boolean has no value set it means it
// is true. // is true.
if p.IsBool && !pvStrValOk { if p.IsBool && !pvStrValOk {
pvStrVal = "true" pvStrVal = "true"
pvStrValOk = true
} else if !pvStrValOk { } else if !pvStrValOk {
// everything else should have a value. if pvStrVal isn't filled it // everything else should have a value. if pvStrVal isn't filled it
// means the next arg should be one. Continue the loop, it'll get // means the next arg should be one. Continue the loop, it'll get
@ -117,11 +126,11 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) {
key = "" key = ""
p = Param{} p = Param{}
pvOk = false pOk = false
pvStrVal = "" pvStrVal = ""
pvStrValOk = false pvStrValOk = false
} }
if pvOk && !pvStrValOk { if pOk && !pvStrValOk {
ctx := mctx.Annotate(p.Context, "param", key) ctx := mctx.Annotate(p.Context, "param", key)
return nil, merr.New("param expected a value", ctx) return nil, merr.New("param expected a value", ctx)
} }

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/mediocregopher/mediocre-go-lib/mtest/massert"
"github.com/mediocregopher/mediocre-go-lib/mtest/mchk" "github.com/mediocregopher/mediocre-go-lib/mtest/mchk"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -103,3 +104,52 @@ func TestSourceCLI(t *T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestSourceCLITailCallback(t *T) {
ctx := context.Background()
ctx, _ = WithInt(ctx, "foo", 5, "")
ctx, _ = WithBool(ctx, "bar", "")
var tail []string
src := SourceCLI{TailCallback: func(gotTail []string) {
tail = gotTail
}}
type testCase struct {
args []string
expTail []string
}
cases := []testCase{
{
args: []string{"--foo", "5"},
expTail: []string{},
},
{
args: []string{"--foo", "5", "a", "b", "c"},
expTail: []string{"a", "b", "c"},
},
{
args: []string{"--foo=5", "a", "b", "c"},
expTail: []string{"a", "b", "c"},
},
{
args: []string{"--foo", "5", "--bar"},
expTail: []string{},
},
{
args: []string{"--foo", "5", "--bar", "a", "b", "c"},
expTail: []string{"a", "b", "c"},
},
}
for _, tc := range cases {
tail = []string{}
src.Args = tc.args
err := Populate(ctx, src)
massert.Require(t, massert.Comment(massert.All(
massert.Nil(err),
massert.Equal(tc.expTail, tail),
), "tc: %#v", tc))
}
}