added tree stacking
This commit is contained in:
parent
ea66c0e810
commit
86e979c80c
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,5 @@
|
||||
.idea
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
@ -11,4 +13,4 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
image
|
||||
image
|
||||
|
122
filetree.go
122
filetree.go
@ -3,31 +3,108 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type FileTree interface {
|
||||
AddPath(string, interface{})
|
||||
RemovePath(string)
|
||||
Visit(Visiter)
|
||||
AddPath(string, interface{}) *Node
|
||||
RemovePath(string) error
|
||||
Visit(Visiter) error
|
||||
// Diff(*Tree) error
|
||||
Stack(*Tree) (Tree, error)
|
||||
}
|
||||
|
||||
type Visiter func(*Node)
|
||||
type Visiter func(*Node) error
|
||||
|
||||
func (tree *Tree) Visit(visiter Visiter) {
|
||||
tree.root.Visit(visiter)
|
||||
func (tree *Tree) Visit(visiter Visiter) error {
|
||||
return tree.root.Visit(visiter)
|
||||
}
|
||||
|
||||
func (node *Node) Visit(visiter Visiter) {
|
||||
func (node *Node) Visit(visiter Visiter) error {
|
||||
for _, child := range node.children {
|
||||
child.Visit(visiter)
|
||||
err := child.Visit(visiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
visiter(node)
|
||||
return visiter(node)
|
||||
}
|
||||
|
||||
func (node *Node) IsWhiteout() bool {
|
||||
return strings.HasPrefix(node.name, ".wh.")
|
||||
}
|
||||
|
||||
func (node *Node) Path() string {
|
||||
path := []string{}
|
||||
curNode := node
|
||||
for {
|
||||
if curNode.parent == nil{
|
||||
break
|
||||
}
|
||||
path = append([]string{curNode.name}, path...)
|
||||
curNode = curNode.parent
|
||||
}
|
||||
return "/" + strings.Join(path, "/")
|
||||
}
|
||||
|
||||
|
||||
func (node *Node) WhiteoutPath() string {
|
||||
path := []string{}
|
||||
curNode := node
|
||||
for {
|
||||
if curNode.parent == nil{
|
||||
break
|
||||
}
|
||||
|
||||
name := curNode.name
|
||||
if curNode == node {
|
||||
name = strings.TrimPrefix(name, ".wh.")
|
||||
}
|
||||
|
||||
path = append([]string{name}, path...)
|
||||
curNode = curNode.parent
|
||||
}
|
||||
return "/" + strings.Join(path, "/")
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (tree *Tree) Stack(upper *Tree) (error) {
|
||||
graft := func(node *Node) error {
|
||||
if node.IsWhiteout() {
|
||||
err := tree.RemovePath(node.WhiteoutPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
|
||||
}
|
||||
} else {
|
||||
newNode, err := tree.AddPath(node.Path(), node.data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return upper.Visit(graft)
|
||||
}
|
||||
|
||||
func (tree *Tree) GetNode(path string) (*Node, error) {
|
||||
nodeNames := strings.Split(path, "/")
|
||||
node := tree.Root()
|
||||
for _, name := range nodeNames {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if node.children[name] == nil {
|
||||
return nil, errors.New("Path does not exist")
|
||||
}
|
||||
node = node.children[name]
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (tree *Tree) AddPath(path string, data interface{}) (*Node, error) {
|
||||
nodeNames := strings.Split(path, "/")
|
||||
node := tree.Root()
|
||||
var err error
|
||||
for idx, name := range nodeNames {
|
||||
if name == "" {
|
||||
continue
|
||||
@ -36,15 +113,12 @@ func (tree *Tree) AddPath(path string, data interface{}) (*Node, error) {
|
||||
if node.children[name] != nil {
|
||||
node = node.children[name]
|
||||
} else {
|
||||
|
||||
node, _ = node.AddChild(name, nil)
|
||||
if err != nil {
|
||||
|
||||
return node, err
|
||||
}
|
||||
// don't attach the payload. The payload is destined for the
|
||||
// path's end node, not any intermediary node.
|
||||
node = node.AddChild(name, nil)
|
||||
}
|
||||
|
||||
// attach payload
|
||||
// attach payload to the last specified node
|
||||
if idx == len(nodeNames)-1 {
|
||||
node.data = data
|
||||
}
|
||||
@ -54,17 +128,9 @@ func (tree *Tree) AddPath(path string, data interface{}) (*Node, error) {
|
||||
}
|
||||
|
||||
func (tree *Tree) RemovePath(path string) error {
|
||||
nodeNames := strings.Split(path, "/")
|
||||
node := tree.Root()
|
||||
for _, name := range nodeNames {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if node.children[name] == nil {
|
||||
return errors.New("Path does not exist")
|
||||
}
|
||||
node = node.children[name]
|
||||
node, err := tree.GetNode(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// this node's parent should be a leaf
|
||||
return node.Remove()
|
||||
}
|
||||
|
@ -59,3 +59,83 @@ func TestRemovePath(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPath(t *testing.T) {
|
||||
expected := "/etc/nginx/nginx.conf"
|
||||
tree := NewTree()
|
||||
node, _ := tree.AddPath(expected, nil)
|
||||
|
||||
actual := node.Path()
|
||||
if expected != actual {
|
||||
t.Errorf("Expected path '%s' got '%s'", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWhiteout(t *testing.T) {
|
||||
tree1 := NewTree()
|
||||
p1, _ := tree1.AddPath("/etc/nginx/public1", 2)
|
||||
p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", 2)
|
||||
|
||||
if p1.IsWhiteout() != false {
|
||||
t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name)
|
||||
}
|
||||
|
||||
if p2.IsWhiteout() != true {
|
||||
t.Errorf("Expected path '%s' to be a whiteout file", p2.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestStack(t *testing.T) {
|
||||
payloadKey := "/var/run/systemd"
|
||||
payloadValue := 1263487
|
||||
|
||||
tree1 := NewTree()
|
||||
|
||||
tree1.AddPath("/etc/nginx/public", 2)
|
||||
tree1.AddPath(payloadKey, 3)
|
||||
tree1.AddPath("/var/run/bashful", 4)
|
||||
tree1.AddPath("/tmp", 5)
|
||||
tree1.AddPath("/tmp/nonsense", 6)
|
||||
|
||||
tree2 := NewTree()
|
||||
// add new files
|
||||
tree2.AddPath("/etc/nginx/nginx.conf", 1)
|
||||
// modify current files
|
||||
tree2.AddPath(payloadKey, payloadValue)
|
||||
// whiteout the following files
|
||||
tree2.AddPath("/var/run/.wh.bashful", nil)
|
||||
tree2.AddPath("/.wh.tmp", nil)
|
||||
|
||||
err := tree1.Stack(tree2)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Could not stack trees: %v", err)
|
||||
}
|
||||
|
||||
expected := `.
|
||||
├── etc
|
||||
│ └── nginx
|
||||
│ ├── nginx.conf
|
||||
│ └── public
|
||||
└── var
|
||||
└── run
|
||||
└── systemd
|
||||
`
|
||||
|
||||
node, err := tree1.GetNode(payloadKey)
|
||||
if err != nil {
|
||||
t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey)
|
||||
}
|
||||
|
||||
if node.data != payloadValue {
|
||||
t.Errorf("Expected '%s' value to be %d but got %d", payloadKey, payloadValue, node.data.(int))
|
||||
}
|
||||
|
||||
actual := tree1.String()
|
||||
|
||||
if expected != actual {
|
||||
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
|
||||
}
|
||||
|
||||
}
|
2
main.go
2
main.go
@ -55,7 +55,7 @@ func saveImage(readCloser io.ReadCloser) {
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
func demo() {
|
||||
ctx := context.Background()
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
|
48
tree.go
48
tree.go
@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@ -14,7 +13,7 @@ const (
|
||||
)
|
||||
|
||||
type Tree struct {
|
||||
root Node
|
||||
root *Node
|
||||
size int
|
||||
}
|
||||
|
||||
@ -29,10 +28,9 @@ type Node struct {
|
||||
func NewTree() (tree *Tree) {
|
||||
tree = new(Tree)
|
||||
tree.size = 0
|
||||
root := &tree.root
|
||||
root.tree = tree
|
||||
root.name = ""
|
||||
root.children = make(map[string]*Node)
|
||||
tree.root = new(Node)
|
||||
tree.root.tree = tree
|
||||
tree.root.children = make(map[string]*Node)
|
||||
return tree
|
||||
}
|
||||
|
||||
@ -47,17 +45,19 @@ func NewNode(parent *Node, name string, data interface{}) (node *Node) {
|
||||
}
|
||||
|
||||
func (tree *Tree) Root() *Node {
|
||||
return &tree.root
|
||||
return tree.root
|
||||
}
|
||||
|
||||
func (node *Node) AddChild(name string, data interface{}) (child *Node, error error) {
|
||||
if node.children[name] != nil {
|
||||
return nil, errors.New("Tree node already exists")
|
||||
}
|
||||
func (node *Node) AddChild(name string, data interface{}) (child *Node) {
|
||||
child = NewNode(node, name, data)
|
||||
node.children[name] = child
|
||||
node.tree.size++
|
||||
return child, nil
|
||||
if node.children[name] != nil {
|
||||
// tree node already exists, replace the payload, keep the children
|
||||
node.children[name].data = data
|
||||
} else {
|
||||
node.children[name] = child
|
||||
node.tree.size++
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
func (node *Node) Remove() error {
|
||||
@ -116,3 +116,23 @@ func (tree *Tree) String() string {
|
||||
|
||||
return "." + newLine + walkTree(tree.Root(), []bool{})
|
||||
}
|
||||
|
||||
func (node *Node) Copy() *Node {
|
||||
// newNode := new(Node)
|
||||
// *newNode = *node
|
||||
// return newNode
|
||||
newNode := NewNode(node.parent, node.name, node.data)
|
||||
for name, child := range node.children {
|
||||
newNode.children[name] = child.Copy()
|
||||
}
|
||||
return newNode
|
||||
}
|
||||
|
||||
func (tree *Tree) Copy() *Tree {
|
||||
newTree := NewTree()
|
||||
*newTree = *tree
|
||||
newTree.root = tree.Root().Copy()
|
||||
|
||||
return newTree
|
||||
}
|
||||
|
||||
|
31
tree_test.go
31
tree_test.go
@ -6,24 +6,14 @@ func TestAddChild(t *testing.T) {
|
||||
var expected, actual int
|
||||
tree := NewTree()
|
||||
|
||||
_, err := tree.Root().AddChild("first node!", 1)
|
||||
if err != nil {
|
||||
t.Errorf("Adding valued child should not result in error.")
|
||||
}
|
||||
one := tree.Root().AddChild("first node!", 1)
|
||||
|
||||
two, err := tree.Root().AddChild("nil node!", nil)
|
||||
if err != nil {
|
||||
t.Errorf("Adding nil child should not result in error.")
|
||||
}
|
||||
two := tree.Root().AddChild("nil node!", nil)
|
||||
|
||||
tree.Root().AddChild("third node!", 3)
|
||||
two.AddChild("forth, one level down...", 4)
|
||||
two.AddChild("fifth, one level down...", 5)
|
||||
|
||||
_, err = two.AddChild("fifth, one level down...", 5)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error when adding duplicate nodes, no error given.")
|
||||
}
|
||||
two.AddChild("fifth, one level down...", 5)
|
||||
|
||||
expected, actual = 5, tree.size
|
||||
if expected != actual {
|
||||
@ -40,6 +30,15 @@ func TestAddChild(t *testing.T) {
|
||||
t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual)
|
||||
}
|
||||
|
||||
if two.data != nil {
|
||||
t.Errorf("Expected 'ones' payload to be nil got %d.", two.data)
|
||||
}
|
||||
|
||||
expected, actual = 1, one.data.(int)
|
||||
if expected != actual {
|
||||
t.Errorf("Expected 'ones' payload to be %d got %d.", expected, actual)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveChild(t *testing.T) {
|
||||
@ -47,9 +46,9 @@ func TestRemoveChild(t *testing.T) {
|
||||
|
||||
tree := NewTree()
|
||||
tree.Root().AddChild("first", 1)
|
||||
two, _ := tree.Root().AddChild("nil", nil)
|
||||
two := tree.Root().AddChild("nil", nil)
|
||||
tree.Root().AddChild("third", 3)
|
||||
forth, _ := two.AddChild("forth", 4)
|
||||
forth := two.AddChild("forth", 4)
|
||||
two.AddChild("fifth", 5)
|
||||
|
||||
forth.Remove()
|
||||
@ -79,7 +78,7 @@ func TestRemoveChild(t *testing.T) {
|
||||
func TestPrintTree(t *testing.T) {
|
||||
tree := NewTree()
|
||||
tree.Root().AddChild("first node!", nil)
|
||||
two, _ := tree.Root().AddChild("second node!", nil)
|
||||
two := tree.Root().AddChild("second node!", nil)
|
||||
tree.Root().AddChild("third node!", nil)
|
||||
two.AddChild("forth, one level down...", nil)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user