215 lines
5.4 KiB
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
|