From 488ec1b047fba0839fd2d6c55adc6d13a314b48c Mon Sep 17 00:00:00 2001 From: Alex Goodman <wagoodman@gmail.com> Date: Sun, 20 May 2018 10:39:59 -0400 Subject: [PATCH] add filetree --- Dockerfile | 7 +++- README.md | 2 +- filetree.go | 68 +++++++++++++++++++++++++++++++ filetree_test.go | 61 ++++++++++++++++++++++++++++ tree.go | 101 +++++++++++++++++++++++++++++++++++------------ tree_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 309 insertions(+), 28 deletions(-) create mode 100644 filetree.go create mode 100644 filetree_test.go create mode 100644 tree_test.go diff --git a/Dockerfile b/Dockerfile index 407597d..358f252 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,8 @@ FROM alpine:latest ADD README.md /somefile.txt -RUN cp /somefile.txt /root/somefile.txt \ No newline at end of file +RUN mkdir /root/example +RUN cp /somefile.txt /root/example/somefile1.txt +RUN cp /somefile.txt /root/example/somefile2.txt +RUN cp /somefile.txt /root/example/somefile3.txt +RUN mv /root/example/somefile3.txt /root/saved.txt +RUN rm -rf /root/example/ \ No newline at end of file diff --git a/README.md b/README.md index d3edb03..9be128a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # docker-image-explorer ``` -docker build -t . die-test:latest +docker build -t die-test:latest . go run main.go ``` \ No newline at end of file diff --git a/filetree.go b/filetree.go new file mode 100644 index 0000000..335fbe6 --- /dev/null +++ b/filetree.go @@ -0,0 +1,68 @@ +package main + +import ( + "errors" + "strings" +) + +type FileTree interface { + AddPath(string, interface{}) + RemovePath(string) + Visit(Visiter) +} + +type Visiter func(*Node) + +func (tree *Tree) Visit(visiter Visiter) { + tree.root.Visit(visiter) +} + +func (node *Node) Visit(visiter Visiter) { + for _, child := range node.children { + child.Visit(visiter) + } + visiter(node) +} + +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 + } + // find or create node + if node.children[name] != nil { + node = node.children[name] + } else { + + node, _ = node.AddChild(name, nil) + if err != nil { + + return node, err + } + } + + // attach payload + if idx == len(nodeNames)-1 { + node.data = data + } + + } + return node, nil +} + +func (tree *Tree) RemovePath(path string) error { + nodeNames := strings.Split(path, "/") + node := tree.Root() + for _, name := range nodeNames { + if node.children[name] != nil { + node = node.children[name] + } else { + return errors.New("Path does not exist") + } + } + // this node's parent should be a leaf + return node.Remove() +} diff --git a/filetree_test.go b/filetree_test.go new file mode 100644 index 0000000..ef322bf --- /dev/null +++ b/filetree_test.go @@ -0,0 +1,61 @@ +package main + +import "testing" + +func TestAddPath(t *testing.T) { + tree := NewTree() + tree.AddPath("/etc/nginx/nginx.conf", 1) + tree.AddPath("/etc/nginx/public", 2) + tree.AddPath("/var/run/systemd", 3) + tree.AddPath("/var/run/bashful", 4) + tree.AddPath("/tmp", 5) + tree.AddPath("/tmp/nonsense", 6) + + expected := `. +├── etc +│ └── nginx +│ ├── nginx.conf +│ └── public +├── tmp +│ └── nonsense +└── var + └── run + ├── bashful + └── systemd +` + actual := tree.String() + + if expected != actual { + t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) + } + +} + +func TestRemovePath(t *testing.T) { + tree := NewTree() + tree.AddPath("/etc/nginx/nginx.conf", 1) + tree.AddPath("/etc/nginx/public", 2) + tree.AddPath("/var/run/systemd", 3) + tree.AddPath("/var/run/bashful", 4) + tree.AddPath("/tmp", 5) + tree.AddPath("/tmp/nonsense", 6) + + tree.RemovePath("/var/run/bashful") + tree.RemovePath("/tmp") + + expected := `. +├── etc +│ └── nginx +│ ├── nginx.conf +│ └── public +└── var + └── run + └── systemd +` + actual := tree.String() + + if expected != actual { + t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) + } + +} diff --git a/tree.go b/tree.go index 11c80dd..4a2e005 100644 --- a/tree.go +++ b/tree.go @@ -1,14 +1,21 @@ package main -import "errors" +import ( + "errors" + "sort" +) -type Entity interface { - Visit(Visiter) -} +const ( + newLine = "\n" + emptySpace = " " + middleItem = "├── " + continueItem = "│ " + lastItem = "└── " +) type Tree struct { root Node - size uint + size int } type Node struct { @@ -19,21 +26,23 @@ type Node struct { children map[string]*Node } -type Visiter func(*Node) - func NewTree() (tree *Tree) { tree = new(Tree) - tree.size = 1 + tree.size = 0 root := &tree.root root.tree = tree + root.name = "" root.children = make(map[string]*Node) return tree } -func NewNode(name string, data interface{}) (node *Node) { +func NewNode(parent *Node, name string, data interface{}) (node *Node) { + node = new(Node) node.name = name node.data = data node.children = make(map[string]*Node) + node.parent = parent + node.tree = parent.tree return node } @@ -41,29 +50,69 @@ func (tree *Tree) Root() *Node { return &tree.root } -func (tree *Tree) Visit(visiter Visiter) { - tree.root.Visit(visiter) -} - -func (parent *Node) Add(name string, data interface{}) (child *Node, error error) { - if parent.children[name] != nil { - return nil, errors.New("Duplicate child") +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") } - child = NewNode(name, data) - child.tree = parent.tree - child.parent = parent - child.tree.size++ - parent.children[name] = child + child = NewNode(node, name, data) + node.children[name] = child + node.tree.size++ return child, nil } -func (node *Node) Visit(visiter Visiter) { +func (node *Node) Remove() error { for _, child := range node.children { - child.Visit(visiter) + child.Remove() } - visiter(node) + delete(node.parent.children, node.name) + node.tree.size-- + return nil } -func main() { - +func (node *Node) String() string { + return node.name +} + +func (tree *Tree) String() string { + var renderLine func(string, []bool, bool) string + var walkTree func(*Node, []bool) string + + renderLine = func(text string, spaces []bool, last bool) string { + var result string + for _, space := range spaces { + if space { + result += emptySpace + } else { + result += continueItem + } + } + + indicator := middleItem + if last { + indicator = lastItem + } + + return result + indicator + text + newLine + } + + walkTree = func(node *Node, spaces []bool) string { + var result string + var keys []string + for key := range node.children { + keys = append(keys, key) + } + sort.Strings(keys) + for idx, name := range keys { + child := node.children[name] + last := idx == (len(node.children) - 1) + result += renderLine(child.String(), spaces, last) + if len(child.children) > 0 { + spacesChild := append(spaces, last) + result += walkTree(child, spacesChild) + } + } + return result + } + + return "." + newLine + walkTree(tree.Root(), []bool{}) } diff --git a/tree_test.go b/tree_test.go new file mode 100644 index 0000000..3a835a7 --- /dev/null +++ b/tree_test.go @@ -0,0 +1,98 @@ +package main + +import "testing" + +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.") + } + + two, err := tree.Root().AddChild("nil node!", nil) + if err != nil { + t.Errorf("Adding nil child should not result in error.") + } + + 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.") + } + + expected, actual = 5, tree.size + if expected != actual { + t.Errorf("Expected a tree size of %d got %d.", expected, actual) + } + + expected, actual = 2, len(two.children) + if expected != actual { + t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) + } + + expected, actual = 3, len(tree.Root().children) + if expected != actual { + t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) + } + +} + +func TestRemoveChild(t *testing.T) { + var expected, actual int + + tree := NewTree() + tree.Root().AddChild("first", 1) + two, _ := tree.Root().AddChild("nil", nil) + tree.Root().AddChild("third", 3) + forth, _ := two.AddChild("forth", 4) + two.AddChild("fifth", 5) + + forth.Remove() + + expected, actual = 4, tree.size + if expected != actual { + t.Errorf("Expected a tree size of %d got %d.", expected, actual) + } + + if tree.Root().children["forth"] != nil { + t.Errorf("Expected 'forth' node to be deleted.") + } + + two.Remove() + + expected, actual = 2, tree.size + if expected != actual { + t.Errorf("Expected a tree size of %d got %d.", expected, actual) + } + + if tree.Root().children["nil"] != nil { + t.Errorf("Expected 'nil' node to be deleted.") + } + +} + +func TestPrintTree(t *testing.T) { + tree := NewTree() + tree.Root().AddChild("first node!", nil) + two, _ := tree.Root().AddChild("second node!", nil) + tree.Root().AddChild("third node!", nil) + two.AddChild("forth, one level down...", nil) + + expected := `. +├── first node! +├── second node! +│ └── forth, one level down... +└── third node! +` + actual := tree.String() + + if expected != actual { + t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) + } + +}