162 lines
5.4 KiB
Go
162 lines
5.4 KiB
Go
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 || base.root == nil || base.root.Kind != document.Mapping || overlay == nil || overlay.Kind != document.Mapping {
|
|
return nil, []Diagnostic{{
|
|
Code: CodeOverlayInvalid, Severity: SeverityError,
|
|
Message: "overlay overlay requires mapping document roots",
|
|
}}
|
|
}
|
|
result := cloneComposedDocument(base)
|
|
mergeOverlayOrigin(result.origins, Path{}, overlay.Location, LayerOverlay)
|
|
mergeOverlayMappings(result, result.root, overlay, Path{}, LayerOverlay, true)
|
|
result.applications = append(result.applications, historyApplication{layer: LayerOverlay, 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
|
|
}
|