1
0
Fork 0

Adding upstream version 3.10.8.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-18 09:37:23 +02:00
parent 37e9b6d587
commit 03bfe4079e
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
356 changed files with 28857 additions and 0 deletions

72
tree/generic/compare.go Normal file
View file

@ -0,0 +1,72 @@
// 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"
)
func NodeCompare(ctx context.Context, a, b NodeInterface) bool {
a.Trace("a '%s' | b '%s'", a.GetCurrentPath().ReadableString(), b.GetCurrentPath().ReadableString())
if a.GetKind() != b.GetKind() {
a.Trace("kind is different a = %s | b = %s", a.GetKind(), b.GetKind())
return false
}
if diff := a.GetTree().Diff(a, b); diff != "" {
a.Trace("difference %s", diff)
return false
}
aChildren := a.GetNodeChildren()
bChildren := b.GetNodeChildren()
aLen := len(aChildren)
bLen := len(bChildren)
if aLen != bLen {
a.Trace("children count is different a = %d | b = %d", aLen, bLen)
return false
}
for aID, aChild := range aChildren {
bID := aChild.GetID()
mappedID := aChild.GetMappedID()
if mappedID != id.NilID {
bID = mappedID
}
bChild, ok := bChildren[bID]
if !ok {
a.Trace("there is no child in 'b' with id %s matching the child in 'a' with id %s", bID, aID)
return false
}
if !NodeCompare(ctx, aChild, bChild) {
return false
}
}
return true
}
func TreeCompare(ctx context.Context, aTree TreeInterface, aPath path.Path, bTree TreeInterface, bPath path.Path) bool {
aTree.Trace("'%s' => '%s'", aPath.ReadableString(), bPath.ReadableString())
a := aTree.Find(aPath)
if a == NilNode {
aTree.Trace("a does not have %s", aPath.ReadableString())
return false
}
b := bTree.Find(bPath)
if b == NilNode {
aTree.Trace("b does not have %s", bPath.ReadableString())
return false
}
return NodeCompare(ctx, a, b)
}

View file

@ -0,0 +1,226 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/path"
"github.com/stretchr/testify/assert"
)
type compareFormat struct {
f3.Common
V int
}
func (o *compareFormat) Clone() f3.Interface {
clone := &compareFormat{}
*clone = *o
return clone
}
type compareNodeDriver struct {
NullDriver
v int
ID string
}
func (o *compareNodeDriver) NewFormat() f3.Interface {
return &compareFormat{
Common: f3.NewCommon(o.ID),
}
}
func (o *compareNodeDriver) ToFormat() f3.Interface {
f := o.NewFormat().(*compareFormat)
f.V = o.v
return f
}
func (o *compareNodeDriver) FromFormat(f f3.Interface) {
fc := f.(*compareFormat)
o.v = fc.V
o.ID = fc.GetID()
}
func newCompareNodeDriver() NodeDriverInterface {
return &compareNodeDriver{}
}
type compareTreeDriver struct {
NullTreeDriver
}
func newCompareTreeDriver() TreeDriverInterface {
return &compareTreeDriver{}
}
func (o *compareTreeDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
d := newCompareNodeDriver()
d.SetTreeDriver(o)
return d
}
func newCompareTree() TreeInterface {
tree := &testTree{}
tree.Init(tree, newTestOptions())
tree.Trace("init done")
tree.SetDriver(newCompareTreeDriver())
tree.Register(kindCompareNode, func(ctx context.Context, kind kind.Kind) NodeInterface {
node := &compareNode{}
return node.Init(node)
})
return tree
}
var kindCompareNode = kind.Kind("compare")
type compareNode struct {
Node
}
func TestTreeCompare(t *testing.T) {
tree := newTestTree()
log := logger.NewCaptureLogger()
log.SetLevel(logger.Trace)
tree.SetLogger(log)
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, TreeCompare(context.Background(), tree, path.NewPathFromString(NewElementNode, ""), tree, path.NewPathFromString(NewElementNode, "")))
log.Reset()
assert.False(t, TreeCompare(context.Background(), tree, path.NewPathFromString(NewElementNode, "/notfound"), tree, path.NewPathFromString(NewElementNode, "")))
assert.Contains(t, log.String(), "a does not have /notfound")
log.Reset()
assert.False(t, TreeCompare(context.Background(), tree, path.NewPathFromString(NewElementNode, "/"), tree, path.NewPathFromString(NewElementNode, "/notfound")))
assert.Contains(t, log.String(), "b does not have /notfound")
}
func TestNodeCompare(t *testing.T) {
log := logger.NewCaptureLogger()
log.SetLevel(logger.Trace)
treeA := newCompareTree()
treeA.SetLogger(log)
treeB := newCompareTree()
treeB.SetLogger(log)
nodeA := treeA.Factory(context.Background(), kindCompareNode)
nodeA.SetID(id.NewNodeID("root"))
assert.True(t, NodeCompare(context.Background(), nodeA, nodeA))
t.Run("different kind", func(t *testing.T) {
log.Reset()
other := treeB.Factory(context.Background(), kindCompareNode)
other.SetKind("other")
assert.False(t, NodeCompare(context.Background(), nodeA, other))
assert.Contains(t, log.String(), "kind is different")
})
t.Run("difference", func(t *testing.T) {
log.Reset()
other := treeB.Factory(context.Background(), kindCompareNode)
other.FromFormat(&compareFormat{V: 123456})
assert.False(t, NodeCompare(context.Background(), nodeA, other))
assert.Contains(t, log.String(), "difference")
assert.Contains(t, log.String(), "123456")
})
t.Run("children count", func(t *testing.T) {
log.Reset()
other := treeB.Factory(context.Background(), kindCompareNode)
other.SetChild(treeB.Factory(context.Background(), kindCompareNode))
assert.False(t, NodeCompare(context.Background(), nodeA, other))
assert.Contains(t, log.String(), "children count")
})
nodeAA := treeA.Factory(context.Background(), kindCompareNode)
nodeAA.SetID(id.NewNodeID("levelone"))
nodeA.SetChild(nodeAA)
nodeB := treeB.Factory(context.Background(), kindCompareNode)
nodeB.SetID(id.NewNodeID("root"))
t.Run("children are the same", func(t *testing.T) {
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID(id.NewNodeID("levelone"))
nodeB.SetChild(nodeBB)
assert.True(t, NodeCompare(context.Background(), nodeA, nodeB))
nodeB.DeleteChild(nodeBB.GetID())
})
t.Run("children have different IDs", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID(id.NewNodeID("SOMETHINGELSE"))
nodeB.SetChild(nodeBB)
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
assert.Contains(t, log.String(), "id levelone matching the child")
nodeB.DeleteChild(nodeBB.GetID())
})
t.Run("children have different content", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.FromFormat(&compareFormat{V: 12345678})
nodeBB.SetID(id.NewNodeID("levelone"))
nodeB.SetChild(nodeBB)
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
assert.Contains(t, log.String(), "difference")
assert.Contains(t, log.String(), "12345678")
nodeB.DeleteChild(nodeBB.GetID())
})
t.Run("children are the same because of their mapped ID", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID(id.NewNodeID("REMAPPEDID"))
nodeB.SetChild(nodeBB)
nodeAA.SetMappedID(id.NewNodeID("REMAPPEDID"))
assert.True(t, NodeCompare(context.Background(), nodeA, nodeB))
nodeB.DeleteChild(nodeBB.GetID())
nodeAA.SetMappedID(id.NilID)
})
t.Run("children are different because of their mapped ID", func(t *testing.T) {
log.Reset()
nodeBB := treeB.Factory(context.Background(), kindCompareNode)
nodeBB.SetID(id.NewNodeID("levelone"))
nodeB.SetChild(nodeBB)
nodeAA.SetMappedID(id.NewNodeID("REMAPPEDID"))
assert.False(t, NodeCompare(context.Background(), nodeA, nodeB))
assert.Contains(t, log.String(), "id REMAPPEDID matching the child")
nodeB.DeleteChild(nodeBB.GetID())
nodeAA.SetMappedID(id.NilID)
})
}

