// SPDX-License-Identifier: GPL-3.0-or-later package stm import ( "fmt" "log" "reflect" "strconv" "strings" ) const ( fieldTagName = "stm" structKey = "STMKey" ) type ( Value interface { WriteTo(rv map[string]int64, key string, mul, div int) } ) // ToMap converts struct to a map[string]int64 based on 'stm' tags func ToMap(s ...interface{}) map[string]int64 { rv := map[string]int64{} for _, v := range s { value := reflect.Indirect(reflect.ValueOf(v)) toMap(value, rv, "", 1, 1) } return rv } func toMap(value reflect.Value, rv map[string]int64, key string, mul, div int) { if !value.IsValid() { log.Panicf("value is not valid key=%s", key) } if value.CanInterface() { val, ok := value.Interface().(Value) if ok { val.WriteTo(rv, key, mul, div) return } } switch value.Kind() { case reflect.Ptr: convertPtr(value, rv, key, mul, div) case reflect.Struct: convertStruct(value, rv, key) case reflect.Array, reflect.Slice: convertArraySlice(value, rv, key, mul, div) case reflect.Map: convertMap(value, rv, key, mul, div) case reflect.Bool: convertBool(value, rv, key) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: convertInteger(value, rv, key, mul, div) case reflect.Float32, reflect.Float64: convertFloat(value, rv, key, mul, div) case reflect.Interface: convertInterface(value, rv, key, mul, div) default: log.Panicf("unsupported data type: %v", value.Kind()) } } func convertPtr(value reflect.Value, rv map[string]int64, key string, mul, div int) { if !value.IsNil() { toMap(value.Elem(), rv, key, mul, div) } } func convertStruct(value reflect.Value, rv map[string]int64, key string) { t := value.Type() k := value.FieldByName(structKey) if k.Kind() == reflect.String { key = joinPrefix(key, k.String()) } for i := 0; i < t.NumField(); i++ { ft := t.Field(i) tag, ok := ft.Tag.Lookup(fieldTagName) if !ok || ft.Name == structKey { continue } value := value.Field(i) prefix, mul, div := parseTag(tag) toMap(value, rv, joinPrefix(key, prefix), mul, div) } } func convertMap(value reflect.Value, rv map[string]int64, key string, mul, div int) { if value.IsNil() { log.Panicf("value is nil key=%s", key) } for _, k := range value.MapKeys() { toMap(value.MapIndex(k), rv, joinPrefix(key, k.String()), mul, div) } } func convertArraySlice(value reflect.Value, rv map[string]int64, key string, mul, div int) { for i := 0; i < value.Len(); i++ { toMap(value.Index(i), rv, key, mul, div) } } func convertBool(value reflect.Value, rv map[string]int64, key string) { if _, ok := rv[key]; ok { log.Panic("duplicate key: ", key) } if value.Bool() { rv[key] = 1 } else { rv[key] = 0 } } func convertInteger(value reflect.Value, rv map[string]int64, key string, mul, div int) { if _, ok := rv[key]; ok { log.Panic("duplicate key: ", key) } intVal := value.Int() rv[key] = intVal * int64(mul) / int64(div) } func convertFloat(value reflect.Value, rv map[string]int64, key string, mul, div int) { if _, ok := rv[key]; ok { log.Panic("duplicate key: ", key) } floatVal := value.Float() rv[key] = int64(floatVal * float64(mul) / float64(div)) } func convertInterface(value reflect.Value, rv map[string]int64, key string, mul, div int) { fv := reflect.ValueOf(value.Interface()) toMap(fv, rv, key, mul, div) } func joinPrefix(prefix, key string) string { if prefix == "" { return key } if key == "" { return prefix } return prefix + "_" + key } func parseTag(tag string) (prefix string, mul int, div int) { tokens := strings.Split(tag, ",") mul = 1 div = 1 var err error switch len(tokens) { case 3: div, err = strconv.Atoi(tokens[2]) if err != nil { log.Panic(err) } fallthrough case 2: mul, err = strconv.Atoi(tokens[1]) if err != nil { log.Panic(err) } fallthrough case 1: prefix = tokens[0] default: log.Panic(fmt.Errorf("invalid tag format: %s", tag)) } return }