package configkit import ( "slices" "strings" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) type overlayRecord struct { change Change displaced []displacedDefinition } type displacedDefinition struct { path Path origin Origin } func overlayDocuments(base *composedDocument, overlay *document.Node) (*composedDocument, []Diagnostic) { if base == nil || base.root == nil || base.root.Kind != document.Mapping || overlay == nil || overlay.Kind != document.Mapping { return nil, []Diagnostic{{ Code: CodeProfileInvalid, Severity: SeverityError, Message: "profile overlay requires mapping document roots", }} } result := cloneComposedDocument(base) mergeOverlayOrigin(result.origins, Path{}, overlay.Location, LayerProfile) mergeOverlayMappings(result, result.root, overlay, Path{}, LayerProfile, true) result.applications = append(result.applications, historyApplication{layer: LayerProfile, value: overlay}) return result, nil } func mergeOverlayMappings(result *composedDocument, target, overlay *document.Node, current Path, layer Layer, recordReplacements bool) { for _, incoming := range overlay.Mapping { path := current.Key(incoming.Key) index := documentEntryIndex(target.Mapping, incoming.Key) if index < 0 { cloned := incoming cloned.Value = document.Clone(incoming.Value) target.Mapping = append(target.Mapping, cloned) collectOverlayOrigins(result.origins, incoming.Value, path, layer) continue } existing := target.Mapping[index].Value if existing.Kind == document.Mapping && incoming.Value.Kind == document.Mapping { mergeOverlayOrigin(result.origins, path, incoming.Value.Location, layer) mergeOverlayMappings(result, existing, incoming.Value, path, layer, recordReplacements) continue } displaced := displacedOrigins(existing, result.origins, path) previous := cloneOrigin(result.origins[path.String()]) deleteOriginSubtree(result.origins, path) target.Mapping[index] = incoming target.Mapping[index].Value = document.Clone(incoming.Value) collectOverlayOrigins(result.origins, incoming.Value, path, layer) if recordReplacements { result.overlays = append(result.overlays, overlayRecord{ change: Change{ Path: path.clone(), Origin: overlayOrigin(layer, incoming.Value.Location), Previous: previousOrigins(previous), }, displaced: displaced, }) } } } func collectOverlayOrigins(origins map[string]Origin, node *document.Node, path Path, layer Layer) { origins[path.String()] = overlayOrigin(layer, node.Location) switch node.Kind { case document.Mapping: for _, entry := range node.Mapping { collectOverlayOrigins(origins, entry.Value, path.Key(entry.Key), layer) } case document.Sequence: for index, child := range node.Sequence { collectOverlayOrigins(origins, child, path.Index(index), layer) } } } func mergeOverlayOrigin(origins map[string]Origin, path Path, location document.Location, layer Layer) { pointer := path.String() origin := cloneOrigin(origins[pointer]) origin.Layer = layer origin.Locations = append(origin.Locations, publicLocation(location)) origins[pointer] = origin } func overlayOrigin(layer Layer, location document.Location) Origin { return Origin{Layer: layer, Locations: []Location{publicLocation(location)}} } func previousOrigins(origin Origin) []Origin { if origin.Layer == "" { return nil } return []Origin{origin} } func displacedOrigins(node *document.Node, origins map[string]Origin, path Path) []displacedDefinition { var displaced []displacedDefinition collectDisplacedOrigins(&displaced, node, origins, path, Origin{}) slices.SortFunc(displaced, func(left, right displacedDefinition) int { return strings.Compare(left.path.String(), right.path.String()) }) return displaced } func collectDisplacedOrigins(displaced *[]displacedDefinition, node *document.Node, origins map[string]Origin, path Path, inherited Origin) { origin, ok := origins[path.String()] if !ok { origin = cloneOrigin(inherited) origin.Locations = []Location{publicLocation(node.Location)} } *displaced = append(*displaced, displacedDefinition{path: path.clone(), origin: cloneOrigin(origin)}) switch node.Kind { case document.Mapping: for _, entry := range node.Mapping { collectDisplacedOrigins(displaced, entry.Value, origins, path.Key(entry.Key), origin) } case document.Sequence: for index, child := range node.Sequence { collectDisplacedOrigins(displaced, child, origins, path.Index(index), origin) } } } func deleteOriginSubtree(origins map[string]Origin, path Path) { prefix := path.String() for pointer := range origins { if pointer == prefix || strings.HasPrefix(pointer, prefix+"/") { delete(origins, pointer) } } } func cloneOriginIndex(origins map[string]Origin) map[string]Origin { cloned := make(map[string]Origin, len(origins)) for pointer, origin := range origins { cloned[pointer] = cloneOrigin(origin) } return cloned } func cloneOverlayRecords(records []overlayRecord) []overlayRecord { cloned := make([]overlayRecord, len(records)) for index, record := range records { cloned[index].change = cloneChange(record.change) cloned[index].displaced = make([]displacedDefinition, len(record.displaced)) for displacedIndex, displaced := range record.displaced { cloned[index].displaced[displacedIndex] = displacedDefinition{ path: displaced.path.clone(), origin: cloneOrigin(displaced.origin), } } } return cloned }