2018-05-21 15:46:39 +02:00
|
|
|
// Package configmap provides an abstraction for reading and writing config
|
|
|
|
package configmap
|
|
|
|
|
2021-03-10 12:58:23 +01:00
|
|
|
import (
|
2021-04-04 13:42:18 +02:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
2021-03-10 12:58:23 +01:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2021-04-04 13:42:18 +02:00
|
|
|
"unicode"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2021-03-10 12:58:23 +01:00
|
|
|
)
|
|
|
|
|
2021-04-04 13:41:36 +02:00
|
|
|
// Priority of getters
|
|
|
|
type Priority int8
|
|
|
|
|
|
|
|
// Priority levels for AddGetter
|
|
|
|
const (
|
|
|
|
PriorityNormal Priority = iota
|
|
|
|
PriorityConfig // use for reading from the config
|
|
|
|
PriorityDefault // use for default values
|
|
|
|
PriorityMax
|
|
|
|
)
|
|
|
|
|
2018-05-21 15:46:39 +02:00
|
|
|
// Getter provides an interface to get config items
|
|
|
|
type Getter interface {
|
|
|
|
// Get should get an item with the key passed in and return
|
|
|
|
// the value. If the item is found then it should return true,
|
|
|
|
// otherwise false.
|
|
|
|
Get(key string) (value string, ok bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setter provides an interface to set config items
|
|
|
|
type Setter interface {
|
|
|
|
// Set should set an item into persistent config store.
|
|
|
|
Set(key, value string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mapper provides an interface to read and write config
|
|
|
|
type Mapper interface {
|
|
|
|
Getter
|
|
|
|
Setter
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map provides a wrapper around multiple Setter and
|
|
|
|
// Getter interfaces.
|
|
|
|
type Map struct {
|
2021-04-04 13:41:36 +02:00
|
|
|
setters []Setter
|
|
|
|
getters []getprio
|
|
|
|
}
|
|
|
|
|
|
|
|
type getprio struct {
|
|
|
|
getter Getter
|
|
|
|
priority Priority
|
2018-05-21 15:46:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns an empty Map
|
|
|
|
func New() *Map {
|
|
|
|
return &Map{}
|
|
|
|
}
|
|
|
|
|
2021-04-04 13:41:36 +02:00
|
|
|
// AddGetter appends a getter onto the end of the getters in priority order
|
|
|
|
func (c *Map) AddGetter(getter Getter, priority Priority) *Map {
|
|
|
|
c.getters = append(c.getters, getprio{getter, priority})
|
|
|
|
sort.SliceStable(c.getters, func(i, j int) bool {
|
|
|
|
return c.getters[i].priority < c.getters[j].priority
|
|
|
|
})
|
2018-05-21 15:46:39 +02:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddSetter appends a setter onto the end of the setters
|
|
|
|
func (c *Map) AddSetter(setter Setter) *Map {
|
|
|
|
c.setters = append(c.setters, setter)
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2021-04-03 16:31:13 +02:00
|
|
|
// ClearSetters removes all the setters set so far
|
|
|
|
func (c *Map) ClearSetters() *Map {
|
|
|
|
c.setters = nil
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2021-04-04 13:41:36 +02:00
|
|
|
// ClearGetters removes all the getters with the priority given
|
|
|
|
func (c *Map) ClearGetters(priority Priority) *Map {
|
|
|
|
getters := c.getters[:0]
|
|
|
|
for _, item := range c.getters {
|
|
|
|
if item.priority != priority {
|
|
|
|
getters = append(getters, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.getters = getters
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPriority gets an item with the key passed in and return the
|
|
|
|
// value from the first getter to return a result with priority <=
|
|
|
|
// maxPriority. If the item is found then it returns true, otherwise
|
|
|
|
// false.
|
|
|
|
func (c *Map) GetPriority(key string, maxPriority Priority) (value string, ok bool) {
|
|
|
|
for _, item := range c.getters {
|
|
|
|
if item.priority > maxPriority {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
value, ok = item.getter.Get(key)
|
2018-05-21 15:46:39 +02:00
|
|
|
if ok {
|
|
|
|
return value, ok
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2021-03-10 13:17:08 +01:00
|
|
|
// Get gets an item with the key passed in and return the value from
|
|
|
|
// the first getter. If the item is found then it returns true,
|
|
|
|
// otherwise false.
|
|
|
|
func (c *Map) Get(key string) (value string, ok bool) {
|
2021-04-04 13:41:36 +02:00
|
|
|
return c.GetPriority(key, PriorityMax)
|
2021-03-10 13:17:08 +01:00
|
|
|
}
|
|
|
|
|
2018-05-21 15:46:39 +02:00
|
|
|
// Set sets an item into all the stored setters.
|
|
|
|
func (c *Map) Set(key, value string) {
|
|
|
|
for _, do := range c.setters {
|
|
|
|
do.Set(key, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simple is a simple Mapper for testing
|
|
|
|
type Simple map[string]string
|
|
|
|
|
|
|
|
// Get the value
|
|
|
|
func (c Simple) Get(key string) (value string, ok bool) {
|
|
|
|
value, ok = c[key]
|
|
|
|
return value, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the value
|
|
|
|
func (c Simple) Set(key, value string) {
|
|
|
|
c[key] = value
|
|
|
|
}
|
2021-03-10 12:58:23 +01:00
|
|
|
|
|
|
|
// String the map value the same way the config parser does, but with
|
|
|
|
// sorted keys for reproducability.
|
|
|
|
func (c Simple) String() string {
|
|
|
|
var ks = make([]string, 0, len(c))
|
|
|
|
for k := range c {
|
|
|
|
ks = append(ks, k)
|
|
|
|
}
|
|
|
|
sort.Strings(ks)
|
|
|
|
var out strings.Builder
|
|
|
|
for _, k := range ks {
|
|
|
|
if out.Len() > 0 {
|
|
|
|
out.WriteRune(',')
|
|
|
|
}
|
|
|
|
out.WriteString(k)
|
|
|
|
out.WriteRune('=')
|
|
|
|
out.WriteRune('\'')
|
|
|
|
for _, ch := range c[k] {
|
|
|
|
out.WriteRune(ch)
|
|
|
|
// Escape ' as ''
|
|
|
|
if ch == '\'' {
|
|
|
|
out.WriteRune(ch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out.WriteRune('\'')
|
|
|
|
}
|
|
|
|
return out.String()
|
|
|
|
}
|
2021-04-04 13:42:18 +02:00
|
|
|
|
|
|
|
// Encode from c into a string suitable for putting on the command line
|
|
|
|
func (c Simple) Encode() (string, error) {
|
|
|
|
if len(c) == 0 {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
buf, err := json.Marshal(c)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "encode simple map")
|
|
|
|
}
|
|
|
|
return base64.RawStdEncoding.EncodeToString(buf), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode an Encode~d string in into c
|
|
|
|
func (c Simple) Decode(in string) error {
|
|
|
|
// Remove all whitespace from the input string
|
|
|
|
in = strings.Map(func(r rune) rune {
|
|
|
|
if unicode.IsSpace(r) {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}, in)
|
|
|
|
if len(in) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
decodedM, err := base64.RawStdEncoding.DecodeString(in)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "decode simple map")
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(decodedM, &c)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "parse simple map")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|