mirror of
https://git.sr.ht/~rjarry/aerc
synced 2026-03-02 18:23:33 +01:00
This is the result of the following command: go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./... Signed-off-by: Robin Jarry <robin@jarry.cc>
234 lines
5.6 KiB
Go
234 lines
5.6 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib/templates"
|
|
"github.com/emersion/go-message/mail"
|
|
"github.com/go-ini/ini"
|
|
)
|
|
|
|
func MapToStruct(s *ini.Section, v any, useDefaults bool) error {
|
|
typ := reflect.TypeOf(v)
|
|
val := reflect.ValueOf(v)
|
|
if typ.Kind() == reflect.Ptr {
|
|
typ = typ.Elem()
|
|
val = val.Elem()
|
|
} else {
|
|
panic("MapToStruct requires a pointer")
|
|
}
|
|
if typ.Kind() != reflect.Struct {
|
|
panic("MapToStruct requires a pointer to a struct")
|
|
}
|
|
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
fieldVal := val.Field(i)
|
|
fieldType := typ.Field(i)
|
|
|
|
name := fieldType.Tag.Get("ini")
|
|
if name == "" || name == "-" {
|
|
continue
|
|
}
|
|
key, err := s.GetKey(name)
|
|
if err != nil {
|
|
defValue, found := fieldType.Tag.Lookup("default")
|
|
if useDefaults && found {
|
|
key, _ = s.NewKey(name, defValue)
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
err = setField(s, key, reflect.ValueOf(v), fieldVal, fieldType)
|
|
if err != nil {
|
|
return fmt.Errorf("[%s].%s: %w", s.Name(), name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setField(
|
|
s *ini.Section, key *ini.Key, struc reflect.Value,
|
|
fieldVal reflect.Value, fieldType reflect.StructField,
|
|
) error {
|
|
var methodValue reflect.Value
|
|
method := getParseMethod(s, key, struc, fieldType)
|
|
if method.IsValid() {
|
|
in := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(key)}
|
|
out := method.Call(in)
|
|
err, _ := out[1].Interface().(error)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
methodValue = out[0]
|
|
}
|
|
|
|
ft := fieldType.Type
|
|
|
|
switch ft.Kind() {
|
|
case reflect.String:
|
|
if method.IsValid() {
|
|
fieldVal.SetString(methodValue.String())
|
|
} else {
|
|
fieldVal.SetString(key.String())
|
|
}
|
|
case reflect.Bool:
|
|
if method.IsValid() {
|
|
fieldVal.SetBool(methodValue.Bool())
|
|
} else {
|
|
boolVal, err := key.Bool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.SetBool(boolVal)
|
|
}
|
|
case reflect.Int32:
|
|
// impossible to differentiate rune from int32, they are aliases
|
|
// this is an ugly hack but there is no alternative...
|
|
if fieldType.Tag.Get("type") == "rune" {
|
|
if method.IsValid() {
|
|
fieldVal.Set(methodValue)
|
|
} else {
|
|
runes := []rune(key.String())
|
|
if len(runes) != 1 {
|
|
return errors.New("value must be 1 character long")
|
|
}
|
|
fieldVal.Set(reflect.ValueOf(runes[0]))
|
|
}
|
|
return nil
|
|
}
|
|
fallthrough
|
|
case reflect.Int64:
|
|
// ParseDuration will not return err for `0`, so check the type name
|
|
if ft.PkgPath() == "time" && ft.Name() == "Duration" {
|
|
durationVal, err := key.Duration()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.Set(reflect.ValueOf(durationVal))
|
|
return nil
|
|
}
|
|
fallthrough
|
|
case reflect.Int, reflect.Int8, reflect.Int16:
|
|
if method.IsValid() {
|
|
fieldVal.SetInt(methodValue.Int())
|
|
} else {
|
|
intVal, err := key.Int64()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.SetInt(intVal)
|
|
}
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if method.IsValid() {
|
|
fieldVal.SetUint(methodValue.Uint())
|
|
} else {
|
|
uintVal, err := key.Uint64()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.SetUint(uintVal)
|
|
}
|
|
case reflect.Float32, reflect.Float64:
|
|
if method.IsValid() {
|
|
fieldVal.SetFloat(methodValue.Float())
|
|
} else {
|
|
floatVal, err := key.Float64()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.SetFloat(floatVal)
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
switch {
|
|
case method.IsValid():
|
|
fieldVal.Set(methodValue)
|
|
case ft.Elem().Kind() == reflect.Ptr &&
|
|
typePath(ft.Elem().Elem()) == "net/mail.Address":
|
|
addrs, err := mail.ParseAddressList(key.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.Set(reflect.ValueOf(addrs))
|
|
case ft.Elem().Kind() == reflect.String:
|
|
delim := fieldType.Tag.Get("delim")
|
|
fieldVal.Set(reflect.ValueOf(key.Strings(delim)))
|
|
default:
|
|
panic(fmt.Sprintf("unsupported type []%s", typePath(ft.Elem())))
|
|
}
|
|
case reflect.Struct:
|
|
if method.IsValid() {
|
|
fieldVal.Set(methodValue)
|
|
} else {
|
|
panic(fmt.Sprintf("unsupported type %s", typePath(ft)))
|
|
}
|
|
case reflect.Ptr:
|
|
if method.IsValid() {
|
|
fieldVal.Set(methodValue)
|
|
} else {
|
|
switch typePath(ft.Elem()) {
|
|
case "net/mail.Address":
|
|
addr, err := mail.ParseAddress(key.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.Set(reflect.ValueOf(addr))
|
|
case "regexp.Regexp":
|
|
r, err := regexp.Compile(key.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.Set(reflect.ValueOf(r))
|
|
case "text/template.Template":
|
|
t, err := templates.ParseTemplate(key.String(), key.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldVal.Set(reflect.ValueOf(t))
|
|
default:
|
|
panic(fmt.Sprintf("unsupported type %s", typePath(ft)))
|
|
}
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unsupported type %s", typePath(ft)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getParseMethod(
|
|
section *ini.Section, key *ini.Key,
|
|
struc reflect.Value, typ reflect.StructField,
|
|
) reflect.Value {
|
|
methodName, found := typ.Tag.Lookup("parse")
|
|
if !found {
|
|
return reflect.Value{}
|
|
}
|
|
method := struc.MethodByName(methodName)
|
|
if !method.IsValid() {
|
|
panic(fmt.Sprintf("(*%s).%s: method not found",
|
|
struc, methodName))
|
|
}
|
|
|
|
if method.Type().NumIn() != 2 ||
|
|
method.Type().In(0) != reflect.TypeFor[*ini.Section]() ||
|
|
method.Type().In(1) != reflect.TypeFor[*ini.Key]() ||
|
|
method.Type().NumOut() != 2 {
|
|
panic(fmt.Sprintf("(*%s).%s: invalid signature, expected %s",
|
|
struc.Elem().Type().Name(), methodName,
|
|
"func(*ini.Section, *ini.Key) (any, error)"))
|
|
}
|
|
|
|
return method
|
|
}
|
|
|
|
func typePath(t reflect.Type) string {
|
|
var prefix string
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
prefix = "*"
|
|
}
|
|
return fmt.Sprintf("%s%s.%s", prefix, t.PkgPath(), t.Name())
|
|
}
|