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)
+	}
+
+}