321 lines
9.8 KiB
Go
321 lines
9.8 KiB
Go
|
// Copyright Earl Warren <contact@earl-warren.org>
|
||
|
// Copyright Loïc Dachary <loic@dachary.org>
|
||
|
// 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)
|
||
|
}
|