107
tree/generic/driver_node.go Normal file
View file

@ -0,0 +1,107 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"errors"
"fmt"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/logger"
)
type NodeDriverInterface interface {
logger.MessageInterface
MapIDInterface
IsNull() bool
GetNode() NodeInterface
SetNode(NodeInterface)
SetTreeDriver(treeDriver TreeDriverInterface)
GetTreeDriver() TreeDriverInterface
ListPage(context.Context, int) ChildrenSlice
GetIDFromName(context.Context, string) id.NodeID
Equals(context.Context, NodeInterface) bool
Get(context.Context) bool
Put(context.Context) id.NodeID
Patch(context.Context)
Delete(context.Context)
NewFormat() f3.Interface
FromFormat(f3.Interface)
ToFormat() f3.Interface
LookupMappedID(id.NodeID) id.NodeID
String() string
}
type NullDriver struct {
logger.Logger
node NodeInterface
mapped id.NodeID
treeDriver TreeDriverInterface
}
func NewNullDriver() NodeDriverInterface {
return &NullDriver{}
}
func (o *NullDriver) IsNull() bool { return true }
func (o *NullDriver) SetNode(node NodeInterface) { o.node = node }
func (o *NullDriver) GetNode() NodeInterface { return o.node }
func (o *NullDriver) GetMappedID() id.NodeID {
if o.mapped == nil {
return id.NilID
}
return o.mapped
}
func (o *NullDriver) SetMappedID(mapped id.NodeID) { o.mapped = mapped }
func (o *NullDriver) SetTreeDriver(treeDriver TreeDriverInterface) {
o.treeDriver = treeDriver
if treeDriver != nil {
o.SetLogger(treeDriver)
}
}
func (o *NullDriver) GetTreeDriver() TreeDriverInterface { return o.treeDriver }
func (o *NullDriver) ListPage(context.Context, int) ChildrenSlice {
panic(fmt.Errorf("ListPage %s", o.GetNode().GetKind()))
}
func (o *NullDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
panic(errors.New(name))
}
func (o *NullDriver) Equals(context.Context, NodeInterface) bool {
panic(fmt.Errorf("Equals %s", o.GetNode().GetKind()))
}
func (o *NullDriver) Get(context.Context) bool { panic(fmt.Errorf("Get %s", o.GetNode().GetKind())) }
func (o *NullDriver) Put(context.Context) id.NodeID {
panic(fmt.Errorf("Put %s", o.GetNode().GetKind()))
}
func (o *NullDriver) Patch(context.Context) {
panic(fmt.Errorf("Patch %s", o.GetNode().GetKind()))
}
func (o *NullDriver) Delete(context.Context) { panic(fmt.Errorf("Delete %s", o.GetNode().GetKind())) }
func (o *NullDriver) NewFormat() f3.Interface {
panic(fmt.Errorf("NewFormat %s", o.GetNode().GetKind()))
}
func (o *NullDriver) FromFormat(f3.Interface) {
panic(fmt.Errorf("FromFormat %s", o.GetNode().GetKind()))
}
func (o *NullDriver) ToFormat() f3.Interface { panic(fmt.Errorf("ToFormat %s", o.GetNode().GetKind())) }
func (o *NullDriver) LookupMappedID(id.NodeID) id.NodeID { return id.NilID }
func (o *NullDriver) String() string { return "" }

View file

@ -0,0 +1,78 @@
// 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/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"github.com/google/go-cmp/cmp"
)
type TreeDriverInterface interface {
logger.Interface
GetTree() TreeInterface
SetTree(TreeInterface)
GetPageSize() int
SetPageSize(int)
AllocateID() bool
Init()
Diff(a, b NodeDriverInterface) string
Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface
}
var DefaultPageSize = int(25)
type NullTreeDriver struct {
logger.Logger
tree TreeInterface
pageSize int
}
func (o *NullTreeDriver) Init() {
o.pageSize = DefaultPageSize
}
func NewNullTreeDriver() TreeDriverInterface {
d := &NullTreeDriver{}
d.Init()
return d
}
func (o *NullTreeDriver) SetTree(tree TreeInterface) {
o.tree = tree
if tree != nil {
o.SetLogger(tree)
}
}
func (o *NullTreeDriver) GetTree() TreeInterface { return o.tree }
func (o *NullTreeDriver) GetPageSize() int { return o.pageSize }
func (o *NullTreeDriver) SetPageSize(pageSize int) { o.pageSize = pageSize }
func (o *NullTreeDriver) AllocateID() bool { return true }
func (o *NullTreeDriver) Diff(a, b NodeDriverInterface) string {
aFormat := a.ToFormat()
bFormat := b.ToFormat()
return cmp.Diff(aFormat, bFormat)
}
func (o *NullTreeDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
d := NewNullDriver()
d.SetTreeDriver(o)
return d
}

31
tree/generic/factory.go Normal file
View file

@ -0,0 +1,31 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"fmt"
"strings"
"code.forgejo.org/f3/gof3/v3/options"
)
var treeFactories = make(map[string]TreeFactory, 10)
type TreeFactory func(ctx context.Context, opts options.Interface) TreeInterface
func RegisterFactory(name string, factory TreeFactory) {
name = strings.ToLower(name)
treeFactories[name] = factory
}
func GetFactory(name string) TreeFactory {
name = strings.ToLower(name)
factory, ok := treeFactories[name]
if !ok {
panic(fmt.Errorf("no factory registered for %s", name))
}
return factory
}

View file

