1
0
Fork 0
golang-github-nicholas-fedo.../pkg/format/node.go
Daniel Baumann c0c4addb85
Adding upstream version 0.8.9.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-22 10:16:14 +02:00

390 lines
8.7 KiB
Go

package format
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
"github.com/nicholas-fedor/shoutrrr/pkg/util"
)
// NodeTokenType is used to represent the type of value that a node has for syntax highlighting.
type NodeTokenType int
const (
// UnknownToken represents all unknown/unspecified tokens.
UnknownToken NodeTokenType = iota
// NumberToken represents all numbers.
NumberToken
// StringToken represents strings and keys.
StringToken
// EnumToken represents enum values.
EnumToken
// TrueToken represent boolean true.
TrueToken
// FalseToken represent boolean false.
FalseToken
// PropToken represent a serializable struct prop.
PropToken
// ErrorToken represent a value that was not serializable or otherwise invalid.
ErrorToken
// ContainerToken is used for Array/Slice and Map tokens.
ContainerToken
)
// Constants for number bases.
const (
BaseDecimalLen = 10
BaseHexLen = 16
)
// Node is the generic config tree item.
type Node interface {
Field() *FieldInfo
TokenType() NodeTokenType
Update(tv reflect.Value)
}
// ValueNode is a Node without any child items.
type ValueNode struct {
*FieldInfo
Value string
tokenType NodeTokenType
}
// Field returns the inner FieldInfo.
func (n *ValueNode) Field() *FieldInfo {
return n.FieldInfo
}
// TokenType returns a NodeTokenType that matches the value.
func (n *ValueNode) TokenType() NodeTokenType {
return n.tokenType
}
// Update updates the value string from the provided value.
func (n *ValueNode) Update(tv reflect.Value) {
value, token := getValueNodeValue(tv, n.FieldInfo)
n.Value = value
n.tokenType = token
}
// ContainerNode is a Node with child items.
type ContainerNode struct {
*FieldInfo
Items []Node
MaxKeyLength int
}
// Field returns the inner FieldInfo.
func (n *ContainerNode) Field() *FieldInfo {
return n.FieldInfo
}
// TokenType always returns ContainerToken for ContainerNode.
func (n *ContainerNode) TokenType() NodeTokenType {
return ContainerToken
}
// Update updates the items to match the provided value.
func (n *ContainerNode) Update(tv reflect.Value) {
switch n.Type.Kind() {
case reflect.Array, reflect.Slice:
n.updateArrayNode(tv)
case reflect.Map:
n.updateMapNode(tv)
case reflect.Invalid,
reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128,
reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Pointer,
reflect.String,
reflect.Struct,
reflect.UnsafePointer:
// No-op for unsupported kinds
default:
// No-op for any remaining kinds
}
}
func (n *ContainerNode) updateArrayNode(arrayValue reflect.Value) {
itemCount := arrayValue.Len()
n.Items = make([]Node, 0, itemCount)
elemType := arrayValue.Type().Elem()
for i := range itemCount {
key := strconv.Itoa(i)
val := arrayValue.Index(i)
n.Items = append(n.Items, getValueNode(val, &FieldInfo{
Name: key,
Type: elemType,
}))
}
}
func getArrayNode(arrayValue reflect.Value, fieldInfo *FieldInfo) *ContainerNode {
node := &ContainerNode{
FieldInfo: fieldInfo,
MaxKeyLength: 0,
}
node.updateArrayNode(arrayValue)
return node
}
func sortNodeItems(nodeItems []Node) {
sort.Slice(nodeItems, func(i, j int) bool {
return nodeItems[i].Field().Name < nodeItems[j].Field().Name
})
}
func (n *ContainerNode) updateMapNode(mapValue reflect.Value) {
base := n.Base
if base == 0 {
base = BaseDecimalLen
}
elemType := mapValue.Type().Elem()
mapKeys := mapValue.MapKeys()
nodeItems := make([]Node, len(mapKeys))
maxKeyLength := 0
for i, keyVal := range mapKeys {
// The keys will always be strings
key := keyVal.String()
val := mapValue.MapIndex(keyVal)
nodeItems[i] = getValueNode(val, &FieldInfo{
Name: key,
Type: elemType,
Base: base,
})
maxKeyLength = util.Max(len(key), maxKeyLength)
}
sortNodeItems(nodeItems)
n.Items = nodeItems
n.MaxKeyLength = maxKeyLength
}
func getMapNode(mapValue reflect.Value, fieldInfo *FieldInfo) *ContainerNode {
if mapValue.Kind() == reflect.Ptr {
mapValue = mapValue.Elem()
}
node := &ContainerNode{
FieldInfo: fieldInfo,
}
node.updateMapNode(mapValue)
return node
}
func getNode(fieldVal reflect.Value, fieldInfo *FieldInfo) Node {
switch fieldInfo.Type.Kind() {
case reflect.Array, reflect.Slice:
return getArrayNode(fieldVal, fieldInfo)
case reflect.Map:
return getMapNode(fieldVal, fieldInfo)
case reflect.Invalid,
reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128,
reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Pointer,
reflect.String,
reflect.Struct,
reflect.UnsafePointer:
return getValueNode(fieldVal, fieldInfo)
default:
return getValueNode(fieldVal, fieldInfo)
}
}
func getRootNode(value any) *ContainerNode {
structValue := reflect.ValueOf(value)
if structValue.Kind() == reflect.Ptr {
structValue = structValue.Elem()
}
structType := structValue.Type()
enums := map[string]types.EnumFormatter{}
if enummer, isEnummer := value.(types.Enummer); isEnummer {
enums = enummer.Enums()
}
infoFields := getStructFieldInfo(structType, enums)
nodeItems := make([]Node, 0, len(infoFields))
maxKeyLength := 0
for _, fieldInfo := range infoFields {
fieldValue := structValue.FieldByName(fieldInfo.Name)
if !fieldValue.IsValid() {
fieldValue = reflect.Zero(fieldInfo.Type)
}
nodeItems = append(nodeItems, getNode(fieldValue, &fieldInfo))
maxKeyLength = util.Max(len(fieldInfo.Name), maxKeyLength)
}
sortNodeItems(nodeItems)
return &ContainerNode{
FieldInfo: &FieldInfo{Type: structType},
Items: nodeItems,
MaxKeyLength: maxKeyLength,
}
}
func getValueNode(fieldVal reflect.Value, fieldInfo *FieldInfo) *ValueNode {
value, tokenType := getValueNodeValue(fieldVal, fieldInfo)
return &ValueNode{
FieldInfo: fieldInfo,
Value: value,
tokenType: tokenType,
}
}
func getValueNodeValue(fieldValue reflect.Value, fieldInfo *FieldInfo) (string, NodeTokenType) {
kind := fieldValue.Kind()
base := fieldInfo.Base
if base == 0 {
base = BaseDecimalLen
}
if fieldInfo.IsEnum() {
return fieldInfo.EnumFormatter.Print(int(fieldValue.Int())), EnumToken
}
switch kind {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val := strconv.FormatUint(fieldValue.Uint(), base)
if base == BaseHexLen {
val = "0x" + val
}
return val, NumberToken
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(fieldValue.Int(), base), NumberToken
case reflect.String:
return fieldValue.String(), StringToken
case reflect.Bool:
val := fieldValue.Bool()
if val {
return PrintBool(val), TrueToken
}
return PrintBool(val), FalseToken
case reflect.Array, reflect.Slice, reflect.Map:
return getContainerValueString(fieldValue, fieldInfo), UnknownToken
case reflect.Ptr, reflect.Struct:
if val, err := GetConfigPropString(fieldValue); err == nil {
return val, PropToken
}
return "<ERR>", ErrorToken
case reflect.Invalid,
reflect.Uintptr,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128,
reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.UnsafePointer:
return fmt.Sprintf("<?%s>", kind.String()), UnknownToken
default:
return fmt.Sprintf("<?%s>", kind.String()), UnknownToken
}
}
func getContainerValueString(fieldValue reflect.Value, fieldInfo *FieldInfo) string {
itemSeparator := fieldInfo.ItemSeparator
sliceLength := fieldValue.Len()
var mapKeys []reflect.Value
if fieldInfo.Type.Kind() == reflect.Map {
mapKeys = fieldValue.MapKeys()
sort.Slice(mapKeys, func(a, b int) bool {
return mapKeys[a].String() < mapKeys[b].String()
})
}
stringBuilder := strings.Builder{}
var itemFieldInfo *FieldInfo
for i := range sliceLength {
var itemValue reflect.Value
if i > 0 {
stringBuilder.WriteRune(itemSeparator)
}
if mapKeys != nil {
mapKey := mapKeys[i]
stringBuilder.WriteString(mapKey.String())
stringBuilder.WriteRune(':')
itemValue = fieldValue.MapIndex(mapKey)
} else {
itemValue = fieldValue.Index(i)
}
if i == 0 {
itemFieldInfo = &FieldInfo{
Type: itemValue.Type(),
// Inherit the base from the container
Base: fieldInfo.Base,
}
if itemFieldInfo.Base == 0 {
itemFieldInfo.Base = BaseDecimalLen
}
}
strVal, _ := getValueNodeValue(itemValue, itemFieldInfo)
stringBuilder.WriteString(strVal)
}
return stringBuilder.String()
}