You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ntool/nmap/setval.go

340 lines
7.5 KiB
Go

package nmap
import (
"fmt"
"git.noahlan.cn/noahlan/ntool/nstr"
"reflect"
"strconv"
"strings"
)
// SetByPath set sub-map value by key path.
// Supports dot syntax to set deep values.
//
// For example:
//
// SetByPath("name.first", "Mat")
func SetByPath(mp *map[string]any, path string, val any) error {
return SetByKeys(mp, strings.Split(path, KeySepStr), val)
}
// SetByKeys set sub-map value by path keys.
// Supports dot syntax to set deep values.
//
// For example:
//
// SetByKeys([]string{"name", "first"}, "Mat")
func SetByKeys(mp *map[string]any, keys []string, val any) (err error) {
kln := len(keys)
if kln == 0 {
return nil
}
mpv := *mp
if len(mpv) == 0 {
*mp = MakeByKeys(keys, val)
return nil
}
topK := keys[0]
if kln == 1 {
mpv[topK] = val
return nil
}
if _, ok := mpv[topK]; !ok {
mpv[topK] = MakeByKeys(keys[1:], val)
return nil
}
rv := reflect.ValueOf(mp).Elem()
return setMapByKeys(rv, keys, reflect.ValueOf(val))
}
func setMapByKeys(rv reflect.Value, keys []string, nv reflect.Value) (err error) {
if rv.Kind() != reflect.Map {
return fmt.Errorf("input parameter#rv must be a Map, but was %s", rv.Kind())
}
// If the map is nil, make a new map
if rv.IsNil() {
mapType := reflect.MapOf(rv.Type().Key(), rv.Type().Elem())
rv.Set(reflect.MakeMap(mapType))
}
var ok bool
maxI := len(keys) - 1
for i, key := range keys {
idx := -1
isMap := rv.Kind() == reflect.Map
isSlice := rv.Kind() == reflect.Slice
isLast := i == len(keys)-1
// slice index key must be ended on the keys.
// eg: "top.arr[2]" -> "arr[2]"
if pos := strings.IndexRune(key, '['); pos > 0 {
var realKey string
if realKey, idx, ok = parseArrKeyIndex(key); ok {
// update value
key = realKey
if !isMap {
err = fmt.Errorf(
"current value#%s type is %s, cannot get sub-value by key: %s",
strings.Join(keys[i:], "."),
rv.Kind(),
key,
)
break
}
rftK := reflect.ValueOf(key)
tmpV := rv.MapIndex(rftK)
if !tmpV.IsValid() {
if isLast {
sliVal := reflect.MakeSlice(reflect.SliceOf(nv.Type()), idx+1, idx+1)
sliVal.Index(idx).Set(nv)
rv.SetMapIndex(rftK, sliVal)
} else {
// deep make map by keys
newVal := MakeByKeys(keys[i+1:], nv.Interface())
mpVal := reflect.ValueOf(newVal)
sliVal := reflect.MakeSlice(reflect.SliceOf(mpVal.Type()), idx+1, idx+1)
sliVal.Index(idx).Set(mpVal)
rv.SetMapIndex(rftK, sliVal)
}
break
}
// get real type: any -> map
if tmpV.Kind() == reflect.Interface {
tmpV = tmpV.Elem()
}
if tmpV.Kind() != reflect.Slice {
err = fmt.Errorf(
"current value#%s type is %s, cannot set sub by index: %d",
strings.Join(keys[i:], "."),
tmpV.Kind(),
idx,
)
break
}
wantLen := idx + 1
sliLen := tmpV.Len()
elemTyp := tmpV.Type().Elem()
if wantLen > sliLen {
newAdd := reflect.MakeSlice(tmpV.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
tmpV = reflect.AppendSlice(tmpV, newAdd)
}
if !isLast {
if elemTyp.Kind() == reflect.Map {
err := setMapByKeys(tmpV.Index(idx), keys[i+1:], nv)
if err != nil {
return err
}
// tmpV.Index(idx).Set(elemV)
rv.SetMapIndex(rftK, tmpV)
} else {
err = fmt.Errorf(
"key %s[%d] elem must be map for set sub-value by remain path: %s",
key,
idx,
strings.Join(keys[i:], "."),
)
}
} else {
// last - set value
tmpV.Index(idx).Set(nv)
rv.SetMapIndex(rftK, tmpV)
}
break
}
}
// set value on last key
if isLast {
if isMap {
rv.SetMapIndex(reflect.ValueOf(key), nv)
break
}
if isSlice {
// key is slice index
if nstr.IsNumberStr(key) {
idx, _ = strconv.Atoi(key)
}
if idx > -1 {
wantLen := idx + 1
sliLen := rv.Len()
if wantLen > sliLen {
elemTyp := rv.Type().Elem()
newAdd := reflect.MakeSlice(rv.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
if !rv.CanAddr() {
err = fmt.Errorf("cannot set value to a cannot addr slice, key: %s", key)
break
}
rv.Set(reflect.AppendSlice(rv, newAdd))
}
rv.Index(idx).Set(nv)
} else {
err = fmt.Errorf("cannot set slice value by named key %q", key)
}
} else {
err = fmt.Errorf(
"cannot set sub-value for type %q(path %q, key %q)",
rv.Kind(),
strings.Join(keys[:i], "."),
key,
)
}
break
}
if isMap {
rftK := reflect.ValueOf(key)
if tmpV := rv.MapIndex(rftK); tmpV.IsValid() {
var isPtr bool
// get real type: any -> map
tmpV, isPtr = getRealVal(tmpV)
if tmpV.Kind() == reflect.Map {
rv = tmpV
continue
}
// sub is slice and is not ptr
if tmpV.Kind() == reflect.Slice {
if isPtr {
rv = tmpV
continue // to (E)
}
// next key is index number.
nxtKey := keys[i+1]
if nstr.IsNumberStr(nxtKey) {
idx, _ = strconv.Atoi(nxtKey)
sliLen := tmpV.Len()
wantLen := idx + 1
if wantLen > sliLen {
elemTyp := tmpV.Type().Elem()
newAdd := reflect.MakeSlice(tmpV.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
tmpV = reflect.AppendSlice(tmpV, newAdd)
}
// rv = tmpV.Index(idx) // TODO
if i+1 == maxI {
tmpV.Index(idx).Set(nv)
} else {
err := setMapByKeys(tmpV.Index(idx), keys[i+1:], nv)
if err != nil {
return err
}
}
rv.SetMapIndex(rftK, tmpV)
} else {
err = fmt.Errorf("cannot set slice value by named key %s(parent: %s)", nxtKey, key)
}
} else {
err = fmt.Errorf(
"map item type is %s(path:%q), cannot set sub-value by path %q",
tmpV.Kind(),
strings.Join(keys[0:i+1], "."),
strings.Join(keys[i+1:], "."),
)
}
} else {
// deep make map by keys
newVal := MakeByKeys(keys[i+1:], nv.Interface())
rv.SetMapIndex(rftK, reflect.ValueOf(newVal))
}
break
} else if isSlice && nstr.IsNumberStr(key) { // (E). slice from ptr slice
idx, _ = strconv.Atoi(key)
sliLen := rv.Len()
wantLen := idx + 1
if wantLen > sliLen {
elemTyp := rv.Type().Elem()
newAdd := reflect.MakeSlice(rv.Type(), 0, wantLen-sliLen)
for i := 0; i < wantLen-sliLen; i++ {
newAdd = reflect.Append(newAdd, reflect.New(elemTyp).Elem())
}
rv = reflect.AppendSlice(rv, newAdd)
}
rv = rv.Index(idx)
} else {
err = fmt.Errorf(
"map item type is %s, cannot set sub-value by path %q",
rv.Kind(),
strings.Join(keys[i:], "."),
)
}
}
return
}
func getRealVal(rv reflect.Value) (reflect.Value, bool) {
// get real type: any -> map
if rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
isPtr := false
if rv.Kind() == reflect.Ptr {
isPtr = true
rv = rv.Elem()
}
return rv, isPtr
}
// "arr[2]" => "arr", 2, true
func parseArrKeyIndex(key string) (string, int, bool) {
pos := strings.IndexRune(key, '[')
if pos < 1 || !strings.HasSuffix(key, "]") {
return key, 0, false
}
var idx int
var err error
idxStr := key[pos+1 : len(key)-1]
if idxStr != "" {
idx, err = strconv.Atoi(idxStr)
if err != nil {
return key, 0, false
}
}
key = key[:pos]
return key, idx, true
}