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

More details

Full run details