0.0.0.0 Initial commit, basic core functionality

This commit is contained in:
2025-01-08 15:41:41 -05:00
commit 049927d242
8 changed files with 352 additions and 0 deletions

149
config.go Normal file
View File

@ -0,0 +1,149 @@
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/LixenWraith/tinytoml"
)
type cliArg struct {
key string
value string
}
func Load(path string, config interface{}, args []string) (bool, error) {
if config == nil {
return false, fmt.Errorf("config cannot be nil")
}
configExists := false
if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
configExists = true
data, err := os.ReadFile(path)
if err != nil {
return false, fmt.Errorf("failed to read config file: %w", err)
}
if err := tinytoml.Unmarshal(data, config); err != nil {
return false, fmt.Errorf("failed to parse config file: %w", err)
}
}
if len(args) > 0 {
overrides, err := parseArgs(args)
if err != nil {
return configExists, fmt.Errorf("failed to parse CLI args: %w", err)
}
if err := mergeConfig(config, overrides); err != nil {
return configExists, fmt.Errorf("failed to merge CLI args: %w", err)
}
}
return configExists, nil
}
func Save(path string, config interface{}) error {
v := reflect.ValueOf(config)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return fmt.Errorf("config must be a struct or pointer to struct")
}
data, err := tinytoml.Marshal(v.Interface())
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
tempFile := path + ".tmp"
if err := os.WriteFile(tempFile, data, 0644); err != nil {
return fmt.Errorf("failed to write temp config file: %w", err)
}
if err := os.Rename(tempFile, path); err != nil {
os.Remove(tempFile)
return fmt.Errorf("failed to save config file: %w", err)
}
return nil
}
func parseArgs(args []string) (map[string]interface{}, error) {
parsed := make([]cliArg, 0, len(args))
for i := 0; i < len(args); i++ {
arg := args[i]
if !strings.HasPrefix(arg, "--") {
continue
}
key := strings.TrimPrefix(arg, "--")
if i+1 >= len(args) || strings.HasPrefix(args[i+1], "--") {
parsed = append(parsed, cliArg{key: key, value: "true"})
continue
}
parsed = append(parsed, cliArg{key: key, value: args[i+1]})
i++
}
result := make(map[string]interface{})
for _, arg := range parsed {
keys := strings.Split(arg.key, ".")
current := result
for i, k := range keys[:len(keys)-1] {
if _, exists := current[k]; !exists {
current[k] = make(map[string]interface{})
}
if nested, ok := current[k].(map[string]interface{}); ok {
current = nested
} else {
return nil, fmt.Errorf("invalid nested key at %s", strings.Join(keys[:i+1], "."))
}
}
lastKey := keys[len(keys)-1]
if val, err := strconv.ParseBool(arg.value); err == nil {
current[lastKey] = val
} else if val, err := strconv.ParseInt(arg.value, 10, 64); err == nil {
current[lastKey] = val
} else if val, err := strconv.ParseFloat(arg.value, 64); err == nil {
current[lastKey] = val
} else {
current[lastKey] = arg.value
}
}
return result, nil
}
func mergeConfig(base interface{}, override map[string]interface{}) error {
baseValue := reflect.ValueOf(base)
if baseValue.Kind() != reflect.Ptr || baseValue.IsNil() {
return fmt.Errorf("base config must be a non-nil pointer")
}
data, err := json.Marshal(override)
if err != nil {
return fmt.Errorf("failed to marshal override values: %w", err)
}
if err := json.Unmarshal(data, base); err != nil {
return fmt.Errorf("failed to merge override values: %w", err)
}
return nil
}