mediocre-go-lib/mcfg/param.go
2019-04-20 16:49:12 -04:00

230 lines
8.6 KiB
Go

package mcfg
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/mediocregopher/mediocre-go-lib/mctx"
"github.com/mediocregopher/mediocre-go-lib/mtime"
)
// Param is a configuration parameter which can be populated by Populate. The
// Param will exist as part of a Context, relative to its path (see the mctx
// package for more on Context path). For example, a Param with name "addr"
// under a Context with path of []string{"foo","bar"} will be setable on the CLI
// via "--foo-bar-addr". Other configuration Sources may treat the path/name
// differently, however.
//
// Param values are always unmarshaled as JSON values into the Into field of the
// Param, regardless of the actual Source.
type Param struct {
// How the parameter will be identified within a Context.
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 add
// double-quotes around the value if they aren't already there.
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 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{}
// The Context this Param was added to. NOTE that this will be automatically
// filled in by WithParam when the Param is added to the Context.
Context context.Context
}
func paramFullName(path []string, name string) string {
return strings.Join(append(path, name), "-")
}
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)
}
type ctxKey string
func getParam(ctx context.Context, name string) (Param, bool) {
param, ok := mctx.LocalValue(ctx, ctxKey(name)).(Param)
return param, ok
}
// WithParam returns a Context with the given Param added to it. It will panic
// if a Param with the same Name already exists in the Context.
func WithParam(ctx context.Context, param Param) context.Context {
param.Name = strings.ToLower(param.Name)
param.Context = ctx
if _, ok := getParam(ctx, param.Name); ok {
path := mctx.Path(ctx)
panic(fmt.Sprintf("Context Path:%#v Name:%q already exists", path, param.Name))
}
return mctx.WithLocalValue(ctx, ctxKey(param.Name), param)
}
func getLocalParams(ctx context.Context) []Param {
localVals := mctx.LocalValues(ctx)
params := make([]Param, 0, len(localVals))
for _, val := range localVals {
if param, ok := val.(Param); ok {
params = append(params, param)
}
}
return params
}
// WithInt64 returns an *int64 which will be populated once Populate is run on
// the returned Context.
func WithInt64(ctx context.Context, name string, defaultVal int64, usage string) (context.Context, *int64) {
i := defaultVal
ctx = WithParam(ctx, Param{Name: name, Usage: usage, Into: &i})
return ctx, &i
}
// WithRequiredInt64 returns an *int64 which will be populated once Populate is
// run on the returned Context, and which must be supplied by a configuration
// Source.
func WithRequiredInt64(ctx context.Context, name string, usage string) (context.Context, *int64) {
var i int64
ctx = WithParam(ctx, Param{Name: name, Required: true, Usage: usage, Into: &i})
return ctx, &i
}
// WithInt returns an *int which will be populated once Populate is run on the
// returned Context.
func WithInt(ctx context.Context, name string, defaultVal int, usage string) (context.Context, *int) {
i := defaultVal
ctx = WithParam(ctx, Param{Name: name, Usage: usage, Into: &i})
return ctx, &i
}
// WithRequiredInt returns an *int which will be populated once Populate is run
// on the returned Context, and which must be supplied by a configuration
// Source.
func WithRequiredInt(ctx context.Context, name string, usage string) (context.Context, *int) {
var i int
ctx = WithParam(ctx, Param{Name: name, Required: true, Usage: usage, Into: &i})
return ctx, &i
}
// WithFloat64 returns a *float64 which will be populated once Populate is run on
// the returned Context.
func WithFloat64(ctx context.Context, name string, defaultVal float64, usage string) (context.Context, *float64) {
f := defaultVal
ctx = WithParam(ctx, Param{Name: name, Usage: usage, Into: &f})
return ctx, &f
}
// WithRequiredFloat64 returns a *float64 which will be populated once Populate
// is run on the returned Context, and which must be supplied by a configuration
// Source.
func WithRequiredFloat64(ctx context.Context, name string, defaultVal float64, usage string) (context.Context, *float64) {
f := defaultVal
ctx = WithParam(ctx, Param{Name: name, Required: true, Usage: usage, Into: &f})
return ctx, &f
}
// WithString returns a *string which will be populated once Populate is run on
// the returned Context.
func WithString(ctx context.Context, name, defaultVal, usage string) (context.Context, *string) {
s := defaultVal
ctx = WithParam(ctx, Param{Name: name, Usage: usage, IsString: true, Into: &s})
return ctx, &s
}
// WithRequiredString returns a *string which will be populated once Populate is
// run on the returned Context, and which must be supplied by a configuration
// Source.
func WithRequiredString(ctx context.Context, name, usage string) (context.Context, *string) {
var s string
ctx = WithParam(ctx, Param{Name: name, Required: true, Usage: usage, IsString: true, Into: &s})
return ctx, &s
}
// WithBool returns a *bool which will be populated once Populate is run on the
// returned Context, 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 WithBool(ctx context.Context, name, usage string) (context.Context, *bool) {
var b bool
ctx = WithParam(ctx, Param{Name: name, Usage: usage, IsBool: true, Into: &b})
return ctx, &b
}
// WithTS returns an *mtime.TS which will be populated once Populate is run on
// the returned Context.
func WithTS(ctx context.Context, name string, defaultVal mtime.TS, usage string) (context.Context, *mtime.TS) {
t := defaultVal
ctx = WithParam(ctx, Param{Name: name, Usage: usage, Into: &t})
return ctx, &t
}
// WithRequiredTS returns an *mtime.TS which will be populated once Populate is
// run on the returned Context, and which must be supplied by a configuration
// Source.
func WithRequiredTS(ctx context.Context, name, usage string) (context.Context, *mtime.TS) {
var t mtime.TS
ctx = WithParam(ctx, Param{Name: name, Required: true, Usage: usage, Into: &t})
return ctx, &t
}
// WithDuration returns an *mtime.Duration which will be populated once Populate
// is run on the returned Context.
func WithDuration(ctx context.Context, name string, defaultVal mtime.Duration, usage string) (context.Context, *mtime.Duration) {
d := defaultVal
ctx = WithParam(ctx, Param{Name: name, Usage: usage, IsString: true, Into: &d})
return ctx, &d
}
// WithRequiredDuration returns an *mtime.Duration which will be populated once
// Populate is run on the returned Context, and which must be supplied by a
// configuration Source.
func WithRequiredDuration(ctx context.Context, name string, defaultVal mtime.Duration, usage string) (context.Context, *mtime.Duration) {
var d mtime.Duration
ctx = WithParam(ctx, Param{Name: name, Required: true, Usage: usage, IsString: true, Into: &d})
return ctx, &d
}
// WithJSON 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 WithJSON(ctx context.Context, name string, into interface{}, usage string) context.Context {
return WithParam(ctx, Param{Name: name, Usage: usage, Into: into})
}
// WithRequiredJSON reads the parameter value as a JSON value and unmarshals it
// into the given interface{} (which should be a pointer). The value must be
// supplied by a configuration Source.
func WithRequiredJSON(ctx context.Context, name string, into interface{}, usage string) context.Context {
return WithParam(ctx, Param{Name: name, Required: true, Usage: usage, Into: into})
}