github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/pkg/genall/help/pretty/print.go (about)

     1  package pretty
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/fatih/color"
     9  )
    10  
    11  // NB(directxman12): this isn't particularly elegant, but it's also
    12  // sufficiently simple as to be maintained here.   Man (roff) would've
    13  // probably worked, but it's not necessarily on Windows by default.
    14  
    15  // Span is a chunk of content that is writable to an output, but knows how to
    16  // calculate its apparent visual "width" on the terminal (not to be confused
    17  // with the raw length, which may include zero-width coloring sequences).
    18  type Span interface {
    19  	// VisualLength reports the "width" as perceived by the user on the terminal
    20  	// (i.e. widest line, ignoring ANSI escape characters).
    21  	VisualLength() int
    22  	// WriteTo writes the full span contents to the given writer.
    23  	WriteTo(io.Writer) error
    24  }
    25  
    26  // Table is a Span that writes its data in table form, with sizing controlled
    27  // by the given table calculator.  Rows are started with StartRow, followed by
    28  // some calls to Column, followed by a call to EndRow.  Once all rows are
    29  // added, the table can be used as a Span.
    30  type Table struct {
    31  	Sizing *TableCalculator
    32  
    33  	cellsByRow [][]Span
    34  	colSizes   []int
    35  }
    36  
    37  // StartRow starts a new row.
    38  // It must eventually be followed by EndRow.
    39  func (t *Table) StartRow() {
    40  	t.cellsByRow = append(t.cellsByRow, []Span(nil))
    41  }
    42  
    43  // EndRow ends the currently started row.
    44  func (t *Table) EndRow() {
    45  	lastRow := t.cellsByRow[len(t.cellsByRow)-1]
    46  	sizes := make([]int, len(lastRow))
    47  	for i, cell := range lastRow {
    48  		sizes[i] = cell.VisualLength()
    49  	}
    50  	t.Sizing.AddRowSizes(sizes...)
    51  }
    52  
    53  // Column adds the given span as a new column to the current row.
    54  func (t *Table) Column(contents Span) {
    55  	currentRowInd := len(t.cellsByRow) - 1
    56  	t.cellsByRow[currentRowInd] = append(t.cellsByRow[currentRowInd], contents)
    57  }
    58  
    59  // SkipRow prints a span without having it contribute to the table calculation.
    60  func (t *Table) SkipRow(contents Span) {
    61  	t.cellsByRow = append(t.cellsByRow, []Span{contents})
    62  }
    63  
    64  func (t *Table) WriteTo(out io.Writer) error {
    65  	if t.colSizes == nil {
    66  		t.colSizes = t.Sizing.ColumnWidths()
    67  	}
    68  
    69  	for _, cells := range t.cellsByRow {
    70  		currentPosition := 0
    71  		for colInd, cell := range cells {
    72  			colSize := t.colSizes[colInd]
    73  			diff := colSize - cell.VisualLength()
    74  
    75  			if err := cell.WriteTo(out); err != nil {
    76  				return err
    77  			}
    78  
    79  			if diff > 0 {
    80  				if err := writePadding(out, columnPadding, diff); err != nil {
    81  					return err
    82  				}
    83  			}
    84  			currentPosition += colSize
    85  		}
    86  
    87  		if _, err := fmt.Fprint(out, "\n"); err != nil {
    88  			return err
    89  		}
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (t *Table) VisualLength() int {
    96  	if t.colSizes == nil {
    97  		t.colSizes = t.Sizing.ColumnWidths()
    98  	}
    99  
   100  	res := 0
   101  	for _, colSize := range t.colSizes {
   102  		res += colSize
   103  	}
   104  	return res
   105  }
   106  
   107  // Text is a span that simply contains raw text.  It's a good starting point.
   108  type Text string
   109  
   110  func (t Text) VisualLength() int { return len(t) }
   111  func (t Text) WriteTo(w io.Writer) error {
   112  	_, err := w.Write([]byte(t))
   113  	return err
   114  }
   115  
   116  // indented is a span that indents all lines by the given number of tabs.
   117  type indented struct {
   118  	Amount  int
   119  	Content Span
   120  }
   121  
   122  func (i *indented) VisualLength() int { return i.Content.VisualLength() }
   123  func (i *indented) WriteTo(w io.Writer) error {
   124  	var out bytes.Buffer
   125  	if err := i.Content.WriteTo(&out); err != nil {
   126  		return err
   127  	}
   128  
   129  	lines := bytes.Split(out.Bytes(), []byte("\n"))
   130  	for lineInd, line := range lines {
   131  		if lineInd != 0 {
   132  			if _, err := w.Write([]byte("\n")); err != nil {
   133  				return err
   134  			}
   135  		}
   136  		if len(line) == 0 {
   137  			continue
   138  		}
   139  
   140  		if err := writePadding(w, indentPadding, i.Amount); err != nil {
   141  			return err
   142  		}
   143  		if _, err := w.Write(line); err != nil {
   144  			return err
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  // Indented returns a span that indents all lines by the given number of tabs.
   151  func Indented(amt int, content Span) Span {
   152  	return &indented{Amount: amt, Content: content}
   153  }
   154  
   155  // fromWriter is a span that takes content from a function expecting a Writer.
   156  type fromWriter struct {
   157  	cache      []byte
   158  	cacheError error
   159  	run        func(io.Writer) error
   160  }
   161  
   162  func (f *fromWriter) VisualLength() int {
   163  	if f.cache == nil {
   164  		var buf bytes.Buffer
   165  		if err := f.run(&buf); err != nil {
   166  			f.cacheError = err
   167  		}
   168  		f.cache = buf.Bytes()
   169  	}
   170  	return len(f.cache)
   171  }
   172  func (f *fromWriter) WriteTo(w io.Writer) error {
   173  	if f.cache != nil {
   174  		if f.cacheError != nil {
   175  			return f.cacheError
   176  		}
   177  		_, err := w.Write(f.cache)
   178  		return err
   179  	}
   180  	return f.run(w)
   181  }
   182  
   183  // FromWriter returns a span that takes content from a function expecting a Writer.
   184  func FromWriter(run func(io.Writer) error) Span {
   185  	return &fromWriter{run: run}
   186  }
   187  
   188  // Decoration represents a terminal decoration.
   189  type Decoration color.Color
   190  
   191  // Containing returns a Span that has the given decoration applied.
   192  func (d Decoration) Containing(contents Span) Span {
   193  	return &decorated{
   194  		Contents:   contents,
   195  		Attributes: color.Color(d),
   196  	}
   197  }
   198  
   199  // decorated is a span that has some terminal decoration applied.
   200  type decorated struct {
   201  	Contents   Span
   202  	Attributes color.Color
   203  }
   204  
   205  func (d *decorated) VisualLength() int { return d.Contents.VisualLength() }
   206  func (d *decorated) WriteTo(w io.Writer) error {
   207  	oldOut := color.Output
   208  	color.Output = w
   209  	defer func() { color.Output = oldOut }()
   210  
   211  	d.Attributes.Set()
   212  	defer color.Unset()
   213  
   214  	return d.Contents.WriteTo(w)
   215  }
   216  
   217  // SpanWriter is a span that contains multiple sub-spans.
   218  type SpanWriter struct {
   219  	contents []Span
   220  }
   221  
   222  func (m *SpanWriter) VisualLength() int {
   223  	res := 0
   224  	for _, span := range m.contents {
   225  		res += span.VisualLength()
   226  	}
   227  	return res
   228  }
   229  func (m *SpanWriter) WriteTo(w io.Writer) error {
   230  	for _, span := range m.contents {
   231  		if err := span.WriteTo(w); err != nil {
   232  			return err
   233  		}
   234  	}
   235  	return nil
   236  }
   237  
   238  // Print adds a new span to this SpanWriter.
   239  func (m *SpanWriter) Print(s Span) {
   240  	m.contents = append(m.contents, s)
   241  }
   242  
   243  // lines is a span that adds some newlines, optionally followed by some content.
   244  type lines struct {
   245  	content      Span
   246  	amountBefore int
   247  }
   248  
   249  func (l *lines) VisualLength() int {
   250  	if l.content == nil {
   251  		return 0
   252  	}
   253  	return l.content.VisualLength()
   254  }
   255  func (l *lines) WriteTo(w io.Writer) error {
   256  	if err := writePadding(w, linesPadding, l.amountBefore); err != nil {
   257  		return err
   258  	}
   259  	if l.content != nil {
   260  		if err := l.content.WriteTo(w); err != nil {
   261  			return err
   262  		}
   263  	}
   264  	return nil
   265  }
   266  
   267  // Newlines returns a span just containing some newlines.
   268  func Newlines(amt int) Span {
   269  	return &lines{amountBefore: amt}
   270  }
   271  
   272  // Line returns a span that emits a newline, followed by the given content.
   273  func Line(content Span) Span {
   274  	return &lines{amountBefore: 1, content: content}
   275  }
   276  
   277  var (
   278  	columnPadding = []byte("                                                                       ")
   279  	indentPadding = []byte("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t")
   280  	linesPadding  = []byte("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
   281  )
   282  
   283  // writePadding writes out padding of the given type in the given amount to the writer.
   284  // Each byte in the padding buffer contributes 1 to the amount -- the padding being
   285  // a buffer is just for efficiency.
   286  func writePadding(out io.Writer, typ []byte, amt int) error {
   287  	if amt <= len(typ) {
   288  		_, err := out.Write(typ[:amt])
   289  		return err
   290  	}
   291  
   292  	num := amt / len(typ)
   293  	rem := amt % len(typ)
   294  	for i := 0; i < num; i++ {
   295  		if _, err := out.Write(typ); err != nil {
   296  			return err
   297  		}
   298  	}
   299  
   300  	if _, err := out.Write(typ[:rem]); err != nil {
   301  		return err
   302  	}
   303  	return nil
   304  }