github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/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 horLineChr = rune('─') 24 bulletChr = rune('•') 25 ) 26 27 // Node is a handle associated with a specific depth in a tree. See below for 28 // sample usage. 29 type Node struct { 30 tree *tree 31 level int 32 } 33 34 // New creates a tree printer and returns a sentinel node reference which 35 // should be used to add the root. Sample usage: 36 // 37 // tp := New() 38 // root := tp.Child("root") 39 // root.Child("child-1") 40 // root.Child("child-2").Child("grandchild\ngrandchild-more-info") 41 // root.Child("child-3") 42 // 43 // fmt.Print(tp.String()) 44 // 45 // Output: 46 // 47 // root 48 // ├── child-1 49 // ├── child-2 50 // │ └── grandchild 51 // │ grandchild-more-info 52 // └── child-3 53 // 54 // Note that the Child calls can't be rearranged arbitrarily; they have 55 // to be in the order they need to be displayed (depth-first pre-order). 56 func New() Node { 57 return NewWithStyle(DefaultStyle) 58 } 59 60 // NewWithStyle creates a tree printer like New, permitting customization of 61 // the style of the resulting tree. 62 func NewWithStyle(style Style) Node { 63 t := &tree{style: style} 64 65 switch style { 66 case CompactStyle: 67 t.edgeLink = []rune{edgeLinkChr} 68 t.edgeMid = []rune{edgeMidChr, ' '} 69 t.edgeLast = []rune{edgeLastChr, ' '} 70 71 case BulletStyle: 72 t.edgeLink = []rune{edgeLinkChr} 73 t.edgeMid = []rune{edgeMidChr, horLineChr, horLineChr, ' '} 74 t.edgeLast = []rune{edgeLastChr, horLineChr, horLineChr, ' '} 75 76 default: 77 t.edgeLink = []rune{' ', edgeLinkChr} 78 t.edgeMid = []rune{' ', edgeMidChr, horLineChr, horLineChr, ' '} 79 t.edgeLast = []rune{' ', edgeLastChr, horLineChr, horLineChr, ' '} 80 } 81 82 return Node{ 83 tree: t, 84 level: 0, 85 } 86 } 87 88 // Style is one of the predefined treeprinter styles. 89 type Style int 90 91 const ( 92 // DefaultStyle is the default style. Example: 93 // 94 // foo 95 // ├── bar1 96 // │ bar2 97 // │ └── baz 98 // └── qux 99 // 100 DefaultStyle Style = iota 101 102 // CompactStyle is a compact style, for deeper trees. Example: 103 // 104 // foo 105 // ├ bar1 106 // │ bar2 107 // │ └ baz 108 // └ qux 109 // 110 CompactStyle 111 112 // BulletStyle is a style that shows a bullet for each node, and groups any 113 // other lines under that bullet. Example: 114 // 115 // • foo 116 // │ 117 // ├── • bar1 118 // │ │ bar2 119 // │ │ 120 // │ └── • baz 121 // │ 122 // └── • qux 123 // 124 BulletStyle 125 ) 126 127 // tree implements the tree printing machinery. 128 // 129 // All Nodes hold a reference to the tree and Node calls result in modification 130 // of the tree. At any point in time, tree.rows contains the formatted tree that 131 // was described by the Node calls performed so far. 132 // 133 // When new nodes are added, some of the characters of the previous formatted 134 // tree need to be updated. Here is an example stepping through the state: 135 // 136 // API call Rows 137 // 138 // 139 // tp := New() <empty> 140 // 141 // 142 // root := tp.Child("root") root 143 // 144 // 145 // root.Child("child-1") root 146 // └── child-1 147 // 148 // 149 // c2 := root.Child("child-2") root 150 // ├── child-1 151 // └── child-2 152 // 153 // Note: here we had to go back up and change └─ into ├─ for child-1. 154 // 155 // 156 // c2.Child("grandchild") root 157 // ├── child-1 158 // └── child-2 159 // └── grandchild 160 // 161 // 162 // root.Child("child-3" root 163 // ├── child-1 164 // ├── child-2 165 // │ └── grandchild 166 // └── child-3 167 // 168 // Note: here we had to go back up and change └─ into ├─ for child-2, and 169 // add a │ on the grandchild row. In general, we may need to add an 170 // arbitrary number of vertical bars. 171 // 172 // In order to perform these character changes, we maintain information about 173 // the nodes on the bottom-most path. 174 type tree struct { 175 style Style 176 177 // rows maintains the rows accumulated so far, as rune arrays. 178 rows [][]rune 179 180 // stack contains information pertaining to the nodes on the bottom-most path 181 // of the tree. 182 stack []nodeInfo 183 184 edgeLink []rune 185 edgeMid []rune 186 edgeLast []rune 187 } 188 189 type nodeInfo struct { 190 // firstChildConnectRow is the index (in tree.rows) of the row up to which we 191 // have to connect the first child of this node. 192 firstChildConnectRow int 193 194 // nextSiblingConnectRow is the index (in tree.rows) of the row up to which we 195 // have to connect the next sibling of this node. Typically this is the same 196 // with firstChildConnectRow, except when the node has multiple rows. For 197 // example: 198 // 199 // foo 200 // └── bar1 <---- nextSiblingConnectRow 201 // bar2 <---- firstChildConnectRow 202 // 203 // firstChildConnectRow is used when adding "baz", nextSiblingConnectRow 204 // is used when adding "qux": 205 // foo 206 // ├── bar1 207 // │ bar2 208 // │ └── baz 209 // └── qux 210 // 211 nextSiblingConnectRow int 212 } 213 214 // set copies the string of runes into a given row, at a specific position. The 215 // row is extended with spaces if needed. 216 func (t *tree) set(rowIdx int, colIdx int, what []rune) { 217 // Extend the line if necessary. 218 for len(t.rows[rowIdx]) < colIdx+len(what) { 219 t.rows[rowIdx] = append(t.rows[rowIdx], ' ') 220 } 221 copy(t.rows[rowIdx][colIdx:], what) 222 } 223 224 // addRow adds a row with a given text, with the proper indentation for the 225 // given level. 226 func (t *tree) addRow(level int, text string) (rowIdx int) { 227 runes := []rune(text) 228 // Each level indents by this much. 229 k := len(t.edgeLast) 230 indent := level * k 231 row := make([]rune, indent+len(runes)) 232 for i := 0; i < indent; i++ { 233 row[i] = ' ' 234 } 235 copy(row[indent:], runes) 236 t.rows = append(t.rows, row) 237 return len(t.rows) - 1 238 } 239 240 // Childf adds a node as a child of the given node. 241 func (n Node) Childf(format string, args ...interface{}) Node { 242 return n.Child(fmt.Sprintf(format, args...)) 243 } 244 245 // Child adds a node as a child of the given node. Multi-line strings are 246 // supported with appropriate indentation. 247 func (n Node) Child(text string) Node { 248 if strings.ContainsRune(text, '\n') { 249 splitLines := strings.Split(text, "\n") 250 node := n.childLine(splitLines[0]) 251 for _, l := range splitLines[1:] { 252 node.AddLine(l) 253 } 254 return node 255 } 256 return n.childLine(text) 257 } 258 259 // AddLine adds a new line to a node without an edge. 260 func (n Node) AddLine(text string) { 261 t := n.tree 262 if t.style == BulletStyle { 263 text = " " + text 264 } 265 rowIdx := t.addRow(n.level-1, text) 266 if t.style != BulletStyle { 267 t.stack[n.level-1].firstChildConnectRow = rowIdx 268 } 269 } 270 271 // childLine adds a node as a child of the given node. 272 func (n Node) childLine(text string) Node { 273 t := n.tree 274 if t.style == BulletStyle { 275 text = fmt.Sprintf("%c %s", bulletChr, text) 276 if n.level > 0 { 277 n.AddEmptyLine() 278 } 279 } 280 rowIdx := t.addRow(n.level, text) 281 edgePos := (n.level - 1) * len(t.edgeLast) 282 if n.level == 0 { 283 // Case 1: root. 284 if len(t.stack) != 0 { 285 panic("multiple root nodes") 286 } 287 } else if len(t.stack) <= n.level { 288 // Case 2: first child. Connect to parent. 289 if len(t.stack) != n.level { 290 panic("misuse of node") 291 } 292 parentRow := t.stack[n.level-1].firstChildConnectRow 293 for i := parentRow + 1; i < rowIdx; i++ { 294 t.set(i, edgePos, t.edgeLink) 295 } 296 t.set(rowIdx, edgePos, t.edgeLast) 297 } else { 298 // Case 3: non-first child. Connect to sibling. 299 siblingRow := t.stack[n.level].nextSiblingConnectRow 300 t.set(siblingRow, edgePos, t.edgeMid) 301 for i := siblingRow + 1; i < rowIdx; i++ { 302 t.set(i, edgePos, t.edgeLink) 303 } 304 t.set(rowIdx, edgePos, t.edgeLast) 305 // Update the nextSiblingConnectRow. 306 t.stack = t.stack[:n.level] 307 } 308 309 t.stack = append(t.stack, nodeInfo{ 310 firstChildConnectRow: rowIdx, 311 nextSiblingConnectRow: rowIdx, 312 }) 313 314 // Return a TreePrinter that can be used for children of this node. 315 return Node{ 316 tree: t, 317 level: n.level + 1, 318 } 319 } 320 321 // AddEmptyLine adds an empty line to the output; used to introduce vertical 322 // spacing as needed. 323 func (n Node) AddEmptyLine() { 324 n.tree.rows = append(n.tree.rows, []rune{}) 325 } 326 327 // FormattedRows returns the formatted rows. Can only be called on the result of 328 // treeprinter.New. 329 func (n Node) FormattedRows() []string { 330 if n.level != 0 { 331 panic("Only the root can be stringified") 332 } 333 res := make([]string, len(n.tree.rows)) 334 for i, r := range n.tree.rows { 335 res[i] = string(r) 336 } 337 return res 338 } 339 340 func (n Node) String() string { 341 if n.level != 0 { 342 panic("Only the root can be stringified") 343 } 344 var buf bytes.Buffer 345 for _, r := range n.tree.rows { 346 buf.WriteString(string(r)) 347 buf.WriteByte('\n') 348 } 349 return buf.String() 350 }