Merge branch 'master' into filter-view

This commit is contained in:
Will Murphy 2018-06-24 13:53:00 -04:00
commit ef2c76930f
12 changed files with 250 additions and 171 deletions

2
.gitignore vendored
View File

@ -12,7 +12,7 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
/tmp
/build /build
/_vendor* /_vendor*
/vendor /vendor

View File

@ -18,7 +18,7 @@ const (
type NodeData struct { type NodeData struct {
ViewInfo ViewInfo ViewInfo ViewInfo
FileInfo *FileInfo FileInfo FileInfo
DiffType DiffType DiffType DiffType
} }
@ -31,6 +31,7 @@ type FileInfo struct {
Path string Path string
Typeflag byte Typeflag byte
MD5sum [16]byte MD5sum [16]byte
TarHeader tar.Header
} }
type DiffType int type DiffType int
@ -38,7 +39,7 @@ type DiffType int
func NewNodeData() (*NodeData) { func NewNodeData() (*NodeData) {
return &NodeData{ return &NodeData{
ViewInfo: *NewViewInfo(), ViewInfo: *NewViewInfo(),
FileInfo: nil, FileInfo: FileInfo{},
DiffType: Unchanged, DiffType: Unchanged,
} }
} }
@ -46,7 +47,7 @@ func NewNodeData() (*NodeData) {
func (data *NodeData) Copy() (*NodeData) { func (data *NodeData) Copy() (*NodeData) {
return &NodeData{ return &NodeData{
ViewInfo: *data.ViewInfo.Copy(), ViewInfo: *data.ViewInfo.Copy(),
FileInfo: data.FileInfo.Copy(), FileInfo: *data.FileInfo.Copy(),
DiffType: data.DiffType, DiffType: data.DiffType,
} }
} }
@ -71,6 +72,7 @@ func NewFileInfo(reader *tar.Reader, header *tar.Header, path string) FileInfo {
Path: path, Path: path,
Typeflag: header.Typeflag, Typeflag: header.Typeflag,
MD5sum: [16]byte{}, MD5sum: [16]byte{},
TarHeader: *header,
} }
} }
fileBytes := make([]byte, header.Size) fileBytes := make([]byte, header.Size)
@ -78,10 +80,12 @@ func NewFileInfo(reader *tar.Reader, header *tar.Header, path string) FileInfo {
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
panic(err) panic(err)
} }
return FileInfo{ return FileInfo{
Path: path, Path: path,
Typeflag: header.Typeflag, Typeflag: header.Typeflag,
MD5sum: md5.Sum(fileBytes), MD5sum: md5.Sum(fileBytes),
TarHeader: *header,
} }
} }
@ -115,16 +119,11 @@ func (data *FileInfo) Copy() *FileInfo {
Path: data.Path, Path: data.Path,
Typeflag: data.Typeflag, Typeflag: data.Typeflag,
MD5sum: data.MD5sum, MD5sum: data.MD5sum,
TarHeader: data.TarHeader,
} }
} }
func (data *FileInfo) getDiffType(other *FileInfo) DiffType { func (data *FileInfo) getDiffType(other FileInfo) DiffType {
if data == nil && other == nil {
return Unchanged
}
if data == nil || other == nil {
return Changed
}
if data.Typeflag == other.Typeflag { if data.Typeflag == other.Typeflag {
if bytes.Compare(data.MD5sum[:], other.MD5sum[:]) == 0 { if bytes.Compare(data.MD5sum[:], other.MD5sum[:]) == 0 {
return Unchanged return Unchanged

View File

@ -6,7 +6,7 @@ import (
func TestAssignDiffType(t *testing.T) { func TestAssignDiffType(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
node, err := tree.AddPath("/usr", BlankFileChangeInfo("/usr")) node, err := tree.AddPath("/usr", *BlankFileChangeInfo("/usr"))
if err != nil { if err != nil {
t.Errorf("Expected no error from fetching path. got: %v", err) t.Errorf("Expected no error from fetching path. got: %v", err)
} }

View File

@ -6,6 +6,13 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"fmt" "fmt"
"github.com/phayes/permbits"
"github.com/dustin/go-humanize"
"github.com/wagoodman/docker-image-explorer/_vendor-20180604210951/github.com/Microsoft/go-winio/archive/tar"
)
const (
AttributeFormat = "%s%s %10s %10s "
) )
type FileNode struct { type FileNode struct {
@ -16,13 +23,12 @@ type FileNode struct {
Children map[string]*FileNode Children map[string]*FileNode
} }
func NewNode(parent *FileNode, name string, data *FileInfo) (node *FileNode) { func NewNode(parent *FileNode, name string, data FileInfo) (node *FileNode) {
node = new(FileNode) node = new(FileNode)
node.Name = name node.Name = name
node.Data = *NewNodeData() node.Data = *NewNodeData()
if data != nil { node.Data.FileInfo = *data.Copy()
node.Data.FileInfo = data.Copy()
}
node.Children = make(map[string]*FileNode) node.Children = make(map[string]*FileNode)
node.Parent = parent node.Parent = parent
if parent != nil { if parent != nil {
@ -42,11 +48,11 @@ func (node *FileNode) Copy(parent *FileNode) *FileNode {
return newNode return newNode
} }
func (node *FileNode) AddChild(name string, data *FileInfo) (child *FileNode) { func (node *FileNode) AddChild(name string, data FileInfo) (child *FileNode) {
child = NewNode(node, name, data) 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 // tree node already exists, replace the payload, keep the children
node.Children[name].Data.FileInfo = data.Copy() node.Children[name].Data.FileInfo = *data.Copy()
} else { } else {
node.Children[name] = child node.Children[name] = child
node.Tree.Size++ node.Tree.Size++
@ -68,6 +74,7 @@ func (node *FileNode) Remove() error {
func (node *FileNode) String() string { func (node *FileNode) String() string {
var style *color.Color var style *color.Color
var display string
if node == nil { if node == nil {
return "" return ""
} }
@ -83,7 +90,42 @@ func (node *FileNode) String() string {
default: default:
style = color.New(color.BgMagenta) style = color.New(color.BgMagenta)
} }
return style.Sprint(node.Name) display = node.Name
if node.Data.FileInfo.TarHeader.Typeflag == tar.TypeSymlink || node.Data.FileInfo.TarHeader.Typeflag == tar.TypeLink {
display += " -> " + node.Data.FileInfo.TarHeader.Linkname
}
return style.Sprint(display)
}
func (node *FileNode) MetadataString() string {
var style *color.Color
if node == nil {
return ""
}
switch node.Data.DiffType {
case Added:
style = color.New(color.FgGreen)
case Removed:
style = color.New(color.FgRed)
case Changed:
style = color.New(color.FgYellow)
case Unchanged:
style = color.New(color.Reset)
default:
style = color.New(color.BgMagenta)
}
fileMode := permbits.FileMode(node.Data.FileInfo.TarHeader.FileInfo().Mode()).String()
dir := "-"
if node.Data.FileInfo.TarHeader.FileInfo().IsDir() {
dir = "d"
}
user := node.Data.FileInfo.TarHeader.Uid
group := node.Data.FileInfo.TarHeader.Gid
userGroup := fmt.Sprintf("%d:%d", user, group)
size := humanize.Bytes(uint64(node.Data.FileInfo.TarHeader.FileInfo().Size()))
return style.Sprint(fmt.Sprintf(AttributeFormat,dir, fileMode, userGroup, size))
} }
func (node *FileNode) VisitDepthChildFirst(visiter Visiter, evaluator VisitEvaluator) error { func (node *FileNode) VisitDepthChildFirst(visiter Visiter, evaluator VisitEvaluator) error {

View File

@ -12,14 +12,14 @@ func TestAddChild(t *testing.T) {
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!", FileInfo{})
tree.Root.AddChild("third node!", nil) tree.Root.AddChild("third node!", FileInfo{})
two.AddChild("forth, one level down...", nil) two.AddChild("forth, one level down...", FileInfo{})
two.AddChild("fifth, one level down...", nil) two.AddChild("fifth, one level down...", FileInfo{})
two.AddChild("fifth, one level down...", nil) two.AddChild("fifth, one level down...", FileInfo{})
expected, actual = 5, tree.Size expected, actual = 5, tree.Size
if expected != actual { if expected != actual {
@ -36,17 +36,14 @@ func TestAddChild(t *testing.T) {
t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual) t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual)
} }
expectedFC := &FileInfo{ expectedFC := FileInfo{
Path: "stufffffs", Path: "stufffffs",
} }
actualFC := one.Data.FileInfo actualFC := one.Data.FileInfo
if *expectedFC != *actualFC { if expectedFC.Path != actualFC.Path {
t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC) t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC)
} }
if two.Data.FileInfo != nil {
t.Errorf("Expected 'twos' payload to be nil got %+v.", two.Data.FileInfo)
}
} }
@ -54,11 +51,11 @@ func TestRemoveChild(t *testing.T) {
var expected, actual int var expected, actual int
tree := NewFileTree() tree := NewFileTree()
tree.Root.AddChild("first", nil) tree.Root.AddChild("first", FileInfo{})
two := tree.Root.AddChild("nil", nil) two := tree.Root.AddChild("nil", FileInfo{})
tree.Root.AddChild("third", nil) tree.Root.AddChild("third", FileInfo{})
forth := two.AddChild("forth", nil) forth := two.AddChild("forth", FileInfo{})
two.AddChild("fifth", nil) two.AddChild("fifth", FileInfo{})
forth.Remove() forth.Remove()
@ -87,7 +84,7 @@ func TestRemoveChild(t *testing.T) {
func TestPath(t *testing.T) { func TestPath(t *testing.T) {
expected := "/etc/nginx/nginx.conf" expected := "/etc/nginx/nginx.conf"
tree := NewFileTree() tree := NewFileTree()
node, _ := tree.AddPath(expected, nil) node, _ := tree.AddPath(expected, FileInfo{})
actual := node.Path() actual := node.Path()
if expected != actual { if expected != actual {
@ -97,8 +94,8 @@ func TestPath(t *testing.T) {
func TestIsWhiteout(t *testing.T) { func TestIsWhiteout(t *testing.T) {
tree1 := NewFileTree() tree1 := NewFileTree()
p1, _ := tree1.AddPath("/etc/nginx/public1", nil) p1, _ := tree1.AddPath("/etc/nginx/public1", FileInfo{})
p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", nil) p2, _ := tree1.AddPath("/etc/nginx/.wh.public2", FileInfo{})
if p1.IsWhiteout() != false { 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)
@ -111,15 +108,15 @@ func TestIsWhiteout(t *testing.T) {
func TestDiffTypeFromAddedChildren(t *testing.T) { func TestDiffTypeFromAddedChildren(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
node, _ := tree.AddPath("/usr", BlankFileChangeInfo("/usr")) node, _ := tree.AddPath("/usr", *BlankFileChangeInfo("/usr"))
node.Data.DiffType = Unchanged node.Data.DiffType = Unchanged
info1 := BlankFileChangeInfo("/usr/bin") info1 := BlankFileChangeInfo("/usr/bin")
node, _ = tree.AddPath("/usr/bin", info1) node, _ = tree.AddPath("/usr/bin", *info1)
node.Data.DiffType = Added node.Data.DiffType = Added
info2 := BlankFileChangeInfo("/usr/bin2") info2 := BlankFileChangeInfo("/usr/bin2")
node, _ = tree.AddPath("/usr/bin2", info2) node, _ = tree.AddPath("/usr/bin2", *info2)
node.Data.DiffType = Removed node.Data.DiffType = Removed
tree.Root.Children["usr"].deriveDiffType(Unchanged) tree.Root.Children["usr"].deriveDiffType(Unchanged)
@ -130,14 +127,14 @@ func TestDiffTypeFromAddedChildren(t *testing.T) {
} }
func TestDiffTypeFromRemovedChildren(t *testing.T) { func TestDiffTypeFromRemovedChildren(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
node, _ := tree.AddPath("/usr", BlankFileChangeInfo("/usr")) node, _ := tree.AddPath("/usr", *BlankFileChangeInfo("/usr"))
info1 := BlankFileChangeInfo("/usr/.wh.bin") info1 := BlankFileChangeInfo("/usr/.wh.bin")
node, _ = tree.AddPath("/usr/.wh.bin", info1) node, _ = tree.AddPath("/usr/.wh.bin", *info1)
node.Data.DiffType = Removed node.Data.DiffType = Removed
info2 := BlankFileChangeInfo("/usr/.wh.bin2") info2 := BlankFileChangeInfo("/usr/.wh.bin2")
node, _ = tree.AddPath("/usr/.wh.bin2", info2) node, _ = tree.AddPath("/usr/.wh.bin2", *info2)
node.Data.DiffType = Removed node.Data.DiffType = Removed
tree.Root.Children["usr"].deriveDiffType(Unchanged) tree.Root.Children["usr"].deriveDiffType(Unchanged)

View File

@ -32,11 +32,11 @@ func NewFileTree() (tree *FileTree) {
return tree return tree
} }
func (tree *FileTree) String() string { func (tree *FileTree) String(showAttributes bool) string {
var renderLine func(string, []bool, bool, bool) string var renderTreeLine func(string, []bool, bool, bool) string
var walkTree func(*FileNode, []bool, int) string var walkTree func(*FileNode, []bool, int) string
renderLine = func(nodeText string, spaces []bool, last bool, collapsed bool) string { renderTreeLine = func(nodeText string, spaces []bool, last bool, collapsed bool) string {
var otherBranches string var otherBranches string
for _, space := range spaces { for _, space := range spaces {
if space { if space {
@ -73,7 +73,10 @@ func (tree *FileTree) String() string {
} }
last := idx == (len(node.Children) - 1) last := idx == (len(node.Children) - 1)
showCollapsed := child.Data.ViewInfo.Collapsed && len(child.Children) > 0 showCollapsed := child.Data.ViewInfo.Collapsed && len(child.Children) > 0
result += renderLine(child.String(), spaces, last, showCollapsed) if showAttributes {
result += child.MetadataString() + " "
}
result += renderTreeLine(child.String(), spaces, last, showCollapsed)
if len(child.Children) > 0 && !child.Data.ViewInfo.Collapsed { if len(child.Children) > 0 && !child.Data.ViewInfo.Collapsed {
spacesChild := append(spaces, last) spacesChild := append(spaces, last)
result += walkTree(child, spacesChild, depth+1) result += walkTree(child, spacesChild, depth+1)
@ -82,7 +85,7 @@ func (tree *FileTree) String() string {
return result return result
} }
return "." + newLine + walkTree(tree.Root, []bool{}, 0) return walkTree(tree.Root, []bool{}, 0)
} }
func (tree *FileTree) Copy() *FileTree { func (tree *FileTree) Copy() *FileTree {
@ -131,7 +134,7 @@ func (tree *FileTree) Stack(upper *FileTree) error {
} }
func (tree *FileTree) GetNode(path string) (*FileNode, error) { func (tree *FileTree) GetNode(path string) (*FileNode, error) {
nodeNames := strings.Split(path, "/") nodeNames := strings.Split(strings.Trim(path, "/"), "/")
node := tree.Root node := tree.Root
for _, name := range nodeNames { for _, name := range nodeNames {
if name == "" { if name == "" {
@ -145,9 +148,9 @@ func (tree *FileTree) GetNode(path string) (*FileNode, error) {
return node, nil return node, nil
} }
func (tree *FileTree) AddPath(path string, data *FileInfo) (*FileNode, error) { func (tree *FileTree) AddPath(path string, data FileInfo) (*FileNode, error) {
// fmt.Printf("ADDPATH: %s %+v\n", path, data) // fmt.Printf("ADDPATH: %s %+v\n", path, data)
nodeNames := strings.Split(path, "/") nodeNames := strings.Split(strings.Trim(path, "/"), "/")
node := tree.Root node := tree.Root
for idx, name := range nodeNames { for idx, name := range nodeNames {
if name == "" { if name == "" {
@ -159,7 +162,7 @@ func (tree *FileTree) AddPath(path string, data *FileInfo) (*FileNode, error) {
} else { } else {
// don't attach the payload. The payload is destined for the // 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) node = node.AddChild(name, FileInfo{})
} }
// attach payload to the last specified node // attach payload to the last specified node

View File

@ -15,9 +15,6 @@ func stringInSlice(a string, list []string) bool {
} }
func AssertDiffType(node *FileNode, expectedDiffType DiffType) error { func AssertDiffType(node *FileNode, expectedDiffType DiffType) error {
if node.Data.FileInfo == nil {
return fmt.Errorf("expected *FileInfo but got nil at Path %s", node.Path())
}
if node.Data.DiffType != expectedDiffType { if node.Data.DiffType != expectedDiffType {
return fmt.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.Data.DiffType) return fmt.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.Data.DiffType)
} }
@ -26,18 +23,18 @@ func AssertDiffType(node *FileNode, expectedDiffType DiffType) error {
func TestPrintTree(t *testing.T) { func TestPrintTree(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
tree.Root.AddChild("first node!", nil) tree.Root.AddChild("first node!", FileInfo{})
two := tree.Root.AddChild("second node!", nil) two := tree.Root.AddChild("second node!", FileInfo{})
tree.Root.AddChild("third node!", nil) tree.Root.AddChild("third node!", FileInfo{})
two.AddChild("forth, one level down...", nil) two.AddChild("forth, one level down...", FileInfo{})
expected := `. expected :=
first node! ` first node!
second node! second node!
forth, one level down... forth, one level down...
third node! third node!
` `
actual := tree.String() actual := tree.String(false)
if expected != actual { if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
@ -47,15 +44,15 @@ func TestPrintTree(t *testing.T) {
func TestAddPath(t *testing.T) { func TestAddPath(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/nginx.conf", FileInfo{})
tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/etc/nginx/public", FileInfo{})
tree.AddPath("/var/run/systemd", nil) tree.AddPath("/var/run/systemd", FileInfo{})
tree.AddPath("/var/run/bashful", nil) tree.AddPath("/var/run/bashful", FileInfo{})
tree.AddPath("/tmp", nil) tree.AddPath("/tmp", FileInfo{})
tree.AddPath("/tmp/nonsense", nil) tree.AddPath("/tmp/nonsense", FileInfo{})
expected := `. expected :=
etc ` etc
nginx nginx
nginx.conf nginx.conf
public public
@ -66,7 +63,7 @@ func TestAddPath(t *testing.T) {
bashful bashful
systemd systemd
` `
actual := tree.String() actual := tree.String(false)
if expected != actual { if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
@ -76,18 +73,18 @@ func TestAddPath(t *testing.T) {
func TestRemovePath(t *testing.T) { func TestRemovePath(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/nginx.conf", FileInfo{})
tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/etc/nginx/public", FileInfo{})
tree.AddPath("/var/run/systemd", nil) tree.AddPath("/var/run/systemd", FileInfo{})
tree.AddPath("/var/run/bashful", nil) tree.AddPath("/var/run/bashful", FileInfo{})
tree.AddPath("/tmp", nil) tree.AddPath("/tmp", FileInfo{})
tree.AddPath("/tmp/nonsense", nil) tree.AddPath("/tmp/nonsense", FileInfo{})
tree.RemovePath("/var/run/bashful") tree.RemovePath("/var/run/bashful")
tree.RemovePath("/tmp") tree.RemovePath("/tmp")
expected := `. expected :=
etc ` etc
nginx nginx
nginx.conf nginx.conf
public public
@ -95,7 +92,7 @@ func TestRemovePath(t *testing.T) {
run run
systemd systemd
` `
actual := tree.String() actual := tree.String(false)
if expected != actual { if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
@ -111,20 +108,20 @@ func TestStack(t *testing.T) {
tree1 := NewFileTree() tree1 := NewFileTree()
tree1.AddPath("/etc/nginx/public", nil) tree1.AddPath("/etc/nginx/public", FileInfo{})
tree1.AddPath(payloadKey, nil) tree1.AddPath(payloadKey, FileInfo{})
tree1.AddPath("/var/run/bashful", nil) tree1.AddPath("/var/run/bashful", FileInfo{})
tree1.AddPath("/tmp", nil) tree1.AddPath("/tmp", FileInfo{})
tree1.AddPath("/tmp/nonsense", nil) tree1.AddPath("/tmp/nonsense", FileInfo{})
tree2 := NewFileTree() tree2 := NewFileTree()
// add new files // add new files
tree2.AddPath("/etc/nginx/nginx.conf", nil) tree2.AddPath("/etc/nginx/nginx.conf", FileInfo{})
// modify current files // modify current files
tree2.AddPath(payloadKey, &payloadValue) tree2.AddPath(payloadKey, payloadValue)
// whiteout the following files // whiteout the following files
tree2.AddPath("/var/run/.wh.bashful", nil) tree2.AddPath("/var/run/.wh.bashful", FileInfo{})
tree2.AddPath("/.wh.tmp", nil) tree2.AddPath("/.wh.tmp", FileInfo{})
err := tree1.Stack(tree2) err := tree1.Stack(tree2)
@ -132,8 +129,8 @@ func TestStack(t *testing.T) {
t.Errorf("Could not stack refTrees: %v", err) t.Errorf("Could not stack refTrees: %v", err)
} }
expected := `. expected :=
etc ` etc
nginx nginx
nginx.conf nginx.conf
public public
@ -147,11 +144,11 @@ func TestStack(t *testing.T) {
t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey) t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey)
} }
if *node.Data.FileInfo != payloadValue { if node.Data.FileInfo.Path != payloadValue.Path {
t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.Data.FileInfo) t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.Data.FileInfo)
} }
actual := tree1.String() actual := tree1.String(false)
if expected != actual { if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
@ -161,18 +158,18 @@ func TestStack(t *testing.T) {
func TestCopy(t *testing.T) { func TestCopy(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/nginx.conf", FileInfo{})
tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/etc/nginx/public", FileInfo{})
tree.AddPath("/var/run/systemd", nil) tree.AddPath("/var/run/systemd", FileInfo{})
tree.AddPath("/var/run/bashful", nil) tree.AddPath("/var/run/bashful", FileInfo{})
tree.AddPath("/tmp", nil) tree.AddPath("/tmp", FileInfo{})
tree.AddPath("/tmp/nonsense", nil) tree.AddPath("/tmp/nonsense", FileInfo{})
tree.RemovePath("/var/run/bashful") tree.RemovePath("/var/run/bashful")
tree.RemovePath("/tmp") tree.RemovePath("/tmp")
expected := `. expected :=
etc ` etc
nginx nginx
nginx.conf nginx.conf
public public
@ -182,7 +179,7 @@ func TestCopy(t *testing.T) {
` `
NewFileTree := tree.Copy() NewFileTree := tree.Copy()
actual := NewFileTree.String() actual := NewFileTree.String(false)
if expected != actual { if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
@ -201,18 +198,14 @@ func TestCompareWithNoChanges(t *testing.T) {
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
} }
lowerTree.AddPath(value, &fakeData) lowerTree.AddPath(value, fakeData)
upperTree.AddPath(value, &fakeData) upperTree.AddPath(value, fakeData)
} }
lowerTree.Compare(upperTree) lowerTree.Compare(upperTree)
asserter := func(n *FileNode) error { asserter := func(n *FileNode) error {
if n.Path() == "/" { if n.Path() == "/" {
return nil return nil
} }
if n.Data.FileInfo == nil {
t.Errorf("Expected *FileInfo but got nil")
return fmt.Errorf("expected *FileInfo but got nil")
}
if (n.Data.DiffType) != Unchanged { if (n.Data.DiffType) != Unchanged {
t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.Data.DiffType) t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.Data.DiffType)
} }
@ -231,7 +224,7 @@ func TestCompareWithAdds(t *testing.T) {
upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"} upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"}
for _, value := range lowerPaths { for _, value := range lowerPaths {
lowerTree.AddPath(value, &FileInfo{ lowerTree.AddPath(value, FileInfo{
Path: value, Path: value,
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
@ -239,7 +232,7 @@ func TestCompareWithAdds(t *testing.T) {
} }
for _, value := range upperPaths { for _, value := range upperPaths {
upperTree.AddPath(value, &FileInfo{ upperTree.AddPath(value, FileInfo{
Path: value, Path: value,
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
@ -291,12 +284,12 @@ func TestCompareWithChanges(t *testing.T) {
paths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"} paths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
for _, value := range paths { for _, value := range paths {
lowerTree.AddPath(value, &FileInfo{ lowerTree.AddPath(value, FileInfo{
Path: value, Path: value,
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}) })
upperTree.AddPath(value, &FileInfo{ upperTree.AddPath(value, FileInfo{
Path: value, Path: value,
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
@ -348,7 +341,7 @@ func TestCompareWithRemoves(t *testing.T) {
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
} }
lowerTree.AddPath(value, &fakeData) lowerTree.AddPath(value, fakeData)
} }
for _, value := range upperPaths { for _, value := range upperPaths {
@ -357,7 +350,7 @@ func TestCompareWithRemoves(t *testing.T) {
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
} }
upperTree.AddPath(value, &fakeData) upperTree.AddPath(value, fakeData)
} }
lowerTree.Compare(upperTree) lowerTree.Compare(upperTree)
@ -397,12 +390,12 @@ func TestCompareWithRemoves(t *testing.T) {
func TestStackRange(t *testing.T) { func TestStackRange(t *testing.T) {
tree := NewFileTree() tree := NewFileTree()
tree.AddPath("/etc/nginx/nginx.conf", nil) tree.AddPath("/etc/nginx/nginx.conf", FileInfo{})
tree.AddPath("/etc/nginx/public", nil) tree.AddPath("/etc/nginx/public", FileInfo{})
tree.AddPath("/var/run/systemd", nil) tree.AddPath("/var/run/systemd", FileInfo{})
tree.AddPath("/var/run/bashful", nil) tree.AddPath("/var/run/bashful", FileInfo{})
tree.AddPath("/tmp", nil) tree.AddPath("/tmp", FileInfo{})
tree.AddPath("/tmp/nonsense", nil) tree.AddPath("/tmp/nonsense", FileInfo{})
tree.RemovePath("/var/run/bashful") tree.RemovePath("/var/run/bashful")
tree.RemovePath("/tmp") tree.RemovePath("/tmp")
@ -418,7 +411,7 @@ func TestStackRange(t *testing.T) {
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
} }
lowerTree.AddPath(value, &fakeData) lowerTree.AddPath(value, fakeData)
} }
for _, value := range upperPaths { for _, value := range upperPaths {
@ -427,7 +420,7 @@ func TestStackRange(t *testing.T) {
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
} }
upperTree.AddPath(value, &fakeData) upperTree.AddPath(value, fakeData)
} }
trees := []*FileTree{lowerTree, upperTree, tree} trees := []*FileTree{lowerTree, upperTree, tree}
StackRange(trees, 2) StackRange(trees, 2)
@ -445,7 +438,7 @@ func TestRemoveOnIterate(t *testing.T) {
Typeflag: 1, Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
} }
node, err := tree.AddPath(value, &fakeData) node, err := tree.AddPath(value, fakeData)
if err == nil && stringInSlice(node.Path(), []string{"/etc"}) { if err == nil && stringInSlice(node.Path(), []string{"/etc"}) {
node.Data.ViewInfo.Hidden = true node.Data.ViewInfo.Hidden = true
} }
@ -458,12 +451,12 @@ func TestRemoveOnIterate(t *testing.T) {
return nil return nil
}, nil) }, nil)
expected := `. expected :=
usr ` usr
bin bin
something something
` `
actual := tree.String() actual := tree.String(false)
if expected != actual { if expected != actual {
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual) t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
} }

View File

@ -19,6 +19,10 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
const (
LayerFormat = "%-25s %7s %s"
)
func check(e error) { func check(e error) {
if e != nil { if e != nil {
panic(e) panic(e)
@ -56,7 +60,7 @@ func (layer *Layer) String() string {
if len(layer.History.Tags) > 0 { if len(layer.History.Tags) > 0 {
id = "[" + strings.Join(layer.History.Tags, ",") + "]" id = "[" + strings.Join(layer.History.Tags, ",") + "]"
} }
return fmt.Sprintf("%25s %7s %s", id, humanize.Bytes(uint64(layer.History.Size)), layer.History.CreatedBy) return fmt.Sprintf(LayerFormat, id, humanize.Bytes(uint64(layer.History.Size)), layer.History.CreatedBy)
} }
func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) { func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
@ -103,7 +107,7 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
tree.Name = name tree.Name = name
fileInfos := getFileList(tarReader, header) fileInfos := getFileList(tarReader, header)
for _, element := range fileInfos { for _, element := range fileInfos {
tree.AddPath(element.Path, &element) tree.AddPath(element.Path, element)
} }
layerMap[tree.Name] = tree layerMap[tree.Name] = tree
} }
@ -184,9 +188,9 @@ func saveImage(imageID string) (string, string) {
return imageTarPath, tmpDir return imageTarPath, tmpDir
} }
func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileInfo { func getFileList(parentReader *tar.Reader, header *tar.Header) []filetree.FileInfo {
var files []filetree.FileInfo var files []filetree.FileInfo
var tarredBytes = make([]byte, h.Size) var tarredBytes = make([]byte, header.Size)
_, err := parentReader.Read(tarredBytes) _, err := parentReader.Read(tarredBytes)
if err != nil { if err != nil {

View File

@ -7,8 +7,11 @@ import (
"strings" "strings"
"strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jroimartin/gocui" "github.com/jroimartin/gocui"
"github.com/lunixbochs/vtclean"
"github.com/wagoodman/docker-image-explorer/filetree" "github.com/wagoodman/docker-image-explorer/filetree"
) )
@ -16,6 +19,7 @@ type FileTreeView struct {
Name string Name string
gui *gocui.Gui gui *gocui.Gui
view *gocui.View view *gocui.View
header *gocui.View
TreeIndex int TreeIndex int
ModelTree *filetree.FileTree ModelTree *filetree.FileTree
ViewTree *filetree.FileTree ViewTree *filetree.FileTree
@ -36,7 +40,7 @@ func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTr
return treeview return treeview
} }
func (view *FileTreeView) Setup(v *gocui.View) error { func (view *FileTreeView) Setup(v *gocui.View, header *gocui.View) error {
// set view options // set view options
view.view = v view.view = v
@ -45,7 +49,12 @@ func (view *FileTreeView) Setup(v *gocui.View) error {
//view.view.Highlight = true //view.view.Highlight = true
//view.view.SelBgColor = gocui.ColorGreen //view.view.SelBgColor = gocui.ColorGreen
//view.view.SelFgColor = gocui.ColorBlack //view.view.SelFgColor = gocui.ColorBlack
view.view.Frame = true view.view.Frame = false
view.header = header
view.header.Editable = false
view.header.Wrap = false
view.header.Frame = false
// set keybindings // set keybindings
if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil { if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
@ -76,6 +85,9 @@ func (view *FileTreeView) Setup(v *gocui.View) error {
view.updateViewTree() view.updateViewTree()
view.Render() view.Render()
headerStr := fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree")
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
return nil return nil
} }
@ -110,6 +122,8 @@ func (view *FileTreeView) setLayer(layerIndex int) error {
} }
func (view *FileTreeView) CursorDown() error { func (view *FileTreeView) CursorDown() error {
// cannot easily (quickly) check the model length, allow the view
// to let us know what is a valid bounds (i.e. when it hits an empty line)
err := CursorDown(view.gui, view.view) err := CursorDown(view.gui, view.view)
if err == nil { if err == nil {
view.TreeIndex++ view.TreeIndex++
@ -118,10 +132,12 @@ func (view *FileTreeView) CursorDown() error {
} }
func (view *FileTreeView) CursorUp() error { func (view *FileTreeView) CursorUp() error {
if view.TreeIndex > 0 {
err := CursorUp(view.gui, view.view) err := CursorUp(view.gui, view.view)
if err == nil { if err == nil {
view.TreeIndex-- view.TreeIndex--
} }
}
return view.Render() return view.Render()
} }
@ -130,16 +146,11 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) {
var evaluator func(*filetree.FileNode) bool var evaluator func(*filetree.FileNode) bool
var dfsCounter int var dfsCounter int
// special case: the root node is never visited
if view.TreeIndex == 0 {
return view.ModelTree.Root
}
visiter = func(curNode *filetree.FileNode) error { visiter = func(curNode *filetree.FileNode) error {
dfsCounter++
if dfsCounter == view.TreeIndex { if dfsCounter == view.TreeIndex {
node = curNode node = curNode
} }
dfsCounter++
return nil return nil
} }
var filterBytes []byte var filterBytes []byte
@ -236,12 +247,12 @@ func (view *FileTreeView) KeyHelp() string {
func (view *FileTreeView) Render() error { func (view *FileTreeView) Render() error {
// print the tree to the view // print the tree to the view
lines := strings.Split(view.ViewTree.String(), "\n") lines := strings.Split(view.ViewTree.String(true), "\n")
view.gui.Update(func(g *gocui.Gui) error { view.gui.Update(func(g *gocui.Gui) error {
view.view.Clear() view.view.Clear()
for idx, line := range lines { for idx, line := range lines {
if idx == view.TreeIndex { if idx == view.TreeIndex {
fmt.Fprintln(view.view, Formatting.Header(line)) fmt.Fprintln(view.view, Formatting.StatusBar(vtclean.Clean(line, false)))
} else { } else {
fmt.Fprintln(view.view, line) fmt.Fprintln(view.view, line)
} }

View File

@ -5,12 +5,14 @@ import (
"github.com/jroimartin/gocui" "github.com/jroimartin/gocui"
"github.com/wagoodman/docker-image-explorer/image" "github.com/wagoodman/docker-image-explorer/image"
"github.com/lunixbochs/vtclean"
) )
type LayerView struct { type LayerView struct {
Name string Name string
gui *gocui.Gui gui *gocui.Gui
view *gocui.View view *gocui.View
header *gocui.View
LayerIndex int LayerIndex int
Layers []*image.Layer Layers []*image.Layer
} }
@ -26,16 +28,22 @@ func NewLayerView(name string, gui *gocui.Gui, layers []*image.Layer) (layerview
return layerview return layerview
} }
func (view *LayerView) Setup(v *gocui.View) error { func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error {
// set view options // set view options
view.view = v view.view = v
view.view.Editable = false
view.view.Wrap = false view.view.Wrap = false
//view.view.Highlight = true //view.view.Highlight = true
//view.view.SelBgColor = gocui.ColorGreen //view.view.SelBgColor = gocui.ColorGreen
//view.view.SelFgColor = gocui.ColorBlack //view.view.SelFgColor = gocui.ColorBlack
view.view.Frame = false view.view.Frame = false
view.header = header
view.header.Editable = false
view.header.Wrap = false
view.header.Frame = false
// set keybindings // set keybindings
if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil { if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
return err return err
@ -44,9 +52,10 @@ func (view *LayerView) Setup(v *gocui.View) error {
return err return err
} }
view.Render() headerStr := fmt.Sprintf(image.LayerFormat, "Image ID", "Size", "Command")
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
return nil return view.Render()
} }
func (view *LayerView) Render() error { func (view *LayerView) Render() error {
@ -57,7 +66,7 @@ func (view *LayerView) Render() error {
idx := (len(view.Layers)-1) - revIdx idx := (len(view.Layers)-1) - revIdx
if idx == view.LayerIndex { if idx == view.LayerIndex {
fmt.Fprintln(view.view, Formatting.Header(layer.String())) fmt.Fprintln(view.view, Formatting.StatusBar(layer.String()))
} else { } else {
fmt.Fprintln(view.view, layer.String()) fmt.Fprintln(view.view, layer.String())
} }
@ -70,7 +79,7 @@ func (view *LayerView) Render() error {
} }
func (view *LayerView) CursorDown() error { func (view *LayerView) CursorDown() error {
if int(view.LayerIndex) < len(view.Layers) { if view.LayerIndex < len(view.Layers) {
err := CursorDown(view.gui, view.view) err := CursorDown(view.gui, view.view)
if err == nil { if err == nil {
view.LayerIndex++ view.LayerIndex++
@ -82,7 +91,7 @@ func (view *LayerView) CursorDown() error {
} }
func (view *LayerView) CursorUp() error { func (view *LayerView) CursorUp() error {
if int(view.LayerIndex) > 0 { if view.LayerIndex > 0 {
err := CursorUp(view.gui, view.view) err := CursorUp(view.gui, view.view)
if err == nil { if err == nil {
view.LayerIndex-- view.LayerIndex--

View File

@ -23,7 +23,7 @@ func NewStatusView(name string, gui *gocui.Gui) (statusview *StatusView) {
return statusview return statusview
} }
func (view *StatusView) Setup(v *gocui.View) error { func (view *StatusView) Setup(v *gocui.View, header *gocui.View) error {
// set view options // set view options
view.view = v view.view = v

View File

@ -1,6 +1,7 @@
package ui package ui
import ( import (
"errors"
"log" "log"
"github.com/fatih/color" "github.com/fatih/color"
@ -13,6 +14,7 @@ const debug = false
var Formatting struct { var Formatting struct {
Header func(...interface{}) string Header func(...interface{}) string
StatusBar func(...interface{}) string
} }
var Views struct { var Views struct {
@ -24,7 +26,7 @@ var Views struct {
} }
type View interface { type View interface {
Setup(*gocui.View) error Setup(*gocui.View, *gocui.View) error
CursorDown() error CursorDown() error
CursorUp() error CursorUp() error
Render() error Render() error
@ -63,7 +65,7 @@ func CursorDown(g *gocui.Gui, v *gocui.View) error {
// todo: handle error // todo: handle error
} }
if len(line) == 0 { if len(line) == 0 {
return nil return errors.New("unable to move cursor down, empty line")
} }
if err := v.SetCursor(cx, cy+1); err != nil { if err := v.SetCursor(cx, cy+1); err != nil {
ox, oy := v.Origin() ox, oy := v.Origin()
@ -121,24 +123,40 @@ func layout(g *gocui.Gui) error {
} }
debugCols := maxX - debugWidth debugCols := maxX - debugWidth
bottomRows := 1 bottomRows := 1
if view, err := g.SetView(Views.Layer.Name, -1, -1, splitCols, maxY-bottomRows); err != nil { headerRows := 1
// Layers
if view, err := g.SetView(Views.Layer.Name, -1, -1+headerRows, splitCols, maxY-bottomRows); err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
return err return err
} }
Views.Layer.Setup(view) if header, err := g.SetView(Views.Layer.Name+"header", -1, -1, splitCols, headerRows); err != nil {
if err != gocui.ErrUnknownView {
return err
}
Views.Layer.Setup(view, header)
if _, err := g.SetCurrentView(Views.Layer.Name); err != nil { if _, err := g.SetCurrentView(Views.Layer.Name); err != nil {
return err return err
} }
}
} }
if view, err := g.SetView(Views.Tree.Name, splitCols, -1, debugCols, maxY-bottomRows); err != nil { // Filetree
if view, err := g.SetView(Views.Tree.Name, splitCols, -1+headerRows, debugCols, maxY-bottomRows); err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
return err return err
} }
Views.Tree.Setup(view) if header, err := g.SetView(Views.Tree.Name+"header", splitCols, -1, debugCols, headerRows); err != nil {
if err != gocui.ErrUnknownView {
return err
} }
Views.Tree.Setup(view, header)
}
}
// Debug pane
if debug { if debug {
if _, err := g.SetView("debug", debugCols, -1, maxX, maxY-bottomRows); err != nil { if _, err := g.SetView("debug", debugCols, -1, maxX, maxY-bottomRows); err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
@ -146,11 +164,13 @@ func layout(g *gocui.Gui) error {
} }
} }
} }
// StatusBar
if view, err := g.SetView(Views.Status.Name, -1, maxY-bottomRows-1, maxX, maxY); err != nil { if view, err := g.SetView(Views.Status.Name, -1, maxY-bottomRows-1, maxX, maxY); err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
return err return err
} }
Views.Status.Setup(view) Views.Status.Setup(view, nil)
} }
if view, err := g.SetView(Views.Command.Name, -1, maxY-bottomRows-2, maxX, maxY-1); err != nil { if view, err := g.SetView(Views.Command.Name, -1, maxY-bottomRows-2, maxX, maxY-1); err != nil {
@ -171,7 +191,8 @@ func Render() {
} }
func Run(layers []*image.Layer, refTrees []*filetree.FileTree) { func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
Formatting.Header = color.New(color.ReverseVideo, color.Bold).SprintFunc() Formatting.StatusBar = color.New(color.ReverseVideo, color.Bold).SprintFunc()
Formatting.Header = color.New(color.Bold).SprintFunc()
g, err := gocui.NewGui(gocui.OutputNormal) g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil { if err != nil {