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..f42faf8 --- /dev/null +++ b/filetree.go @@ -0,0 +1,70 @@ +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 name == "" { + continue + } + if node.children[name] == nil { + return errors.New("Path does not exist") + } + node = node.children[name] + } + // 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 new file mode 100644 index 0000000..4a2e005 --- /dev/null +++ b/tree.go @@ -0,0 +1,118 @@ +package main + +import ( + "errors" + "sort" +) + +const ( + newLine = "\n" + emptySpace = " " + middleItem = "├── " + continueItem = "│ " + lastItem = "└── " +) + +type Tree struct { + root Node + size int +} + +type Node struct { + tree *Tree + parent *Node + name string + data interface{} + children map[string]*Node +} + +func NewTree() (tree *Tree) { + tree = new(Tree) + tree.size = 0 + root := &tree.root + root.tree = tree + root.name = "" + root.children = make(map[string]*Node) + return tree +} + +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 +} + +func (tree *Tree) Root() *Node { + 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") + } + child = NewNode(node, name, data) + node.children[name] = child + node.tree.size++ + return child, nil +} + +func (node *Node) Remove() error { + for _, child := range node.children { + child.Remove() + } + delete(node.parent.children, node.name) + node.tree.size-- + return nil +} + +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) + } + +}