github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/treeprinter/tree_printer.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package treeprinter 12 13 import ( 14 "bytes" 15 "fmt" 16 "strings" 17 ) 18 19 var ( 20 edgeLinkChr = rune('│') 21 edgeMidChr = rune('├') 22 edgeLastChr = rune('└') 23 ) 24 25 // Node is a handle associated with a specific depth in a tree. See below for 26 // sample usage. 27 type Node struct { 28 tree *tree 29 level int 30 } 31 32 // New creates a tree printer and returns a sentinel node reference which 33 // should be used to add the root. Sample usage: 34 // 35 // tp := New() 36 // root := n.Child("root") 37 // root.Child("child-1") 38 // root.Child("child-2").Child("grandchild\ngrandchild-more-info") 39 // root.Child("child-3") 40 // 41 // fmt.Print(tp.String()) 42 // 43 // Output: 44 // 45 // root 46 // ├── child-1 47 // ├── child-2 48 // │ └── grandchild 49 // │ grandchild-more-info 50 // └── child-3 51 // 52 // Note that the Child calls can't be rearranged arbitrarily; they have 53 // to be in the order they need to be displayed (depth-first pre-order). 54 func New() Node { 55 return NewWithIndent(true, true, 2) 56 } 57 58 // NewWithIndent creates a tree printer like New, permitting customization of 59 // the width of the outputted tree. 60 // leftPad controls whether the tree lines are padded from the first character 61 // of their parent. 62 // rightPad controls whether children are separated from their edges by a space. 63 // edgeLength controls how many characters wide each edge is. 64 func NewWithIndent(leftPad, rightPad bool, edgeLength int) Node { 65 var leftPadStr, rightPadStr string 66 var edgeBuilder strings.Builder 67 for i := 0; i < edgeLength; i++ { 68 edgeBuilder.WriteRune('─') 69 } 70 edgeStr := edgeBuilder.String() 71 if leftPad { 72 leftPadStr = " " 73 } 74 if rightPad { 75 rightPadStr = " " 76 } 77 edgeLink := fmt.Sprintf("%s%c", leftPadStr, edgeLinkChr) 78 edgeMid := fmt.Sprintf("%s%c%s%s", leftPadStr, edgeMidChr, edgeStr, rightPadStr) 79 edgeLast := fmt.Sprintf("%s%c%s%s", leftPadStr, edgeLastChr, edgeStr, rightPadStr) 80 return Node{ 81 tree: &tree{ 82 edgeLink: []rune(edgeLink), 83 edgeMid: []rune(edgeMid), 84 edgeLast: []rune(edgeLast), 85 }, 86 level: 0, 87 } 88 } 89 90 type tree struct { 91 // rows maintains the rows accumulated so far, as rune arrays. 92 // 93 // When a new child is added (e.g. child2 above), we may have to 94 // go back up and fix edges. 95 rows [][]rune 96 97 // row index of the last row for a given level. Grows as needed. 98 lastNode []int 99 100 edgeLink []rune 101 edgeMid []rune 102 edgeLast []rune 103 } 104 105 // Childf adds a node as a child of the given node. 106 func (n Node) Childf(format string, args ...interface{}) Node { 107 return n.Child(fmt.Sprintf(format, args...)) 108 } 109 110 // Child adds a node as a child of the given node. Multi-line strings are 111 // supported with appropriate indentation. 112 func (n Node) Child(text string) Node { 113 if strings.ContainsRune(text, '\n') { 114 splitLines := strings.Split(text, "\n") 115 node := n.childLine(splitLines[0]) 116 for _, l := range splitLines[1:] { 117 n.AddLine(l) 118 } 119 return node 120 } 121 return n.childLine(text) 122 } 123 124 // AddLine adds a new line to a child node without an edge. 125 func (n Node) AddLine(text string) { 126 runes := []rune(text) 127 // Each level indents by this much. 128 k := len(n.tree.edgeLast) 129 indent := n.level * k 130 row := make([]rune, indent+len(runes)) 131 for i := 0; i < indent; i++ { 132 row[i] = ' ' 133 } 134 for i, r := range runes { 135 row[indent+i] = r 136 } 137 n.tree.rows = append(n.tree.rows, row) 138 } 139 140 // childLine adds a node as a child of the given node. 141 func (n Node) childLine(text string) Node { 142 runes := []rune(text) 143 144 // Each level indents by this much. 145 k := len(n.tree.edgeLast) 146 indent := n.level * k 147 row := make([]rune, indent+len(runes)) 148 for i := 0; i < indent-k; i++ { 149 row[i] = ' ' 150 } 151 if indent >= k { 152 // Connect through any empty lines. 153 for i := len(n.tree.rows) - 1; i >= 0 && len(n.tree.rows[i]) == 0; i-- { 154 n.tree.rows[i] = make([]rune, indent-k+len(n.tree.edgeLink)) 155 for j := 0; j < indent-k+len(n.tree.edgeLink); j++ { 156 n.tree.rows[i][j] = ' ' 157 } 158 copy(n.tree.rows[i][indent-k:], n.tree.edgeLink) 159 } 160 copy(row[indent-k:], n.tree.edgeLast) 161 } 162 copy(row[indent:], runes) 163 164 for len(n.tree.lastNode) <= n.level+1 { 165 n.tree.lastNode = append(n.tree.lastNode, -1) 166 } 167 n.tree.lastNode[n.level+1] = -1 168 169 if last := n.tree.lastNode[n.level]; last != -1 { 170 if n.level == 0 { 171 panic("multiple root nodes") 172 } 173 // Connect to the previous sibling. 174 copy(n.tree.rows[last][indent-k:], n.tree.edgeMid) 175 for i := last + 1; i < len(n.tree.rows); i++ { 176 // Add spaces if necessary. 177 for len(n.tree.rows[i]) < indent-k+len(n.tree.edgeLink) { 178 n.tree.rows[i] = append(n.tree.rows[i], ' ') 179 } 180 copy(n.tree.rows[i][indent-k:], n.tree.edgeLink) 181 } 182 } 183 184 n.tree.lastNode[n.level] = len(n.tree.rows) 185 n.tree.rows = append(n.tree.rows, row) 186 187 // Return a TreePrinter that can be used for children of this node. 188 return Node{ 189 tree: n.tree, 190 level: n.level + 1, 191 } 192 } 193 194 // AddEmptyLine adds an empty line to the output; used to introduce vertical 195 // spacing as needed. 196 func (n Node) AddEmptyLine() { 197 n.tree.rows = append(n.tree.rows, []rune{}) 198 } 199 200 // FormattedRows returns the formatted rows. Can only be called on the result of 201 // treeprinter.New. 202 func (n Node) FormattedRows() []string { 203 if n.level != 0 { 204 panic("Only the root can be stringified") 205 } 206 res := make([]string, len(n.tree.rows)) 207 for i, r := range n.tree.rows { 208 res[i] = string(r) 209 } 210 return res 211 } 212 213 func (n Node) String() string { 214 if n.level != 0 { 215 panic("Only the root can be stringified") 216 } 217 var buf bytes.Buffer 218 for _, r := range n.tree.rows { 219 buf.WriteString(string(r)) 220 buf.WriteByte('\n') 221 } 222 return buf.String() 223 }