ut/utini/load.go

215 lines
5.4 KiB
Go

package utini
import (
"errors"
"fmt"
"io"
"reflect"
"regexp"
"strconv"
"strings"
"opslag.de/schobers/ut/utio"
)
var errStructPtrExpected = errors.New(`expected pointer to struct`)
var keyValuePairRE = regexp.MustCompile(`^\s*(\w+)\s*=\s*(.*)\s*$`)
var sectionRE = regexp.MustCompile(`\[((?:\w+\.)*(\w+))\]`)
var commentRE = regexp.MustCompile(`^\s*[;#].*$`)
func appendToPrefix(prefix, name string) string {
if prefix == "" {
return name
}
return fmt.Sprintf(`%s.%s`, prefix, name)
}
// func fmtErrNoSection(name string) error { return fmt.Errorf(`no section with name %s in file`, name) }
func load(lines []string) (*File, error) {
file := &File{}
var section *Section
for _, line := range lines {
if commentRE.MatchString(line) {
continue
}
match := sectionRE.FindStringSubmatch(line)
if match != nil {
section = file.CreateIfNotExists(match[1])
continue
}
match = keyValuePairRE.FindStringSubmatch(line)
if match != nil {
if section == nil {
section = file.CreateIfNotExists("")
}
section.Properties = append(section.Properties, &Property{
Key: match[1],
Value: match[2],
})
}
}
return file, nil
}
func Load(r io.Reader) (*File, error) {
lines := utio.Lines()
if err := lines.Decode(r); err != nil {
return nil, err
}
return load(lines.Lines)
}
func LoadFile(path string) (*File, error) {
var err error
var file *File
utio.ReadFile(path, func(r io.Reader) error {
file, err = Load(r)
return err
})
return file, err
}
func loadMap(file *File, err error, v interface{}) error {
if err != nil {
return err
}
return MapFromFile(file, v)
}
func LoadMap(r io.Reader, v interface{}) error {
file, err := Load(r)
return loadMap(file, err, v)
}
func LoadMapFile(path string, v interface{}) error {
file, err := LoadFile(path)
return loadMap(file, err, v)
}
func mapFileToStruct(ptr reflect.Value, file *File, prefix string) error {
if ptr.Type().Kind() != reflect.Ptr {
return errStructPtrExpected
}
value := ptr.Elem()
if value.Type().Kind() != reflect.Struct {
return errStructPtrExpected
}
valueTyp := value.Type()
n := valueTyp.NumField()
for i := 0; i < n; i++ {
field := valueTyp.Field(i)
name := appendToPrefix(prefix, field.Name)
kind := field.Type.Kind()
fieldValue := value.FieldByName(field.Name)
switch {
case kind == reflect.Struct:
if err := mapFileToStruct(fieldValue.Addr(), file, name); err != nil {
return err
}
case kind == reflect.Bool:
if err := mapPropertyToField(file, prefix, field, fieldValue, setBoolValue); err != nil {
return err
}
case kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64:
if err := mapPropertyToField(file, prefix, field, fieldValue, setIntValue); err != nil {
return err
}
case kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64:
if err := mapPropertyToField(file, prefix, field, fieldValue, setUintValue); err != nil {
return err
}
case kind == reflect.String:
if err := mapPropertyToField(file, prefix, field, fieldValue, setStringValue); err != nil {
return err
}
}
}
return nil
}
func MapFromFile(file *File, v interface{}) error {
ptr := reflect.ValueOf(v)
return mapFileToStruct(ptr, file, "")
}
func mapPropertyToField(file *File, prefix string, field reflect.StructField, value reflect.Value, set setValueFn) error {
property := propertyValueForField(file, prefix, field)
if property == "" {
return nil
}
err := set(value, iniTag(field), property)
if err != nil {
return fmt.Errorf(`failed to map property "%s"; error: %v`, appendToPrefix(prefix, field.Name), err)
}
return nil
}
func propertyValueForField(file *File, prefix string, field reflect.StructField) string {
sectionKey := camelCaseToSnakeCase(prefix)
section := file.Sections[sectionKey]
if section == nil {
return defaultTagValue(iniTag(field))
}
key := camelCaseToSnakeCase(field.Name)
property := section.PropertyByKey(key)
if property == nil {
return defaultTagValue(iniTag(field))
}
return property.Value
}
func setBoolValue(value reflect.Value, tag string, property string) error {
states := statesTagValue(tag)
p := strings.ToLower(property)
if p == states[0] {
value.SetBool(false)
} else if p == states[1] {
value.SetBool(true)
} else {
return fmt.Errorf(`failed to convert value "%s" to its boolean value, expected either "%s" or "%s"`, property, states[0], states[1])
}
return nil
}
func setIntValue(value reflect.Value, _ string, property string) error {
i, err := strconv.ParseInt(property, 10, 64)
if err != nil {
return fmt.Errorf(`failed to convert value "%s" to an integer`, property)
}
value.SetInt(i)
return nil
}
func setStringValue(value reflect.Value, _ string, property string) error {
n := len(property)
if n > 1 {
delimiter := property[0]
if (delimiter == '"' || delimiter == '\'' || delimiter == '`') && property[n-1] == delimiter {
property = property[1 : n-1]
}
}
value.SetString(property)
return nil
}
func setUintValue(value reflect.Value, _ string, property string) error {
i, err := strconv.ParseUint(property, 10, 64)
if err != nil {
return fmt.Errorf(`failed to convert value "%s" to an unsigned integer`, property)
}
value.SetUint(i)
return nil
}
type setValueFn func(value reflect.Value, tag, property string) error