@ -0,0 +1,101 @@
// 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/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/path"
)
type NodeAccessorsInterface interface {
SetIsNil(bool)
GetIsNil() bool
SetIsSync(bool)
GetIsSync() bool
GetParent() NodeInterface
SetParent(NodeInterface)
GetKind() kind.Kind
SetKind(kind.Kind)
GetID() id.NodeID
SetID(id.NodeID)
GetTree() TreeInterface
SetTree(TreeInterface)
GetNodeChildren() NodeChildren
GetChildren() ChildrenSlice
SetChildren(NodeChildren)
GetDriver() NodeDriverInterface
SetDriver(NodeDriverInterface)
}
type NodeTreeInterface interface {
GetChild(id.NodeID) NodeInterface
GetIDFromName(context.Context, string) id.NodeID
SetChild(NodeInterface) NodeInterface
DeleteChild(id.NodeID) NodeInterface
CreateChild(context.Context) NodeInterface
MustFind(path.Path) NodeInterface
Find(path.Path) NodeInterface
FindAndGet(context.Context, path.Path) NodeInterface
GetCurrentPath() path.Path
Walk(ctx context.Context, parent path.Path, options *WalkOptions)
WalkAndGet(ctx context.Context, parent path.Path, options *WalkOptions)
Apply(ctx context.Context, parent, path path.Path, options *ApplyOptions) bool
ApplyAndGet(ctx context.Context, path path.Path, options *ApplyOptions) bool
List(context.Context) ChildrenSlice
}
type NodeDriverProxyInterface interface {
MapIDInterface
ListPage(context.Context, int) ChildrenSlice
GetIDFromName(context.Context, string) id.NodeID
Equals(context.Context, NodeInterface) bool
Get(context.Context) NodeInterface
Upsert(context.Context) NodeInterface
Delete(context.Context) NodeInterface
NewFormat() f3.Interface
FromFormat(f3.Interface) NodeInterface
ToFormat() f3.Interface
LookupMappedID(id.NodeID) id.NodeID
}
type MapIDInterface interface {
GetMappedID() id.NodeID
SetMappedID(id.NodeID)
}
type NodeInterface interface {
logger.MessageInterface
NodeAccessorsInterface
NodeTreeInterface
NodeDriverProxyInterface
path.PathElement
Init(NodeInterface) NodeInterface
GetSelf() NodeInterface
SetSelf(NodeInterface)
String() string
}

View file

@ -0,0 +1,58 @@
// 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/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/path"
)
type TreeInterface interface {
logger.Interface
Init(TreeInterface, options.Interface) TreeInterface
GetOptions() options.Interface
SetOptions(options.Interface)
GetSelf() TreeInterface
SetSelf(TreeInterface)
SetRoot(NodeInterface)
GetRoot() NodeInterface
SetLogger(logger.Interface)
GetLogger() logger.Interface
GetDriver() TreeDriverInterface
SetDriver(TreeDriverInterface)
GetChildrenKind(kind.Kind) kind.Kind
GetPageSize() int
AllocateID() bool
Clear(context.Context)
Diff(a, b NodeInterface) string
MustFind(path.Path) NodeInterface
Find(path.Path) NodeInterface
FindAndGet(context.Context, path.Path) NodeInterface
Exists(context.Context, path.Path) bool
Walk(context.Context, *WalkOptions)
WalkAndGet(context.Context, *WalkOptions)
Apply(context.Context, path.Path, *ApplyOptions) bool
ApplyAndGet(context.Context, path.Path, *ApplyOptions) bool
Register(kind kind.Kind, factory FactoryFun)
Factory(ctx context.Context, kind kind.Kind) NodeInterface
}

139
tree/generic/main_test.go Normal file
View file

@ -0,0 +1,139 @@
// 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/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/options"
options_logger "code.forgejo.org/f3/gof3/v3/options/logger"
)
type Storage map[string]int
type testTree struct {
Tree
}
type testOptions struct {
options.Options
options_logger.OptionsLogger
}
func newTestOptions() options.Interface {
opts := &testOptions{}
opts.SetName("test")
l := logger.NewLogger()
l.SetLevel(logger.Trace)
opts.SetLogger(l)
return opts
}
type noopNodeDriver struct {
NullDriver
}
func (o *noopNodeDriver) ListPage(context.Context, int) ChildrenSlice {
return ChildrenSlice{}
}
func (o *noopNodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
return id.NilID
}
func (o *noopNodeDriver) Equals(context.Context, NodeInterface) bool {
return false
}
func (o *noopNodeDriver) Put(context.Context) id.NodeID {
return o.GetNode().GetID()
}
func (o *noopNodeDriver) Patch(context.Context) {
}
func (o *noopNodeDriver) NewFormat() f3.Interface {
f := f3.NewCommon(id.NilID.String())
return &f
}
func (o *noopNodeDriver) ToFormat() f3.Interface {
f := f3.NewCommon(o.GetNode().GetID().String())
return &f
}
func (o *noopNodeDriver) FromFormat(f f3.Interface) {
o.GetNode().SetID(id.NewNodeID(f.GetID()))
}
func newTestNodeDriver() NodeDriverInterface {
return &noopNodeDriver{}
}
type testTreeDriver struct {
NullTreeDriver
}
func newTestTreeDriver() TreeDriverInterface {
return &testTreeDriver{}
}
func (o *testTreeDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
d := newTestNodeDriver()
d.SetTreeDriver(o)
return d
}
func newTestTree() TreeInterface {
tree := &testTree{}
tree.Init(tree, newTestOptions())
tree.Trace("init done")
tree.SetDriver(newTestTreeDriver())
tree.Register(kindTestNodeLevelOne, func(ctx context.Context, kind kind.Kind) NodeInterface {
node := &testNodeLevelOne{}
return node.Init(node)
})
tree.Register(kindTestNodeLevelTwo, func(ctx context.Context, kind kind.Kind) NodeInterface {
node := &testNodeLevelTwo{}
return node.Init(node)
})
return tree
}
type testNodeInterface interface {
GetV() int
}
type testNode struct {
Node
v int
}
func (o *testNode) GetV() int {
return o.v
}
func (o *testNode) Equals(ctx context.Context, other NodeInterface) bool {
if !o.Node.Equals(ctx, other) {
return false
}
return o.GetV() == other.(testNodeInterface).GetV()
}
var kindTestNodeLevelOne = kind.Kind("levelone")
type testNodeLevelOne struct {
testNode
}
var kindTestNodeLevelTwo = kind.Kind("leveltwo")
type testNodeLevelTwo struct {
testNode
}

52
tree/generic/mirror.go Normal file
View file

@ -0,0 +1,52 @@
// 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/path"
)
type MirrorOptions struct {
noremap bool
}
func NewMirrorOptions() *MirrorOptions {
return &MirrorOptions{}
}
func (o *MirrorOptions) SetNoRemap(noremap bool) *MirrorOptions {
o.noremap = noremap
return o
}
func NodeMirror(ctx context.Context, origin, destination NodeInterface, options *MirrorOptions) {
origin.Trace("origin '%s' | destination '%s'", origin.GetCurrentPath().ReadableString(), destination.GetCurrentPath().ReadableString())
origin.Trace("start unify references")
for _, reference := range NodeCollectReferences(ctx, origin) {
origin.Trace("unify reference %s", reference)
TreeUnifyPath(ctx, origin.GetTree(), reference, destination.GetTree(), NewUnifyOptions(destination.GetTree()))
}
origin.Trace("end unify references")
originParents := origin.GetParent().GetCurrentPath()
destinationParents := destination.GetParent().GetCurrentPath()
NodeUnify(ctx, origin, originParents, destination, destinationParents, NewUnifyOptions(destination.GetTree()))
}
func TreeMirror(ctx context.Context, originTree, destinationTree TreeInterface, path path.Path, options *MirrorOptions) {
originTree.Trace("'%s'", path.ReadableString())
TreeUnifyPath(ctx, originTree, path, destinationTree, NewUnifyOptions(destinationTree))
TreeParallelApply(ctx, originTree, path, destinationTree, NewParallelApplyOptions(func(ctx context.Context, origin, destination NodeInterface) {
NodeMirror(ctx, origin, destination, NewMirrorOptions())
}))
}
func TreePartialMirror(ctx context.Context, originTree TreeInterface, originPath path.Path, destinationTree TreeInterface, destinationPath path.Path, options *MirrorOptions) {
originTree.Trace("'%s => %s'", originPath, destinationPath)
originNode := originTree.MustFind(originPath)
destinationNode := destinationTree.MustFind(destinationPath)
NodeMirror(ctx, originNode, destinationNode, NewMirrorOptions())
}

