2018-01-11 22:19:25 +00:00
|
|
|
package mcfg
|
|
|
|
|
|
|
|
import (
|
2018-08-14 01:02:06 +00:00
|
|
|
"crypto/md5"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
2018-01-11 22:19:25 +00:00
|
|
|
"fmt"
|
2018-08-13 15:50:57 +00:00
|
|
|
"strings"
|
2018-01-11 22:19:25 +00:00
|
|
|
|
|
|
|
"github.com/mediocregopher/mediocre-go-lib/mtime"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Param is a configuration parameter which can be added to a Cfg. The Param
|
|
|
|
// will exist relative to a Cfg's Path. For example, a Param with name "addr"
|
|
|
|
// under a Cfg with Path of []string{"foo","bar"} will be setabble on the CLI
|
|
|
|
// via "--foo-bar-addr". Other configuration Sources may treat the path/name
|
|
|
|
// differently, however.
|
|
|
|
type Param struct {
|
|
|
|
// How the parameter will be identified within a Cfg instance
|
|
|
|
Name string
|
|
|
|
// A helpful description of how a parameter is expected to be used
|
|
|
|
Usage string
|
|
|
|
|
|
|
|
// If the parameter's value is expected to be read as a go string. This is
|
|
|
|
// used for configuration sources like CLI which will automatically escape
|
|
|
|
// the parameter's value with double-quotes
|
|
|
|
IsString bool
|
|
|
|
|
|
|
|
// If the parameter's value is expected to be a boolean. This is used for
|
|
|
|
// configuration sources like CLI which treat boolean parameters (aka flags)
|
|
|
|
// differently.
|
|
|
|
IsBool bool
|
|
|
|
|
|
|
|
// If true then the parameter _must_ be set by at least one configuration
|
|
|
|
// source
|
|
|
|
Required bool
|
|
|
|
|
|
|
|
// The pointer/interface into which the configuration value will be
|
|
|
|
// json.Unmarshal'd. The value being pointed to also determines the default
|
|
|
|
// value of the parameter.
|
|
|
|
Into interface{}
|
2018-08-14 01:02:06 +00:00
|
|
|
|
|
|
|
// The Path field of the Cfg this Param is attached to. NOTE that this
|
|
|
|
// will be automatically filled in when the Param is added to the Cfg.
|
|
|
|
Path []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p Param) fuzzyParse(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)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p Param) fullName() string {
|
|
|
|
return strings.Join(append(p.Path, p.Name), "-")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p Param) hash() string {
|
|
|
|
h := md5.New()
|
|
|
|
for _, path := range p.Path {
|
|
|
|
fmt.Fprintf(h, "pathEl:%q\n", path)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(h, "name:%q\n", p.Name)
|
|
|
|
hStr := hex.EncodeToString(h.Sum(nil))
|
|
|
|
// we add the displayName to it to make debugging easier
|
|
|
|
return p.fullName() + "/" + hStr
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ParamAdd adds the given Param to the Cfg. It will panic if a Param of the
|
|
|
|
// same Name already exists in the Cfg.
|
|
|
|
func (c *Cfg) ParamAdd(p Param) {
|
2018-08-13 15:50:57 +00:00
|
|
|
p.Name = strings.ToLower(p.Name)
|
2018-01-11 22:19:25 +00:00
|
|
|
if _, ok := c.Params[p.Name]; ok {
|
|
|
|
panic(fmt.Sprintf("Cfg.Path:%#v name:%q already exists", c.Path, p.Name))
|
|
|
|
}
|
2018-08-14 01:02:06 +00:00
|
|
|
p.Path = c.Path
|
2018-01-11 22:19:25 +00:00
|
|
|
c.Params[p.Name] = p
|
|
|
|
}
|
|
|
|
|
2018-08-14 01:02:06 +00:00
|
|
|
func (c *Cfg) allParams() []Param {
|
|
|
|
params := make([]Param, 0, len(c.Params))
|
|
|
|
for _, p := range c.Params {
|
|
|
|
params = append(params, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, child := range c.Children {
|
|
|
|
params = append(params, child.allParams()...)
|
|
|
|
}
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
2018-01-11 22:19:25 +00:00
|
|
|
// ParamInt64 returns an *int64 which will be populated once the Cfg is run
|
|
|
|
func (c *Cfg) ParamInt64(name string, defaultVal int64, usage string) *int64 {
|
|
|
|
i := defaultVal
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, Into: &i})
|
|
|
|
return &i
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamRequiredInt64 returns an *int64 which will be populated once the Cfg is
|
|
|
|
// run, and which must be supplied by a configuration Source
|
|
|
|
func (c *Cfg) ParamRequiredInt64(name string, usage string) *int64 {
|
|
|
|
var i int64
|
|
|
|
c.ParamAdd(Param{Name: name, Required: true, Usage: usage, Into: &i})
|
|
|
|
return &i
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamInt returns an *int which will be populated once the Cfg is run
|
|
|
|
func (c *Cfg) ParamInt(name string, defaultVal int, usage string) *int {
|
|
|
|
i := defaultVal
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, Into: &i})
|
|
|
|
return &i
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamRequiredInt returns an *int which will be populated once the Cfg is run,
|
|
|
|
// and which must be supplied by a configuration Source
|
|
|
|
func (c *Cfg) ParamRequiredInt(name string, usage string) *int {
|
|
|
|
var i int
|
|
|
|
c.ParamAdd(Param{Name: name, Required: true, Usage: usage, Into: &i})
|
|
|
|
return &i
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamString returns a *string which will be populated once the Cfg is run
|
|
|
|
func (c *Cfg) ParamString(name, defaultVal, usage string) *string {
|
|
|
|
s := defaultVal
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, IsString: true, Into: &s})
|
|
|
|
return &s
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamRequiredString returns a *string which will be populated once the Cfg is
|
|
|
|
// run, and which must be supplied by a configuration Source
|
|
|
|
func (c *Cfg) ParamRequiredString(name, usage string) *string {
|
|
|
|
var s string
|
|
|
|
c.ParamAdd(Param{Name: name, Required: true, Usage: usage, IsString: true, Into: &s})
|
|
|
|
return &s
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamBool returns a *bool which will be populated once the Cfg is run, and
|
|
|
|
// which defaults to false if unconfigured
|
2018-08-14 00:44:03 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2018-01-11 22:19:25 +00:00
|
|
|
func (c *Cfg) ParamBool(name, usage string) *bool {
|
|
|
|
var b bool
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, IsBool: true, Into: &b})
|
|
|
|
return &b
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamTS returns an *mtime.TS which will be populated once the Cfg is run
|
|
|
|
func (c *Cfg) ParamTS(name string, defaultVal mtime.TS, usage string) *mtime.TS {
|
|
|
|
t := defaultVal
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, Into: &t})
|
|
|
|
return &t
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamDuration returns an *mtime.Duration which will be populated once the Cfg
|
|
|
|
// is run
|
|
|
|
func (c *Cfg) ParamDuration(name string, defaultVal mtime.Duration, usage string) *mtime.Duration {
|
|
|
|
d := defaultVal
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, IsString: true, Into: &d})
|
|
|
|
return &d
|
|
|
|
}
|
2018-08-10 00:05:04 +00:00
|
|
|
|
|
|
|
// ParamJSON reads the parameter value as a JSON value and unmarshals it into
|
|
|
|
// the given interface{} (which should be a pointer). The receiver (into) is
|
|
|
|
// also used to determine the default value.
|
|
|
|
func (c *Cfg) ParamJSON(name string, into interface{}, usage string) {
|
|
|
|
c.ParamAdd(Param{Name: name, Usage: usage, Into: into})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParamRequiredJSON reads the parameter value as a JSON value and unmarshals it
|
|
|
|
// into the given interface{} (which should be a pointer).
|
|
|
|
func (c *Cfg) ParamRequiredJSON(name string, into interface{}, usage string) {
|
|
|
|
c.ParamAdd(Param{Name: name, Required: true, Usage: usage, Into: into})
|
|
|
|
}
|