// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package generic import ( "context" "code.forgejo.org/f3/gof3/v3/id" "code.forgejo.org/f3/gof3/v3/path" "code.forgejo.org/f3/gof3/v3/util" ) type ( UnifyUpsertFunc func(ctx context.Context, origin NodeInterface, originParent path.Path, destination NodeInterface, destinationParent path.Path) UnifyDeleteFunc func(ctx context.Context, destination NodeInterface, destinationParent path.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, p path.Path, destination TreeInterface, options *UnifyOptions) { if p.Empty() || p.First().(NodeInterface).GetID().String() == "." { return } originRoot := origin.GetRoot() destinationRoot := destination.GetRoot() if destinationRoot == nil { destinationRoot = destination.Factory(ctx, originRoot.GetKind()) destination.SetRoot(destinationRoot) } p = p.RemoveFirst() if p.Empty() { return } originNode := originRoot.GetChild(p.First().(NodeInterface).GetID()).GetSelf() NodeUnifyPath(ctx, originNode, path.NewPath(originRoot.(path.PathElement)), p.RemoveFirst(), path.NewPath(destinationRoot.(path.PathElement)), 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, path.NewPath(), destinationRoot, path.NewPath(), options) } func NodeCopy(ctx context.Context, origin, destination NodeInterface, destinationID id.NodeID, options *UnifyOptions) { f := origin.GetSelf().ToFormat() if options.noremap { origin.Trace("noremap") } else { RemapReferences(ctx, origin, f) } f.SetID(destinationID.String()) destination.GetSelf().FromFormat(f) destination.Upsert(ctx) } func NodeUnify(ctx context.Context, origin NodeInterface, originPath path.Path, destination NodeInterface, destinationPath path.Path, options *UnifyOptions) { origin.Trace("origin '%s' | destination '%s'", origin.GetCurrentPath().ReadableString(), destination.GetCurrentPath().ReadableString()) util.MaybeTerminate(ctx) originPath = originPath.Append(origin.(path.PathElement)) destinationPath = destinationPath.Append(destination.(path.PathElement)) originChildren := origin.GetSelf().GetChildren() existing := make(map[id.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) id.NodeID { if options.noremap { return origin.GetID() } if i := origin.GetMappedID(); i != id.NilID { return i } return destinationParent.LookupMappedID(origin.GetID()) } func NodeUnifyOne(ctx context.Context, origin NodeInterface, originPath, path, destinationPath path.Path, options *UnifyOptions) NodeInterface { destinationParent := destinationPath.Last().(NodeInterface) destinationID := GetMappedID(ctx, origin, destinationParent, options) origin.Trace("'%s' '%s' '%s' %v", originPath.ReadableString(), path.ReadableString(), destinationPath.ReadableString(), 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.Path, options *UnifyOptions) { origin.Trace("origin '%s' '%s' | destination '%s' | path '%s'", originPath.ReadableString(), origin.GetID(), destinationPath.ReadableString(), path.ReadableString()) util.MaybeTerminate(ctx) if path.Empty() { NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options) return } id := path.First().(NodeInterface).GetID().String() if id == "." { NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options) return } if id == ".." { parent, originPath := originPath.Pop() _, destinationPath := destinationPath.Pop() NodeUnifyPath(ctx, parent.(NodeInterface), originPath, path.RemoveFirst(), destinationPath, options) return } destination := NodeUnifyOne(ctx, origin, originPath, path, destinationPath, options) originPath = originPath.Append(origin.GetSelf()) destinationPath = destinationPath.Append(destination.GetSelf()) child := origin.GetSelf().GetChild(path.First().(NodeInterface).GetID()) if child == NilNode { panic(NewError[ErrorNodeNotFound]("%s has no child with id %s", originPath.String(), path.First().(NodeInterface).GetID())) } 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, p path.Path, destination TreeInterface) path.Path { remappedPath := path.NewPath() remap := func(ctx context.Context, origin, destination NodeInterface) { remappedPath = destination.GetCurrentPath() } TreeParallelApply(ctx, origin, p, destination, NewParallelApplyOptions(remap)) return remappedPath } func TreeParallelApply(ctx context.Context, origin TreeInterface, path 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.Path, destination NodeInterface, options *ParallelApplyOptions) bool { origin.Trace("origin '%s' | destination '%s' | path '%s'", origin.GetCurrentPath().ReadableString(), destination.GetCurrentPath().ReadableString(), path.ReadableString()) util.MaybeTerminate(ctx) if path.Empty() { options.fun(ctx, origin, destination) return true } i := path.First().(NodeInterface).GetID().String() if i == "." { return NodeParallelApply(ctx, origin, path.RemoveFirst(), destination, options) } if i == ".." { 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().(NodeInterface).GetID()) if originChild == NilNode { origin.Trace("no child %s", path.First().(NodeInterface).GetID()) return false } var mappedID id.NodeID if options.noremap { mappedID = originChild.GetID() } else { mappedID = originChild.GetMappedID() if mappedID == id.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) }