414
tree/generic/node.go Normal file
View file

@ -0,0 +1,414 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"fmt"
"sort"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/util"
)
type ChildrenSlice []NodeInterface
func NewChildrenSlice(len int) ChildrenSlice {
return make([]NodeInterface, 0, len)
}
func (o ChildrenSlice) Len() int {
return len(o)
}
func (o ChildrenSlice) Swap(i, j int) {
o[i], o[j] = o[j], o[i]
}
func (o ChildrenSlice) Less(i, j int) bool {
return o[i].GetID().String() < o[j].GetID().String()
}
type NodeChildren map[id.NodeID]NodeInterface
func NewNodeChildren() NodeChildren {
return make(NodeChildren)
}
var (
NilNode = &Node{isNil: true}
NilParent = NilNode
)
type Node struct {
logger.Logger
tree TreeInterface
isNil bool
sync bool
kind kind.Kind
id id.NodeID
driver NodeDriverInterface
self NodeInterface
parent NodeInterface
children NodeChildren
}
func NewElementNode() path.PathElement {
return NewNode().(path.PathElement)
}
func NewNode() NodeInterface {
node := &Node{}
return node.Init(node)
}
func NewNodeFromID[T any](i T) NodeInterface {
node := NewNode()
node.SetID(id.NewNodeID(i))
return node
}
func (o *Node) Init(self NodeInterface) NodeInterface {
o.SetTree(nil)
o.SetIsNil(true)
o.SetKind(kind.KindNil)
o.SetID(id.NilID)
o.SetSelf(self)
o.SetParent(NilNode)
o.children = NewNodeChildren()
return self
}
func (o *Node) GetIsNil() bool { return o == nil || o.isNil }
func (o *Node) SetIsNil(isNil bool) { o.isNil = isNil }
func (o *Node) GetIsSync() bool { return o.sync }
func (o *Node) SetIsSync(sync bool) { o.sync = sync }
func (o *Node) GetSelf() NodeInterface { return o.self }
func (o *Node) SetSelf(self NodeInterface) { o.self = self }
func (o *Node) GetParent() NodeInterface { return o.parent }
func (o *Node) SetParent(parent NodeInterface) { o.parent = parent }
func (o *Node) GetKind() kind.Kind { return o.kind }
func (o *Node) SetKind(kind kind.Kind) { o.kind = kind }
func (o *Node) GetID() id.NodeID {
if o.id == nil {
return id.NilID
}
return o.id
}
func (o *Node) SetID(id id.NodeID) { o.id = id }
func (o *Node) GetMappedID() id.NodeID {
mappedID := o.GetDriver().GetMappedID()
if mappedID == nil {
mappedID = id.NilID
}
return mappedID
}
func (o *Node) SetMappedID(mapped id.NodeID) {
o.GetDriver().SetMappedID(mapped)
}
func (o *Node) GetTree() TreeInterface { return o.tree }
func (o *Node) SetTree(tree TreeInterface) {
o.tree = tree
if tree != nil {
o.SetLogger(tree.GetLogger())
}
}
func (o *Node) GetDriver() NodeDriverInterface { return o.driver }
func (o *Node) SetDriver(driver NodeDriverInterface) {
driver.SetNode(o)
o.driver = driver
}
func (o *Node) GetNodeChildren() NodeChildren {
return o.children
}
func (o *Node) GetChildren() ChildrenSlice {
children := NewChildrenSlice(len(o.children))
for _, child := range o.children {
children = append(children, child)
}
sort.Sort(children)
return children
}
func (o *Node) SetChildren(children NodeChildren) { o.children = children }
func (o *Node) String() string {
driver := o.GetDriver().String()
if driver != "" {
driver = "=" + driver
}
return o.GetID().String() + driver
}
func (o *Node) Equals(ctx context.Context, other NodeInterface) bool {
return o.GetKind() == other.GetKind() && o.GetID() == other.GetID()
}
func (o *Node) GetCurrentPath() path.Path {
var p path.Path
if o.GetIsNil() {
p = path.NewPath()
} else {
p = o.GetParent().GetCurrentPath().Append(o)
}
return p
}
func (o *Node) ListPage(ctx context.Context, page int) ChildrenSlice {
return o.GetDriver().ListPage(ctx, page)
}
func (o *Node) List(ctx context.Context) ChildrenSlice {
o.Trace("%s", o.GetKind())
children := NewNodeChildren()
self := o.GetSelf()
for page := 1; ; page++ {
util.MaybeTerminate(ctx)
childrenPage := self.ListPage(ctx, page)
for _, child := range childrenPage {
children[child.GetID()] = child
}
if len(childrenPage) < o.GetTree().GetPageSize() {
break
}
}
o.children = children
return o.GetChildren()
}
func (o *Node) GetChild(id id.NodeID) NodeInterface {
child, ok := o.children[id]
if !ok {
return NilNode
}
return child
}
func (o *Node) SetChild(child NodeInterface) NodeInterface {
o.children[child.GetID()] = child
return child
}
func (o *Node) DeleteChild(id id.NodeID) NodeInterface {
if child, ok := o.children[id]; ok {
delete(o.children, id)
return child
}
return NilNode
}
func (o *Node) CreateChild(ctx context.Context) NodeInterface {
tree := o.GetTree()
child := tree.Factory(ctx, tree.GetChildrenKind(o.GetKind()))
child.SetParent(o)
return child
}
func (o *Node) GetIDFromName(ctx context.Context, name string) id.NodeID {
return o.GetDriver().GetIDFromName(ctx, name)
}
func (o *Node) Get(ctx context.Context) NodeInterface {
if o.GetDriver().Get(ctx) {
o.SetIsSync(true)
}
return o
}
func (o *Node) Upsert(ctx context.Context) NodeInterface {
if o.GetID() != id.NilID {
o.GetDriver().Patch(ctx)
} else {
o.SetID(o.GetDriver().Put(ctx))
}
if o.GetParent() != NilNode {
o.GetParent().SetChild(o)
}
return o
}
func (o *Node) Delete(ctx context.Context) NodeInterface {
o.GetDriver().Delete(ctx)
return o.GetParent().DeleteChild(o.GetID())
}
func (o *Node) NewFormat() f3.Interface {
return o.GetDriver().NewFormat()
}
func (o *Node) FromFormat(f f3.Interface) NodeInterface {
o.SetID(id.NewNodeID(f.GetID()))
o.GetDriver().FromFormat(f)
return o
}
func (o *Node) ToFormat() f3.Interface {
if o == nil || o.GetDriver() == nil {
return nil
}
return o.GetDriver().ToFormat()
}
func (o *Node) LookupMappedID(id id.NodeID) id.NodeID { return o.GetDriver().LookupMappedID(id) }
func (o *Node) ApplyAndGet(ctx context.Context, p path.Path, options *ApplyOptions) bool {
applyWrapInGet := func(options *ApplyOptions) ApplyFunc {
return func(ctx context.Context, parent, p path.Path, node NodeInterface) {
if options.fun != nil && (p.Empty() || options.where == ApplyEachNode) {
options.fun(ctx, parent, p, node)
}
if p.Empty() {
return
}
childID := p.First().(NodeInterface).GetID()
// is it a known child?
child := node.GetChild(childID)
// if not, maybe it is a known child referenced by name
if child.GetIsNil() {
childIDFromName := node.GetIDFromName(ctx, childID.String())
if childIDFromName != id.NilID {
childID = childIDFromName
child = node.GetChild(childID)
p.First().(NodeInterface).SetID(childID)
}
}
// not a known child by ID or by Name: make one
if child.GetIsNil() {
child = node.CreateChild(ctx)
child.SetID(childID)
if child.Get(ctx).GetIsSync() {
// only set the child if the driver is able to get it, otherwise
// it means that although the ID is known the child itself cannot be
// obtained and is assumed to be not found
node.SetChild(child)
}
}
}
}
return o.Apply(ctx, path.NewPath(), p, NewApplyOptions(applyWrapInGet(options)).SetWhere(ApplyEachNode))
}
func (o *Node) WalkAndGet(ctx context.Context, parent path.Path, options *WalkOptions) {
walkWrapInGet := func(fun WalkFunc) WalkFunc {
return func(ctx context.Context, p path.Path, node NodeInterface) {
node.Get(ctx)
node.List(ctx)
if fun != nil {
fun(ctx, p, node)
}
}
}
o.Walk(ctx, parent, NewWalkOptions(walkWrapInGet(options.fun)))
}
func (o *Node) Walk(ctx context.Context, parent path.Path, options *WalkOptions) {
util.MaybeTerminate(ctx)
options.fun(ctx, parent, o.GetSelf())
parent = parent.Append(o.GetSelf().(path.PathElement))
for _, child := range o.GetSelf().GetChildren() {
child.Walk(ctx, parent, options)
}
}
func (o *Node) FindAndGet(ctx context.Context, p path.Path) NodeInterface {
var r NodeInterface
r = NilNode
set := func(ctx context.Context, parent, p path.Path, node NodeInterface) {
r = node
}
o.ApplyAndGet(ctx, p, NewApplyOptions(set))
return r
}
func (o *Node) MustFind(p path.Path) NodeInterface {
found := o.Find(p)
if found.GetIsNil() {
panic(fmt.Errorf("%s not found", p))
}
return found
}
func (o *Node) Find(p path.Path) NodeInterface {
if p.Empty() {
return o
}
child := o.GetChild(p.First().(NodeInterface).GetID())
if child.GetIsNil() {
o.Info("'%s' not found", p.String())
return NilNode
}
return child.Find(p.RemoveFirst())
}
func (o *Node) Apply(ctx context.Context, parentPath, p path.Path, options *ApplyOptions) bool {
o.Trace("parent '%s', node '%s', path '%s'", parentPath.ReadableString(), o.String(), p.ReadableString())
util.MaybeTerminate(ctx)
if p.Empty() {
options.fun(ctx, parentPath, p, o.GetSelf())
return true
}
i := p.First().(NodeInterface).GetID().String()
if i == "." {
return o.Apply(ctx, parentPath, p.RemoveFirst(), options)
}
if i == ".." {
parent, parentPath := parentPath.Pop()
return parent.(NodeInterface).Apply(ctx, parentPath, p.RemoveFirst(), options)
}
if options.where == ApplyEachNode {
options.fun(ctx, parentPath, p, o.GetSelf())
}
child := o.GetChild(p.First().(NodeInterface).GetID())
if child.GetIsNil() && options.search == ApplySearchByName {
if childID := o.GetIDFromName(ctx, p.First().(NodeInterface).GetID().String()); childID != id.NilID {
child = o.GetChild(childID)
}
}
if child.GetIsNil() {
return false
}
parentPath = parentPath.Append(o.GetSelf().(path.PathElement))
return child.Apply(ctx, parentPath, p.RemoveFirst(), options)
}
type ErrorNodeNotFound error
func NewError[T error](message string, args ...any) T {
e := fmt.Errorf(message, args...)
return e.(T)
}

