|
| 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