refactor project structure; use subpackages
This commit is contained in:
parent
ed62646bac
commit
d78abce2e6
7
.gitignore
vendored
7
.gitignore
vendored
@ -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
104
Gopkg.lock
generated
Normal 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
43
Gopkg.toml
Normal 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
|
44
Makefile
44
Makefile
@ -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
25
cmd/die/main.go
Normal 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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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())
|
@ -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)
|
||||
}
|
114
filetreeview.go
114
filetreeview.go
@ -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
104
image/image.go
Normal 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")
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
79
layerview.go
79
layerview.go
@ -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
229
main.go
@ -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
121
ui/filetreeview.go
Normal 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
85
ui/layerview.go
Normal 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
122
ui/ui.go
Normal 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
10
ui/view.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user