296
tree/generic/node_test.go Normal file
View file

@ -0,0 +1,296 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"fmt"
"testing"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"github.com/stretchr/testify/assert"
)
func TestNodeInit(t *testing.T) {
node := NewNode()
assert.NotNil(t, node)
}
func TestNewNodeFromID(t *testing.T) {
{
i := "nodeID"
node := NewNodeFromID(i)
assert.Equal(t, id.NewNodeID(i), node.GetID())
}
{
i := 123
node := NewNodeFromID(i)
assert.Equal(t, id.NewNodeID(fmt.Sprintf("%d", i)), node.GetID())
}
}
func TestNodeIsNil(t *testing.T) {
node := NewNode()
assert.True(t, node.GetIsNil())
node.SetIsNil(false)
assert.False(t, node.GetIsNil())
node.SetIsNil(true)
assert.True(t, node.GetIsNil())
var nilNode *Node
assert.True(t, nilNode.GetIsNil())
}
func TestNodeEquals(t *testing.T) {
ctx := context.Background()
tree := newTestTree()
one := tree.Factory(ctx, kindTestNodeLevelOne)
one.(*testNodeLevelOne).v = 1
assert.True(t, one.Equals(ctx, one))
two := tree.Factory(ctx, kindTestNodeLevelOne)
two.(*testNodeLevelOne).v = 2
assert.False(t, one.Equals(ctx, two))
}
func TestNodeParent(t *testing.T) {
node := NewNode()
assert.True(t, node.GetParent().GetIsNil())
parent := NewNode()
node.SetParent(parent)
assert.True(t, parent == node.GetParent())
}
func TestNodeKind(t *testing.T) {
node := NewNode()
assert.EqualValues(t, kind.KindNil, node.GetKind())
kind := kind.Kind("something")
node.SetKind(kind)
assert.True(t, kind == node.GetKind())
}
func TestNodeMappedID(t *testing.T) {
node := NewNode()
node.SetDriver(NewNullDriver())
assert.EqualValues(t, id.NilID, node.GetMappedID())
mapped := id.NewNodeID("mapped")
node.SetMappedID(mapped)
assert.True(t, mapped == node.GetMappedID())
}
func TestNodeNewFormat(t *testing.T) {
node := NewNode()
node.SetDriver(NewNullDriver())
assert.Panics(t, func() { node.NewFormat() })
}
func TestChildren(t *testing.T) {
parent := NewNode()
assert.Empty(t, parent.GetChildren())
id1 := id.NewNodeID("one")
child1 := NewNode()
child1.SetID(id1)
parent.SetChild(child1)
id2 := id.NewNodeID("two")
child2 := NewNode()
child2.SetID(id2)
parent.SetChild(child2)
children := parent.GetChildren()
assert.Len(t, children, 2)
assert.Equal(t, children[0].GetID(), id1)
assert.Equal(t, children[1].GetID(), id2)
nodeChildren := parent.GetNodeChildren()
assert.Len(t, nodeChildren, 2)
delete(nodeChildren, id1)
parent.SetChildren(nodeChildren)
nodeChildren = parent.GetNodeChildren()
assert.Len(t, nodeChildren, 1)
}
func TestNodeID(t *testing.T) {
node := NewNode()
assert.EqualValues(t, id.NilID, node.GetID())
i := id.NewNodeID("1234")
node.SetID(i)
assert.True(t, i == node.GetID())
}
func TestNodeTree(t *testing.T) {
node := NewNode()
assert.Nil(t, node.GetTree())
tree := NewTree(newTestOptions())
node.SetTree(tree)
assert.True(t, tree == node.GetTree())
}
type driverChildren struct {
NullDriver
}
func (o *driverChildren) ListPage(cxt context.Context, page int) ChildrenSlice {
node := o.GetNode()
tree := node.GetTree()
count := tree.GetPageSize()
if page > 1 {
count = 1
}
offset := (page - 1) * tree.GetPageSize()
children := NewChildrenSlice(0)
for i := 0; i < count; i++ {
child := tree.GetSelf().Factory(cxt, kind.KindNil)
child.SetID(id.NewNodeID(i + offset))
children = append(children, child)
}
return children
}
type driverTreeChildren struct {
NullTreeDriver
}
func (o *driverTreeChildren) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
return &driverChildren{}
}
type treeChildren struct {
Tree
}
func newTreeChildren() TreeInterface {
tree := &treeChildren{}
tree.Init(tree, newTestOptions())
treeDriver := &driverTreeChildren{}
treeDriver.Init()
tree.SetDriver(treeDriver)
tree.Register(kind.KindNil, func(ctx context.Context, kind kind.Kind) NodeInterface {
node := NewNode()
node.SetIsNil(false)
return node
})
return tree
}
func TestNodeListPage(t *testing.T) {
tree := newTreeChildren()
ctx := context.Background()
node := tree.Factory(ctx, kind.KindNil)
children := node.ListPage(context.Background(), int(1))
assert.NotNil(t, children)
assert.EqualValues(t, tree.GetPageSize(), len(children))
children = node.ListPage(context.Background(), int(2))
assert.NotNil(t, children)
assert.EqualValues(t, 1, len(children))
}
func TestNodeList(t *testing.T) {
tree := newTreeChildren()
ctx := context.Background()
node := tree.Factory(ctx, kind.KindNil)
children := node.List(ctx)
assert.EqualValues(t, tree.GetPageSize()+1, len(children))
idThree := id.NewNodeID("3")
childThree := node.GetChild(idThree)
assert.False(t, childThree.GetIsNil())
assert.EqualValues(t, idThree, childThree.GetID())
node.DeleteChild(idThree)
childThree = node.GetChild(idThree)
assert.True(t, childThree.GetIsNil())
}
type nodeGetIDFromName struct {
Node
name string
}
func (o *nodeGetIDFromName) GetIDFromName(ctx context.Context, name string) id.NodeID {
for _, child := range o.GetChildren() {
if child.(*nodeGetIDFromName).name == name {
return child.GetID()
}
}
return id.NilID
}
func newNodeGetIDFromName(i, name string) NodeInterface {
node := &nodeGetIDFromName{}
node.Init(node)
node.SetIsNil(false)
node.SetID(id.NewNodeID(i))
node.name = name
return node
}
func TestNodeGetIDFromName(t *testing.T) {
i := "1243"
name := "NAME"
node := newNodeGetIDFromName(i, name)
ctx := context.Background()
parent := newNodeGetIDFromName("parent", "PARENT")
parent.SetChild(node)
r := parent.GetIDFromName(ctx, "OTHERNAME")
assert.EqualValues(t, id.NilID, r)
r = parent.GetIDFromName(ctx, name)
assert.True(t, r == id.NewNodeID(i))
}
func TestNodePath(t *testing.T) {
{
path := NilNode.GetCurrentPath()
assert.True(t, path.Empty())
pathString := path.PathString()
assert.True(t, pathString.Empty())
assert.EqualValues(t, "", pathString.Join())
}
root := NewNode()
root.SetIsNil(false)
root.SetKind(kind.KindRoot)
level1 := NewNode()
level1.SetIsNil(false)
level1.SetParent(root)
id1 := id.NewNodeID("1")
level1.SetID(id1)
level2 := NewNode()
level2.SetIsNil(false)
level2.SetParent(level1)
id2 := id.NewNodeID("2")
level2.SetID(id2)
{
p := level2.GetCurrentPath()
assert.False(t, p.Empty())
if assert.EqualValues(t, 3, p.Length()) {
assert.True(t, root == p.First(), p.First().(NodeInterface).GetID())
rest := p.RemoveFirst()
assert.True(t, level1 == rest.First(), p.First().(NodeInterface).GetID())
rest = rest.RemoveFirst()
assert.True(t, level2 == rest.First(), p.First().(NodeInterface).GetID())
}
pathString := p.PathString()
assert.False(t, pathString.Empty())
assert.EqualValues(t, 3, len(pathString.Elements()))
assert.EqualValues(t, "/1/2", pathString.Join())
}
}

