working commit

This commit is contained in:
2026-03-13 19:02:42 +02:00
parent bebbf79c7a
commit 5c1da77f4c
1329 changed files with 314708 additions and 39 deletions
+3
View File
@@ -0,0 +1,3 @@
vendor/**
.idea
**/**.iml
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright © 2016 Maxim Kupriianov <max@kc.vc>
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.
+154
View File
@@ -0,0 +1,154 @@
treeprint [![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.
<a href="https://upload.wikimedia.org/wikipedia/commons/5/58/ENC_SYSTEME_FIGURE.jpeg"><img alt="SYSTEME FIGURE" src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/ENC_SYSTEME_FIGURE.jpeg/896px-ENC_SYSTEME_FIGURE.jpeg" align="left" width="300"></a>
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
+47
View File
@@ -0,0 +1,47 @@
package 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, " ")
}
+322
View File
@@ -0,0 +1,322 @@
package 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
}
+294
View File
@@ -0,0 +1,294 @@
// 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}
}