diff --git a/.gitignore b/.gitignore index fac93dda..79dd3810 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.idea +/.idea # Binaries for programs and plugins *.exe @@ -13,4 +13,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -image +/build +/_vendor* +/vendor +/.image diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 00000000..5166b9e6 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,104 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/Microsoft/go-winio" + packages = ["."] + revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f" + version = "v0.4.7" + +[[projects]] + name = "github.com/docker/distribution" + packages = [ + "digest", + "reference" + ] + revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89" + version = "v2.6.2" + +[[projects]] + name = "github.com/docker/docker" + packages = [ + "api/types", + "api/types/blkiodev", + "api/types/container", + "api/types/events", + "api/types/filters", + "api/types/mount", + "api/types/network", + "api/types/reference", + "api/types/registry", + "api/types/strslice", + "api/types/swarm", + "api/types/time", + "api/types/versions", + "api/types/volume", + "client", + "pkg/tlsconfig" + ] + revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363" + version = "v1.13.1" + +[[projects]] + name = "github.com/docker/go-connections" + packages = [ + "nat", + "sockets", + "tlsconfig" + ] + revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" + version = "v0.3.0" + +[[projects]] + name = "github.com/docker/go-units" + packages = ["."] + revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" + version = "v0.3.3" + +[[projects]] + branch = "master" + name = "github.com/jroimartin/gocui" + packages = ["."] + revision = "c055c87ae801372cd74a0839b972db4f7697ae5f" + +[[projects]] + name = "github.com/mattn/go-runewidth" + packages = ["."] + revision = "9e777a8366cce605130a531d2cd6363d07ad7317" + version = "v0.0.2" + +[[projects]] + branch = "master" + name = "github.com/nsf/termbox-go" + packages = ["."] + revision = "21a4d435a86280a2927985fd6296de56cbce453e" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = [ + "context", + "context/ctxhttp", + "internal/socks", + "proxy" + ] + revision = "1e491301e022f8f977054da4c2d852decd59571f" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["windows"] + revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "73c0fae1988538f4def02f2bd28830793264b4260b25d078b561413085a81845" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 00000000..2fa2ebae --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,43 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/docker/docker" + version = "1.13.1" + +[[constraint]] + name = "github.com/jroimartin/gocui" + # version = "0.3.0" + branch = "master" + +[[constraint]] + branch = "master" + name = "golang.org/x/net" + +[prune] + go-tests = true + unused-packages = true diff --git a/Makefile b/Makefile index 36ef9db6..796f6da3 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,34 @@ -SHELL := /bin/bash -.DEFAULT_GOAL := run -.PHONY: run - -run: - go run main.go \ - filechangeinfo.go \ - filenode.go \ - filetree.go \ - tar_read.go \ - filetreeview.go \ - layerview.go +BIN = die + +all: clean build + +run: build + ./build/$(BIN) + +build: deps + go build -o build/$(BIN) ./cmd/... + +install: deps + go install ./... + +deps: + command -v dep >/dev/null || go get -u github.com/golang/dep/cmd/dep + dep ensure + +test: build + @! git grep tcell -- ':!tui/' ':!Gopkg.lock' ':!Gopkg.toml' ':!Makefile' + go test -v ./... + +lint: lintdeps build + golint -set_exit_status $$(go list ./... | grep -v /vendor/) + +lintdeps: + go get -d -v -t ./... + command -v golint >/dev/null || go get -u github.com/golang/lint/golint + +clean: + rm -rf build + rm -rf vendor + go clean + +.PHONY: build install deps test lint lintdeps clean \ No newline at end of file diff --git a/cmd/die/main.go b/cmd/die/main.go new file mode 100644 index 00000000..ab39eade --- /dev/null +++ b/cmd/die/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "os" + "github.com/wagoodman/docker-image-explorer/image" + "github.com/wagoodman/docker-image-explorer/ui" +) + +const name = "die" +const version = "v0.0.0" +const author = "wagoodman" + +func main() { + os.Exit(run(os.Args)) +} + + +func run(args []string) int { + image.WriteImage() + manifest, refTrees := image.InitializeData() + + ui.Run(manifest, refTrees) + return 0 +} + diff --git a/filechangeinfo.go b/filetree/changeinfo.go similarity index 80% rename from filechangeinfo.go rename to filetree/changeinfo.go index faa85ba4..f1b19baf 100644 --- a/filechangeinfo.go +++ b/filetree/changeinfo.go @@ -1,4 +1,4 @@ -package main +package filetree import ( "bytes" @@ -6,10 +6,10 @@ import ( ) type FileChangeInfo struct { - path string - typeflag byte - md5sum [16]byte - diffType DiffType + Path string + Typeflag byte + MD5sum [16]byte + DiffType DiffType } type DiffType int @@ -51,8 +51,8 @@ func (a *FileChangeInfo) getDiffType(b *FileChangeInfo) DiffType { if a == nil || b == nil { return Changed } - if a.typeflag == b.typeflag { - if bytes.Compare(a.md5sum[:], b.md5sum[:]) == 0 { + if a.Typeflag == b.Typeflag { + if bytes.Compare(a.MD5sum[:], b.MD5sum[:]) == 0 { return Unchanged } } diff --git a/filechangeinfo_test.go b/filetree/changeinfo_test.go similarity index 62% rename from filechangeinfo_test.go rename to filetree/changeinfo_test.go index a1152ff7..1892ac7e 100644 --- a/filechangeinfo_test.go +++ b/filetree/changeinfo_test.go @@ -1,4 +1,4 @@ -package main +package filetree import ( "fmt" @@ -6,9 +6,9 @@ import ( ) func TestAssignDiffType(t *testing.T) { - tree := NewTree() + tree := NewFileTree() tree.AddPath("/usr", BlankFileChangeInfo("/usr", Changed)) - if tree.root.children["usr"].data.diffType != Changed { + if tree.Root.Children["usr"].Data.DiffType != Changed { t.Fail() } } @@ -29,25 +29,25 @@ func TestMergeDiffTypes(t *testing.T) { } func TestDiffTypeFromChildren(t *testing.T) { - tree := NewTree() + tree := NewFileTree() tree.AddPath("/usr", BlankFileChangeInfo("/usr", Unchanged)) info1 := BlankFileChangeInfo("/usr/bin", Added) tree.AddPath("/usr/bin", info1) info2 := BlankFileChangeInfo("/usr/bin2", Removed) tree.AddPath("/usr/bin2", info2) - tree.root.children["usr"].deriveDiffType(Unchanged) - if tree.root.children["usr"].data.diffType != Changed { - t.Errorf("Expected Changed but got %v", tree.root.children["usr"].data.diffType) + tree.Root.Children["usr"].deriveDiffType(Unchanged) + if tree.Root.Children["usr"].Data.DiffType != Changed { + t.Errorf("Expected Changed but got %v", tree.Root.Children["usr"].Data.DiffType) } } func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) error { - if node.data == nil { - t.Errorf("Expected *FileChangeInfo but got nil at path %s", node.Path()) - return fmt.Errorf("expected *FileChangeInfo but got nil at path %s", node.Path()) + if node.Data == nil { + t.Errorf("Expected *FileChangeInfo but got nil at Path %s", node.Path()) + return fmt.Errorf("expected *FileChangeInfo but got nil at Path %s", node.Path()) } - if node.data.diffType != expectedDiffType { - t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.data.diffType) + if node.Data.DiffType != expectedDiffType { + t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.Data.DiffType) return fmt.Errorf("Assertion failed") } return nil @@ -55,10 +55,10 @@ func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) err func BlankFileChangeInfo(path string, diffType DiffType) (f *FileChangeInfo) { result := FileChangeInfo{ - path: path, - typeflag: 1, - md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, - diffType: diffType, + Path: path, + Typeflag: 1, + MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + DiffType: diffType, } return &result } diff --git a/filenode.go b/filetree/node.go similarity index 69% rename from filenode.go rename to filetree/node.go index ec06eef1..394cf930 100644 --- a/filenode.go +++ b/filetree/node.go @@ -1,4 +1,4 @@ -package main +package filetree import ( "sort" @@ -6,25 +6,25 @@ import ( ) type FileNode struct { - tree *FileTree - parent *FileNode - name string - collapsed bool - data *FileChangeInfo - children map[string]*FileNode + Tree *FileTree + Parent *FileNode + Name string + Collapsed bool + Data *FileChangeInfo + Children map[string]*FileNode } func NewNode(parent *FileNode, name string, data *FileChangeInfo) (node *FileNode) { node = new(FileNode) - node.name = name + node.Name = name if data == nil { data = &FileChangeInfo{} } - node.data = data - node.children = make(map[string]*FileNode) - node.parent = parent + node.Data = data + node.Children = make(map[string]*FileNode) + node.Parent = parent if parent != nil { - node.tree = parent.tree + node.Tree = parent.Tree } return node } @@ -33,46 +33,46 @@ func (node *FileNode) Copy() *FileNode { // newNode := new(FileNode) // *newNode = *node // return newNode - newNode := NewNode(node.parent, node.name, node.data) - for name, child := range node.children { - newNode.children[name] = child.Copy() + newNode := NewNode(node.Parent, node.Name, node.Data) + for name, child := range node.Children { + newNode.Children[name] = child.Copy() } return newNode } func (node *FileNode) AddChild(name string, data *FileChangeInfo) (child *FileNode) { child = NewNode(node, name, data) - if node.children[name] != nil { + if node.Children[name] != nil { // tree node already exists, replace the payload, keep the children - node.children[name].data = data + node.Children[name].Data = data } else { - node.children[name] = child - node.tree.size++ + node.Children[name] = child + node.Tree.Size++ } return child } func (node *FileNode) Remove() error { - for _, child := range node.children { + for _, child := range node.Children { child.Remove() } - delete(node.parent.children, node.name) - node.tree.size-- + delete(node.Parent.Children, node.Name) + node.Tree.Size-- return nil } func (node *FileNode) String() string { - return node.name + return node.Name } func (node *FileNode) Visit(visiter Visiter) error { var keys []string - for key := range node.children { + for key := range node.Children { keys = append(keys, key) } sort.Strings(keys) for _, name := range keys { - child := node.children[name] + child := node.Children[name] err := child.Visit(visiter) if err != nil { return err @@ -88,12 +88,12 @@ func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEval } var keys []string - for key := range node.children { + for key := range node.Children { keys = append(keys, key) } sort.Strings(keys) for _, name := range keys { - child := node.children[name] + child := node.Children[name] if evaluator == nil || !evaluator(node) { continue } @@ -106,31 +106,31 @@ func (node *FileNode) VisitDepthParentFirst(visiter Visiter, evaluator VisitEval } func (node *FileNode) IsWhiteout() bool { - return strings.HasPrefix(node.name, whiteoutPrefix) + return strings.HasPrefix(node.Name, whiteoutPrefix) } func (node *FileNode) Path() string { path := []string{} curNode := node for { - if curNode.parent == nil { + if curNode.Parent == nil { break } - name := curNode.name + name := curNode.Name if curNode == node { // white out prefixes are fictitious on leaf nodes name = strings.TrimPrefix(name, whiteoutPrefix) } path = append([]string{name}, path...) - curNode = curNode.parent + curNode = curNode.Parent } return "/" + strings.Join(path, "/") } func (node *FileNode) IsLeaf() bool { - return len(node.children) == 0 + return len(node.Children) == 0 } func (node *FileNode) deriveDiffType(diffType DiffType) error { @@ -143,9 +143,9 @@ func (node *FileNode) deriveDiffType(diffType DiffType) error { } myDiffType := diffType - for _, v := range node.children { - vData := v.data - myDiffType = myDiffType.merge(vData.diffType) + for _, v := range node.Children { + vData := v.Data + myDiffType = myDiffType.merge(vData.DiffType) } node.AssignDiffType(myDiffType) @@ -156,7 +156,7 @@ func (node *FileNode) AssignDiffType(diffType DiffType) error { if node.Path() == "/" { return nil } - node.data.diffType = diffType + node.Data.DiffType = diffType return nil } @@ -177,10 +177,10 @@ func (a *FileNode) compare(b *FileNode) DiffType { if b.IsWhiteout() { return Removed } - if a.name != b.name { + if a.Name != b.Name { panic("comparing mismatched nodes") } // TODO: fails on nil - return a.data.getDiffType(b.data) + return a.Data.getDiffType(b.Data) } diff --git a/filenode_test.go b/filetree/node_test.go similarity index 61% rename from filenode_test.go rename to filetree/node_test.go index 910912c5..17fa86b8 100644 --- a/filenode_test.go +++ b/filetree/node_test.go @@ -1,49 +1,49 @@ -package main +package filetree import "testing" func TestAddChild(t *testing.T) { var expected, actual int - tree := NewTree() + tree := NewFileTree() payload := FileChangeInfo{ - path: "stufffffs", + Path: "stufffffs", } - one := tree.Root().AddChild("first node!", &payload) + one := tree.Root.AddChild("first node!", &payload) - two := tree.Root().AddChild("nil node!", nil) + two := tree.Root.AddChild("nil node!", nil) - tree.Root().AddChild("third node!", nil) + tree.Root.AddChild("third node!", nil) two.AddChild("forth, one level down...", nil) two.AddChild("fifth, one level down...", nil) two.AddChild("fifth, one level down...", nil) - expected, actual = 5, tree.size + 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) + 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) + expected, actual = 3, len(tree.Root.Children) if expected != actual { t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) } expectedFC := &FileChangeInfo{ - path: "stufffffs", + Path: "stufffffs", } - actualFC := one.data + actualFC := one.Data if *expectedFC != *actualFC { t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC) } - if *two.data != *new(FileChangeInfo) { - t.Errorf("Expected 'twos' payload to be nil got %d.", two.data) + if *two.Data != *new(FileChangeInfo) { + t.Errorf("Expected 'twos' payload to be nil got %d.", two.Data) } } @@ -51,32 +51,32 @@ func TestAddChild(t *testing.T) { func TestRemoveChild(t *testing.T) { var expected, actual int - tree := NewTree() - tree.Root().AddChild("first", nil) - two := tree.Root().AddChild("nil", nil) - tree.Root().AddChild("third", nil) + tree := NewFileTree() + tree.Root.AddChild("first", nil) + two := tree.Root.AddChild("nil", nil) + tree.Root.AddChild("third", nil) forth := two.AddChild("forth", nil) two.AddChild("fifth", nil) forth.Remove() - expected, actual = 4, tree.size + 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 { + if tree.Root.Children["forth"] != nil { t.Errorf("Expected 'forth' node to be deleted.") } two.Remove() - expected, actual = 2, tree.size + 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 { + if tree.Root.Children["nil"] != nil { t.Errorf("Expected 'nil' node to be deleted.") } @@ -84,25 +84,25 @@ func TestRemoveChild(t *testing.T) { func TestPath(t *testing.T) { expected := "/etc/nginx/nginx.conf" - tree := NewTree() + tree := NewFileTree() node, _ := tree.AddPath(expected, nil) actual := node.Path() if expected != actual { - t.Errorf("Expected path '%s' got '%s'", expected, actual) + t.Errorf("Expected Path '%s' got '%s'", expected, actual) } } func TestIsWhiteout(t *testing.T) { - tree1 := NewTree() + tree1 := NewFileTree() p1, _ := tree1.AddPath("/etc/nginx/public1", nil) p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", nil) if p1.IsWhiteout() != false { - t.Errorf("Expected path '%s' to **not** be a whiteout file", p1.name) + 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) + t.Errorf("Expected Path '%s' to be a whiteout file", p2.Name) } } diff --git a/filetree.go b/filetree/tree.go similarity index 79% rename from filetree.go rename to filetree/tree.go index 3c872318..d1efd1a3 100644 --- a/filetree.go +++ b/filetree/tree.go @@ -1,4 +1,4 @@ -package main +package filetree import ( "errors" @@ -19,24 +19,20 @@ const ( ) type FileTree struct { - root *FileNode - size int - name string + Root *FileNode + Size int + Name string } -func NewTree() (tree *FileTree) { +func NewFileTree() (tree *FileTree) { tree = new(FileTree) - tree.size = 0 - tree.root = new(FileNode) - tree.root.tree = tree - tree.root.children = make(map[string]*FileNode) + tree.Size = 0 + tree.Root = new(FileNode) + tree.Root.Tree = tree + tree.Root.Children = make(map[string]*FileNode) return tree } -func (tree *FileTree) Root() *FileNode { - return tree.root -} - func (tree *FileTree) String() string { var renderLine func(string, []bool, bool, bool) string var walkTree func(*FileNode, []bool, int) string @@ -67,16 +63,16 @@ func (tree *FileTree) String() string { walkTree = func(node *FileNode, spaces []bool, depth int) string { var result string var keys []string - for key := range node.children { + 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) - showCollapsed := child.collapsed && len(child.children) > 0 + child := node.Children[name] + last := idx == (len(node.Children) - 1) + showCollapsed := child.Collapsed && len(child.Children) > 0 result += renderLine(child.String(), spaces, last, showCollapsed) - if len(child.children) > 0 && !child.collapsed { + if len(child.Children) > 0 && !child.Collapsed { spacesChild := append(spaces, last) result += walkTree(child, spacesChild, depth+1) } @@ -84,15 +80,15 @@ func (tree *FileTree) String() string { return result } - return "." + newLine + walkTree(tree.Root(), []bool{}, 0) + return "." + newLine + walkTree(tree.Root, []bool{}, 0) } func (tree *FileTree) Copy() *FileTree { - newTree := NewTree() + newTree := NewFileTree() *newTree = *tree - newTree.root = tree.Root().Copy() + newTree.Root = tree.Root.Copy() newTree.Visit(func(node *FileNode) error { - node.tree = newTree + node.Tree = newTree return nil }) @@ -103,11 +99,11 @@ type Visiter func(*FileNode) error type VisitEvaluator func(*FileNode) bool func (tree *FileTree) Visit(visiter Visiter) error { - return tree.root.Visit(visiter) + return tree.Root.Visit(visiter) } func (tree *FileTree) VisitDepthParentFirst(visiter Visiter, evaluator VisitEvaluator) error { - return tree.root.VisitDepthParentFirst(visiter, evaluator) + return tree.Root.VisitDepthParentFirst(visiter, evaluator) } func (tree *FileTree) Stack(upper *FileTree) error { @@ -118,7 +114,7 @@ func (tree *FileTree) Stack(upper *FileTree) error { return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error()) } } else { - newNode, err := tree.AddPath(node.Path(), node.data) + newNode, err := tree.AddPath(node.Path(), node.Data) if err != nil { return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error()) } @@ -130,38 +126,38 @@ func (tree *FileTree) Stack(upper *FileTree) error { func (tree *FileTree) GetNode(path string) (*FileNode, error) { nodeNames := strings.Split(path, "/") - node := tree.Root() + node := tree.Root for _, name := range nodeNames { if name == "" { continue } - if node.children[name] == nil { + if node.Children[name] == nil { return nil, errors.New("Path does not exist") } - node = node.children[name] + node = node.Children[name] } return node, nil } func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) { nodeNames := strings.Split(path, "/") - node := tree.Root() + node := tree.Root for idx, name := range nodeNames { if name == "" { continue } // find or create node - if node.children[name] != nil { - node = node.children[name] + if node.Children[name] != nil { + node = node.Children[name] } else { // don't attach the payload. The payload is destined for the - // path's end node, not any intermediary node. + // Path's end node, not any intermediary node. node = node.AddChild(name, nil) } // attach payload to the last specified node if idx == len(nodeNames)-1 { - node.data = data + node.Data = data } } @@ -186,7 +182,7 @@ func (tree *FileTree) compare(upper *FileTree) error { } else { existingNode, _ := tree.GetNode(node.Path()) if existingNode == nil { - newNode, err := tree.AddPath(node.Path(), node.data) + newNode, err := tree.AddPath(node.Path(), node.Data) fmt.Printf("added new node at %s\n", newNode.Path()) if err != nil { return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error()) diff --git a/filetree_test.go b/filetree/tree_test.go similarity index 80% rename from filetree_test.go rename to filetree/tree_test.go index cf82ff0e..67d6c2e0 100644 --- a/filetree_test.go +++ b/filetree/tree_test.go @@ -1,4 +1,4 @@ -package main +package filetree import ( "fmt" @@ -6,10 +6,10 @@ import ( ) 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) + tree := NewFileTree() + 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 := `. @@ -27,7 +27,7 @@ func TestPrintTree(t *testing.T) { } func TestAddPath(t *testing.T) { - tree := NewTree() + tree := NewFileTree() tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/var/run/systemd", nil) @@ -56,7 +56,7 @@ func TestAddPath(t *testing.T) { } func TestRemovePath(t *testing.T) { - tree := NewTree() + tree := NewFileTree() tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/var/run/systemd", nil) @@ -87,10 +87,10 @@ func TestRemovePath(t *testing.T) { func TestStack(t *testing.T) { payloadKey := "/var/run/systemd" payloadValue := FileChangeInfo{ - path: "yup", + Path: "yup", } - tree1 := NewTree() + tree1 := NewFileTree() tree1.AddPath("/etc/nginx/public", nil) tree1.AddPath(payloadKey, nil) @@ -98,7 +98,7 @@ func TestStack(t *testing.T) { tree1.AddPath("/tmp", nil) tree1.AddPath("/tmp/nonsense", nil) - tree2 := NewTree() + tree2 := NewFileTree() // add new files tree2.AddPath("/etc/nginx/nginx.conf", nil) // modify current files @@ -128,8 +128,8 @@ func TestStack(t *testing.T) { t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey) } - if *node.data != payloadValue { - t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.data) + if *node.Data != payloadValue { + t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.Data) } actual := tree1.String() @@ -141,7 +141,7 @@ func TestStack(t *testing.T) { } func TestCopy(t *testing.T) { - tree := NewTree() + tree := NewFileTree() tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/var/run/systemd", nil) @@ -162,8 +162,8 @@ func TestCopy(t *testing.T) { └── systemd ` - newTree := tree.Copy() - actual := newTree.String() + NewFileTree := tree.Copy() + actual := NewFileTree.String() if expected != actual { t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) @@ -172,16 +172,16 @@ func TestCopy(t *testing.T) { } func TestCompareWithNoChanges(t *testing.T) { - lowerTree := NewTree() - upperTree := NewTree() + lowerTree := NewFileTree() + upperTree := NewFileTree() paths := [...]string{"/etc", "/etc/sudoers", "/etc/hosts", "/usr/bin", "/usr/bin/bash", "/usr"} for _, value := range paths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } lowerTree.AddPath(value, &fakeData) upperTree.AddPath(value, &fakeData) @@ -191,12 +191,12 @@ func TestCompareWithNoChanges(t *testing.T) { if n.Path() == "/" { return nil } - if n.data == nil { + if n.Data == nil { t.Errorf("Expected *FileChangeInfo but got nil") return fmt.Errorf("expected *FileChangeInfo but got nil") } - if (n.data.diffType) != Unchanged { - t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.data.diffType) + if (n.Data.DiffType) != Unchanged { + t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.Data.DiffType) } return nil } @@ -207,27 +207,27 @@ func TestCompareWithNoChanges(t *testing.T) { } func TestCompareWithAdds(t *testing.T) { - lowerTree := NewTree() - upperTree := NewTree() + lowerTree := NewFileTree() + upperTree := NewFileTree() lowerPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin"} upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"} for _, value := range lowerPaths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } lowerTree.AddPath(value, &fakeData) } for _, value := range upperPaths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } upperTree.AddPath(value, &fakeData) } @@ -258,27 +258,27 @@ func TestCompareWithAdds(t *testing.T) { } func TestCompareWithChanges(t *testing.T) { - lowerTree := NewTree() - upperTree := NewTree() + lowerTree := NewFileTree() + upperTree := NewFileTree() lowerPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} for _, value := range lowerPaths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } lowerTree.AddPath(value, &fakeData) } for _, value := range upperPaths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } upperTree.AddPath(value, &fakeData) } @@ -298,7 +298,7 @@ func TestCompareWithChanges(t *testing.T) { } func TestStackRange(t *testing.T) { - tree := NewTree() + tree := NewFileTree() tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/var/run/systemd", nil) @@ -309,27 +309,27 @@ func TestStackRange(t *testing.T) { tree.RemovePath("/var/run/bashful") tree.RemovePath("/tmp") - lowerTree := NewTree() - upperTree := NewTree() + lowerTree := NewFileTree() + upperTree := NewFileTree() lowerPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} for _, value := range lowerPaths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } lowerTree.AddPath(value, &fakeData) } for _, value := range upperPaths { fakeData := FileChangeInfo{ - path: value, - typeflag: 1, - md5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, - diffType: Unchanged, + Path: value, + Typeflag: 1, + MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + DiffType: Unchanged, } upperTree.AddPath(value, &fakeData) } diff --git a/filetreeview.go b/filetreeview.go deleted file mode 100644 index 1a24fdf0..00000000 --- a/filetreeview.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/jroimartin/gocui" -) - -type FileTreeView struct { - name string - gui *gocui.Gui - view *gocui.View - absTreeIndex uint - tree *FileTree -} - -func NewFileTreeView(name string, gui *gocui.Gui, view *gocui.View, tree *FileTree) (treeview *FileTreeView) { - treeview = new(FileTreeView) - - // populate main fields - treeview.name = name - treeview.gui = gui - treeview.view = view - treeview.tree = tree - - // set view options - treeview.view.Editable = false - treeview.view.Wrap = false - treeview.view.Highlight = true - treeview.view.SelBgColor = gocui.ColorGreen - treeview.view.SelFgColor = gocui.ColorBlack - - treeview.render() - - return treeview -} - -func (view *FileTreeView) keybindings() error { - if err := view.gui.SetKeybinding(view.name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorDown() }); err != nil { - return err - } - if err := view.gui.SetKeybinding(view.name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorUp() }); err != nil { - return err - } - if err := view.gui.SetKeybinding(view.name, gocui.KeySpace, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil { - return err - } - return nil -} - -// Mehh, this is just a bad method -func (view *FileTreeView) reset(tree *FileTree) error { - view.tree = tree - view.view.SetCursor(0, 0) - view.absTreeIndex = 0 - return view.render() -} - -func (view *FileTreeView) cursorDown() error { - err := cursorDown(view.gui, view.view) - if err == nil { - view.absTreeIndex++ - } - return nil -} - -func (view *FileTreeView) cursorUp() error { - err := cursorUp(view.gui, view.view) - if err == nil { - view.absTreeIndex-- - } - return nil -} - -func (view *FileTreeView) getAbsPositionNode() (node *FileNode) { - var visiter func(*FileNode) error - var evaluator func(*FileNode) bool - var dfsCounter uint - - visiter = func(curNode *FileNode) error { - if dfsCounter == view.absTreeIndex { - node = curNode - } - dfsCounter++ - return nil - } - - evaluator = func(curNode *FileNode) bool { - return !curNode.collapsed - } - - err := view.tree.VisitDepthParentFirst(visiter, evaluator) - if err != nil { - // todo: you guessed it, check errors - } - - return node -} - -func (view *FileTreeView) toggleCollapse() error { - node := view.getAbsPositionNode() - node.collapsed = !node.collapsed - return view.render() -} - -func (view *FileTreeView) render() error { - renderString := view.tree.String() - view.gui.Update(func(g *gocui.Gui) error { - view.view.Clear() - _, err := fmt.Fprintln(view.view, renderString) - return err - }) - return nil -} diff --git a/image/image.go b/image/image.go new file mode 100644 index 00000000..e49a4f4e --- /dev/null +++ b/image/image.go @@ -0,0 +1,104 @@ +package image + +import ( + "io" + "os" + "bufio" + "github.com/docker/docker/client" + "fmt" + "encoding/json" + "golang.org/x/net/context" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + + +func saveImage(readCloser io.ReadCloser) { + defer readCloser.Close() + + path := ".image" + if _, err := os.Stat(path); os.IsNotExist(err) { + os.Mkdir(path, 0755) + } + + fo, err := os.Create(".image/cache.tar") + check(err) + + defer func() { + if err := fo.Close(); err != nil { + panic(err) + } + }() + w := bufio.NewWriter(fo) + + buf := make([]byte, 1024) + for { + n, err := readCloser.Read(buf) + if err != nil && err != io.EOF { + panic(err) + } + if n == 0 { + break + } + + if _, err := w.Write(buf[:n]); err != nil { + panic(err) + } + } + + if err = w.Flush(); err != nil { + panic(err) + } +} + + +func WriteImage() { + ctx := context.Background() + cli, err := client.NewEnvClient() + if err != nil { + panic(err) + } + + // imageID := "golang:alpine" + imageID := "die-test:latest" + + fmt.Println("Saving Image...") + readCloser, err := cli.ImageSave(ctx, []string{imageID}) + check(err) + saveImage(readCloser) + + for { + inspect, _, err := cli.ImageInspectWithRaw(ctx, imageID) + check(err) + + history, err := cli.ImageHistory(ctx, imageID) + check(err) + + historyStr, err := json.MarshalIndent(history, "", " ") + check(err) + + layerStr := "" + for idx, layer := range inspect.RootFS.Layers { + prefix := "├── " + if idx == len(inspect.RootFS.Layers)-1 { + prefix = "└── " + } + layerStr += fmt.Sprintf("%s%s\n", prefix, layer) + } + + fmt.Printf("Image: %s\nId: %s\nParent: %s\nLayers: %d\n%sHistory: %s\n", imageID, inspect.ID, inspect.Parent, len(inspect.RootFS.Layers), layerStr, historyStr) + + fmt.Println("\n") + + if inspect.Parent == "" { + break + } else { + imageID = inspect.Parent + } + } + fmt.Println("See './.image' for the cached image tar") +} diff --git a/tar_read.go b/image/tar_read.go similarity index 70% rename from tar_read.go rename to image/tar_read.go index 68cdcff9..7b9cca5f 100644 --- a/tar_read.go +++ b/image/tar_read.go @@ -1,4 +1,4 @@ -package main +package image import ( "archive/tar" @@ -9,10 +9,12 @@ import ( "io" "os" "strings" + + "github.com/wagoodman/docker-image-explorer/filetree" ) -func initializeData() { - f, err := os.Open("image/cache.tar") +func InitializeData() (*Manifest, []*filetree.FileTree) { + f, err := os.Open("./.image/cache.tar") if err != nil { fmt.Println(err) os.Exit(1) @@ -22,8 +24,8 @@ func initializeData() { tarReader := tar.NewReader(f) targetName := "manifest.json" var manifest Manifest - var layerMap map[string]*FileTree - layerMap = make(map[string]*FileTree) + var layerMap map[string]*filetree.FileTree + layerMap = make(map[string]*filetree.FileTree) for { header, err := tarReader.Next() @@ -49,14 +51,14 @@ func initializeData() { if strings.HasSuffix(name, "layer.tar") { fmt.Println("Containing:") - tree := NewTree() - tree.name = name - fmt.Printf("%s\n", tree.name) + tree := filetree.NewFileTree() + tree.Name = name + fmt.Printf("%s\n", tree.Name) fileInfos := getFileList(tarReader, header) for _, element := range fileInfos { - tree.AddPath(element.path, &element) + tree.AddPath(element.Path, &element) } - layerMap[tree.name] = tree + layerMap[tree.Name] = tree } default: fmt.Printf("%s : %c %s %s\n", @@ -67,18 +69,17 @@ func initializeData() { ) } } - var trees []*FileTree - trees = make([]*FileTree, 0) + var trees []*filetree.FileTree + trees = make([]*filetree.FileTree, 0) for _, treeName := range manifest.Layers { trees = append(trees, layerMap[treeName]) } - data.manifest = &manifest - data.refTrees = trees + return &manifest, trees } -func getFileList(parentReader *tar.Reader, h *tar.Header) []FileChangeInfo { - var files []FileChangeInfo +func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo { + var files []filetree.FileChangeInfo size := h.Size tarredBytes := make([]byte, size) _, err := parentReader.Read(tarredBytes) @@ -121,12 +122,12 @@ func getFileList(parentReader *tar.Reader, h *tar.Header) []FileChangeInfo { return files } -func makeEntry(r *tar.Reader, h *tar.Header, path string) FileChangeInfo { +func makeEntry(r *tar.Reader, h *tar.Header, path string) filetree.FileChangeInfo { if h.Typeflag == tar.TypeDir { - return FileChangeInfo{ - path: path, - typeflag: h.Typeflag, - md5sum: [16]byte{}, + return filetree.FileChangeInfo{ + Path: path, + Typeflag: h.Typeflag, + MD5sum: [16]byte{}, } } fileBytes := make([]byte, h.Size) @@ -135,11 +136,11 @@ func makeEntry(r *tar.Reader, h *tar.Header, path string) FileChangeInfo { panic(err) } hash := md5.Sum(fileBytes) - return FileChangeInfo{ - path: path, - typeflag: h.Typeflag, - md5sum: hash, - diffType: Unchanged, + return filetree.FileChangeInfo{ + Path: path, + Typeflag: h.Typeflag, + MD5sum: hash, + DiffType: filetree.Unchanged, } } diff --git a/layerview.go b/layerview.go deleted file mode 100644 index 1cbd4376..00000000 --- a/layerview.go +++ /dev/null @@ -1,79 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/jroimartin/gocui" -) - -type LayerView struct { - name string - gui *gocui.Gui - view *gocui.View - layerIndex uint - manifest *Manifest -} - -func NewLayerView(name string, gui *gocui.Gui, view *gocui.View, manifest *Manifest) (layerview *LayerView) { - layerview = new(LayerView) - - // populate main fields - layerview.name = name - layerview.gui = gui - layerview.view = view - layerview.manifest = manifest - - // set view options - layerview.view.Wrap = true - layerview.view.Highlight = true - layerview.view.SelBgColor = gocui.ColorGreen - layerview.view.SelFgColor = gocui.ColorBlack - - layerview.render() - - return layerview -} - -func (view *LayerView) keybindings() error { - - if err := view.gui.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorDown() }); err != nil { - return err - } - if err := view.gui.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.cursorUp() }); err != nil { - return err - } - - return nil -} - -func (view *LayerView) render() error { - view.gui.Update(func(g *gocui.Gui) error { - view.view.Clear() - for ix, layerName := range view.manifest.Layers { - fmt.Fprintf(view.view, "%d: %s\n", ix+1, layerName[0:25]) - } - return nil - }) - // todo: blerg - return nil -} - -func (view *LayerView) cursorDown() error { - if int(view.layerIndex) < len(data.manifest.Layers) { - cursorDown(view.gui, view.view) - view.layerIndex++ - view.render() - views.treeView.reset(StackRange(data.refTrees, view.layerIndex)) - } - return nil -} - -func (view *LayerView) cursorUp() error { - if int(view.layerIndex) > 0 { - cursorUp(view.gui, view.view) - view.layerIndex-- - view.render() - views.treeView.reset(StackRange(data.refTrees, view.layerIndex)) - } - return nil -} diff --git a/main.go b/main.go deleted file mode 100644 index 6ea032c4..00000000 --- a/main.go +++ /dev/null @@ -1,229 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "io" - "os" - - "log" - - "github.com/docker/docker/client" - "github.com/jroimartin/gocui" - "golang.org/x/net/context" -) - -var data struct { - refTrees []*FileTree - manifest *Manifest -} - -var views struct { - treeView *FileTreeView - layerView *LayerView -} - -func check(e error) { - if e != nil { - panic(e) - } -} - -func saveImage(readCloser io.ReadCloser) { - defer readCloser.Close() - - path := "image" - if _, err := os.Stat(path); os.IsNotExist(err) { - os.Mkdir(path, 0755) - } - - fo, err := os.Create("image/cache.tar") - check(err) - - defer func() { - if err := fo.Close(); err != nil { - panic(err) - } - }() - w := bufio.NewWriter(fo) - - buf := make([]byte, 1024) - for { - n, err := readCloser.Read(buf) - if err != nil && err != io.EOF { - panic(err) - } - if n == 0 { - break - } - - if _, err := w.Write(buf[:n]); err != nil { - panic(err) - } - } - - if err = w.Flush(); err != nil { - panic(err) - } -} - -func demo() { - ctx := context.Background() - cli, err := client.NewEnvClient() - if err != nil { - panic(err) - } - - // imageID := "golang:alpine" - imageID := "die-test:latest" - - fmt.Println("Saving Image...") - readCloser, err := cli.ImageSave(ctx, []string{imageID}) - check(err) - saveImage(readCloser) - - for { - inspect, _, err := cli.ImageInspectWithRaw(ctx, imageID) - check(err) - - history, err := cli.ImageHistory(ctx, imageID) - check(err) - - historyStr, err := json.MarshalIndent(history, "", " ") - check(err) - - layerStr := "" - for idx, layer := range inspect.RootFS.Layers { - prefix := "├── " - if idx == len(inspect.RootFS.Layers)-1 { - prefix = "└── " - } - layerStr += fmt.Sprintf("%s%s\n", prefix, layer) - } - - fmt.Printf("Image: %s\nId: %s\nParent: %s\nLayers: %d\n%sHistory: %s\n", imageID, inspect.ID, inspect.Parent, len(inspect.RootFS.Layers), layerStr, historyStr) - - fmt.Println("\n") - - if inspect.Parent == "" { - break - } else { - imageID = inspect.Parent - } - } - fmt.Println("See './image' for the cached image tar") -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -func nextView(g *gocui.Gui, v *gocui.View) error { - if v == nil || v.Name() == views.layerView.name { - _, err := g.SetCurrentView(views.treeView.name) - return err - } - _, err := g.SetCurrentView(views.layerView.name) - return err -} - -func cursorDown(g *gocui.Gui, v *gocui.View) error { - cx, cy := v.Cursor() - - // if there isn't a next line - line, err := v.Line(cy + 1) - if err != nil { - // todo: handle error - } - if len(line) == 0 { - return nil - } - if err := v.SetCursor(cx, cy+1); err != nil { - ox, oy := v.Origin() - if err := v.SetOrigin(ox, oy+1); err != nil { - return err - } - } - return nil -} - -func cursorUp(g *gocui.Gui, v *gocui.View) error { - ox, oy := v.Origin() - cx, cy := v.Cursor() - if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 { - if err := v.SetOrigin(ox, oy-1); err != nil { - return err - } - } - return nil -} - -func quit(g *gocui.Gui, v *gocui.View) error { - return gocui.ErrQuit -} - -func keybindings(g *gocui.Gui) error { - if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - return err - } - //if err := g.SetKeybinding("main", gocui.MouseLeft, gocui.ModNone, toggleCollapse); err != nil { - // return err - //} - if err := g.SetKeybinding("side", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { - return err - } - if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { - return err - } - - return nil -} - -func layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - splitCol := 50 - if v, err := g.SetView("side", -1, -1, splitCol, maxY); err != nil { - if err != gocui.ErrUnknownView { - return err - } - views.layerView = NewLayerView("side", g, v, data.manifest) - views.layerView.keybindings() - - } - if v, err := g.SetView("main", splitCol, -1, maxX, maxY); err != nil { - if err != gocui.ErrUnknownView { - return err - } - - views.treeView = NewFileTreeView("main", g, v, StackRange(data.refTrees, 0)) - views.treeView.keybindings() - - if _, err := g.SetCurrentView(views.treeView.name); err != nil { - return err - } - } - return nil -} - -func main() { - demo() - initializeData() - - g, err := gocui.NewGui(gocui.OutputNormal) - if err != nil { - log.Panicln(err) - } - defer g.Close() - - g.Cursor = false - //g.Mouse = true - g.SetManagerFunc(layout) - - if err := keybindings(g); err != nil { - log.Panicln(err) - } - - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { - log.Panicln(err) - } - -} diff --git a/ui/filetreeview.go b/ui/filetreeview.go new file mode 100644 index 00000000..138c7960 --- /dev/null +++ b/ui/filetreeview.go @@ -0,0 +1,121 @@ +package ui + +import ( + "fmt" + + "github.com/jroimartin/gocui" + "github.com/wagoodman/docker-image-explorer/filetree" +) + + +type FileTreeView struct { + Name string + gui *gocui.Gui + view *gocui.View + TreeIndex uint + Tree *filetree.FileTree + RefTrees []*filetree.FileTree +} + +func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree) (treeview *FileTreeView) { + treeview = new(FileTreeView) + + // populate main fields + treeview.Name = name + treeview.gui = gui + treeview.Tree = tree + treeview.RefTrees = refTrees + + return treeview +} + +func (view *FileTreeView) Setup(v *gocui.View) error { + + // set view options + view.view = v + view.view.Editable = false + view.view.Wrap = false + view.view.Highlight = true + view.view.SelBgColor = gocui.ColorGreen + view.view.SelFgColor = gocui.ColorBlack + + // set keybindings + if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil { + return err + } + if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil { + return err + } + if err := view.gui.SetKeybinding(view.Name, gocui.KeySpace, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.toggleCollapse() }); err != nil { + return err + } + + view.Render() + + return nil +} + +// Mehh, this is just a bad method +func (view *FileTreeView) reset(tree *filetree.FileTree) error { + view.Tree = tree + view.view.SetCursor(0, 0) + view.TreeIndex = 0 + return view.Render() +} + +func (view *FileTreeView) CursorDown() error { + err := CursorDown(view.gui, view.view) + if err == nil { + view.TreeIndex++ + } + return nil +} + +func (view *FileTreeView) CursorUp() error { + err := CursorUp(view.gui, view.view) + if err == nil { + view.TreeIndex-- + } + return nil +} + +func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) { + var visiter func(*filetree.FileNode) error + var evaluator func(*filetree.FileNode) bool + var dfsCounter uint + + visiter = func(curNode *filetree.FileNode) error { + if dfsCounter == view.TreeIndex { + node = curNode + } + dfsCounter++ + return nil + } + + evaluator = func(curNode *filetree.FileNode) bool { + return !curNode.Collapsed + } + + err := view.Tree.VisitDepthParentFirst(visiter, evaluator) + if err != nil { + // todo: you guessed it, check errors + } + + return node +} + +func (view *FileTreeView) toggleCollapse() error { + node := view.getAbsPositionNode() + node.Collapsed = !node.Collapsed + return view.Render() +} + +func (view *FileTreeView) Render() error { + renderString := view.Tree.String() + view.gui.Update(func(g *gocui.Gui) error { + view.view.Clear() + _, err := fmt.Fprintln(view.view, renderString) + return err + }) + return nil +} diff --git a/ui/layerview.go b/ui/layerview.go new file mode 100644 index 00000000..281e20d8 --- /dev/null +++ b/ui/layerview.go @@ -0,0 +1,85 @@ +package ui + +import ( + "fmt" + + "github.com/jroimartin/gocui" + "github.com/wagoodman/docker-image-explorer/image" + "github.com/wagoodman/docker-image-explorer/filetree" +) + + +type LayerView struct { + Name string + gui *gocui.Gui + view *gocui.View + LayerIndex uint + Manifest *image.Manifest +} + +func NewLayerView(name string, gui *gocui.Gui, manifest *image.Manifest) (layerview *LayerView) { + layerview = new(LayerView) + + // populate main fields + layerview.Name = name + layerview.gui = gui + layerview.Manifest = manifest + + return layerview +} + +func (view *LayerView) Setup(v *gocui.View) error { + + // set view options + view.view = v + view.view.Wrap = true + view.view.Highlight = true + view.view.SelBgColor = gocui.ColorGreen + view.view.SelFgColor = gocui.ColorBlack + + // set keybindings + if err := view.gui.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil { + return err + } + if err := view.gui.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil { + return err + } + + view.Render() + + return nil +} + +func (view *LayerView) Render() error { + view.gui.Update(func(g *gocui.Gui) error { + view.view.Clear() + for ix, layerName := range view.Manifest.Layers { + fmt.Fprintf(view.view, "%d: %s\n", ix+1, layerName[0:25]) + } + return nil + }) + // todo: blerg + return nil +} + +func (view *LayerView) CursorDown() error { + if int(view.LayerIndex) < len(view.Manifest.Layers) { + CursorDown(view.gui, view.view) + view.LayerIndex++ + view.Render() + // this line is evil + Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex)) + } + return nil +} + +func (view *LayerView) CursorUp() error { + if int(view.LayerIndex) > 0 { + CursorUp(view.gui, view.view) + view.LayerIndex-- + view.Render() + // this line is evil + Views.Tree.reset(filetree.StackRange(Views.Tree.RefTrees, view.LayerIndex)) + } + return nil +} diff --git a/ui/ui.go b/ui/ui.go new file mode 100644 index 00000000..48321ce4 --- /dev/null +++ b/ui/ui.go @@ -0,0 +1,122 @@ +package ui + +import ( + "github.com/jroimartin/gocui" + "github.com/wagoodman/docker-image-explorer/filetree" + "log" + "github.com/wagoodman/docker-image-explorer/image" +) + +var Views struct { + Tree *FileTreeView + Layer *LayerView +} + +func nextView(g *gocui.Gui, v *gocui.View) error { + if v == nil || v.Name() == Views.Layer.Name { + _, err := g.SetCurrentView(Views.Tree.Name) + return err + } + _, err := g.SetCurrentView(Views.Layer.Name) + return err +} + +func CursorDown(g *gocui.Gui, v *gocui.View) error { + cx, cy := v.Cursor() + + // if there isn't a next line + line, err := v.Line(cy + 1) + if err != nil { + // todo: handle error + } + if len(line) == 0 { + return nil + } + if err := v.SetCursor(cx, cy+1); err != nil { + ox, oy := v.Origin() + if err := v.SetOrigin(ox, oy+1); err != nil { + return err + } + } + return nil +} + +func CursorUp(g *gocui.Gui, v *gocui.View) error { + ox, oy := v.Origin() + cx, cy := v.Cursor() + if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 { + if err := v.SetOrigin(ox, oy-1); err != nil { + return err + } + } + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} + +func keybindings(g *gocui.Gui) error { + if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + return err + } + //if err := g.SetKeybinding("main", gocui.MouseLeft, gocui.ModNone, toggleCollapse); err != nil { + // return err + //} + if err := g.SetKeybinding("side", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { + return err + } + if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { + return err + } + + return nil +} + +func layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + splitCol := 50 + if view, err := g.SetView("side", -1, -1, splitCol, maxY); err != nil { + if err != gocui.ErrUnknownView { + return err + } + Views.Layer.Setup(view) + + } + if view, err := g.SetView("main", splitCol, -1, maxX, maxY); err != nil { + if err != gocui.ErrUnknownView { + return err + } + + Views.Tree.Setup(view) + + if _, err := g.SetCurrentView(Views.Tree.Name); err != nil { + return err + } + } + return nil +} + +func Run(manifest *image.Manifest, refTrees []*filetree.FileTree) { + + g, err := gocui.NewGui(gocui.OutputNormal) + if err != nil { + log.Panicln(err) + } + defer g.Close() + + Views.Layer = NewLayerView("side", g, manifest) + Views.Tree = NewFileTreeView("main", g, filetree.StackRange(refTrees, 0), refTrees) + + g.Cursor = false + //g.Mouse = true + g.SetManagerFunc(layout) + + if err := keybindings(g); err != nil { + log.Panicln(err) + } + + if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + log.Panicln(err) + } +} \ No newline at end of file diff --git a/ui/view.go b/ui/view.go new file mode 100644 index 00000000..bac308cb --- /dev/null +++ b/ui/view.go @@ -0,0 +1,10 @@ +package ui + +import "github.com/jroimartin/gocui" + +type View interface { + Setup(*gocui.View) error + CursorDown() error + CursorUp() error + Render() error +} diff --git a/view.go b/view.go deleted file mode 100644 index 6334732f..00000000 --- a/view.go +++ /dev/null @@ -1,8 +0,0 @@ -package main - -type View interface { - keybindings() error - cursorDown() error - cursorUp() error - render() error -}