102 lines
2.5 KiB
Go
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}
|
|
}
|