1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
// Package text provides utilities for formatting text data.
package text
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
type Cell struct {
contents string
feed bool
}
type GridWriter struct {
ColumnPadding int
MinWidth int
Grid [][]Cell
CurrentRow int
colWidths []int
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// init() makes the initial row if this is the first time any data is being written.
// otherwise, no-op.
func (gw *GridWriter) init() {
if len(gw.Grid) <= gw.CurrentRow {
gw.Grid = append(gw.Grid, []Cell{})
}
}
// WriteCell writes the given string into the next cell in the current row.
func (gw *GridWriter) WriteCell(data string) {
gw.init()
gw.Grid[gw.CurrentRow] = append(gw.Grid[gw.CurrentRow], Cell{data, false})
}
// WriteCells writes multiple cells by calling WriteCell for each argument.
func (gw *GridWriter) WriteCells(data ...string) {
for _, s := range data {
gw.WriteCell(s)
}
}
// Feed writes the given string into the current cell but allowing the cell contents
// to extend past the width of the current column, and ends the row.
func (gw *GridWriter) Feed(data string) {
gw.init()
gw.Grid[gw.CurrentRow] = append(gw.Grid[gw.CurrentRow], Cell{data, true})
gw.EndRow()
}
// EndRow terminates the row of cells and begins a new row in the grid.
func (gw *GridWriter) EndRow() {
gw.CurrentRow++
if len(gw.Grid) <= gw.CurrentRow {
gw.Grid = append(gw.Grid, []Cell{})
}
}
// Reset discards any grid data and resets the current row.
func (gw *GridWriter) Reset() {
gw.CurrentRow = 0
gw.Grid = [][]Cell{}
}
// updateWidths sets the column widths in the Grid. For each column in the Grid,
// it updates the cached width if its value is less than the current width.
func (gw *GridWriter) updateWidths(colWidths []int) {
if gw.colWidths == nil {
gw.colWidths = make([]int, len(colWidths))
copy(gw.colWidths, colWidths)
}
for i, cw := range colWidths {
if gw.colWidths[i] < cw {
gw.colWidths[i] = cw
}
}
}
// calculateWidths returns an array containing the correct padded size for
// each column in the grid.
func (gw *GridWriter) calculateWidths() []int {
colWidths := []int{}
// Loop over each column
for j := 0; ; j++ {
found := false
// Examine all the rows at column 'j'
for i := range gw.Grid {
if len(gw.Grid[i]) <= j {
continue
}
found = true
if len(colWidths) <= j {
colWidths = append(colWidths, 0)
}
if gw.Grid[i][j].feed {
// we're at a row-terminating cell - skip over the rest of this row
continue
}
// Set the size for the row to be the largest
// of all the cells in the column
newMin := max(gw.MinWidth, len(gw.Grid[i][j].contents))
if newMin > colWidths[j] {
colWidths[j] = newMin
}
}
// This column did not have any data in it at all, so we've hit the
// end of the grid - stop.
if !found {
break
}
}
return colWidths
}
// Flush writes the fully-formatted grid to the given io.Writer.
func (gw *GridWriter) Flush(w io.Writer) {
colWidths := gw.calculateWidths()
// invalidate all cached widths if new cells are added/removed
if len(gw.colWidths) != len(colWidths) {
gw.colWidths = make([]int, len(colWidths))
copy(gw.colWidths, colWidths)
} else {
gw.updateWidths(colWidths)
}
for i, row := range gw.Grid {
lastRow := i == (len(gw.Grid) - 1)
for j, cell := range row {
lastCol := (j == len(row)-1)
fmt.Fprintf(w, fmt.Sprintf("%%%vs", gw.colWidths[j]), cell.contents)
if gw.ColumnPadding > 0 && !lastCol {
fmt.Fprint(w, strings.Repeat(" ", gw.ColumnPadding))
}
}
if !lastRow {
fmt.Fprint(w, "\n")
}
}
}
// FlushRows writes the fully-formatted grid to the given io.Writer, but
// gives each row its own Write() call instead of using newlines.
func (gw *GridWriter) FlushRows(w io.Writer) {
gridBuff := &bytes.Buffer{}
gw.Flush(gridBuff)
lineScanner := bufio.NewScanner(gridBuff)
for lineScanner.Scan() {
w.Write(lineScanner.Bytes())
}
}
|