136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
package engine
|
|
|
|
import (
|
|
"slices"
|
|
"sort"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
)
|
|
|
|
type inheritanceHistoryIndex struct {
|
|
paths pathPrefixIndex
|
|
}
|
|
|
|
func newInheritanceHistoryIndex(operations []inheritanceOperation) *inheritanceHistoryIndex {
|
|
if len(operations) == 0 {
|
|
return nil
|
|
}
|
|
index := &inheritanceHistoryIndex{}
|
|
for operationID, operation := range operations {
|
|
if operation.control != nil {
|
|
index.paths.insert(operation.control.path.String(), operationID)
|
|
continue
|
|
}
|
|
if operation.transfer != nil {
|
|
index.paths.insert(operation.transfer.to.String(), operationID)
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func (index *inheritanceHistoryIndex) matchingOperations(path Path, operations []inheritanceOperation) []inheritanceOperation {
|
|
if index == nil {
|
|
return operations
|
|
}
|
|
var ids []int
|
|
seen := make(map[int]struct{})
|
|
collect := func(id int) {
|
|
if _, found := seen[id]; found {
|
|
return
|
|
}
|
|
seen[id] = struct{}{}
|
|
ids = append(ids, id)
|
|
}
|
|
pointer := path.String()
|
|
index.paths.visitAncestors(pointer, collect)
|
|
index.paths.visitDescendants(pointer, collect)
|
|
sort.Ints(ids)
|
|
matching := make([]inheritanceOperation, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id >= 0 && id < len(operations) {
|
|
matching = append(matching, operations[id])
|
|
}
|
|
}
|
|
return matching
|
|
}
|
|
|
|
// Retain source history at original paths. Structural operations are held once
|
|
// per relationship and projected onto a requested destination path only when
|
|
// History or Explain is queried.
|
|
func applyInheritanceMetadata(metadata *Metadata, raw *document.Node, rawOrigins map[string]Origin, overlays []overlayRecord, expansion *inheritanceScope) {
|
|
if metadata == nil || expansion == nil {
|
|
return
|
|
}
|
|
sourceView := Metadata{
|
|
fields: make(map[string]FieldInfo),
|
|
history: metadata.history,
|
|
}
|
|
walkDocument(raw, Path{}, func(path Path, node *document.Node) {
|
|
origin, found := rawOrigins[path.String()]
|
|
if !found {
|
|
origin = Origin{Layer: LayerBase, Locations: []Location{publicLocation(node.Location)}}
|
|
}
|
|
sourceView.fields[path.String()] = FieldInfo{Explicit: true, Null: node.Kind == document.Null, Origin: cloneOrigin(origin)}
|
|
})
|
|
applyOverlayMetadata(&sourceView, overlays)
|
|
metadata.history = sourceView.history
|
|
}
|
|
|
|
func historyWithInheritance(path Path, stored []Change, operations []inheritanceOperation) []Change {
|
|
split := len(stored)
|
|
for index, change := range stored {
|
|
if change.Origin.Layer == LayerCLI || change.Origin.Layer == LayerResolution {
|
|
split = index
|
|
break
|
|
}
|
|
}
|
|
history := cloneChanges(stored[:split])
|
|
for _, operation := range operations {
|
|
for _, change := range projectedInheritanceChanges(path, operation) {
|
|
if len(history) != 0 {
|
|
change.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
|
|
}
|
|
history = append(history, change)
|
|
}
|
|
}
|
|
for _, change := range stored[split:] {
|
|
cloned := cloneChange(change)
|
|
if len(history) != 0 {
|
|
cloned.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
|
|
}
|
|
history = append(history, cloned)
|
|
}
|
|
return history
|
|
}
|
|
|
|
func projectedInheritanceChanges(query Path, operation inheritanceOperation) []Change {
|
|
return inheritanceChangesForQuery(query, operation)
|
|
}
|
|
|
|
func transferredOperationOrigin(operation inheritanceOperation, destination Path) (Origin, bool) {
|
|
if inheritanceOperationIdentity(operation, destination) {
|
|
return Origin{}, false
|
|
}
|
|
relative, ok := relativeHistoryPath(operation.transfer.to, destination)
|
|
if !ok {
|
|
return Origin{}, false
|
|
}
|
|
source := appendInheritancePath(operation.transfer.from, relative)
|
|
origin, found := operation.parentOrigins[source.String()]
|
|
if !found {
|
|
return Origin{}, false
|
|
}
|
|
transferred := transferredInheritanceOrigins(map[string]Origin{source.String(): origin},
|
|
operation.transfer.from, operation.transfer.to, *operation.transfer)
|
|
return transferred[destination.String()], true
|
|
}
|
|
|
|
func inheritanceOperationIdentity(operation inheritanceOperation, path Path) bool {
|
|
if operation.transfer == nil || len(operation.identityFields) == 0 {
|
|
return false
|
|
}
|
|
relative, within := relativeHistoryPath(operation.transfer.to, path)
|
|
return within && len(relative.segments) != 0 && relative.segments[0].kind != pathSegmentIndex &&
|
|
slices.Contains(operation.identityFields, relative.segments[0].key)
|
|
}
|