mcfg: centralize logic for fuzzy parsing strings, use it to implement SourceMap
This commit is contained in:
parent
8ff2abf02c
commit
3de30eb819
27
mcfg/cli.go
27
mcfg/cli.go
@ -1,7 +1,6 @@
|
||||
package mcfg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@ -23,6 +22,11 @@ import (
|
||||
// stdout and the process will exit. Since all normally-defined parameters must
|
||||
// being with double-dash ("--") they won't ever conflict with the help option.
|
||||
//
|
||||
// SourceCLI behaves a little differently with boolean parameters. Setting the
|
||||
// value of a boolean parameter directly _must_ be done with an equals, for
|
||||
// example: `--boolean-flag=1` or `--boolean-flag=false`. Using the
|
||||
// space-separated format will not work. If a boolean has no equal-separated
|
||||
// value it is assumed to be setting the value to `true`, as would be expected.
|
||||
type SourceCLI struct {
|
||||
Args []string // if nil then os.Args[1:] is used
|
||||
|
||||
@ -86,27 +90,20 @@ func (cli SourceCLI) Parse(cfg *Cfg) ([]ParamValue, error) {
|
||||
|
||||
// pvOk is always true at this point, and so pv is filled in
|
||||
|
||||
if pv.IsBool {
|
||||
// if it's a boolean we don't expect there to be a following value,
|
||||
// it's just a flag
|
||||
if pvStrValOk {
|
||||
return nil, fmt.Errorf("param %q is a boolean and cannot have a value", arg)
|
||||
}
|
||||
pv.Value = json.RawMessage("true")
|
||||
|
||||
// As a special case for CLI, if a boolean has no value set it means it
|
||||
// is true.
|
||||
if pv.IsBool && !pvStrValOk {
|
||||
pvStrVal = "true"
|
||||
pvStrValOk = true
|
||||
} else if !pvStrValOk {
|
||||
// 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
|
||||
// filled with the next one (hopefully)
|
||||
continue
|
||||
|
||||
} else if pv.IsString && (pvStrVal == "" || pvStrVal[0] != '"') {
|
||||
pv.Value = json.RawMessage(`"` + pvStrVal + `"`)
|
||||
|
||||
} else {
|
||||
pv.Value = json.RawMessage(pvStrVal)
|
||||
}
|
||||
|
||||
pv.Value = fuzzyParse(pv.Param, pvStrVal)
|
||||
|
||||
pvs = append(pvs, pv)
|
||||
key = ""
|
||||
pv = ParamValue{}
|
||||
|
14
mcfg/env.go
14
mcfg/env.go
@ -1,7 +1,6 @@
|
||||
package mcfg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@ -58,18 +57,7 @@ func (env SourceEnv) Parse(cfg *Cfg) ([]ParamValue, error) {
|
||||
}
|
||||
k, v := split[0], split[1]
|
||||
if pv, ok := pvM[k]; ok {
|
||||
if pv.IsBool {
|
||||
if v == "" {
|
||||
pv.Value = json.RawMessage("false")
|
||||
} else {
|
||||
pv.Value = json.RawMessage("true")
|
||||
}
|
||||
} else if pv.IsString && (v == "" || v[0] != '"') {
|
||||
pv.Value = json.RawMessage(`"` + v + `"`)
|
||||
|
||||
} else {
|
||||
pv.Value = json.RawMessage(v)
|
||||
}
|
||||
pv.Value = fuzzyParse(pv.Param, v)
|
||||
pvs = append(pvs, pv)
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,11 @@ func (c *Cfg) ParamRequiredString(name, usage string) *string {
|
||||
|
||||
// ParamBool returns a *bool which will be populated once the Cfg is run, and
|
||||
// which defaults to false if unconfigured
|
||||
//
|
||||
// The default behavior of all Sources is that a boolean parameter will be set
|
||||
// to true unless the value is "", 0, or false. In the case of the CLI Source
|
||||
// the value will also be true when the parameter is used with no value at all,
|
||||
// as would be expected.
|
||||
func (c *Cfg) ParamBool(name, usage string) *bool {
|
||||
var b bool
|
||||
c.ParamAdd(Param{Name: name, Usage: usage, IsBool: true, Into: &b})
|
||||
|
@ -8,6 +8,22 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func fuzzyParse(p Param, v string) json.RawMessage {
|
||||
if p.IsBool {
|
||||
if v == "" || v == "0" || v == "false" {
|
||||
return json.RawMessage("false")
|
||||
}
|
||||
return json.RawMessage("true")
|
||||
|
||||
} else if p.IsString && (v == "" || v[0] != '"') {
|
||||
return json.RawMessage(`"` + v + `"`)
|
||||
}
|
||||
|
||||
return json.RawMessage(v)
|
||||
}
|
||||
|
||||
// TODO moving Path into Param would make a lot more sense
|
||||
|
||||
// ParamValue describes a value for a parameter which has been parsed by a
|
||||
// Source
|
||||
type ParamValue struct {
|
||||
@ -69,3 +85,20 @@ func (ss Sources) Parse(c *Cfg) ([]ParamValue, error) {
|
||||
}
|
||||
return pvs, nil
|
||||
}
|
||||
|
||||
// SourceMap implements the Source interface by mapping parameter names to
|
||||
// values for them. The names are comprised of the path and name of a Param
|
||||
// joined by "-" characters, i.e. `strings.Join(append(p.Path, p.Name), "-")`.
|
||||
// Values will be parsed in the same way that SourceEnv parses its variables.
|
||||
type SourceMap map[string]string
|
||||
|
||||
func (m SourceMap) Parse(c *Cfg) ([]ParamValue, error) {
|
||||
pvs := make([]ParamValue, 0, len(m))
|
||||
for _, pv := range c.allParamValues() {
|
||||
if v, ok := m[pv.displayName()]; ok {
|
||||
pv.Value = fuzzyParse(pv.Param, v)
|
||||
pvs = append(pvs, pv)
|
||||
}
|
||||
}
|
||||
return pvs, nil
|
||||
}
|
||||
|
@ -159,3 +159,23 @@ func TestSources(t *T) {
|
||||
massert.Equal(3, *c),
|
||||
))
|
||||
}
|
||||
|
||||
func TestSourceMap(t *T) {
|
||||
cfg := New()
|
||||
a := cfg.ParamRequiredInt("a", "")
|
||||
foo := cfg.Child("foo")
|
||||
b := foo.ParamRequiredString("b", "")
|
||||
c := foo.ParamBool("c", "")
|
||||
|
||||
err := cfg.populateParams(SourceMap{
|
||||
"a": "4",
|
||||
"foo-b": "bbb",
|
||||
"foo-c": "1",
|
||||
})
|
||||
massert.Fatal(t, massert.All(
|
||||
massert.Nil(err),
|
||||
massert.Equal(4, *a),
|
||||
massert.Equal("bbb", *b),
|
||||
massert.Equal(true, *c),
|
||||
))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user