Skip to content

Commit 0c0d7ca

Browse files
committed
initial impl
1 parent 36263a0 commit 0c0d7ca

File tree

6 files changed

+600
-36
lines changed

6 files changed

+600
-36
lines changed

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/kevmurray/printtree
2+
3+
go 1.16
4+
5+
require github.com/stretchr/testify v1.7.0

go.sum

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
11+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

printtree.go

-27
This file was deleted.

printttree_test.go

-9
This file was deleted.

tree.go

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package printtree
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
)
8+
9+
// A simple tree that prints heirarchically. Create a new tree with "NewTree()", then add
10+
// children to it by calling Add(name), Addf(format, args...) or AddAll(name,...). Each of these
11+
// will return the child(ren) that were added so you can continue to recursively add more nodes
12+
// to the tree.
13+
//
14+
// When done, you can call Print() or PrintStyle(style) to get a string representing the tree
15+
// that has traditional ascii-like heirarchy markup.
16+
//
17+
// Example:
18+
// root := NewTree()
19+
// colorTree := root.Add("Monitors")
20+
// monoTree := colorTree.Add("Monocrome")
21+
// monoTree.Add("Old School").AddAll("black", "green")
22+
// monoTree.Add("Contemporary").AddAll("black", "white")
23+
// colorTree.Add("Color").AddAll("red", "green", "blue")
24+
// fmt.Println(root.PrintStyle(Heavy))
25+
//
26+
// Which will print
27+
// Monitors
28+
// ├── Monocrome
29+
// │ ├── Old School
30+
// │ │ ├── black
31+
// │ │ ╰── green
32+
// │ ╰── Contemporary
33+
// │ ├── black
34+
// │ ╰── white
35+
// ╰── Color
36+
// ├── red
37+
// ├── green
38+
// ╰── blue
39+
//
40+
// You can use PrintStyle() to access other styles
41+
// |-- ASCII
42+
// ├── Box
43+
// ┣━━ Heavy
44+
// |-ASCIINarrow
45+
// ├ BoxNarrow
46+
// ┣ HeavyNarrow
47+
// WhiteSpace
48+
// ··· Dots
49+
// → Arrows
50+
51+
// TreeStyle is the markup style of the tree
52+
type TreeStyle int
53+
54+
const (
55+
ASCII TreeStyle = 0
56+
Box TreeStyle = 1
57+
Heavy TreeStyle = 2
58+
ASCIINarrow TreeStyle = 3
59+
BoxNarrow TreeStyle = 4
60+
HeavyNarrow TreeStyle = 5
61+
WhiteSpace TreeStyle = 6
62+
Dots TreeStyle = 7
63+
Arrows TreeStyle = 8
64+
)
65+
66+
// for the different branch types
67+
const (
68+
childBranch int = 0
69+
bypassBranch int = 1
70+
lastChildBranch int = 2
71+
noBranch int = 3
72+
)
73+
74+
type Tree struct {
75+
Label string
76+
Children []*Tree
77+
}
78+
79+
// NewTree returns a new tree node that has no label. This is the root of a tree that you can
80+
// work with later. When you print this tree, this root node will not be printed, only the
81+
// children of this root node. This allows for creating multiple visual roots like
82+
// root := NewTree()
83+
// root.Add("1. First").Add("a. Alpha")
84+
// root.Add("2. second").Add("b. Bravo")
85+
// will print the following
86+
// 1. First
87+
// '-- a. Alpha
88+
// 2. Second
89+
// '-- b. Bravo
90+
func NewTree() *Tree {
91+
return &Tree{}
92+
}
93+
94+
// Add creates a new child Tree (at the end of the child list) in this tree and returns that child Tree
95+
func (tree *Tree) Add(childName string) *Tree {
96+
childTree := Tree{
97+
Label: childName,
98+
}
99+
tree.Children = append(tree.Children, &childTree)
100+
return &childTree
101+
}
102+
103+
// AddAll creates multiple children at one time in the order they are listed
104+
func (tree *Tree) AddAll(childrenNames ...string) []*Tree {
105+
children := make([]*Tree, 0, len(childrenNames))
106+
for _, childName := range childrenNames {
107+
child := tree.Add(childName)
108+
children = append(children, child)
109+
}
110+
return children
111+
}
112+
113+
// Add creates a new child Tree (at the end of the child list) in this tree and returns that child Tree
114+
func (tree *Tree) Addf(label string, a ...interface{}) *Tree {
115+
return tree.Add(fmt.Sprintf(label, a...))
116+
}
117+
118+
// AddTree adds another tree as a child of this tree. If the other tree has no label, then it is
119+
// assumed to be a root node, and all it's children will be added. If it does have a label, then
120+
// it will be added as a child
121+
//
122+
// WARNING: It is up to you not to create infinite tree loops
123+
func (tree *Tree) AddTree(other *Tree) {
124+
if other.Label == "" {
125+
// this is a root tree, copy all it's children
126+
tree.Children = append(tree.Children, other.Children...)
127+
} else {
128+
// tree branch, add the branch to this tree
129+
tree.Children = append(tree.Children, other)
130+
}
131+
}
132+
133+
// Sort sorts the children of this tree by the labels
134+
func (tree *Tree) Sort() {
135+
sort.SliceStable(tree.Children, func(i, j int) bool {
136+
return tree.Children[i].Label < tree.Children[j].Label
137+
})
138+
}
139+
140+
// DeepSort sorts the children of this tree and all sub-trees by the labels
141+
func (tree *Tree) DeepSort() {
142+
tree.Sort()
143+
for index := range tree.Children {
144+
tree.Children[index].DeepSort()
145+
}
146+
}
147+
148+
// String returns a string representation of this tree indented with whitespace
149+
func (tree *Tree) String() string {
150+
return tree.PrintStyle(WhiteSpace)
151+
}
152+
153+
// Print returns a string which is this tree in a heirarchical format
154+
func (tree *Tree) Print() string {
155+
return tree.PrintStyle(Box)
156+
}
157+
func (tree *Tree) PrintStyle(style TreeStyle) string {
158+
dict := [][]string{
159+
{"|-- ", "| ", "'-- ", " "},
160+
{"├── ", "│ ", "╰── ", " "},
161+
{"┣━━ ", "┃ ", "┗━━ ", " "},
162+
{"|-", "| ", "'-", " "},
163+
{"├ ", "│ ", "╰ ", " "},
164+
{"┣ ", "┃ ", "┗ ", " "},
165+
{" ", " ", " ", " "},
166+
{"··· ", "····", "··· ", "····"},
167+
{"→ ", " ", "→ ", " "},
168+
}[style]
169+
buf := strings.Builder{}
170+
tree.print(&buf, true, "", dict)
171+
return buf.String()
172+
}
173+
174+
// print is the internal, recursive hook for printing the tree
175+
func (tree *Tree) print(buf *strings.Builder, isRoot bool, padding string, dict []string) {
176+
for index := range tree.Children {
177+
child := tree.Children[index]
178+
for lineIndex, line := range strings.Split(child.Label, "\n") {
179+
if lineIndex == 0 {
180+
// first line of the block, normal padding
181+
buf.WriteString(padding + tree.labelPadding(isRoot, index, dict) + line + "\n")
182+
} else {
183+
// subsequent lines of the block, flow padding
184+
buf.WriteString(padding + tree.flowPadding(isRoot, index, dict) + line + "\n")
185+
}
186+
}
187+
child.print(buf, false, padding+tree.flowPadding(isRoot, index, dict), dict)
188+
}
189+
}
190+
191+
func (tree *Tree) labelPadding(isRoot bool, index int, dict []string) string {
192+
switch {
193+
case isRoot:
194+
return ""
195+
case index == len(tree.Children)-1:
196+
return dict[lastChildBranch]
197+
}
198+
return dict[childBranch]
199+
}
200+
201+
func (tree *Tree) flowPadding(isRoot bool, index int, dict []string) string {
202+
switch {
203+
case isRoot:
204+
return ""
205+
case index == len(tree.Children)-1:
206+
return dict[noBranch]
207+
}
208+
return dict[bypassBranch]
209+
}

0 commit comments

Comments
 (0)