2023-04-26 18:16:42 +03:00
|
|
|
// Package options implements config options. This package is currently a bit
|
|
|
|
// of an experiment. Objectives:
|
|
|
|
// - Options are key-value pairs.
|
|
|
|
// - Options can come from default config, individual source config, and flags.
|
|
|
|
// - Support the ability to edit config in $EDITOR, providing contextual information
|
|
|
|
// about the Opt instance.
|
|
|
|
// - Values are strongly typed (e.g. int, time.Duration)
|
|
|
|
// - An individual Opt instance can be specified near where it is used.
|
|
|
|
// - New types of Opt can be defined, near where they are used.
|
|
|
|
//
|
|
|
|
// It is noted that these requirements could probably largely be met using
|
|
|
|
// packages such as spf13/viper. AGain, this is largely an experiment.
|
2020-08-06 20:58:47 +03:00
|
|
|
package options
|
|
|
|
|
|
|
|
import (
|
2023-04-26 18:16:42 +03:00
|
|
|
"fmt"
|
|
|
|
"sync"
|
2023-04-30 17:18:56 +03:00
|
|
|
"time"
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
"golang.org/x/exp/slog"
|
|
|
|
|
|
|
|
"github.com/samber/lo"
|
|
|
|
"golang.org/x/exp/slices"
|
2020-08-06 20:58:47 +03:00
|
|
|
)
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Registry is a registry of Opt instances.
|
|
|
|
type Registry struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
opts []Opt
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Add adds an Opt to r. It panics if opt is already registered.
|
|
|
|
func (r *Registry) Add(opt Opt) {
|
|
|
|
r.mu.Lock()
|
|
|
|
defer r.mu.Unlock()
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
for i := range r.opts {
|
|
|
|
if r.opts[i].Key() == opt.Key() {
|
|
|
|
panic(fmt.Sprintf("Opt %s is already registered", opt.Key()))
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
r.opts = append(r.opts, opt)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// LogValue implements slog.LogValuer.
|
|
|
|
func (r *Registry) LogValue() slog.Value {
|
|
|
|
r.mu.Lock()
|
|
|
|
defer r.mu.Unlock()
|
|
|
|
as := make([]slog.Attr, len(r.opts))
|
|
|
|
for i, opt := range r.opts {
|
|
|
|
as[i] = slog.String(opt.Key(), fmt.Sprintf("%T", opt))
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
2023-04-26 18:16:42 +03:00
|
|
|
return slog.GroupValue(as...)
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Visit visits each Opt in r. Be careful with concurrent access
|
|
|
|
// to this method.
|
|
|
|
func (r *Registry) Visit(fn func(opt Opt) error) error {
|
|
|
|
if r == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
for i := range r.opts {
|
|
|
|
if err := fn(r.opts[i]); err != nil {
|
|
|
|
return err
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
}
|
2023-04-26 18:16:42 +03:00
|
|
|
return nil
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Get returns the Opt registered in r using key, or nil.
|
|
|
|
func (r *Registry) Get(key string) Opt {
|
|
|
|
r.mu.Lock()
|
|
|
|
defer r.mu.Unlock()
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
for _, opt := range r.opts {
|
|
|
|
if opt.Key() == key {
|
|
|
|
return opt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Keys returns the keys of each Opt in r.
|
|
|
|
func (r *Registry) Keys() []string {
|
|
|
|
r.mu.Lock()
|
|
|
|
defer r.mu.Unlock()
|
|
|
|
|
|
|
|
keys := make([]string, len(r.opts))
|
|
|
|
for i := range r.opts {
|
|
|
|
keys[i] = r.opts[i].Key()
|
|
|
|
}
|
|
|
|
return keys
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Opts returns a new slice containing each Opt registered with r.
|
|
|
|
func (r *Registry) Opts() []Opt {
|
|
|
|
r.mu.Lock()
|
|
|
|
defer r.mu.Unlock()
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
opts := make([]Opt, len(r.opts))
|
|
|
|
copy(opts, r.opts)
|
|
|
|
return opts
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Process implements options.Processor. It processes arg options, returning a
|
|
|
|
// new Options. Process should be invoked after the Options has been loaded from
|
|
|
|
// config, but before it is used by the program. Process iterates over the
|
|
|
|
// registered Opts, and invokes Process for each Opt that implements Processor.
|
|
|
|
// This facilitates munging of underlying values, e.g. for options.Duration, a
|
|
|
|
// string "1m30s" is converted to a time.Duration.
|
2023-04-30 17:18:56 +03:00
|
|
|
func (r *Registry) Process(o Options) (Options, error) {
|
|
|
|
return process(o, r.Opts())
|
2023-04-26 18:16:42 +03:00
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
func process(options Options, opts []Opt) (Options, error) {
|
|
|
|
if options == nil {
|
|
|
|
return nil, nil //nolint:nilnil
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
o2 := Options{}
|
|
|
|
for _, opt := range opts {
|
|
|
|
if v, ok := options[opt.Key()]; ok {
|
|
|
|
o2[opt.Key()] = v
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
var err error
|
|
|
|
for _, o := range opts {
|
|
|
|
if n, ok := o.(Processor); ok {
|
|
|
|
if o2, err = n.Process(o2); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
return o2, nil
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Options is a map of Opt.Key to a value.
|
|
|
|
type Options map[string]any
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Clone clones o.
|
|
|
|
func (o Options) Clone() Options {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
o2 := Options{}
|
|
|
|
for k, v := range o {
|
|
|
|
o2[k] = v
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
return o2
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Keys returns the sorted set of keys in o.
|
|
|
|
func (o Options) Keys() []string {
|
|
|
|
keys := lo.Keys(o)
|
|
|
|
slices.Sort(keys)
|
|
|
|
return keys
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// IsSet returns true if opt is set on o.
|
|
|
|
func (o Options) IsSet(opt Opt) bool {
|
|
|
|
_, ok := o[opt.Key()]
|
|
|
|
return ok
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-30 17:18:56 +03:00
|
|
|
// LogValue implements slog.LogValuer.
|
|
|
|
func (o Options) LogValue() slog.Value {
|
|
|
|
if o == nil {
|
|
|
|
return slog.Value{}
|
|
|
|
}
|
|
|
|
|
|
|
|
attrs := make([]slog.Attr, 0, len(o))
|
|
|
|
for k, v := range o {
|
|
|
|
switch v := v.(type) {
|
|
|
|
case int:
|
|
|
|
attrs = append(attrs, slog.Int(k, v))
|
|
|
|
case string:
|
|
|
|
attrs = append(attrs, slog.String(k, v))
|
|
|
|
case bool:
|
|
|
|
attrs = append(attrs, slog.Bool(k, v))
|
|
|
|
case time.Duration:
|
|
|
|
attrs = append(attrs, slog.Duration(k, v))
|
|
|
|
default:
|
|
|
|
attrs = append(attrs, slog.Any(k, v))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return slog.GroupValue(attrs...)
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Merge overlays each of overlays onto base, returning a new Options.
|
|
|
|
func Merge(base Options, overlays ...Options) Options {
|
|
|
|
o := base.Clone()
|
|
|
|
for _, overlay := range overlays {
|
|
|
|
for k, v := range overlay {
|
|
|
|
o[k] = v
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
}
|
2023-04-26 18:16:42 +03:00
|
|
|
return o
|
|
|
|
}
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-04-30 17:18:56 +03:00
|
|
|
// Effective returns a new Options containing the effective values
|
|
|
|
// of each Opt. That is to say, the returned Options contains either
|
|
|
|
// the actual value of each Opt in o, or the default value for that Opt,
|
|
|
|
// but it will not contain values for any Opt not in opts.
|
|
|
|
func Effective(o Options, opts ...Opt) Options {
|
|
|
|
o2 := Options{}
|
|
|
|
for _, opt := range opts {
|
|
|
|
v := opt.GetAny(o)
|
|
|
|
o2[opt.Key()] = v
|
|
|
|
}
|
|
|
|
return o2
|
|
|
|
}
|
|
|
|
|
2023-04-26 18:16:42 +03:00
|
|
|
// Processor performs processing on o.
|
|
|
|
type Processor interface {
|
|
|
|
// Process processes o. The returned Options may be a new instance,
|
|
|
|
// with mutated values.
|
|
|
|
Process(o Options) (Options, error)
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|