| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- package pretty
- import (
- "bytes"
- "fmt"
- "io"
- "github.com/fatih/color"
- )
- // NB(directxman12): this isn't particularly elegant, but it's also
- // sufficiently simple as to be maintained here. Man (roff) would've
- // probably worked, but it's not necessarily on Windows by default.
- // Span is a chunk of content that is writable to an output, but knows how to
- // calculate its apparent visual "width" on the terminal (not to be confused
- // with the raw length, which may include zero-width coloring sequences).
- type Span interface {
- // VisualLength reports the "width" as perceived by the user on the terminal
- // (i.e. widest line, ignoring ANSI escape characters).
- VisualLength() int
- // WriteTo writes the full span contents to the given writer.
- WriteTo(io.Writer) error
- }
- // Table is a Span that writes its data in table form, with sizing controlled
- // by the given table calculator. Rows are started with StartRow, followed by
- // some calls to Column, followed by a call to EndRow. Once all rows are
- // added, the table can be used as a Span.
- type Table struct {
- Sizing *TableCalculator
- cellsByRow [][]Span
- colSizes []int
- }
- // StartRow starts a new row.
- // It must eventually be followed by EndRow.
- func (t *Table) StartRow() {
- t.cellsByRow = append(t.cellsByRow, []Span(nil))
- }
- // EndRow ends the currently started row.
- func (t *Table) EndRow() {
- lastRow := t.cellsByRow[len(t.cellsByRow)-1]
- sizes := make([]int, len(lastRow))
- for i, cell := range lastRow {
- sizes[i] = cell.VisualLength()
- }
- t.Sizing.AddRowSizes(sizes...)
- }
- // Column adds the given span as a new column to the current row.
- func (t *Table) Column(contents Span) {
- currentRowInd := len(t.cellsByRow) - 1
- t.cellsByRow[currentRowInd] = append(t.cellsByRow[currentRowInd], contents)
- }
- // SkipRow prints a span without having it contribute to the table calculation.
- func (t *Table) SkipRow(contents Span) {
- t.cellsByRow = append(t.cellsByRow, []Span{contents})
- }
- func (t *Table) WriteTo(out io.Writer) error {
- if t.colSizes == nil {
- t.colSizes = t.Sizing.ColumnWidths()
- }
- for _, cells := range t.cellsByRow {
- currentPosition := 0
- for colInd, cell := range cells {
- colSize := t.colSizes[colInd]
- diff := colSize - cell.VisualLength()
- if err := cell.WriteTo(out); err != nil {
- return err
- }
- if diff > 0 {
- if err := writePadding(out, columnPadding, diff); err != nil {
- return err
- }
- }
- currentPosition += colSize
- }
- if _, err := fmt.Fprint(out, "\n"); err != nil {
- return err
- }
- }
- return nil
- }
- func (t *Table) VisualLength() int {
- if t.colSizes == nil {
- t.colSizes = t.Sizing.ColumnWidths()
- }
- res := 0
- for _, colSize := range t.colSizes {
- res += colSize
- }
- return res
- }
- // Text is a span that simply contains raw text. It's a good starting point.
- type Text string
- func (t Text) VisualLength() int { return len(t) }
- func (t Text) WriteTo(w io.Writer) error {
- _, err := w.Write([]byte(t))
- return err
- }
- // indented is a span that indents all lines by the given number of tabs.
- type indented struct {
- Amount int
- Content Span
- }
- func (i *indented) VisualLength() int { return i.Content.VisualLength() }
- func (i *indented) WriteTo(w io.Writer) error {
- var out bytes.Buffer
- if err := i.Content.WriteTo(&out); err != nil {
- return err
- }
- lines := bytes.Split(out.Bytes(), []byte("\n"))
- for lineInd, line := range lines {
- if lineInd != 0 {
- if _, err := w.Write([]byte("\n")); err != nil {
- return err
- }
- }
- if len(line) == 0 {
- continue
- }
- if err := writePadding(w, indentPadding, i.Amount); err != nil {
- return err
- }
- if _, err := w.Write(line); err != nil {
- return err
- }
- }
- return nil
- }
- // Indented returns a span that indents all lines by the given number of tabs.
- func Indented(amt int, content Span) Span {
- return &indented{Amount: amt, Content: content}
- }
- // fromWriter is a span that takes content from a function expecting a Writer.
- type fromWriter struct {
- cache []byte
- cacheError error
- run func(io.Writer) error
- }
- func (f *fromWriter) VisualLength() int {
- if f.cache == nil {
- var buf bytes.Buffer
- if err := f.run(&buf); err != nil {
- f.cacheError = err
- }
- f.cache = buf.Bytes()
- }
- return len(f.cache)
- }
- func (f *fromWriter) WriteTo(w io.Writer) error {
- if f.cache != nil {
- if f.cacheError != nil {
- return f.cacheError
- }
- _, err := w.Write(f.cache)
- return err
- }
- return f.run(w)
- }
- // FromWriter returns a span that takes content from a function expecting a Writer.
- func FromWriter(run func(io.Writer) error) Span {
- return &fromWriter{run: run}
- }
- // Decoration represents a terminal decoration.
- type Decoration color.Color
- // Containing returns a Span that has the given decoration applied.
- func (d Decoration) Containing(contents Span) Span {
- return &decorated{
- Contents: contents,
- Attributes: color.Color(d),
- }
- }
- // decorated is a span that has some terminal decoration applied.
- type decorated struct {
- Contents Span
- Attributes color.Color
- }
- func (d *decorated) VisualLength() int { return d.Contents.VisualLength() }
- func (d *decorated) WriteTo(w io.Writer) error {
- oldOut := color.Output
- color.Output = w
- defer func() { color.Output = oldOut }()
- d.Attributes.Set()
- defer color.Unset()
- return d.Contents.WriteTo(w)
- }
- // SpanWriter is a span that contains multiple sub-spans.
- type SpanWriter struct {
- contents []Span
- }
- func (m *SpanWriter) VisualLength() int {
- res := 0
- for _, span := range m.contents {
- res += span.VisualLength()
- }
- return res
- }
- func (m *SpanWriter) WriteTo(w io.Writer) error {
- for _, span := range m.contents {
- if err := span.WriteTo(w); err != nil {
- return err
- }
- }
- return nil
- }
- // Print adds a new span to this SpanWriter.
- func (m *SpanWriter) Print(s Span) {
- m.contents = append(m.contents, s)
- }
- // lines is a span that adds some newlines, optionally followed by some content.
- type lines struct {
- content Span
- amountBefore int
- }
- func (l *lines) VisualLength() int {
- if l.content == nil {
- return 0
- }
- return l.content.VisualLength()
- }
- func (l *lines) WriteTo(w io.Writer) error {
- if err := writePadding(w, linesPadding, l.amountBefore); err != nil {
- return err
- }
- if l.content != nil {
- if err := l.content.WriteTo(w); err != nil {
- return err
- }
- }
- return nil
- }
- // Newlines returns a span just containing some newlines.
- func Newlines(amt int) Span {
- return &lines{amountBefore: amt}
- }
- // Line returns a span that emits a newline, followed by the given content.
- func Line(content Span) Span {
- return &lines{amountBefore: 1, content: content}
- }
- var (
- columnPadding = []byte(" ")
- 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")
- 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")
- )
- // writePadding writes out padding of the given type in the given amount to the writer.
- // Each byte in the padding buffer contributes 1 to the amount -- the padding being
- // a buffer is just for efficiency.
- func writePadding(out io.Writer, typ []byte, amt int) error {
- if amt <= len(typ) {
- _, err := out.Write(typ[:amt])
- return err
- }
- num := amt / len(typ)
- rem := amt % len(typ)
- for i := 0; i < num; i++ {
- if _, err := out.Write(typ); err != nil {
- return err
- }
- }
- if _, err := out.Write(typ[:rem]); err != nil {
- return err
- }
- return nil
- }
|