13
tree/generic/path.go Normal file
View file

@ -0,0 +1,13 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"code.forgejo.org/f3/gof3/v3/path"
)
func NewPathFromString(pathString string) path.Path {
return path.NewPathFromString(NewElementNode, pathString)
}

View file

@ -0,0 +1,92 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"strings"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/path"
"code.forgejo.org/f3/gof3/v3/util"
)
type ErrorRemapReferencesRelative error
func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) {
for _, reference := range f.GetReferences() {
toPath := path.NewPath()
collectTo := func(ctx context.Context, parent, p path.Path, node NodeInterface) {
element := NewNode()
mappedID := node.GetMappedID()
if mappedID.String() == "" {
node.Trace("mapped ID for %s is not defined", p.ReadableString())
}
element.SetID(mappedID)
toPath = toPath.Append(element.(path.PathElement))
}
from := reference.Get()
isRelative := !strings.HasPrefix(from, "/")
if isRelative && !strings.HasPrefix(from, "..") {
panic(NewError[ErrorRemapReferencesRelative]("relative references that do not start with .. are not supported '%s'", from))
}
current := node.GetCurrentPath().String()
fromPath := path.PathAbsolute(NewElementNode, current, from)
node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode))
to := toPath.String()
node.Trace("from '%s' to '%s'", fromPath.ReadableString(), toPath.ReadableString())
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 = path.PathRelativeString(currentMapped, to)
}
node.Trace("convert reference %s => %s", reference.Get(), to)
reference.Set(to)
}
}
func NodeCollectReferences(ctx context.Context, node NodeInterface) []path.Path {
pathToReferences := make(map[string]path.Path, 5)
tree := node.GetTree()
collect := func(ctx context.Context, parent path.Path, node NodeInterface) {
util.MaybeTerminate(ctx)
f := node.GetSelf().ToFormat()
for _, reference := range f.GetReferences() {
absoluteReference := path.PathAbsoluteString(node.GetCurrentPath().String(), reference.Get())
if _, ok := pathToReferences[absoluteReference]; ok {
continue
}
tree.ApplyAndGet(ctx, path.NewPathFromString(NewElementNode, absoluteReference), NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
pathToReferences[absoluteReference] = node.GetCurrentPath()
}))
}
}
node.Walk(ctx, path.NewPath(node.(path.PathElement)), NewWalkOptions(collect))
references := make([]path.Path, 0, len(pathToReferences))
for _, reference := range pathToReferences {
tree.Debug("collect %s", reference)
references = append(references, reference)
}
return references
}
func TreeCollectReferences(ctx context.Context, tree TreeInterface, p path.Path) []path.Path {
var references []path.Path
tree.Apply(ctx, p, NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
references = NodeCollectReferences(ctx, node)
}))
return references
}

