package engine 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 { return nil, invalidOverlayDocument() } assembly := newAssemblyAccumulator(cloneComposedDocument(base)) if diagnostics := assembly.applyOverlay(overlay, nil, nil, LayerOverlay); len(diagnostics) != 0 { return nil, diagnostics } return assembly.current, nil } func invalidOverlayDocument() []Diagnostic { return []Diagnostic{{ Code: CodeOverlayInvalid, Severity: SeverityError, Message: "overlay requires mapping document roots", }} } func (a *assemblyAccumulator) applyOverlay(overlay *document.Node, incomingOrigins map[string]Origin, applications []historyApplication, layer Layer) []Diagnostic { if a == nil || a.current == nil || a.current.root == nil || a.current.root.Kind != document.Mapping || overlay == nil || overlay.Kind != document.Mapping { return invalidOverlayDocument() } mergeOriginContribution(a.current.origins, Path{}, incomingOrigin(incomingOrigins, Path{}, overlay, layer)) mergeOverlayMappings(a.current, a.current.root, overlay, Path{}, layer, true, incomingOrigins, &a.index) if len(applications) == 0 { a.current.applications = append(a.current.applications, historyApplication{layer: layer, value: overlay}) } else { for _, application := range applications { application.layer = layer a.current.applications = append(a.current.applications, application) } } return nil } func mergeOverlayMappings(result *composedDocument, target, overlay *document.Node, current Path, layer Layer, recordReplacements bool, incomingOrigins map[string]Origin, index *mappingIndex) { mergeRecursiveMappings(target, overlay, current, index, recursiveMergeHooks{ added: func(path Path, incoming *document.Node) { collectOverlayOriginsFrom(result.origins, incomingOrigins, incoming, path, layer) }, combined: func(path Path, incoming *document.Node) { mergeOriginContribution(result.origins, path, incomingOrigin(incomingOrigins, path, incoming, layer)) }, replaced: func(path Path, existing, incoming *document.Node) { displaced := displacedOrigins(existing, result.origins, path) previous := cloneOrigin(result.origins[path.String()]) deleteOriginSubtree(result.origins, path, existing) collectOverlayOriginsFrom(result.origins, incomingOrigins, incoming, path, layer) if recordReplacements { result.overlays = append(result.overlays, overlayRecord{ change: Change{ Path: path.clone(), Origin: incomingOrigin(incomingOrigins, path, incoming, layer), Previous: previousOrigins(previous), }, displaced: displaced, }) } }, }) } func collectOverlayOrigins(origins map[string]Origin, node *document.Node, path Path, layer Layer) { collectOverlayOriginsFrom(origins, nil, node, path, layer) } func collectOverlayOriginsFrom(origins, incomingOrigins map[string]Origin, node *document.Node, path Path, layer Layer) { origins[path.String()] = incomingOrigin(incomingOrigins, path, node, layer) switch node.Kind { case document.Mapping: for _, entry := range node.Mapping { collectOverlayOriginsFrom(origins, incomingOrigins, entry.Value, path.Key(entry.Key), layer) } case document.Sequence: for index, child := range node.Sequence { collectOverlayOriginsFrom(origins, incomingOrigins, child, path.Index(index), layer) } } } func mergeOverlayOrigin(origins map[string]Origin, path Path, location document.Location, layer Layer) { mergeOriginContribution(origins, path, overlayOrigin(layer, location)) } func mergeOriginContribution(origins map[string]Origin, path Path, incoming Origin) { pointer := path.String() origin := origins[pointer] origin.Layer = incoming.Layer origin.Locations = append(origin.Locations, incoming.Locations...) origins[pointer] = origin } func incomingOrigin(origins map[string]Origin, path Path, node *document.Node, layer Layer) Origin { if origin, ok := origins[path.String()]; ok { origin = cloneOrigin(origin) origin.Layer = layer return origin } return overlayOrigin(layer, node.Location) } 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, node *document.Node) { delete(origins, path.String()) switch node.Kind { case document.Mapping: for _, entry := range node.Mapping { deleteOriginSubtree(origins, path.Key(entry.Key), entry.Value) } case document.Sequence: for index, child := range node.Sequence { deleteOriginSubtree(origins, path.Index(index), child) } } } 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 }