cfger/cfger.go
2024-10-04 17:14:12 +01:00

102 lines
2.5 KiB
Go

package cfger
import (
"errors"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
// FatalErrorHandler is called with a fatal error before cfger calls os.Exit.
// This is to allow fatal errors to be processed correctly depending on the
// application itself.
var FatalErrorHandler = func(err error) {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
}
type ConfigLoader struct {
rawConfigFileContents map[string]any
lastKey string
}
func New() *ConfigLoader {
return &ConfigLoader{}
}
// Load reads a YAML config file from fname and holds it in memory.
//
// If fname cannot be found, a blank config file will be loaded
// instead.
func (cl *ConfigLoader) Load(fname string) error {
cl.rawConfigFileContents = make(map[string]any)
fcont, err := os.ReadFile(fname)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
} else {
return err
}
}
if err := yaml.Unmarshal(fcont, &cl.rawConfigFileContents); err != nil {
return fmt.Errorf("cfger: unmarshal %s: %w", fname, err)
}
return nil
}
var indexedPartRegexp = regexp.MustCompile(`(?m)([a-zA-Z]+)(?:\[(\d+)\])?`)
// Get gets a given key from the currently loaded configuration.
//
// Within a key representation, a dot represents a recursion into a named
// object and square brackets represent an index in a named array. You could access the value "hello" using the key `alpha[0].beta` in the below example.
//
// alpha:
// - beta: "hello"
func (cl *ConfigLoader) Get(key string) OptionalItem {
// httpcore[2].bananas
parts := strings.Split(key, ".")
var cursor any = cl.rawConfigFileContents
for _, part := range parts {
components := indexedPartRegexp.FindStringSubmatch(part)
key := components[1]
index, _ := strconv.ParseInt(components[2], 10, 32)
isIndexed := components[2] != ""
item, found := cursor.(map[string]any)[key]
if !found {
return OptionalItem{key, nil, false}
}
if isIndexed {
arr, conversionOk := item.([]any)
if !conversionOk {
panic(fmt.Errorf("cfger: attempted to index non-indexable config item %s", key))
}
cursor = arr[index]
} else {
cursor = item
}
}
return OptionalItem{key, cursor, true}
}
// GetEnv gets a given key from the environment variables.
func (cl *ConfigLoader) GetEnv(envKey string) OptionalItem {
return GetEnv(envKey)
}
// GetEnv gets a given key from the environment variables.
func GetEnv(envKey string) OptionalItem {
ev := os.Getenv(envKey)
if ev == "" {
return OptionalItem{envKey, nil, false}
}
return OptionalItem{envKey, ev, true}
}