View file

@ -0,0 +1,166 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/f3"
"code.forgejo.org/f3/gof3/v3/id"
"code.forgejo.org/f3/gof3/v3/kind"
"code.forgejo.org/f3/gof3/v3/path"
"github.com/stretchr/testify/assert"
)
type referenceFormat struct {
f3.Common
R *f3.Reference
}
func (o *referenceFormat) Clone() f3.Interface {
clone := &referenceFormat{}
*clone = *o
return clone
}
func (o *referenceFormat) GetReferences() f3.References {
references := o.Common.GetReferences()
if o.R != nil {
references = append(references, o.R)
}
return references
}
type referencesNodeDriver struct {
NullDriver
f referenceFormat
}
func (o *referencesNodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
return id.NilID
}
func (o *referencesNodeDriver) Get(context.Context) bool {
return true
}
func (o *referencesNodeDriver) NewFormat() f3.Interface {
return &referenceFormat{}
}
func (o *referencesNodeDriver) ToFormat() f3.Interface {
return &o.f
}
func (o *referencesNodeDriver) FromFormat(f f3.Interface) {
o.f = *f.(*referenceFormat)
}
func newReferencesNodeDriver() NodeDriverInterface {
return &referencesNodeDriver{}
}
type testTreeReferencesDriver struct {
NullTreeDriver
}
func newTestTreeReferencesDriver() TreeDriverInterface {
return &testTreeReferencesDriver{}
}
func (o *testTreeReferencesDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
d := newReferencesNodeDriver()
d.SetTreeDriver(o)
return d
}
var kindTestNodeReferences = kind.Kind("references")
type testNodeReferences struct {
testNode
}
func newTestTreeReferences() TreeInterface {
tree := &testTree{}
tree.Init(tree, newTestOptions())
tree.SetDriver(newTestTreeReferencesDriver())
tree.Register(kindTestNodeReferences, func(ctx context.Context, kind kind.Kind) NodeInterface {
node := &testNodeReferences{}
return node.Init(node)
})
return tree
}
func TestTreeCollectReferences(t *testing.T) {
tree := newTestTreeReferences()
root := tree.Factory(context.Background(), kindTestNodeReferences)
tree.SetRoot(root)
one := tree.Factory(context.Background(), kindTestNodeReferences)
one.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
one.SetID(id.NewNodeID("one"))
root.SetChild(one)
// the second node that has the same reference is here to ensure
// they are deduplicated
two := tree.Factory(context.Background(), kindTestNodeReferences)
two.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
two.SetID(id.NewNodeID("two"))
root.SetChild(two)
references := TreeCollectReferences(context.Background(), tree, path.NewPathFromString(NewElementNode, "/"))
assert.Len(t, references, 1)
assert.EqualValues(t, "/somewhere", references[0].String())
}
func TestRemapReferences(t *testing.T) {
tree := newTestTreeReferences()
root := tree.Factory(context.Background(), kindTestNodeReferences)
tree.SetRoot(root)
one := tree.Factory(context.Background(), kindTestNodeReferences)
one.SetID(id.NewNodeID("one"))
one.SetMappedID(id.NewNodeID("remappedone"))
one.SetParent(root)
root.SetChild(one)
two := tree.Factory(context.Background(), kindTestNodeReferences)
two.FromFormat(&referenceFormat{R: f3.NewReference("/one")})
two.SetID(id.NewNodeID("two"))
two.SetMappedID(id.NewNodeID("two"))
two.SetParent(root)
root.SetChild(two)
{
f := two.ToFormat()
RemapReferences(context.Background(), two, f)
r := f.GetReferences()
if assert.Len(t, r, 1) {
assert.Equal(t, "/remappedone", r[0].Get())
}
}
three := tree.Factory(context.Background(), kindTestNodeReferences)
three.FromFormat(&referenceFormat{R: f3.NewReference("../../one")})
three.SetID(id.NewNodeID("three"))
three.SetMappedID(id.NewNodeID("three"))
three.SetParent(two)
two.SetChild(three)
{
f := three.ToFormat()
RemapReferences(context.Background(), three, f)
r := f.GetReferences()
if assert.Len(t, r, 1) {
assert.Equal(t, "../../remappedone", r[0].Get())
}
}
assert.Panics(t, func() { RemapReferences(context.Background(), three, &referenceFormat{R: f3.NewReference("./one")}) })
}

134
tree/generic/tree.go Normal file
View file

