Adding upstream version 3.10.8.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
37e9b6d587
commit
03bfe4079e
356 changed files with 28857 additions and 0 deletions
72
tree/generic/compare.go
Normal file
72
tree/generic/compare.go
Normal 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)
|
||||
}
|
226
tree/generic/compare_test.go
Normal file
226
tree/generic/compare_test.go
Normal 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
107
tree/generic/driver_node.go
Normal 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 "" }
|
78
tree/generic/driver_tree.go
Normal file
78
tree/generic/driver_tree.go
Normal 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
31
tree/generic/factory.go
Normal 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
|
||||
}
|
101
tree/generic/interface_node.go
Normal file
101
tree/generic/interface_node.go
Normal 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
|
||||
}
|
58
tree/generic/interface_tree.go
Normal file
58
tree/generic/interface_tree.go
Normal 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
139
tree/generic/main_test.go
Normal 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
52
tree/generic/mirror.go
Normal 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
414
tree/generic/node.go
Normal 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
296
tree/generic/node_test.go
Normal 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
13
tree/generic/path.go
Normal 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)
|
||||
}
|
92
tree/generic/references.go
Normal file
92
tree/generic/references.go
Normal 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
|
||||
}
|
166
tree/generic/references_test.go
Normal file
166
tree/generic/references_test.go
Normal 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
134
tree/generic/tree.go
Normal 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
90
tree/generic/tree_test.go
Normal 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
320
tree/generic/unify.go
Normal 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)
|
||||
}
|
57
tree/generic/unify_test.go
Normal file
57
tree/generic/unify_test.go
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
63
tree/generic/walk_options.go
Normal file
63
tree/generic/walk_options.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue