master
go 172 lines 3.88 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package stm
4
5 import (
6 "fmt"
7 "log"
8 "reflect"
9 "strconv"
10 "strings"
11 )
12
13 const (
14 fieldTagName = "stm"
15 structKey = "STMKey"
16 )
17
18 type (
19 Value interface {
20 WriteTo(rv map[string]int64, key string, mul, div int)
21 }
22 )
23
24 // ToMap converts struct to a map[string]int64 based on 'stm' tags
25 func ToMap(s ...any) map[string]int64 {
26 rv := map[string]int64{}
27 for _, v := range s {
28 value := reflect.Indirect(reflect.ValueOf(v))
29 toMap(value, rv, "", 1, 1)
30 }
31 return rv
32 }
33
34 func toMap(value reflect.Value, rv map[string]int64, key string, mul, div int) {
35 if !value.IsValid() {
36 log.Panicf("value is not valid key=%s", key)
37 }
38 if value.CanInterface() {
39 val, ok := value.Interface().(Value)
40 if ok {
41 val.WriteTo(rv, key, mul, div)
42 return
43 }
44 }
45 switch value.Kind() {
46 case reflect.Pointer:
47 convertPtr(value, rv, key, mul, div)
48 case reflect.Struct:
49 convertStruct(value, rv, key)
50 case reflect.Array, reflect.Slice:
51 convertArraySlice(value, rv, key, mul, div)
52 case reflect.Map:
53 convertMap(value, rv, key, mul, div)
54 case reflect.Bool:
55 convertBool(value, rv, key)
56 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
57 convertInteger(value, rv, key, mul, div)
58 case reflect.Float32, reflect.Float64:
59 convertFloat(value, rv, key, mul, div)
60 case reflect.Interface:
61 convertInterface(value, rv, key, mul, div)
62 default:
63 log.Panicf("unsupported data type: %v", value.Kind())
64 }
65 }
66
67 func convertPtr(value reflect.Value, rv map[string]int64, key string, mul, div int) {
68 if !value.IsNil() {
69 toMap(value.Elem(), rv, key, mul, div)
70 }
71 }
72
73 func convertStruct(value reflect.Value, rv map[string]int64, key string) {
74 t := value.Type()
75 k := value.FieldByName(structKey)
76 if k.Kind() == reflect.String {
77 key = joinPrefix(key, k.String())
78 }
79 for i := 0; i < t.NumField(); i++ {
80 ft := t.Field(i)
81 tag, ok := ft.Tag.Lookup(fieldTagName)
82 if !ok || ft.Name == structKey {
83 continue
84 }
85 value := value.Field(i)
86 prefix, mul, div := parseTag(tag)
87 toMap(value, rv, joinPrefix(key, prefix), mul, div)
88 }
89 }
90
91 func convertMap(value reflect.Value, rv map[string]int64, key string, mul, div int) {
92 if value.IsNil() {
93 log.Panicf("value is nil key=%s", key)
94 }
95 for _, k := range value.MapKeys() {
96 toMap(value.MapIndex(k), rv, joinPrefix(key, k.String()), mul, div)
97 }
98 }
99
100 func convertArraySlice(value reflect.Value, rv map[string]int64, key string, mul, div int) {
101 for i := 0; i < value.Len(); i++ {
102 toMap(value.Index(i), rv, key, mul, div)
103 }
104 }
105
106 func convertBool(value reflect.Value, rv map[string]int64, key string) {
107 if _, ok := rv[key]; ok {
108 log.Panic("duplicate key: ", key)
109 }
110 if value.Bool() {
111 rv[key] = 1
112 } else {
113 rv[key] = 0
114 }
115 }
116
117 func convertInteger(value reflect.Value, rv map[string]int64, key string, mul, div int) {
118 if _, ok := rv[key]; ok {
119 log.Panic("duplicate key: ", key)
120 }
121 intVal := value.Int()
122 rv[key] = intVal * int64(mul) / int64(div)
123 }
124
125 func convertFloat(value reflect.Value, rv map[string]int64, key string, mul, div int) {
126 if _, ok := rv[key]; ok {
127 log.Panic("duplicate key: ", key)
128 }
129 floatVal := value.Float()
130 rv[key] = int64(floatVal * float64(mul) / float64(div))
131 }
132
133 func convertInterface(value reflect.Value, rv map[string]int64, key string, mul, div int) {
134 fv := reflect.ValueOf(value.Interface())
135 toMap(fv, rv, key, mul, div)
136 }
137
138 func joinPrefix(prefix, key string) string {
139 if prefix == "" {
140 return key
141 }
142 if key == "" {
143 return prefix
144 }
145 return prefix + "_" + key
146 }
147
148 func parseTag(tag string) (prefix string, mul int, div int) {
149 tokens := strings.Split(tag, ",")
150 mul = 1
151 div = 1
152 var err error
153 switch len(tokens) {
154 case 3:
155 div, err = strconv.Atoi(tokens[2])
156 if err != nil {
157 log.Panic(err)
158 }
159 fallthrough
160 case 2:
161 mul, err = strconv.Atoi(tokens[1])
162 if err != nil {
163 log.Panic(err)
164 }
165 fallthrough
166 case 1:
167 prefix = tokens[0]
168 default:
169 log.Panic(fmt.Errorf("invalid tag format: %s", tag))
170 }
171 return
172 }