@ -0,0 +1,134 @@
// 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/kind"
"code.forgejo.org/f3/gof3/v3/logger"
"code.forgejo.org/f3/gof3/v3/options"
"code.forgejo.org/f3/gof3/v3/path"
)
type FactoryFun func(ctx context.Context, kind kind.Kind) NodeInterface
type kindMap map[kind.Kind]FactoryFun
type Tree struct {
logger.Logger
opts options.Interface
self TreeInterface
driver TreeDriverInterface
root NodeInterface
kind kindMap
}
func NewTree(opts options.Interface) TreeInterface {
tree := &Tree{}
return tree.Init(tree, opts)
}
func (o *Tree) Init(self TreeInterface, opts options.Interface) TreeInterface {
o.self = self
o.kind = make(kindMap)
o.SetDriver(NewNullTreeDriver())
o.SetOptions(opts)
o.SetLogger(opts.(options.LoggerInterface).GetLogger())
return o
}
func (o *Tree) GetOptions() options.Interface { return o.opts }
func (o *Tree) SetOptions(opts options.Interface) { o.opts = opts }
func (o *Tree) GetSelf() TreeInterface { return o.self }
func (o *Tree) SetSelf(self TreeInterface) { o.self = self }
func (o *Tree) GetRoot() NodeInterface { return o.root }
func (o *Tree) SetRoot(root NodeInterface) { o.root = root }
func (o *Tree) GetDriver() TreeDriverInterface { return o.driver }
func (o *Tree) SetDriver(driver TreeDriverInterface) {
driver.SetTree(o.GetSelf())
o.driver = driver
}
func (o *Tree) GetChildrenKind(parentKind kind.Kind) kind.Kind {
return kind.KindNil
}
func (o *Tree) GetPageSize() int { return o.GetDriver().GetPageSize() }
func (o *Tree) AllocateID() bool { return o.GetDriver().AllocateID() }
func (o *Tree) Walk(ctx context.Context, options *WalkOptions) {
o.GetRoot().Walk(ctx, path.NewPath(), options)
}
func (o *Tree) WalkAndGet(ctx context.Context, options *WalkOptions) {
o.GetRoot().WalkAndGet(ctx, path.NewPath(), options)
}
func (o *Tree) Clear(ctx context.Context) {
rootKind := o.GetRoot().GetKind()
o.SetRoot(o.Factory(ctx, rootKind))
}
func (o *Tree) Exists(ctx context.Context, path path.Path) bool {
return o.Find(path) != NilNode
}
func (o *Tree) MustFind(path path.Path) NodeInterface {
return o.GetRoot().MustFind(path.RemoveFirst())
}
func (o *Tree) Find(path path.Path) NodeInterface {
return o.GetRoot().Find(path.RemoveFirst())
}
func (o *Tree) FindAndGet(ctx context.Context, path path.Path) NodeInterface {
return o.GetRoot().FindAndGet(ctx, path.RemoveFirst())
}
func (o *Tree) Diff(a, b NodeInterface) string {
return o.GetDriver().Diff(a.GetDriver(), b.GetDriver())
}
func (o *Tree) Apply(ctx context.Context, p path.Path, options *ApplyOptions) bool {
if p.Empty() {
return true
}
return o.GetRoot().Apply(ctx, path.NewPath(), p.RemoveFirst(), options)
}
func (o *Tree) ApplyAndGet(ctx context.Context, path path.Path, options *ApplyOptions) bool {
if path.Empty() {
return true
}
return o.GetRoot().ApplyAndGet(ctx, path.RemoveFirst(), options)
}
func (o *Tree) Factory(ctx context.Context, kind kind.Kind) NodeInterface {
var node NodeInterface
if factory, ok := o.kind[kind]; ok {
node = factory(ctx, kind)
} else {
node = NewNode()
}
node.SetIsNil(false)
node.SetKind(kind)
node.SetTree(o.GetSelf())
if o.GetDriver() != nil {
nodeDriver := o.GetDriver().Factory(ctx, kind)
nodeDriver.SetTreeDriver(o.GetDriver())
node.SetDriver(nodeDriver)
}
return node
}
func (o *Tree) Register(kind kind.Kind, factory FactoryFun) {
o.kind[kind] = factory
}

90
tree/generic/tree_test.go Normal file
View file

@ -0,0 +1,90 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"code.forgejo.org/f3/gof3/v3/path"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTreeInit(t *testing.T) {
tree := NewTree(newTestOptions())
assert.NotNil(t, tree)
assert.True(t, tree == tree.GetSelf())
tree.SetSelf(tree)
assert.True(t, tree == tree.GetSelf())
other := NewTree(newTestOptions())
assert.False(t, other == tree.GetSelf())
assert.False(t, other.GetSelf() == tree)
assert.False(t, other.GetSelf() == tree.GetSelf())
}
func TestTreeRoot(t *testing.T) {
tree := NewTree(newTestOptions())
root := NewNode()
tree.SetRoot(root)
assert.True(t, root == tree.GetRoot())
}
func TestTreePageSize(t *testing.T) {
tree := NewTree(newTestOptions())
assert.EqualValues(t, DefaultPageSize, tree.GetPageSize())
pageSize := int(100)
tree.GetDriver().SetPageSize(pageSize)
assert.EqualValues(t, pageSize, tree.GetPageSize())
}
func TestTreeAllocateID(t *testing.T) {
tree := NewTree(newTestOptions())
assert.True(t, tree.AllocateID())
}
func TestTreeFactoryDerived(t *testing.T) {
tree := newTestTree()
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, root == tree.GetRoot())
assert.True(t, kindTestNodeLevelOne == root.GetKind())
assert.True(t, tree.GetSelf() == root.GetTree().GetSelf())
leveltwo := tree.Factory(context.Background(), kindTestNodeLevelTwo)
leveltwo.SetParent(root)
assert.True(t, root == leveltwo.GetParent())
assert.True(t, kindTestNodeLevelTwo == leveltwo.GetKind())
assert.True(t, tree.GetSelf() == leveltwo.GetTree().GetSelf())
}
func TestTreeApply(t *testing.T) {
tree := newTestTree()
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, tree.Apply(context.Background(), path.NewPath(), nil))
assert.True(t, tree.Apply(context.Background(), path.NewPathFromString(NewElementNode, "/"),
NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
require.Equal(t, root, node)
})),
)
}
func TestTreeApplyAndGet(t *testing.T) {
tree := newTestTree()
root := tree.Factory(context.Background(), kindTestNodeLevelOne)
tree.SetRoot(root)
assert.True(t, tree.ApplyAndGet(context.Background(), path.NewPath(), nil))
assert.True(t, tree.ApplyAndGet(context.Background(), path.NewPathFromString(NewElementNode, "/"),
NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
require.Equal(t, root, node)
})),
)
}

320
tree/generic/unify.go Normal file
View file

@ -0,0 +1,320 @@
// 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)
}

View file

@ -0,0 +1,57 @@
// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT
package generic
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTreeUnify(t *testing.T) {
ctx := context.Background()
for _, testCase := range []struct {
name string
originRoot bool
destinationRoot bool
}{
{
name: "Origin",
originRoot: true,
destinationRoot: false,
},
{
name: "Destination",
originRoot: false,
destinationRoot: true,
},
{
name: "None",
originRoot: false,
destinationRoot: false,
},
} {
t.Run(testCase.name, func(t *testing.T) {
origin := newTestTree()
if testCase.originRoot {
origin.SetRoot(origin.Factory(ctx, kindTestNodeLevelOne))
}
destination := newTestTree()
if testCase.destinationRoot {
destination.SetRoot(destination.Factory(ctx, kindTestNodeLevelTwo))
}
TreeUnify(ctx, origin, destination, NewUnifyOptions(destination))
if testCase.originRoot {
destinationRoot := destination.GetRoot()
assert.NotNil(t, destinationRoot)
assert.EqualValues(t, kindTestNodeLevelOne, destinationRoot.GetKind())
} else {
assert.EqualValues(t, nil, destination.GetRoot())
}
})
}
}

View file

@ -0,0 +1,63 @@
// 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/path"
)
type WalkFunc func(context.Context, path.Path, NodeInterface)
type WalkOptions struct {
fun WalkFunc
}
func NewWalkOptions(fun WalkFunc) *WalkOptions {
return &WalkOptions{
fun: fun,
}
}
type ApplyFunc func(ctx context.Context, parentPath, path path.Path, node NodeInterface)
type ApplyWhere bool
const (
ApplyEachNode ApplyWhere = true
ApplyLastNode ApplyWhere = false
)
type ApplySearch bool
const (
ApplySearchByName ApplySearch = true
ApplySearchByID ApplySearch = false
)
type ApplyOptions struct {
fun ApplyFunc
where ApplyWhere
search ApplySearch
}
func NewApplyOptions(fun ApplyFunc) *ApplyOptions {
return &ApplyOptions{
fun: fun,
where: ApplyLastNode,
search: ApplySearchByID,
}
}
func (o *ApplyOptions) SetWhere(where ApplyWhere) *ApplyOptions {
o.where = where
return o
}
func (o *ApplyOptions) SetSearch(search ApplySearch) *ApplyOptions {
o.search = search
return o
}