New upstream release.
Debian Janitor
2 years ago
40 | 40 | |
41 | 41 | ## Use cases |
42 | 42 | |
43 | When you want to render a complex data structure: | |
43 | ### When you want to render a complex data structure: | |
44 | 44 | |
45 | 45 | ```go |
46 | 46 | func main() { |
47 | // to add a custom root name use `treeprint.NewWithRoot()` instead | |
47 | 48 | tree := treeprint.New() |
48 | 49 | |
49 | 50 | // create a new branch in the root |
85 | 86 | └── outernode |
86 | 87 | ``` |
87 | 88 | |
88 | Another case, when you have to make a tree where any leaf may have some meta-data (as `tree` is capable of it): | |
89 | ### Another case, when you have to make a tree where any leaf may have some meta-data (as `tree` is capable of it): | |
89 | 90 | |
90 | 91 | ```go |
91 | 92 | func main { |
93 | // to add a custom root name use `treeprint.NewWithRoot()` instead | |
92 | 94 | tree := treeprint.New() |
93 | 95 | |
94 | 96 | tree.AddNode("Dockerfile") |
121 | 123 | └── [122K] testtool.a |
122 | 124 | ``` |
123 | 125 | |
126 | ### Iterating over the tree nodes | |
127 | ||
128 | ```go | |
129 | tree := New() | |
130 | ||
131 | one := tree.AddBranch("one") | |
132 | one.AddNode("one-subnode1").AddNode("one-subnode2") | |
133 | one.AddBranch("two").AddNode("two-subnode1").AddNode("two-subnode2"). | |
134 | AddBranch("three").AddNode("three-subnode1").AddNode("three-subnode2") | |
135 | tree.AddNode("outernode") | |
136 | ||
137 | // if you need to iterate over the whole tree | |
138 | // call `VisitAll` from your top root node. | |
139 | tree.VisitAll(func(item *node) { | |
140 | if len(item.Nodes) > 0 { | |
141 | // branch nodes | |
142 | fmt.Println(item.Value) // will output one, two, three | |
143 | } else { | |
144 | // leaf nodes | |
145 | fmt.Println(item.Value) // will output one-*, two-*, three-* and outernode | |
146 | } | |
147 | }) | |
148 | ||
149 | ``` | |
124 | 150 | Yay! So it works. |
125 | 151 | |
126 | 152 | ## License |
0 | golang-github-xlab-treeprint (1.1.0-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Thu, 05 May 2022 10:53:03 -0000 | |
5 | ||
0 | 6 | golang-github-xlab-treeprint (0.0~git20181112.a009c39-2) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Debian Janitor ] |
0 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
2 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
3 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
4 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
5 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | |
6 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |
7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
8 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | |
9 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
5 | 5 | "fmt" |
6 | 6 | "io" |
7 | 7 | "reflect" |
8 | "strings" | |
8 | 9 | ) |
9 | 10 | |
11 | // Value defines any value | |
10 | 12 | type Value interface{} |
13 | ||
14 | // MetaValue defines any meta value | |
11 | 15 | type MetaValue interface{} |
16 | ||
17 | // NodeVisitor function type for iterating over nodes | |
18 | type NodeVisitor func(item *node) | |
12 | 19 | |
13 | 20 | // Tree represents a tree structure with leaf-nodes and branch-nodes. |
14 | 21 | type Tree interface { |
38 | 45 | |
39 | 46 | SetValue(value Value) |
40 | 47 | SetMetaValue(meta MetaValue) |
48 | ||
49 | // VisitAll iterates over the tree, branches and nodes. | |
50 | // If need to iterate over the whole tree, use the root node. | |
51 | // Note this method uses a breadth-first approach. | |
52 | VisitAll(fn NodeVisitor) | |
41 | 53 | } |
42 | 54 | |
43 | 55 | type node struct { |
49 | 61 | |
50 | 62 | func (n *node) FindLastNode() Tree { |
51 | 63 | ns := n.Nodes |
52 | n = ns[len(ns)-1] | |
53 | return n | |
64 | if len(ns) == 0 { | |
65 | return nil | |
66 | } | |
67 | return ns[len(ns)-1] | |
54 | 68 | } |
55 | 69 | |
56 | 70 | func (n *node) AddNode(v Value) Tree { |
58 | 72 | Root: n, |
59 | 73 | Value: v, |
60 | 74 | }) |
61 | if n.Root != nil { | |
62 | return n.Root | |
63 | } | |
64 | 75 | return n |
65 | 76 | } |
66 | 77 | |
70 | 81 | Meta: meta, |
71 | 82 | Value: v, |
72 | 83 | }) |
73 | if n.Root != nil { | |
74 | return n.Root | |
75 | } | |
76 | 84 | return n |
77 | 85 | } |
78 | 86 | |
79 | 87 | func (n *node) AddBranch(v Value) Tree { |
80 | 88 | branch := &node{ |
89 | Root: n, | |
81 | 90 | Value: v, |
82 | 91 | } |
83 | 92 | n.Nodes = append(n.Nodes, branch) |
86 | 95 | |
87 | 96 | func (n *node) AddMetaBranch(meta MetaValue, v Value) Tree { |
88 | 97 | branch := &node{ |
98 | Root: n, | |
89 | 99 | Meta: meta, |
90 | 100 | Value: v, |
91 | 101 | } |
130 | 140 | if n.Meta != nil { |
131 | 141 | buf.WriteString(fmt.Sprintf("[%v] %v", n.Meta, n.Value)) |
132 | 142 | } else { |
133 | buf.WriteString(fmt.Sprintf("%v",n.Value)) | |
143 | buf.WriteString(fmt.Sprintf("%v", n.Value)) | |
134 | 144 | } |
135 | 145 | buf.WriteByte('\n') |
136 | 146 | } else { |
139 | 149 | edge = EdgeTypeEnd |
140 | 150 | levelsEnded = append(levelsEnded, level) |
141 | 151 | } |
142 | printValues(buf, 0, levelsEnded, edge, n.Meta, n.Value) | |
152 | printValues(buf, 0, levelsEnded, edge, n) | |
143 | 153 | } |
144 | 154 | if len(n.Nodes) > 0 { |
145 | 155 | printNodes(buf, level, levelsEnded, n.Nodes) |
151 | 161 | return string(n.Bytes()) |
152 | 162 | } |
153 | 163 | |
154 | func (n *node) SetValue(value Value){ | |
164 | func (n *node) SetValue(value Value) { | |
155 | 165 | n.Value = value |
156 | 166 | } |
157 | 167 | |
158 | func (n *node) SetMetaValue(meta MetaValue){ | |
168 | func (n *node) SetMetaValue(meta MetaValue) { | |
159 | 169 | n.Meta = meta |
170 | } | |
171 | ||
172 | func (n *node) VisitAll(fn NodeVisitor) { | |
173 | for _, node := range n.Nodes { | |
174 | fn(node) | |
175 | ||
176 | if len(node.Nodes) > 0 { | |
177 | node.VisitAll(fn) | |
178 | continue | |
179 | } | |
180 | } | |
160 | 181 | } |
161 | 182 | |
162 | 183 | func printNodes(wr io.Writer, |
168 | 189 | levelsEnded = append(levelsEnded, level) |
169 | 190 | edge = EdgeTypeEnd |
170 | 191 | } |
171 | printValues(wr, level, levelsEnded, edge, node.Meta, node.Value) | |
192 | printValues(wr, level, levelsEnded, edge, node) | |
172 | 193 | if len(node.Nodes) > 0 { |
173 | 194 | printNodes(wr, level+1, levelsEnded, node.Nodes) |
174 | 195 | } |
176 | 197 | } |
177 | 198 | |
178 | 199 | func printValues(wr io.Writer, |
179 | level int, levelsEnded []int, edge EdgeType, meta MetaValue, val Value) { | |
200 | level int, levelsEnded []int, edge EdgeType, node *node) { | |
180 | 201 | |
181 | 202 | for i := 0; i < level; i++ { |
182 | 203 | if isEnded(levelsEnded, i) { |
183 | fmt.Fprint(wr, " ") | |
204 | fmt.Fprint(wr, strings.Repeat(" ", IndentSize+1)) | |
184 | 205 | continue |
185 | 206 | } |
186 | fmt.Fprintf(wr, "%s ", EdgeTypeLink) | |
187 | } | |
207 | fmt.Fprintf(wr, "%s%s", EdgeTypeLink, strings.Repeat(" ", IndentSize)) | |
208 | } | |
209 | ||
210 | val := renderValue(level, node) | |
211 | meta := node.Meta | |
212 | ||
188 | 213 | if meta != nil { |
189 | 214 | fmt.Fprintf(wr, "%s [%v] %v\n", edge, meta, val) |
190 | 215 | return |
201 | 226 | return false |
202 | 227 | } |
203 | 228 | |
229 | func renderValue(level int, node *node) Value { | |
230 | lines := strings.Split(fmt.Sprintf("%v", node.Value), "\n") | |
231 | ||
232 | // If value does not contain multiple lines, return itself. | |
233 | if len(lines) < 2 { | |
234 | return node.Value | |
235 | } | |
236 | ||
237 | // If value contains multiple lines, | |
238 | // generate a padding and prefix each line with it. | |
239 | pad := padding(level, node) | |
240 | ||
241 | for i := 1; i < len(lines); i++ { | |
242 | lines[i] = fmt.Sprintf("%s%s", pad, lines[i]) | |
243 | } | |
244 | ||
245 | return strings.Join(lines, "\n") | |
246 | } | |
247 | ||
248 | // padding returns a padding for the multiline values with correctly placed link edges. | |
249 | // It is generated by traversing the tree upwards (from leaf to the root of the tree) | |
250 | // and, on each level, checking if the node the last one of its siblings. | |
251 | // If a node is the last one, the padding on that level should be empty (there's nothing to link to below it). | |
252 | // 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. | |
253 | func padding(level int, node *node) string { | |
254 | links := make([]string, level+1) | |
255 | ||
256 | for node.Root != nil { | |
257 | if isLast(node) { | |
258 | links[level] = strings.Repeat(" ", IndentSize+1) | |
259 | } else { | |
260 | links[level] = fmt.Sprintf("%s%s", EdgeTypeLink, strings.Repeat(" ", IndentSize)) | |
261 | } | |
262 | level-- | |
263 | node = node.Root | |
264 | } | |
265 | ||
266 | return strings.Join(links, "") | |
267 | } | |
268 | ||
269 | // isLast checks if the node is the last one in the slice of its parent children | |
270 | func isLast(n *node) bool { | |
271 | return n == n.Root.FindLastNode() | |
272 | } | |
273 | ||
204 | 274 | type EdgeType string |
205 | 275 | |
206 | 276 | var ( |
207 | EdgeTypeLink EdgeType = "│" | |
208 | EdgeTypeMid EdgeType = "├──" | |
209 | EdgeTypeEnd EdgeType = "└──" | |
277 | EdgeTypeLink EdgeType = "│" | |
278 | EdgeTypeMid EdgeType = "├──" | |
279 | EdgeTypeEnd EdgeType = "└──" | |
210 | 280 | ) |
211 | 281 | |
282 | // IndentSize is the number of spaces per tree level. | |
283 | var IndentSize = 3 | |
284 | ||
285 | // New Generates new tree | |
212 | 286 | func New() Tree { |
213 | 287 | return &node{Value: "."} |
214 | 288 | } |
289 | ||
290 | // NewWithRoot Generates new tree with the given root value | |
291 | func NewWithRoot(root Value) Tree { | |
292 | return &node{Value: root} | |
293 | } |
5 | 5 | "github.com/stretchr/testify/assert" |
6 | 6 | ) |
7 | 7 | |
8 | func TestZeroNodesWithRoot(t *testing.T) { | |
9 | assert := assert.New(t) | |
10 | ||
11 | tree := NewWithRoot("mytree") | |
12 | actual := tree.String() | |
13 | expected := "mytree\n" | |
14 | assert.Equal(expected, actual) | |
15 | } | |
16 | ||
8 | 17 | func TestOneNode(t *testing.T) { |
9 | 18 | assert := assert.New(t) |
10 | 19 | |
12 | 21 | tree.AddNode("hello") |
13 | 22 | actual := tree.String() |
14 | 23 | expected := `. |
24 | └── hello | |
25 | ` | |
26 | assert.Equal(expected, actual) | |
27 | } | |
28 | ||
29 | func TestOneNodeWithRoot(t *testing.T) { | |
30 | assert := assert.New(t) | |
31 | ||
32 | tree := NewWithRoot("mytree") | |
33 | tree.AddNode("hello") | |
34 | actual := tree.String() | |
35 | expected := `mytree | |
15 | 36 | └── hello |
16 | 37 | ` |
17 | 38 | assert.Equal(expected, actual) |
54 | 75 | actual := tree.String() |
55 | 76 | expected := `. |
56 | 77 | ├── hello |
57 | │ ├── my friend | |
58 | │ └── lol | |
78 | │ ├── my friend | |
79 | │ └── lol | |
59 | 80 | └── world |
60 | 81 | ` |
61 | 82 | assert.Equal(expected, actual) |
71 | 92 | actual := tree.String() |
72 | 93 | expected := `friends |
73 | 94 | ├── hello |
74 | │ ├── my friend | |
75 | │ └── lol | |
95 | │ ├── my friend | |
96 | │ └── lol | |
76 | 97 | └── world |
77 | 98 | ` |
78 | 99 | assert.Equal(expected, actual) |
94 | 115 | actual := tree.String() |
95 | 116 | expected := `. |
96 | 117 | ├── one |
97 | │ ├── subnode1 | |
98 | │ ├── subnode2 | |
99 | │ ├── two | |
100 | │ │ ├── subnode1 | |
101 | │ │ ├── subnode2 | |
102 | │ │ └── three | |
103 | │ │ ├── subnode1 | |
104 | │ │ └── subnode2 | |
105 | │ └── subnode3 | |
118 | │ ├── subnode1 | |
119 | │ ├── subnode2 | |
120 | │ ├── two | |
121 | │ │ ├── subnode1 | |
122 | │ │ ├── subnode2 | |
123 | │ │ └── three | |
124 | │ │ ├── subnode1 | |
125 | │ │ └── subnode2 | |
126 | │ └── subnode3 | |
106 | 127 | └── outernode |
107 | 128 | ` |
108 | 129 | assert.Equal(expected, actual) |
127 | 148 | ├── Makefile |
128 | 149 | ├── aws.sh |
129 | 150 | ├── [ 204] bin |
130 | │ ├── dbmaker | |
131 | │ ├── someserver | |
132 | │ └── testtool | |
151 | │ ├── dbmaker | |
152 | │ ├── someserver | |
153 | │ └── testtool | |
133 | 154 | ├── [ 374] deploy |
134 | │ ├── Makefile | |
135 | │ └── bootstrap.sh | |
155 | │ ├── Makefile | |
156 | │ └── bootstrap.sh | |
136 | 157 | └── [122K] testtool.a |
137 | 158 | ` |
138 | 159 | assert.Equal(expected, actual) |
150 | 171 | actual := tree.String() |
151 | 172 | expected := `. |
152 | 173 | ├── one |
153 | │ └── two | |
174 | │ └── two | |
154 | 175 | └── foo |
155 | 176 | ├── bar |
156 | │ ├── a | |
157 | │ ├── b | |
158 | │ └── c | |
177 | │ ├── a | |
178 | │ ├── b | |
179 | │ └── c | |
159 | 180 | └── end |
160 | 181 | ` |
161 | 182 | assert.Equal(expected, actual) |
162 | 183 | } |
184 | ||
185 | func TestEdgeTypeAndIndent(t *testing.T) { | |
186 | assert := assert.New(t) | |
187 | ||
188 | // Restore to the original values | |
189 | defer func(link, mid, end EdgeType, indent int) { | |
190 | EdgeTypeLink = link | |
191 | EdgeTypeMid = mid | |
192 | EdgeTypeEnd = end | |
193 | IndentSize = indent | |
194 | }(EdgeTypeLink, EdgeTypeMid, EdgeTypeEnd, IndentSize) | |
195 | ||
196 | EdgeTypeLink = "|" | |
197 | EdgeTypeMid = "+-" | |
198 | EdgeTypeEnd = "+-" | |
199 | IndentSize = 2 | |
200 | ||
201 | tree := New() | |
202 | tree.AddBranch("one").AddNode("two") | |
203 | foo := tree.AddBranch("foo") | |
204 | foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c") | |
205 | foo.AddNode("end") | |
206 | ||
207 | actual := tree.String() | |
208 | expected := `. | |
209 | +- one | |
210 | | +- two | |
211 | +- foo | |
212 | +- bar | |
213 | | +- a | |
214 | | +- b | |
215 | | +- c | |
216 | +- end | |
217 | ` | |
218 | assert.Equal(expected, actual) | |
219 | } | |
220 | ||
221 | func TestRelationships(t *testing.T) { | |
222 | assert := assert.New(t) | |
223 | ||
224 | tree := New() | |
225 | tree.AddBranch("one").AddNode("two") | |
226 | foo := tree.AddBranch("foo") | |
227 | foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c") | |
228 | foo.AddNode("end") | |
229 | ||
230 | treeNode := tree.(*node) | |
231 | ||
232 | assert.Nil(treeNode.Root) | |
233 | assert.Len(treeNode.Nodes, 2) | |
234 | assert.Equal(treeNode, treeNode.Nodes[0].Root) | |
235 | assert.Equal(treeNode.Nodes[0], treeNode.Nodes[0].Nodes[0].Root) | |
236 | } | |
237 | ||
238 | func TestMultiline(t *testing.T) { | |
239 | assert := assert.New(t) | |
240 | ||
241 | multi1 := `I am | |
242 | a multiline | |
243 | value` | |
244 | ||
245 | multi2 := `I have | |
246 | many | |
247 | ||
248 | ||
249 | empty lines` | |
250 | ||
251 | multi3 := `I am another | |
252 | multiple | |
253 | lines value` | |
254 | ||
255 | tree := New() | |
256 | tree.AddBranch("one").AddMetaNode("meta", multi1) | |
257 | tree.AddBranch("two") | |
258 | foo := tree.AddBranch("foo") | |
259 | foo.AddBranch("bar").AddNode("a").AddNode(multi2).AddNode("c") | |
260 | foo.AddBranch(multi3) | |
261 | ||
262 | actual := tree.String() | |
263 | expected := `. | |
264 | ├── one | |
265 | │ └── [meta] I am | |
266 | │ a multiline | |
267 | │ value | |
268 | ├── two | |
269 | └── foo | |
270 | ├── bar | |
271 | │ ├── a | |
272 | │ ├── I have | |
273 | │ │ many | |
274 | │ │ | |
275 | │ │ | |
276 | │ │ empty lines | |
277 | │ └── c | |
278 | └── I am another | |
279 | multiple | |
280 | lines value | |
281 | ` | |
282 | ||
283 | assert.Equal(expected, actual) | |
284 | } | |
285 | ||
286 | func TestVisitAll(t *testing.T) { | |
287 | ||
288 | tree := New() | |
289 | one := tree.AddBranch("one") | |
290 | one.AddNode("one-subnode1").AddNode("one-subnode2") | |
291 | one.AddBranch("two").AddNode("two-subnode1").AddNode("two-subnode2"). | |
292 | AddBranch("three").AddNode("three-subnode1").AddNode("three-subnode2") | |
293 | tree.AddNode("outernode") | |
294 | ||
295 | var visitedNodeValues []Value | |
296 | expectedNodeValues := []Value{ | |
297 | "one", | |
298 | "one-subnode1", | |
299 | "one-subnode2", | |
300 | "two", | |
301 | "two-subnode1", | |
302 | "two-subnode2", | |
303 | "three", | |
304 | "three-subnode1", | |
305 | "three-subnode2", | |
306 | "outernode", | |
307 | } | |
308 | ||
309 | tree.VisitAll(func(item *node) { | |
310 | visitedNodeValues = append(visitedNodeValues, item.Value) | |
311 | }) | |
312 | ||
313 | assert := assert.New(t) | |
314 | assert.Equal(expectedNodeValues, visitedNodeValues) | |
315 | ||
316 | } |