mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 02:34:42 +00:00
163 lines
3.6 KiB
Go
163 lines
3.6 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"embed"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/knadh/koanf/maps"
|
|
"github.com/knadh/koanf/v2"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const EnvPrefix = "CLOUD_GAME_"
|
|
|
|
var (
|
|
//go:embed config.yaml
|
|
conf embed.FS
|
|
)
|
|
|
|
type Kv = map[string]any
|
|
type Bytes []byte
|
|
|
|
func (b *Bytes) ReadBytes() ([]byte, error) { return *b, nil }
|
|
func (b *Bytes) Read() (Kv, error) { return nil, nil }
|
|
|
|
type File string
|
|
|
|
func (f *File) ReadBytes() ([]byte, error) { return os.ReadFile(string(*f)) }
|
|
func (f *File) Read() (Kv, error) { return nil, nil }
|
|
|
|
type YAML struct{}
|
|
|
|
func (p *YAML) Marshal(Kv) ([]byte, error) { return nil, nil }
|
|
func (p *YAML) Unmarshal(b []byte) (Kv, error) {
|
|
var out Kv
|
|
klw := keysToLower(b)
|
|
decoder := yaml.NewDecoder(bytes.NewReader(klw))
|
|
if err := decoder.Decode(&out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// keysToLower iterates YAML bytes and tries to lower the keys.
|
|
// Used for merging with environment vars which are lowered as well.
|
|
func keysToLower(in []byte) []byte {
|
|
l, r, ignore := 0, 0, false
|
|
for i, b := range in {
|
|
switch b {
|
|
case '#': // skip comments
|
|
ignore = true
|
|
case ':': // lower left chunk before the next : symbol
|
|
if ignore {
|
|
continue
|
|
}
|
|
r = i
|
|
ignore = true
|
|
for j := l; j <= r; j++ {
|
|
c := in[j]
|
|
// we skip the line with the first explicit " string symbol
|
|
if c == '"' {
|
|
break
|
|
}
|
|
if 'A' <= c && c <= 'Z' {
|
|
in[j] += 'a' - 'A'
|
|
}
|
|
}
|
|
case '\n':
|
|
l = i
|
|
ignore = false
|
|
}
|
|
}
|
|
return in
|
|
}
|
|
|
|
type Env string
|
|
|
|
func (e *Env) ReadBytes() ([]byte, error) { return nil, nil }
|
|
func (e *Env) Read() (Kv, error) {
|
|
var keys []string
|
|
for _, k := range os.Environ() {
|
|
if strings.HasPrefix(k, string(*e)) {
|
|
keys = append(keys, k)
|
|
}
|
|
}
|
|
mp := make(Kv)
|
|
for _, k := range keys {
|
|
parts := strings.SplitN(k, "=", 2)
|
|
if parts == nil {
|
|
continue
|
|
}
|
|
n := strings.ToLower(strings.TrimPrefix(parts[0], string(*e)))
|
|
if n == "" {
|
|
continue
|
|
}
|
|
// convert VAR_VAR to VAR.VAR or if we need to preserve _
|
|
// i.e. VAR_VAR__KEY_HAS_SLASHES to VAR.VAR.KEY_HAS_SLASHES
|
|
// with the result: VAR: { VAR: { KEY_HAS_SLASHES: '' } } }
|
|
x := strings.Index(n, "__")
|
|
var key string
|
|
if x == -1 {
|
|
key = strings.Replace(n, "_", ".", -1)
|
|
} else {
|
|
key = strings.Replace(n[:x+1], "_", ".", -1) + n[x+2:]
|
|
}
|
|
if len(parts) > 1 {
|
|
mp[key] = parts[1]
|
|
}
|
|
}
|
|
return maps.Unflatten(mp, "."), nil
|
|
}
|
|
|
|
// LoadConfig loads a configuration file into the given struct.
|
|
// The path param specifies a custom path to the configuration file.
|
|
// Reads and puts environment variables with the prefix CLOUD_GAME_.
|
|
func LoadConfig(config any, path string) (loaded []string, err error) {
|
|
dirs := []string{".", "configs", "../../../configs"}
|
|
if path != "" {
|
|
dirs = append([]string{path}, dirs...)
|
|
}
|
|
|
|
homeDir := ""
|
|
if home, err := os.UserHomeDir(); err == nil {
|
|
homeDir = home + "/.cr"
|
|
dirs = append(dirs, homeDir)
|
|
}
|
|
|
|
k := koanf.New("_") // move to global scope if configs become dynamic
|
|
defer k.Delete("")
|
|
data, err := conf.ReadFile("config.yaml")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conf := Bytes(data)
|
|
if err := k.Load(&conf, &YAML{}); err != nil {
|
|
return nil, err
|
|
}
|
|
loaded = append(loaded, "default")
|
|
|
|
for _, dir := range dirs {
|
|
path := filepath.Join(filepath.Clean(dir), "config.yaml")
|
|
f := File(path)
|
|
if _, err := os.Stat(string(f)); !os.IsNotExist(err) {
|
|
if err := k.Load(&f, &YAML{}); err != nil {
|
|
return loaded, err
|
|
}
|
|
loaded = append(loaded, path)
|
|
}
|
|
}
|
|
|
|
env := Env(EnvPrefix)
|
|
if err := k.Load(&env, nil); err != nil {
|
|
return loaded, err
|
|
}
|
|
|
|
if err := k.Unmarshal("", config); err != nil {
|
|
return loaded, err
|
|
}
|
|
|
|
return loaded, nil
|
|
}
|