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  }