New Upstream Release - golang-github-xlab-treeprint
Ready changes
Summary
Merged new upstream version: 1.1.0+ds (was: 0.0~git20181112.a009c39).
Resulting package
Built on 2022-10-31T17:13 (took 3m45s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-xlab-treeprint-dev
Lintian Result
Diff
diff --git a/README.md b/README.md
index 6f16282..59fb121 100644
--- a/README.md
+++ b/README.md
@@ -41,10 +41,11 @@ The utility will yield Unicode-friendly trees. The output is predictable and the
## Use cases
-When you want to render a complex data structure:
+### 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
@@ -86,10 +87,11 @@ Will give you:
└── outernode
```
-Another case, when you have to make a tree where any leaf may have some meta-data (as `tree` is capable of it):
+### 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")
@@ -122,6 +124,30 @@ Output:
└── [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
diff --git a/debian/changelog b/debian/changelog
index e08d00e..7f5b1e7 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-xlab-treeprint (1.1.0+ds-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Mon, 31 Oct 2022 17:10:57 -0000
+
golang-github-xlab-treeprint (0.0~git20181112.a009c39-2) unstable; urgency=medium
[ Debian Janitor ]
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..cf24632
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/xlab/treeprint
+
+go 1.13
+
+require github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..b380ae4
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
+github.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=
diff --git a/treeprint.go b/treeprint.go
index 8876f7e..f904414 100644
--- a/treeprint.go
+++ b/treeprint.go
@@ -6,11 +6,18 @@ import (
"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.
@@ -39,6 +46,11 @@ type Tree interface {
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 {
@@ -50,8 +62,10 @@ type node struct {
func (n *node) FindLastNode() Tree {
ns := n.Nodes
- n = ns[len(ns)-1]
- return n
+ if len(ns) == 0 {
+ return nil
+ }
+ return ns[len(ns)-1]
}
func (n *node) AddNode(v Value) Tree {
@@ -59,9 +73,6 @@ func (n *node) AddNode(v Value) Tree {
Root: n,
Value: v,
})
- if n.Root != nil {
- return n.Root
- }
return n
}
@@ -71,14 +82,12 @@ func (n *node) AddMetaNode(meta MetaValue, v Value) Tree {
Meta: meta,
Value: v,
})
- if n.Root != nil {
- return n.Root
- }
return n
}
func (n *node) AddBranch(v Value) Tree {
branch := &node{
+ Root: n,
Value: v,
}
n.Nodes = append(n.Nodes, branch)
@@ -87,6 +96,7 @@ func (n *node) AddBranch(v Value) Tree {
func (n *node) AddMetaBranch(meta MetaValue, v Value) Tree {
branch := &node{
+ Root: n,
Meta: meta,
Value: v,
}
@@ -131,7 +141,7 @@ func (n *node) Bytes() []byte {
if n.Meta != nil {
buf.WriteString(fmt.Sprintf("[%v] %v", n.Meta, n.Value))
} else {
- buf.WriteString(fmt.Sprintf("%v",n.Value))
+ buf.WriteString(fmt.Sprintf("%v", n.Value))
}
buf.WriteByte('\n')
} else {
@@ -140,7 +150,7 @@ func (n *node) Bytes() []byte {
edge = EdgeTypeEnd
levelsEnded = append(levelsEnded, level)
}
- printValues(buf, 0, levelsEnded, edge, n.Meta, n.Value)
+ printValues(buf, 0, levelsEnded, edge, n)
}
if len(n.Nodes) > 0 {
printNodes(buf, level, levelsEnded, n.Nodes)
@@ -152,14 +162,25 @@ func (n *node) String() string {
return string(n.Bytes())
}
-func (n *node) SetValue(value Value){
+func (n *node) SetValue(value Value) {
n.Value = value
}
-func (n *node) SetMetaValue(meta MetaValue){
+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) {
@@ -169,7 +190,7 @@ func printNodes(wr io.Writer,
levelsEnded = append(levelsEnded, level)
edge = EdgeTypeEnd
}
- printValues(wr, level, levelsEnded, edge, node.Meta, node.Value)
+ printValues(wr, level, levelsEnded, edge, node)
if len(node.Nodes) > 0 {
printNodes(wr, level+1, levelsEnded, node.Nodes)
}
@@ -177,15 +198,19 @@ func printNodes(wr io.Writer,
}
func printValues(wr io.Writer,
- level int, levelsEnded []int, edge EdgeType, meta MetaValue, val Value) {
+ level int, levelsEnded []int, edge EdgeType, node *node) {
for i := 0; i < level; i++ {
if isEnded(levelsEnded, i) {
- fmt.Fprint(wr, " ")
+ fmt.Fprint(wr, strings.Repeat(" ", IndentSize+1))
continue
}
- fmt.Fprintf(wr, "%s ", EdgeTypeLink)
+ 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
@@ -202,14 +227,68 @@ func isEnded(levelsEnded []int, level int) bool {
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 = "└──"
+ 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}
+}
diff --git a/treeprint_test.go b/treeprint_test.go
index dd89a73..16488e8 100644
--- a/treeprint_test.go
+++ b/treeprint_test.go
@@ -6,6 +6,15 @@ import (
"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)
@@ -18,6 +27,18 @@ func TestOneNode(t *testing.T) {
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)
@@ -55,8 +76,8 @@ func TestLevel(t *testing.T) {
actual := tree.String()
expected := `.
├── hello
-│ ├── my friend
-│ └── lol
+│ ├── my friend
+│ └── lol
└── world
`
assert.Equal(expected, actual)
@@ -72,8 +93,8 @@ func TestNamedRoot(t *testing.T) {
actual := tree.String()
expected := `friends
├── hello
-│ ├── my friend
-│ └── lol
+│ ├── my friend
+│ └── lol
└── world
`
assert.Equal(expected, actual)
@@ -95,15 +116,15 @@ func TestDeepLevel(t *testing.T) {
actual := tree.String()
expected := `.
├── one
-│ ├── subnode1
-│ ├── subnode2
-│ ├── two
-│ │ ├── subnode1
-│ │ ├── subnode2
-│ │ └── three
-│ │ ├── subnode1
-│ │ └── subnode2
-│ └── subnode3
+│ ├── subnode1
+│ ├── subnode2
+│ ├── two
+│ │ ├── subnode1
+│ │ ├── subnode2
+│ │ └── three
+│ │ ├── subnode1
+│ │ └── subnode2
+│ └── subnode3
└── outernode
`
assert.Equal(expected, actual)
@@ -128,12 +149,12 @@ func TestComplex(t *testing.T) {
├── Makefile
├── aws.sh
├── [ 204] bin
-│ ├── dbmaker
-│ ├── someserver
-│ └── testtool
+│ ├── dbmaker
+│ ├── someserver
+│ └── testtool
├── [ 374] deploy
-│ ├── Makefile
-│ └── bootstrap.sh
+│ ├── Makefile
+│ └── bootstrap.sh
└── [122K] testtool.a
`
assert.Equal(expected, actual)
@@ -151,13 +172,146 @@ func TestIndirectOrder(t *testing.T) {
actual := tree.String()
expected := `.
├── one
-│ └── two
+│ └── two
└── foo
├── bar
- │ ├── a
- │ ├── b
- │ └── c
+ │ ├── 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)
+
+}
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/xlab/treeprint/go.mod -rw-r--r-- root/root /usr/share/gocode/src/github.com/xlab/treeprint/go.sum
No differences were encountered in the control files