pax_global_header00006660000000000000000000000064144023176540014520gustar00rootroot0000000000000052 comment=044005a3b405f660f109e066d9f589755835c2ea treeprint-1.2.0/000077500000000000000000000000001440231765400135345ustar00rootroot00000000000000treeprint-1.2.0/.gitignore000066400000000000000000000000321440231765400155170ustar00rootroot00000000000000vendor/** .idea **/**.iml treeprint-1.2.0/LICENSE000066400000000000000000000021151440231765400145400ustar00rootroot00000000000000The MIT License (MIT) Copyright © 2016 Maxim Kupriianov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. treeprint-1.2.0/README.md000066400000000000000000000112421440231765400150130ustar00rootroot00000000000000treeprint [![GoDoc](https://godoc.org/github.com/xlab/treeprint?status.svg)](https://godoc.org/github.com/xlab/treeprint) ![test coverage](https://img.shields.io/badge/coverage-68.6%25-green.svg) ========= Package `treeprint` provides a simple ASCII tree composing tool. SYSTEME FIGURE If you are familiar with the [tree](http://mama.indstate.edu/users/ice/tree/) utility that is a recursive directory listing command that produces a depth indented listing of files, then you have the idea of what it would look like. On my system the command yields the following ``` $ tree . ├── LICENSE ├── README.md ├── treeprint.go └── treeprint_test.go 0 directories, 4 files ``` and I'd like to have the same format for my Go data structures when I print them. ## Installation ``` $ go get github.com/xlab/treeprint ``` ## Concept of work The general idea is that you initialise a new tree with `treeprint.New()` and then add nodes and branches into it. Use `AddNode()` when you want add a node on the same level as the target or use `AddBranch()` when you want to go a level deeper. So `tree.AddBranch().AddNode().AddNode()` would create a new level with two distinct nodes on it. So `tree.AddNode().AddNode()` is a flat thing and `tree.AddBranch().AddBranch().AddBranch()` is a high thing. Use `String()` or `Bytes()` on a branch to render a subtree, or use it on the root to print the whole tree. The utility will yield Unicode-friendly trees. The output is predictable and there is no platform-dependent exceptions, so if you have issues with displaying the tree in the console, all platform-related transformations can be done after the tree has been rendered: [an example](https://github.com/xlab/treeprint/issues/2#issuecomment-324944141) for Asian locales. ## Use cases ### When you want to render a complex data structure: ```go func main() { // to add a custom root name use `treeprint.NewWithRoot()` instead tree := treeprint.New() // create a new branch in the root one := tree.AddBranch("one") // add some nodes one.AddNode("subnode1").AddNode("subnode2") // create a new sub-branch one.AddBranch("two"). AddNode("subnode1").AddNode("subnode2"). // add some nodes AddBranch("three"). // add a new sub-branch AddNode("subnode1").AddNode("subnode2") // add some nodes too // add one more node that should surround the inner branch one.AddNode("subnode3") // add a new node to the root tree.AddNode("outernode") fmt.Println(tree.String()) } ``` Will give you: ``` . ├── one │   ├── subnode1 │   ├── subnode2 │   ├── two │   │   ├── subnode1 │   │   ├── subnode2 │   │   └── three │   │   ├── subnode1 │   │   └── subnode2 │   └── subnode3 └── outernode ``` ### Another case, when you have to make a tree where any leaf may have some meta-data (as `tree` is capable of it): ```go func main { // to add a custom root name use `treeprint.NewWithRoot()` instead tree := treeprint.New() tree.AddNode("Dockerfile") tree.AddNode("Makefile") tree.AddNode("aws.sh") tree.AddMetaBranch(" 204", "bin"). AddNode("dbmaker").AddNode("someserver").AddNode("testtool") tree.AddMetaBranch(" 374", "deploy"). AddNode("Makefile").AddNode("bootstrap.sh") tree.AddMetaNode("122K", "testtool.a") fmt.Println(tree.String()) } ``` Output: ``` . ├── Dockerfile ├── Makefile ├── aws.sh ├── [ 204] bin │   ├── dbmaker │   ├── someserver │   └── testtool ├── [ 374] deploy │   ├── Makefile │   └── bootstrap.sh └── [122K] testtool.a ``` ### Iterating over the tree nodes ```go tree := New() one := tree.AddBranch("one") one.AddNode("one-subnode1").AddNode("one-subnode2") one.AddBranch("two").AddNode("two-subnode1").AddNode("two-subnode2"). AddBranch("three").AddNode("three-subnode1").AddNode("three-subnode2") tree.AddNode("outernode") // if you need to iterate over the whole tree // call `VisitAll` from your top root node. tree.VisitAll(func(item *node) { if len(item.Nodes) > 0 { // branch nodes fmt.Println(item.Value) // will output one, two, three } else { // leaf nodes fmt.Println(item.Value) // will output one-*, two-*, three-* and outernode } }) ``` Yay! So it works. ## License MIT treeprint-1.2.0/go.mod000066400000000000000000000001261440231765400146410ustar00rootroot00000000000000module github.com/xlab/treeprint go 1.13 require github.com/stretchr/testify v1.7.0 treeprint-1.2.0/go.sum000066400000000000000000000016331440231765400146720ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= treeprint-1.2.0/helpers.go000066400000000000000000000021231440231765400155230ustar00rootroot00000000000000package treeprint import ( "reflect" "strings" ) func isEmpty(v *reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() } return false } func tagSpec(tag string) (name string, omit bool) { parts := strings.Split(tag, ",") if len(parts) < 2 { return tag, false } if parts[1] == "omitempty" { return parts[0], true } return parts[0], false } func filterTags(tag reflect.StructTag) string { tags := strings.Split(string(tag), " ") filtered := make([]string, 0, len(tags)) for i := range tags { if strings.HasPrefix(tags[i], "tree:") { continue } filtered = append(filtered, tags[i]) } return strings.Join(filtered, " ") } treeprint-1.2.0/struct.go000066400000000000000000000167101440231765400154140ustar00rootroot00000000000000package treeprint import ( "fmt" "reflect" "strings" ) type StructTreeOption int const ( StructNameTree StructTreeOption = iota StructValueTree StructTagTree StructTypeTree StructTypeSizeTree ) func FromStruct(v interface{}, opt ...StructTreeOption) (Tree, error) { var treeOpt StructTreeOption if len(opt) > 0 { treeOpt = opt[0] } switch treeOpt { case StructNameTree: tree := New() err := nameTree(tree, v) return tree, err case StructValueTree: tree := New() err := valueTree(tree, v) return tree, err case StructTagTree: tree := New() err := tagTree(tree, v) return tree, err case StructTypeTree: tree := New() err := typeTree(tree, v) return tree, err case StructTypeSizeTree: tree := New() err := typeSizeTree(tree, v) return tree, err default: err := fmt.Errorf("treeprint: invalid StructTreeOption %v", treeOpt) return nil, err } } type FmtFunc func(name string, v interface{}) (string, bool) func FromStructWithMeta(v interface{}, fmtFunc FmtFunc) (Tree, error) { if fmtFunc == nil { tree := New() err := nameTree(tree, v) return tree, err } tree := New() err := metaTree(tree, v, fmtFunc) return tree, err } func Repr(v interface{}) string { tree := New() vType := reflect.TypeOf(v) vValue := reflect.ValueOf(v) _, val, isStruct := getValue(vType, &vValue) if !isStruct { return fmt.Sprintf("%+v", val.Interface()) } err := valueTree(tree, val.Interface()) if err != nil { return err.Error() } return tree.String() } func nameTree(tree Tree, v interface{}) error { typ, val, err := checkType(v) if err != nil { return err } fields := typ.NumField() for i := 0; i < fields; i++ { field := typ.Field(i) fieldValue := val.Field(i) name, skip, omit := getMeta(field.Name, field.Tag) if skip || omit && isEmpty(&fieldValue) { continue } typ, val, isStruct := getValue(field.Type, &fieldValue) if !isStruct { tree.AddNode(name) continue } else if subNum := typ.NumField(); subNum == 0 { tree.AddNode(name) continue } branch := tree.AddBranch(name) if err := nameTree(branch, val.Interface()); err != nil { err := fmt.Errorf("%v on struct branch %s", err, name) return err } } return nil } func getMeta(fieldName string, tag reflect.StructTag) (name string, skip, omit bool) { if tagStr := tag.Get("tree"); len(tagStr) > 0 { name, omit = tagSpec(tagStr) } if name == "-" { return fieldName, true, omit } if len(name) == 0 { name = fieldName } else if trimmed := strings.TrimSpace(name); len(trimmed) == 0 { name = fieldName } return } func valueTree(tree Tree, v interface{}) error { typ, val, err := checkType(v) if err != nil { return err } fields := typ.NumField() for i := 0; i < fields; i++ { field := typ.Field(i) fieldValue := val.Field(i) name, skip, omit := getMeta(field.Name, field.Tag) if skip || omit && isEmpty(&fieldValue) { continue } typ, val, isStruct := getValue(field.Type, &fieldValue) if !isStruct { tree.AddMetaNode(val.Interface(), name) continue } else if subNum := typ.NumField(); subNum == 0 { tree.AddMetaNode(val.Interface(), name) continue } branch := tree.AddBranch(name) if err := valueTree(branch, val.Interface()); err != nil { err := fmt.Errorf("%v on struct branch %s", err, name) return err } } return nil } func tagTree(tree Tree, v interface{}) error { typ, val, err := checkType(v) if err != nil { return err } fields := typ.NumField() for i := 0; i < fields; i++ { field := typ.Field(i) fieldValue := val.Field(i) name, skip, omit := getMeta(field.Name, field.Tag) if skip || omit && isEmpty(&fieldValue) { continue } filteredTag := filterTags(field.Tag) typ, val, isStruct := getValue(field.Type, &fieldValue) if !isStruct { tree.AddMetaNode(filteredTag, name) continue } else if subNum := typ.NumField(); subNum == 0 { tree.AddMetaNode(filteredTag, name) continue } branch := tree.AddMetaBranch(filteredTag, name) if err := tagTree(branch, val.Interface()); err != nil { err := fmt.Errorf("%v on struct branch %s", err, name) return err } } return nil } func typeTree(tree Tree, v interface{}) error { typ, val, err := checkType(v) if err != nil { return err } fields := typ.NumField() for i := 0; i < fields; i++ { field := typ.Field(i) fieldValue := val.Field(i) name, skip, omit := getMeta(field.Name, field.Tag) if skip || omit && isEmpty(&fieldValue) { continue } typ, val, isStruct := getValue(field.Type, &fieldValue) typename := fmt.Sprintf("%T", val.Interface()) if !isStruct { tree.AddMetaNode(typename, name) continue } else if subNum := typ.NumField(); subNum == 0 { tree.AddMetaNode(typename, name) continue } branch := tree.AddMetaBranch(typename, name) if err := typeTree(branch, val.Interface()); err != nil { err := fmt.Errorf("%v on struct branch %s", err, name) return err } } return nil } func typeSizeTree(tree Tree, v interface{}) error { typ, val, err := checkType(v) if err != nil { return err } fields := typ.NumField() for i := 0; i < fields; i++ { field := typ.Field(i) fieldValue := val.Field(i) name, skip, omit := getMeta(field.Name, field.Tag) if skip || omit && isEmpty(&fieldValue) { continue } typ, val, isStruct := getValue(field.Type, &fieldValue) typesize := typ.Size() if !isStruct { tree.AddMetaNode(typesize, name) continue } else if subNum := typ.NumField(); subNum == 0 { tree.AddMetaNode(typesize, name) continue } branch := tree.AddMetaBranch(typesize, name) if err := typeSizeTree(branch, val.Interface()); err != nil { err := fmt.Errorf("%v on struct branch %s", err, name) return err } } return nil } func metaTree(tree Tree, v interface{}, fmtFunc FmtFunc) error { typ, val, err := checkType(v) if err != nil { return err } fields := typ.NumField() for i := 0; i < fields; i++ { field := typ.Field(i) fieldValue := val.Field(i) name, skip, omit := getMeta(field.Name, field.Tag) if skip || omit && isEmpty(&fieldValue) { continue } typ, val, isStruct := getValue(field.Type, &fieldValue) formatted, show := fmtFunc(name, val.Interface()) if !isStruct { if show { tree.AddMetaNode(formatted, name) continue } tree.AddNode(name) continue } else if subNum := typ.NumField(); subNum == 0 { if show { tree.AddMetaNode(formatted, name) continue } tree.AddNode(name) continue } var branch Tree if show { branch = tree.AddMetaBranch(formatted, name) } else { branch = tree.AddBranch(name) } if err := metaTree(branch, val.Interface(), fmtFunc); err != nil { err := fmt.Errorf("%v on struct branch %s", err, name) return err } } return nil } func getValue(typ reflect.Type, val *reflect.Value) (reflect.Type, *reflect.Value, bool) { switch typ.Kind() { case reflect.Ptr: typ = typ.Elem() if typ.Kind() == reflect.Struct { elem := val.Elem() return typ, &elem, true } case reflect.Struct: return typ, val, true } return typ, val, false } func checkType(v interface{}) (reflect.Type, *reflect.Value, error) { typ := reflect.TypeOf(v) val := reflect.ValueOf(v) switch typ.Kind() { case reflect.Ptr: typ = typ.Elem() if typ.Kind() != reflect.Struct { err := fmt.Errorf("treeprint: %T is not a struct we could work with", v) return nil, nil, err } val = val.Elem() case reflect.Struct: default: err := fmt.Errorf("treeprint: %T is not a struct we could work with", v) return nil, nil, err } return typ, &val, nil } treeprint-1.2.0/struct_test.go000066400000000000000000000070251440231765400164520ustar00rootroot00000000000000package treeprint import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) type nameStruct struct { One string `json:"one" tree:"one"` Two int `tree:"two"` Three struct { SubOne []string SubTwo []interface{} SubThree struct { InnerOne *float64 `tree:"inner_one,omitempty"` InnerTwo *struct{} `tree:",omitempty"` InnerThree *float64 `tree:"inner_three"` } } } func TestFromStructName(t *testing.T) { assert := assert.New(t) tree, err := FromStruct(nameStruct{}, StructNameTree) assert.NoError(err) actual := tree.String() expected := `. ├── one ├── two └── Three ├── SubOne ├── SubTwo └── SubThree └── inner_three ` assert.Equal(expected, actual) } func TestFromStructTags(t *testing.T) { assert := assert.New(t) tree, err := FromStruct(nameStruct{}, StructTagTree) assert.NoError(err) actual := tree.String() expected := `. ├── [json:"one"] one ├── [] two └── [] Three ├── [] SubOne ├── [] SubTwo └── [] SubThree └── [] inner_three ` assert.Equal(expected, actual) } type typeStruct struct { One string `json:"one" tree:"one"` Two int `tree:"two"` Three subtypeStruct } type subtypeStruct struct { SubOne []string SubTwo []interface{} SubThree subsubTypeStruct } type subsubTypeStruct struct { InnerOne *float64 `tree:"inner_one,omitempty"` InnerTwo *struct{} `tree:",omitempty"` InnerThree *float64 `tree:"inner_three"` } func TestFromStructType(t *testing.T) { assert := assert.New(t) tree, err := FromStruct(typeStruct{}, StructTypeTree) assert.NoError(err) actual := tree.String() expected := `. ├── [string] one ├── [int] two └── [treeprint.subtypeStruct] Three ├── [[]string] SubOne ├── [[]interface {}] SubTwo └── [treeprint.subsubTypeStruct] SubThree └── [*float64] inner_three ` assert.Equal(expected, actual) } func TestFromStructTypeSize(t *testing.T) { assert := assert.New(t) tree, err := FromStruct(typeStruct{}, StructTypeSizeTree) assert.NoError(err) actual := tree.String() expected := `. ├── [16] one ├── [8] two └── [72] Three ├── [24] SubOne ├── [24] SubTwo └── [24] SubThree └── [8] inner_three ` assert.Equal(expected, actual) } type valueStruct struct { Name string Bio struct { Age int City string Meta interface{} } } func TestFromStructValue(t *testing.T) { assert := assert.New(t) val := valueStruct{ Name: "Max", } val.Bio.Age = 100 val.Bio.City = "NYC" val.Bio.Meta = []byte("hello") tree, err := FromStruct(val, StructValueTree) assert.NoError(err) actual := tree.String() expected := `. ├── [Max] Name └── Bio ├── [100] Age ├── [NYC] City └── [[104 101 108 108 111]] Meta ` assert.Equal(expected, actual) } func TestFromStructWithMeta(t *testing.T) { assert := assert.New(t) val := valueStruct{ Name: "Max", } val.Bio.Age = 100 val.Bio.City = "NYC" val.Bio.Meta = []byte("hello") tree, err := FromStructWithMeta(val, func(_ string, v interface{}) (string, bool) { return fmt.Sprintf("lol %T", v), true }) assert.NoError(err) actual := tree.String() expected := `. ├── [lol string] Name └── [lol struct { Age int; City string; Meta interface {} }] Bio ├── [lol int] Age ├── [lol string] City └── [lol []uint8] Meta ` assert.Equal(expected, actual) } treeprint-1.2.0/treeprint.go000066400000000000000000000150301440231765400160760ustar00rootroot00000000000000// Package treeprint provides a simple ASCII tree composing tool. package treeprint import ( "bytes" "fmt" "io" "reflect" "strings" ) // Value defines any value type Value interface{} // MetaValue defines any meta value type MetaValue interface{} // NodeVisitor function type for iterating over nodes type NodeVisitor func(item *Node) // Tree represents a tree structure with leaf-nodes and branch-nodes. type Tree interface { // AddNode adds a new Node to a branch. AddNode(v Value) Tree // AddMetaNode adds a new Node with meta value provided to a branch. AddMetaNode(meta MetaValue, v Value) Tree // AddBranch adds a new branch Node (a level deeper). AddBranch(v Value) Tree // AddMetaBranch adds a new branch Node (a level deeper) with meta value provided. AddMetaBranch(meta MetaValue, v Value) Tree // Branch converts a leaf-Node to a branch-Node, // applying this on a branch-Node does no effect. Branch() Tree // FindByMeta finds a Node whose meta value matches the provided one by reflect.DeepEqual, // returns nil if not found. FindByMeta(meta MetaValue) Tree // FindByValue finds a Node whose value matches the provided one by reflect.DeepEqual, // returns nil if not found. FindByValue(value Value) Tree // returns the last Node of a tree FindLastNode() Tree // String renders the tree or subtree as a string. String() string // Bytes renders the tree or subtree as byteslice. Bytes() []byte SetValue(value Value) SetMetaValue(meta MetaValue) // VisitAll iterates over the tree, branches and nodes. // If need to iterate over the whole tree, use the root Node. // Note this method uses a breadth-first approach. VisitAll(fn NodeVisitor) } type Node struct { Root *Node Meta MetaValue Value Value Nodes []*Node } func (n *Node) FindLastNode() Tree { ns := n.Nodes if len(ns) == 0 { return nil } return ns[len(ns)-1] } func (n *Node) AddNode(v Value) Tree { n.Nodes = append(n.Nodes, &Node{ Root: n, Value: v, }) return n } func (n *Node) AddMetaNode(meta MetaValue, v Value) Tree { n.Nodes = append(n.Nodes, &Node{ Root: n, Meta: meta, Value: v, }) return n } func (n *Node) AddBranch(v Value) Tree { branch := &Node{ Root: n, Value: v, } n.Nodes = append(n.Nodes, branch) return branch } func (n *Node) AddMetaBranch(meta MetaValue, v Value) Tree { branch := &Node{ Root: n, Meta: meta, Value: v, } n.Nodes = append(n.Nodes, branch) return branch } func (n *Node) Branch() Tree { n.Root = nil return n } func (n *Node) FindByMeta(meta MetaValue) Tree { for _, node := range n.Nodes { if reflect.DeepEqual(node.Meta, meta) { return node } if v := node.FindByMeta(meta); v != nil { return v } } return nil } func (n *Node) FindByValue(value Value) Tree { for _, node := range n.Nodes { if reflect.DeepEqual(node.Value, value) { return node } if v := node.FindByMeta(value); v != nil { return v } } return nil } func (n *Node) Bytes() []byte { buf := new(bytes.Buffer) level := 0 var levelsEnded []int if n.Root == nil { if n.Meta != nil { buf.WriteString(fmt.Sprintf("[%v] %v", n.Meta, n.Value)) } else { buf.WriteString(fmt.Sprintf("%v", n.Value)) } buf.WriteByte('\n') } else { edge := EdgeTypeMid if len(n.Nodes) == 0 { edge = EdgeTypeEnd levelsEnded = append(levelsEnded, level) } printValues(buf, 0, levelsEnded, edge, n) } if len(n.Nodes) > 0 { printNodes(buf, level, levelsEnded, n.Nodes) } return buf.Bytes() } func (n *Node) String() string { return string(n.Bytes()) } func (n *Node) SetValue(value Value) { n.Value = value } func (n *Node) SetMetaValue(meta MetaValue) { n.Meta = meta } func (n *Node) VisitAll(fn NodeVisitor) { for _, node := range n.Nodes { fn(node) if len(node.Nodes) > 0 { node.VisitAll(fn) continue } } } func printNodes(wr io.Writer, level int, levelsEnded []int, nodes []*Node) { for i, node := range nodes { edge := EdgeTypeMid if i == len(nodes)-1 { levelsEnded = append(levelsEnded, level) edge = EdgeTypeEnd } printValues(wr, level, levelsEnded, edge, node) if len(node.Nodes) > 0 { printNodes(wr, level+1, levelsEnded, node.Nodes) } } } func printValues(wr io.Writer, level int, levelsEnded []int, edge EdgeType, node *Node) { for i := 0; i < level; i++ { if isEnded(levelsEnded, i) { fmt.Fprint(wr, strings.Repeat(" ", IndentSize+1)) continue } fmt.Fprintf(wr, "%s%s", EdgeTypeLink, strings.Repeat(" ", IndentSize)) } val := renderValue(level, node) meta := node.Meta if meta != nil { fmt.Fprintf(wr, "%s [%v] %v\n", edge, meta, val) return } fmt.Fprintf(wr, "%s %v\n", edge, val) } func isEnded(levelsEnded []int, level int) bool { for _, l := range levelsEnded { if l == level { return true } } return false } func renderValue(level int, node *Node) Value { lines := strings.Split(fmt.Sprintf("%v", node.Value), "\n") // If value does not contain multiple lines, return itself. if len(lines) < 2 { return node.Value } // If value contains multiple lines, // generate a padding and prefix each line with it. pad := padding(level, node) for i := 1; i < len(lines); i++ { lines[i] = fmt.Sprintf("%s%s", pad, lines[i]) } return strings.Join(lines, "\n") } // padding returns a padding for the multiline values with correctly placed link edges. // It is generated by traversing the tree upwards (from leaf to the root of the tree) // and, on each level, checking if the Node the last one of its siblings. // If a Node is the last one, the padding on that level should be empty (there's nothing to link to below it). // If a Node is not the last one, the padding on that level should be the link edge so the sibling below is correctly connected. func padding(level int, node *Node) string { links := make([]string, level+1) for node.Root != nil { if isLast(node) { links[level] = strings.Repeat(" ", IndentSize+1) } else { links[level] = fmt.Sprintf("%s%s", EdgeTypeLink, strings.Repeat(" ", IndentSize)) } level-- node = node.Root } return strings.Join(links, "") } // isLast checks if the Node is the last one in the slice of its parent children func isLast(n *Node) bool { return n == n.Root.FindLastNode() } type EdgeType string var ( EdgeTypeLink EdgeType = "│" EdgeTypeMid EdgeType = "├──" EdgeTypeEnd EdgeType = "└──" ) // IndentSize is the number of spaces per tree level. var IndentSize = 3 // New Generates new tree func New() Tree { return &Node{Value: "."} } // NewWithRoot Generates new tree with the given root value func NewWithRoot(root Value) Tree { return &Node{Value: root} } treeprint-1.2.0/treeprint_test.go000066400000000000000000000143341440231765400171430ustar00rootroot00000000000000package treeprint import ( "testing" "github.com/stretchr/testify/assert" ) func TestZeroNodesWithRoot(t *testing.T) { assert := assert.New(t) tree := NewWithRoot("mytree") actual := tree.String() expected := "mytree\n" assert.Equal(expected, actual) } func TestOneNode(t *testing.T) { assert := assert.New(t) tree := New() tree.AddNode("hello") actual := tree.String() expected := `. └── hello ` assert.Equal(expected, actual) } func TestOneNodeWithRoot(t *testing.T) { assert := assert.New(t) tree := NewWithRoot("mytree") tree.AddNode("hello") actual := tree.String() expected := `mytree └── hello ` assert.Equal(expected, actual) } func TestMetaNode(t *testing.T) { assert := assert.New(t) tree := New() tree.AddMetaNode(123, "hello") tree.AddMetaNode([]struct{}{}, "world") actual := tree.String() expected := `. ├── [123] hello └── [[]] world ` assert.Equal(expected, actual) } func TestTwoNodes(t *testing.T) { assert := assert.New(t) tree := New() tree.AddNode("hello") tree.AddNode("world") actual := tree.String() expected := `. ├── hello └── world ` assert.Equal(expected, actual) } func TestLevel(t *testing.T) { assert := assert.New(t) tree := New() tree.AddBranch("hello").AddNode("my friend").AddNode("lol") tree.AddNode("world") actual := tree.String() expected := `. ├── hello │ ├── my friend │ └── lol └── world ` assert.Equal(expected, actual) } func TestNamedRoot(t *testing.T) { assert := assert.New(t) tree := New() tree.AddBranch("hello").AddNode("my friend").AddNode("lol") tree.AddNode("world") tree.SetValue("friends") actual := tree.String() expected := `friends ├── hello │ ├── my friend │ └── lol └── world ` assert.Equal(expected, actual) } func TestDeepLevel(t *testing.T) { assert := assert.New(t) tree := New() one := tree.AddBranch("one") one.AddNode("subnode1").AddNode("subnode2") one.AddBranch("two"). AddNode("subnode1").AddNode("subnode2"). AddBranch("three"). AddNode("subnode1").AddNode("subnode2") one.AddNode("subnode3") tree.AddNode("outernode") actual := tree.String() expected := `. ├── one │ ├── subnode1 │ ├── subnode2 │ ├── two │ │ ├── subnode1 │ │ ├── subnode2 │ │ └── three │ │ ├── subnode1 │ │ └── subnode2 │ └── subnode3 └── outernode ` assert.Equal(expected, actual) } func TestComplex(t *testing.T) { assert := assert.New(t) tree := New() tree.AddNode("Dockerfile") tree.AddNode("Makefile") tree.AddNode("aws.sh") tree.AddMetaBranch(" 204", "bin"). AddNode("dbmaker").AddNode("someserver").AddNode("testtool") tree.AddMetaBranch(" 374", "deploy"). AddNode("Makefile").AddNode("bootstrap.sh") tree.AddMetaNode("122K", "testtool.a") actual := tree.String() expected := `. ├── Dockerfile ├── Makefile ├── aws.sh ├── [ 204] bin │ ├── dbmaker │ ├── someserver │ └── testtool ├── [ 374] deploy │ ├── Makefile │ └── bootstrap.sh └── [122K] testtool.a ` assert.Equal(expected, actual) } func TestIndirectOrder(t *testing.T) { assert := assert.New(t) tree := New() tree.AddBranch("one").AddNode("two") foo := tree.AddBranch("foo") foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c") foo.AddNode("end") actual := tree.String() expected := `. ├── one │ └── two └── foo ├── bar │ ├── a │ ├── b │ └── c └── end ` assert.Equal(expected, actual) } func TestEdgeTypeAndIndent(t *testing.T) { assert := assert.New(t) // Restore to the original values defer func(link, mid, end EdgeType, indent int) { EdgeTypeLink = link EdgeTypeMid = mid EdgeTypeEnd = end IndentSize = indent }(EdgeTypeLink, EdgeTypeMid, EdgeTypeEnd, IndentSize) EdgeTypeLink = "|" EdgeTypeMid = "+-" EdgeTypeEnd = "+-" IndentSize = 2 tree := New() tree.AddBranch("one").AddNode("two") foo := tree.AddBranch("foo") foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c") foo.AddNode("end") actual := tree.String() expected := `. +- one | +- two +- foo +- bar | +- a | +- b | +- c +- end ` assert.Equal(expected, actual) } func TestRelationships(t *testing.T) { assert := assert.New(t) tree := New() tree.AddBranch("one").AddNode("two") foo := tree.AddBranch("foo") foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c") foo.AddNode("end") treeNode := tree.(*Node) assert.Nil(treeNode.Root) assert.Len(treeNode.Nodes, 2) assert.Equal(treeNode, treeNode.Nodes[0].Root) assert.Equal(treeNode.Nodes[0], treeNode.Nodes[0].Nodes[0].Root) } func TestMultiline(t *testing.T) { assert := assert.New(t) multi1 := `I am a multiline value` multi2 := `I have many empty lines` multi3 := `I am another multiple lines value` tree := New() tree.AddBranch("one").AddMetaNode("meta", multi1) tree.AddBranch("two") foo := tree.AddBranch("foo") foo.AddBranch("bar").AddNode("a").AddNode(multi2).AddNode("c") foo.AddBranch(multi3) actual := tree.String() expected := `. ├── one │ └── [meta] I am │ a multiline │ value ├── two └── foo ├── bar │ ├── a │ ├── I have │ │ many │ │ │ │ │ │ empty lines │ └── c └── I am another multiple lines value ` assert.Equal(expected, actual) } func TestVisitAll(t *testing.T) { tree := New() one := tree.AddBranch("one") one.AddNode("one-subnode1").AddNode("one-subnode2") one.AddBranch("two").AddNode("two-subnode1").AddNode("two-subnode2"). AddBranch("three").AddNode("three-subnode1").AddNode("three-subnode2") tree.AddNode("outernode") var visitedNodeValues []Value expectedNodeValues := []Value{ "one", "one-subnode1", "one-subnode2", "two", "two-subnode1", "two-subnode2", "three", "three-subnode1", "three-subnode2", "outernode", } tree.VisitAll(func(item *Node) { visitedNodeValues = append(visitedNodeValues, item.Value) }) assert := assert.New(t) assert.Equal(expectedNodeValues, visitedNodeValues) }