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
|
package mcfg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -23,6 +22,11 @@ import (
|
|||||||
// stdout and the process will exit. Since all normally-defined parameters must
|
// 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.
|
// 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 {
|
type SourceCLI struct {
|
||||||
Args []string // if nil then os.Args[1:] is used
|
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
|
// pvOk is always true at this point, and so pv is filled in
|
||||||
|
|
||||||
if pv.IsBool {
|
// As a special case for CLI, if a boolean has no value set it means it
|
||||||
// if it's a boolean we don't expect there to be a following value,
|
// is true.
|
||||||
// it's just a flag
|
if pv.IsBool && !pvStrValOk {
|
||||||
if pvStrValOk {
|
pvStrVal = "true"
|
||||||
return nil, fmt.Errorf("param %q is a boolean and cannot have a value", arg)
|
pvStrValOk = true
|
||||||
}
|
|
||||||
pv.Value = json.RawMessage("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
|
||||||
// filled with the next one (hopefully)
|
// filled with the next one (hopefully)
|
||||||
continue
|
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)
|
pvs = append(pvs, pv)
|
||||||
key = ""
|
key = ""
|
||||||
pv = ParamValue{}
|
pv = ParamValue{}
|
||||||
|
14
mcfg/env.go
14
mcfg/env.go
@ -1,7 +1,6 @@
|
|||||||
package mcfg
|
package mcfg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -58,18 +57,7 @@ func (env SourceEnv) Parse(cfg *Cfg) ([]ParamValue, error) {
|
|||||||
}
|
}
|
||||||
k, v := split[0], split[1]
|
k, v := split[0], split[1]
|
||||||
if pv, ok := pvM[k]; ok {
|
if pv, ok := pvM[k]; ok {
|
||||||
if pv.IsBool {
|
pv.Value = fuzzyParse(pv.Param, v)
|
||||||
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)
|
|
||||||
}
|
|
||||||
pvs = append(pvs, pv)
|
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
|
// ParamBool returns a *bool which will be populated once the Cfg is run, and
|
||||||
// which defaults to false if unconfigured
|
// 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 {
|
func (c *Cfg) ParamBool(name, usage string) *bool {
|
||||||
var b bool
|
var b bool
|
||||||
c.ParamAdd(Param{Name: name, Usage: usage, IsBool: true, Into: &b})
|
c.ParamAdd(Param{Name: name, Usage: usage, IsBool: true, Into: &b})
|
||||||
|
@ -8,6 +8,22 @@ import (
|
|||||||
"strings"
|
"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
|
// ParamValue describes a value for a parameter which has been parsed by a
|
||||||
// Source
|
// Source
|
||||||
type ParamValue struct {
|
type ParamValue struct {
|
||||||
@ -69,3 +85,20 @@ func (ss Sources) Parse(c *Cfg) ([]ParamValue, error) {
|
|||||||
}
|
}
|
||||||
return pvs, nil
|
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),
|
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