mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-12 00:47:26 +02:00
156 lines
3.3 KiB
Go
156 lines
3.3 KiB
Go
package tools
|
|
|
|
import (
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ReflectConfigFields builds []ConfigField from a struct using yaml/iky tags.
|
|
// iky tag format: iky:"desc=...;default=...;required=true;options=a|b|c"
|
|
func ReflectConfigFields(cfg any) []ConfigField {
|
|
v := reflect.ValueOf(cfg)
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
t := v.Type()
|
|
|
|
var fields []ConfigField
|
|
for i := range t.NumField() {
|
|
sf := t.Field(i)
|
|
fv := v.Field(i)
|
|
|
|
yamlKey := sf.Tag.Get("yaml")
|
|
if yamlKey == "" || yamlKey == "-" {
|
|
continue
|
|
}
|
|
yamlKey = strings.SplitN(yamlKey, ",", 2)[0]
|
|
|
|
meta := parseIkyTag(sf.Tag.Get("iky"))
|
|
|
|
fieldType := goKindToString(sf.Type.Kind())
|
|
if len(meta.options) > 0 {
|
|
fieldType = "enum"
|
|
}
|
|
|
|
fields = append(fields, ConfigField{
|
|
Name: yamlKey,
|
|
Type: fieldType,
|
|
Required: meta.required,
|
|
Description: meta.desc,
|
|
Default: parseTypedDefault(meta.rawDefault, sf.Type.Kind()),
|
|
Value: fv.Interface(),
|
|
Options: meta.options,
|
|
})
|
|
}
|
|
return fields
|
|
}
|
|
|
|
// ApplyDefaults sets each field to its iky default if the field is zero.
|
|
func ApplyDefaults(cfg any) {
|
|
v := reflect.ValueOf(cfg)
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
t := v.Type()
|
|
|
|
for i := range t.NumField() {
|
|
sf := t.Field(i)
|
|
fv := v.Field(i)
|
|
|
|
if !fv.CanSet() {
|
|
continue
|
|
}
|
|
meta := parseIkyTag(sf.Tag.Get("iky"))
|
|
if meta.rawDefault == "" || !fv.IsZero() {
|
|
continue
|
|
}
|
|
applyDefault(fv, sf.Type.Kind(), meta.rawDefault)
|
|
}
|
|
}
|
|
|
|
type ikyMeta struct {
|
|
desc string
|
|
rawDefault string
|
|
required bool
|
|
options []string
|
|
}
|
|
|
|
func parseIkyTag(tag string) ikyMeta {
|
|
var m ikyMeta
|
|
for _, part := range strings.Split(tag, ";") {
|
|
k, v, ok := strings.Cut(strings.TrimSpace(part), "=")
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch strings.TrimSpace(k) {
|
|
case "desc":
|
|
m.desc = strings.TrimSpace(v)
|
|
case "default":
|
|
m.rawDefault = strings.TrimSpace(v)
|
|
case "required":
|
|
m.required = strings.TrimSpace(v) == "true"
|
|
case "options":
|
|
for _, opt := range strings.Split(v, "|") {
|
|
if o := strings.TrimSpace(opt); o != "" {
|
|
m.options = append(m.options, o)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func goKindToString(k reflect.Kind) string {
|
|
switch k {
|
|
case reflect.String:
|
|
return "string"
|
|
case reflect.Bool:
|
|
return "bool"
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return "int"
|
|
case reflect.Float32, reflect.Float64:
|
|
return "float"
|
|
default:
|
|
return k.String()
|
|
}
|
|
}
|
|
|
|
func parseTypedDefault(raw string, k reflect.Kind) any {
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
switch k {
|
|
case reflect.Bool:
|
|
b, _ := strconv.ParseBool(raw)
|
|
return b
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
n, _ := strconv.ParseInt(raw, 10, 64)
|
|
return int(n)
|
|
case reflect.Float32, reflect.Float64:
|
|
f, _ := strconv.ParseFloat(raw, 64)
|
|
return f
|
|
default:
|
|
return raw
|
|
}
|
|
}
|
|
|
|
func applyDefault(fv reflect.Value, k reflect.Kind, raw string) {
|
|
switch k {
|
|
case reflect.String:
|
|
fv.SetString(raw)
|
|
case reflect.Bool:
|
|
if b, err := strconv.ParseBool(raw); err == nil {
|
|
fv.SetBool(b)
|
|
}
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if n, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
|
fv.SetInt(n)
|
|
}
|
|
case reflect.Float32, reflect.Float64:
|
|
if f, err := strconv.ParseFloat(raw, 64); err == nil {
|
|
fv.SetFloat(f)
|
|
}
|
|
}
|
|
}
|