175 lines
3.9 KiB
Go
175 lines
3.9 KiB
Go
package yaml
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type AppendProcessor struct{}
|
|
|
|
func NewAppendProcessor() *AppendProcessor {
|
|
return &AppendProcessor{}
|
|
}
|
|
|
|
func (ap *AppendProcessor) ProcessMap(data map[string]any) (map[string]any, error) {
|
|
result := make(map[string]any)
|
|
|
|
// First pass: collect all append keys and their base keys
|
|
appendKeys := make(map[string][]any)
|
|
baseKeys := make(map[string]any)
|
|
|
|
for key, value := range data {
|
|
if IsAppendKey(key) {
|
|
baseKey := GetBaseKey(key)
|
|
appendKeys[baseKey] = append(appendKeys[baseKey], value)
|
|
} else {
|
|
baseKeys[key] = value
|
|
}
|
|
}
|
|
|
|
// Second pass: process all values recursively
|
|
for key, value := range baseKeys {
|
|
processedValue, err := ap.processValue(value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process value for key %s: %w", key, err)
|
|
}
|
|
result[key] = processedValue
|
|
}
|
|
|
|
// Third pass: merge append keys with their base keys
|
|
for baseKey, appendValues := range appendKeys {
|
|
for _, appendValue := range appendValues {
|
|
processedValue, err := ap.processValue(appendValue)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process append value for key %s: %w", baseKey, err)
|
|
}
|
|
if existingValue, exists := result[baseKey]; exists {
|
|
if isSlice(processedValue) && isSlice(existingValue) {
|
|
// Always append to the base key's slice
|
|
result[baseKey] = append(existingValue.([]any), processedValue.([]any)...)
|
|
} else {
|
|
// If not both slices, overwrite (fallback)
|
|
result[baseKey] = processedValue
|
|
}
|
|
} else {
|
|
result[baseKey] = processedValue
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (ap *AppendProcessor) processValue(value any) (any, error) {
|
|
switch v := value.(type) {
|
|
case map[string]any:
|
|
return ap.ProcessMap(v)
|
|
case map[any]any:
|
|
converted := make(map[string]any)
|
|
for k, val := range v {
|
|
if strKey, ok := k.(string); ok {
|
|
converted[strKey] = val
|
|
} else {
|
|
return nil, fmt.Errorf("non-string key in map: %v", k)
|
|
}
|
|
}
|
|
return ap.ProcessMap(converted)
|
|
case []any:
|
|
result := make([]any, len(v))
|
|
for i, elem := range v {
|
|
processed, err := ap.processValue(elem)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process slice element %d: %w", i, err)
|
|
}
|
|
result[i] = processed
|
|
}
|
|
return result, nil
|
|
default:
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
func (ap *AppendProcessor) MergeWithAppend(dest, src map[string]any) error {
|
|
convertToStringMapInPlace(dest)
|
|
convertToStringMapInPlace(src)
|
|
|
|
for key, srcValue := range src {
|
|
if IsAppendKey(key) {
|
|
baseKey := GetBaseKey(key)
|
|
destValue, exists := dest[baseKey]
|
|
if exists {
|
|
if isSlice(srcValue) && isSlice(destValue) {
|
|
destSlice := destValue.([]any)
|
|
srcSlice := srcValue.([]any)
|
|
dest[baseKey] = append(destSlice, srcSlice...)
|
|
} else {
|
|
dest[baseKey] = srcValue
|
|
}
|
|
} else {
|
|
dest[baseKey] = srcValue
|
|
}
|
|
delete(src, key)
|
|
}
|
|
}
|
|
|
|
for key, srcValue := range src {
|
|
if isMap(srcValue) {
|
|
srcMap := srcValue.(map[string]any)
|
|
if destMap, ok := dest[key].(map[string]any); ok {
|
|
if err := ap.MergeWithAppend(destMap, srcMap); err != nil {
|
|
return err
|
|
}
|
|
dest[key] = destMap
|
|
} else {
|
|
dest[key] = srcMap
|
|
}
|
|
} else {
|
|
dest[key] = srcValue
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func convertToStringMapInPlace(v any) any {
|
|
switch t := v.(type) {
|
|
case map[string]any:
|
|
for k, v2 := range t {
|
|
t[k] = convertToStringMapInPlace(v2)
|
|
}
|
|
return t
|
|
case map[any]any:
|
|
m := make(map[string]any, len(t))
|
|
for k, v2 := range t {
|
|
if ks, ok := k.(string); ok {
|
|
m[ks] = convertToStringMapInPlace(v2)
|
|
}
|
|
}
|
|
return m
|
|
case []any:
|
|
for i, v2 := range t {
|
|
t[i] = convertToStringMapInPlace(v2)
|
|
}
|
|
return t
|
|
default:
|
|
return v
|
|
}
|
|
}
|
|
|
|
func isSlice(value any) bool {
|
|
_, ok := value.([]any)
|
|
return ok
|
|
}
|
|
|
|
func isMap(value any) bool {
|
|
_, ok := value.(map[string]any)
|
|
return ok
|
|
}
|
|
|
|
func IsAppendKey(key string) bool {
|
|
return strings.HasSuffix(key, "+")
|
|
}
|
|
|
|
func GetBaseKey(key string) string {
|
|
return strings.TrimSuffix(key, "+")
|
|
}
|