// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package generic import ( "context" "fmt" "strings" "lab.forgefriends.org/friendlyforgeformat/gof3/f3" "lab.forgefriends.org/friendlyforgeformat/gof3/util" ) type ( UnifyUpsertFunc func(ctx context.Context, origin NodeInterface, originParent Path, destination NodeInterface, destinationParent Path) UnifyDeleteFunc func(ctx context.Context, destination NodeInterface, destinationParent Path) ) type UnifyOptions struct { destinationTree TreeInterface upsert UnifyUpsertFunc delete UnifyDeleteFunc noremap bool } func NewUnifyOptions(destinationTree TreeInterface) *UnifyOptions { return &UnifyOptions{ destinationTree: destinationTree, } } func (o *UnifyOptions) SetUpsert(upsert UnifyUpsertFunc) *UnifyOptions { o.upsert = upsert return o } func (o *UnifyOptions) SetDelete(delete UnifyDeleteFunc) *UnifyOptions { o.delete = delete return o } func (o *UnifyOptions) SetNoRemap(noremap bool) *UnifyOptions { o.noremap = noremap return o } func TreeUnifyPath(ctx context.Context, origin TreeInterface, path Path, destination TreeInterface, options *UnifyOptions) bool { if path.Empty() || string(path.First().GetID()) == "." { return true } originRoot := origin.GetRoot() destinationRoot := destination.GetRoot() if destinationRoot == nil { destinationRoot = destination.Factory(ctx, originRoot.GetKind()) destination.SetRoot(destinationRoot) } path = path.RemoveFirst() if path.Empty() { return true } originNode := originRoot.GetChild(path.First().GetID()).GetSelf() return NodeUnifyPath(ctx, originNode, NewPath(originRoot), path.RemoveFirst(), NewPath(destinationRoot), options) } func TreeUnify(ctx context.Context, origin, destination TreeInterface, options *UnifyOptions) { origin.Trace("") originRoot := origin.GetRoot() if originRoot == nil { destination.SetRoot(nil) return } destinationRoot := destination.GetRoot() if destinationRoot == nil { destinationRoot = destination.Factory(ctx, originRoot.GetKind()) destination.SetRoot(destinationRoot) NodeCopy(ctx, originRoot, destinationRoot, originRoot.GetID(), options) } NodeUnify(ctx, originRoot, NewPath(), destinationRoot, NewPath(), options) } func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) { for _, reference := range f.GetReferences() { toPath := NewPath() collectTo := func(ctx context.Context, parent, path Path, node NodeInterface) { element := NewNode() element.SetID(node.GetMappedID()) toPath = toPath.Append(element) } from := reference.Get() isRelative := !strings.HasPrefix(from, "/") if isRelative && !strings.HasPrefix(from, "..") { panic(fmt.Errorf("relative references that do not start with .. are not supported %s", from)) } current := node.GetCurrentPath().String() fromPath := PathAbsolute(current, from) node.Trace("collectTo %s", from) node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode)) to := toPath.String() if isRelative { currentMapped := node.GetParent().GetCurrentPath().PathMappedString().Join() // because the mapped ID of the current node has not been allocated yet // and it does not matter as long as it is replaced with .. // it will not work at all if a relative reference does not start with .. currentMapped += "/PLACEHODLER" to = PathStringRelative(currentMapped, to) } node.Trace("convert reference %s => %s", reference.Get(), to) reference.Set(to) } } func NodeCopy(ctx context.Context, origin, destination NodeInterface, destinationID NodeID, options *UnifyOptions) { f := origin.GetSelf().ToFormat() if options.noremap { origin.Trace("noremap") } else { RemapReferences(ctx, origin, f) } f.SetID(string(destinationID)) destination.GetSelf().FromFormat(f) destination.Upsert(ctx) } func NodeUnify(ctx context.Context, origin NodeInterface, originPath Path, destination NodeInterface, destinationPath Path, options *UnifyOptions) { origin.Trace("origin '%s' | destination '%s'", origin.GetCurrentPath(), destination.GetCurrentPath()) util.MaybeTerminate(ctx) originPath = originPath.Append(origin) destinationPath = destinationPath.Append(destination) originChildren := origin.GetSelf().GetChildren() existing := make(map[NodeID]any, len(originChildren)) for _, originChild := range originChildren { destinationID := GetMappedID(ctx, originChild, destination, options) destinationChild := destination.GetChild(destinationID) createDestinationChild := destinationChild == NilNode if createDestinationChild { destinationChild = options.destinationTree.Factory(ctx, originChild.GetKind()) destinationChild.SetParent(destination) } NodeCopy(ctx, originChild, destinationChild, destinationID, options) if options.upsert != nil { options.upsert(ctx, originChild.GetSelf(), originPath, destinationChild.GetSelf(), destinationPath) } if createDestinationChild { destination.SetChild(destinationChild) } SetMappedID(ctx, originChild, destinationChild, options) existing[destinationChild.GetID()] = true NodeUnify(ctx, originChild, originPath, destinationChild, destinationPath, options) } destinationChildren := destination.GetSelf().GetChildren() for _, destinationChild := range destinationChildren { destinationID := destinationChild.GetID() if _, ok := existing[destinationID]; !ok { destinationChild.GetSelf().Delete(ctx) if options.delete != nil { options.delete(ctx, destinationChild.GetSelf(), destinationPath) } destination.DeleteChild(destinationID) } } } func SetMappedID(ctx context.Context, origin, destination NodeInterface, options *UnifyOptions) { if options.noremap { return } origin.SetMappedID(destination.GetID()) } func GetMappedID(ctx context.Context, origin, destinationParent NodeInterface, options *UnifyOptions) NodeID { if options.noremap { return origin.GetID() } if id := origin.GetMappedID(); id != NilID { return id } return destinationParent.LookupMappedID(origin.GetID()) } func NodeUnifyOne(ctx context.Context, origin NodeInterface, originPath, path, destinationPath Path, options *UnifyOptions) NodeInterface { destinationParent := destinationPath.Last() destinationID := GetMappedID(ctx, origin, destinationParent, options) origin.Trace("'%s' '%s' '%s' %v", originPath, path, destinationPath, destinationID) destination := destinationParent.GetChild(destinationID) createDestination := destination == NilNode if createDestination { destination = options.destinationTree.Factory(ctx, origin.GetKind()) destination.SetParent(destinationParent) } NodeCopy(ctx, origin, destination, destinationID, options) if options.upsert != nil { options.upsert(ctx, origin.GetSelf(), originPath, destination.GetSelf(), destinationPath) } if createDestination { destinationParent.SetChild(destination) } origin.SetMappedID(destination.GetID()) return destination } func NodeUnifyPath(ctx context.Context, origin NodeInterface, originPath, path, destinationPath Path, options *UnifyOptions) bool { origin.Trace("origin '%s' '%s' | destination '%s' | path '%s'", originPath.String(), origin.GetID(), destinationPath.String(), path.String()) util.MaybeTerminate(ctx) if path.Empty() { NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options) return true } id := string(path.First().GetID()) if id == "." { NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options) return true } if id == ".." { parent, originPath := originPath.Pop() _, destinationPath := destinationPath.Pop() return NodeUnifyPath(ctx, parent, originPath, path.RemoveFirst(), destinationPath, options) } destination := NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options) originPath = originPath.Append(origin.GetSelf()) destinationPath = destinationPath.Append(destination.GetSelf()) child := origin.GetSelf().GetChild(path.First().GetID()) if child == NilNode { panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", originPath.String(), path.First().GetID())) } return NodeUnifyPath(ctx, child, originPath, path.RemoveFirst(), destinationPath, options) } type ParallelApplyFunc func(ctx context.Context, origin, destination NodeInterface) type ParallelApplyOptions struct { fun ParallelApplyFunc where ApplyWhere noremap bool } func NewParallelApplyOptions(fun ParallelApplyFunc) *ParallelApplyOptions { return &ParallelApplyOptions{ fun: fun, } } func (o *ParallelApplyOptions) SetWhere(where ApplyWhere) *ParallelApplyOptions { o.where = where return o } func (o *ParallelApplyOptions) SetNoRemap(noremap bool) *ParallelApplyOptions { o.noremap = noremap return o } func TreePathRemap(ctx context.Context, origin TreeInterface, path Path, destination TreeInterface) Path { remappedPath := NewPath() remap := func(ctx context.Context, origin, destination NodeInterface) { remappedPath = destination.GetCurrentPath() } TreeParallelApply(ctx, origin, path, destination, NewParallelApplyOptions(remap)) return remappedPath } func TreeParallelApply(ctx context.Context, origin TreeInterface, path Path, destination TreeInterface, options *ParallelApplyOptions) bool { if path.Empty() { return true } return NodeParallelApply(ctx, origin.GetRoot(), path.RemoveFirst(), destination.GetRoot(), options) } func NodeParallelApply(ctx context.Context, origin NodeInterface, path Path, destination NodeInterface, options *ParallelApplyOptions) bool { origin.Trace("origin '%s' | destination '%s' | path '%s'", origin.GetCurrentPath(), destination.GetCurrentPath(), path.String()) util.MaybeTerminate(ctx) if path.Empty() { options.fun(ctx, origin, destination) return true } id := string(path.First().GetID()) if id == "." { return NodeParallelApply(ctx, origin, path.RemoveFirst(), destination, options) } if id == ".." { return NodeParallelApply(ctx, origin.GetParent(), path.RemoveFirst(), destination.GetParent(), options) } if options.where == ApplyEachNode { options.fun(ctx, origin, destination) } originChild := origin.GetSelf().GetChild(path.First().GetID()) if originChild == NilNode { origin.Trace("no child %s", path.First().GetID()) return false } var mappedID NodeID if options.noremap { mappedID = originChild.GetID() } else { mappedID = originChild.GetMappedID() if mappedID == NilID { origin.Trace("%s no mapped", originChild.GetID()) return false } } destinationChild := destination.GetChild(mappedID) if destinationChild == NilNode { panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", destination.String(), originChild.GetMappedID())) } return NodeParallelApply(ctx, originChild, path.RemoveFirst(), destinationChild, options) }