2018-01-11 22:19:25 +00:00
|
|
|
package mcfg
|
|
|
|
|
|
|
|
import (
|
2019-02-05 20:18:17 +00:00
|
|
|
"context"
|
2018-08-14 01:02:06 +00:00
|
|
|
"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
|
|
|
|
2019-01-08 19:21:55 +00:00
|
|
|
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
2018-01-11 22:19:25 +00:00
|
|
|
"github.com/mediocregopher/mediocre-go-lib/mtime"
|
|
|
|
)
|
|
|
|
|
2019-01-08 19:21:55 +00:00
|
|
|
// Param is a configuration parameter which can be populated by Populate. The
|
2019-02-05 20:18:17 +00:00
|
|
|
// 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.
|
2019-01-08 19:21:55 +00:00
|
|
|
//
|
|
|
|
// Param values are always unmarshaled as JSON values into the Into field of the
|
|
|
|
// Param, regardless of the actual Source.
|
2018-01-11 22:19:25 +00:00
|
|
|
type Param struct {
|
2019-02-05 20:18:17 +00:00
|
|
|
// How the parameter will be identified within a Context.
|
2018-01-11 22:19:25 +00:00
|
|
|
Name string
|
2019-01-08 19:21:55 +00:00
|
|
|
|
|
|
|
// A helpful description of how a parameter is expected to be used.
|
2018-01-11 22:19:25 +00:00
|
|
|
Usage string
|
|
|
|
|
|
|
|
// If the parameter's value is expected to be read as a go string. This is
|
2019-01-08 19:21:55 +00:00
|
|
|
// used for configuration sources like CLI which will automatically add
|
|
|
|
// double-quotes around the value if they aren't already there.
|
2018-01-11 22:19:25 +00:00
|
|
|
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
|
|
|
|
|
2019-01-08 19:21:55 +00:00
|
|
|
// If true then the parameter _must_ be set by at least one Source.
|
2018-01-11 22:19:25 +00:00
|
|
|
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
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// The Context this Param was added to. NOTE that this will be automatically
|
|
|
|
// filled in by MustAdd when the Param is added to the Context.
|
|
|
|
Context context.Context
|
2018-08-14 01:02:06 +00:00
|
|
|
}
|
|
|
|
|
2019-01-25 22:33:36 +00:00
|
|
|
func paramFullName(path []string, name string) string {
|
|
|
|
return strings.Join(append(path, name), "-")
|
|
|
|
}
|
|
|
|
|
2018-08-14 01:02:06 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
type ctxKey string
|
|
|
|
|
|
|
|
func getParam(ctx context.Context, name string) (Param, bool) {
|
|
|
|
param, ok := mctx.LocalValue(ctx, ctxKey(name)).(Param)
|
|
|
|
return param, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// MustAdd 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 MustAdd(ctx context.Context, param Param) context.Context {
|
2019-01-08 19:21:55 +00:00
|
|
|
param.Name = strings.ToLower(param.Name)
|
2019-02-05 20:18:17 +00:00
|
|
|
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)
|
|
|
|
}
|
2018-08-14 01:02:06 +00:00
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
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)
|
|
|
|
}
|
2018-08-14 01:02:06 +00:00
|
|
|
}
|
2019-02-05 20:18:17 +00:00
|
|
|
return params
|
2018-08-14 01:02:06 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// Int64 returns an *int64 which will be populated once Populate is run on the
|
|
|
|
// returned Context.
|
|
|
|
func Int64(ctx context.Context, name string, defaultVal int64, usage string) (context.Context, *int64) {
|
2018-01-11 22:19:25 +00:00
|
|
|
i := defaultVal
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Usage: usage, Into: &i})
|
|
|
|
return ctx, &i
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// RequiredInt64 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 RequiredInt64(ctx context.Context, name string, usage string) (context.Context, *int64) {
|
2018-01-11 22:19:25 +00:00
|
|
|
var i int64
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Required: true, Usage: usage, Into: &i})
|
|
|
|
return ctx, &i
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// Int returns an *int which will be populated once Populate is run on the
|
|
|
|
// returned Context.
|
|
|
|
func Int(ctx context.Context, name string, defaultVal int, usage string) (context.Context, *int) {
|
2018-01-11 22:19:25 +00:00
|
|
|
i := defaultVal
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Usage: usage, Into: &i})
|
|
|
|
return ctx, &i
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// RequiredInt 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 RequiredInt(ctx context.Context, name string, usage string) (context.Context, *int) {
|
2018-01-11 22:19:25 +00:00
|
|
|
var i int
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Required: true, Usage: usage, Into: &i})
|
|
|
|
return ctx, &i
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// String returns a *string which will be populated once Populate is run on the
|
|
|
|
// returned Context.
|
|
|
|
func String(ctx context.Context, name, defaultVal, usage string) (context.Context, *string) {
|
2018-01-11 22:19:25 +00:00
|
|
|
s := defaultVal
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Usage: usage, IsString: true, Into: &s})
|
|
|
|
return ctx, &s
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 19:21:55 +00:00
|
|
|
// RequiredString returns a *string which will be populated once Populate is
|
2019-02-05 20:18:17 +00:00
|
|
|
// run on the returned Context, and which must be supplied by a configuration
|
|
|
|
// Source.
|
|
|
|
func RequiredString(ctx context.Context, name, usage string) (context.Context, *string) {
|
2018-01-11 22:19:25 +00:00
|
|
|
var s string
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Required: true, Usage: usage, IsString: true, Into: &s})
|
|
|
|
return ctx, &s
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// Bool returns a *bool which will be populated once Populate is run on the
|
|
|
|
// returned Context, 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.
|
2019-02-05 20:18:17 +00:00
|
|
|
func Bool(ctx context.Context, name, usage string) (context.Context, *bool) {
|
2018-01-11 22:19:25 +00:00
|
|
|
var b bool
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Usage: usage, IsBool: true, Into: &b})
|
|
|
|
return ctx, &b
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// TS returns an *mtime.TS which will be populated once Populate is run on the
|
|
|
|
// returned Context.
|
|
|
|
func TS(ctx context.Context, name string, defaultVal mtime.TS, usage string) (context.Context, *mtime.TS) {
|
2018-01-11 22:19:25 +00:00
|
|
|
t := defaultVal
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Usage: usage, Into: &t})
|
|
|
|
return ctx, &t
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// RequiredTS 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 RequiredTS(ctx context.Context, name, usage string) (context.Context, *mtime.TS) {
|
2019-01-08 19:21:55 +00:00
|
|
|
var t mtime.TS
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Required: true, Usage: usage, Into: &t})
|
|
|
|
return ctx, &t
|
2019-01-08 19:21:55 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
// Duration returns an *mtime.Duration which will be populated once Populate is
|
|
|
|
// run on the returned Context.
|
|
|
|
func Duration(ctx context.Context, name string, defaultVal mtime.Duration, usage string) (context.Context, *mtime.Duration) {
|
2018-01-11 22:19:25 +00:00
|
|
|
d := defaultVal
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Usage: usage, IsString: true, Into: &d})
|
|
|
|
return ctx, &d
|
2019-01-08 19:21:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RequiredDuration returns an *mtime.Duration which will be populated once
|
2019-02-05 20:18:17 +00:00
|
|
|
// Populate is run on the returned Context, and which must be supplied by a
|
|
|
|
// configuration Source.
|
|
|
|
func RequiredDuration(ctx context.Context, name string, defaultVal mtime.Duration, usage string) (context.Context, *mtime.Duration) {
|
2019-01-08 19:21:55 +00:00
|
|
|
var d mtime.Duration
|
2019-02-05 20:18:17 +00:00
|
|
|
ctx = MustAdd(ctx, Param{Name: name, Required: true, Usage: usage, IsString: true, Into: &d})
|
|
|
|
return ctx, &d
|
2018-01-11 22:19:25 +00:00
|
|
|
}
|
2018-08-10 00:05:04 +00:00
|
|
|
|
2019-01-08 19:21:55 +00:00
|
|
|
// JSON 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.
|
2019-02-05 20:18:17 +00:00
|
|
|
func JSON(ctx context.Context, name string, into interface{}, usage string) context.Context {
|
|
|
|
return MustAdd(ctx, Param{Name: name, Usage: usage, Into: into})
|
2018-08-10 00:05:04 +00:00
|
|
|
}
|
|
|
|
|
2019-01-08 19:21:55 +00:00
|
|
|
// RequiredJSON 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.
|
2019-02-05 20:18:17 +00:00
|
|
|
func RequiredJSON(ctx context.Context, name string, into interface{}, usage string) context.Context {
|
|
|
|
return MustAdd(ctx, Param{Name: name, Required: true, Usage: usage, Into: into})
|
2018-08-10 00:05:04 +00:00
|
|
|
}
|