refactor project structure; use subpackages

This commit is contained in:
Alex Goodman 2018-06-04 21:22:45 -04:00
parent ed62646bac
commit d78abce2e6
No known key found for this signature in database
GPG Key ID: 05328C611D8A520E
21 changed files with 851 additions and 645 deletions

7
.gitignore vendored
View File

@ -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

104
Gopkg.lock generated Normal file
View File

@ -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

43
Gopkg.toml Normal file
View File

@ -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

View File

@ -1,12 +1,34 @@
SHELL := /bin/bash
.DEFAULT_GOAL := run
.PHONY: run
BIN = die
run:
go run main.go \
filechangeinfo.go \
filenode.go \
filetree.go \
tar_read.go \
filetreeview.go \
layerview.go
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

25
cmd/die/main.go Normal file
View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

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

View File

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

View File

@ -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())

View File

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

View File

@ -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
}

104
image/image.go Normal file
View File

@ -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")
}

View File

@ -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,
}
}

View File

@ -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
}

229
main.go
View File

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

121
ui/filetreeview.go Normal file
View File

@ -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
}

85
ui/layerview.go Normal file
View File

@ -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
}

122
ui/ui.go Normal file
View File

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

10
ui/view.go Normal file
View File

@ -0,0 +1,10 @@
package ui
import "github.com/jroimartin/gocui"
type View interface {
Setup(*gocui.View) error
CursorDown() error
CursorUp() error
Render() error
}

View File

@ -1,8 +0,0 @@
package main
type View interface {
keybindings() error
cursorDown() error
cursorUp() error
render() error
}