This makes the approach from `pr list` reusable across other commands that may benefit from table-based output, e.g. `issue list` or `pr status` The idea is: instantiate a printer, connect it to stdout, feed it some data, and it does the rest: colored, truncated column output that fits into a terminal, or tab-delimited output (no color, no truncation) for scripts.
108 lines
2.2 KiB
Go
108 lines
2.2 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
)
|
|
|
|
func NewTablePrinter(w io.Writer) *TTYTablePrinter {
|
|
tty := false
|
|
ttyWidth := 80
|
|
if outFile, isFile := w.(*os.File); isFile {
|
|
fd := int(outFile.Fd())
|
|
tty = terminal.IsTerminal(fd)
|
|
if w, _, err := terminal.GetSize(fd); err == nil {
|
|
ttyWidth = w
|
|
}
|
|
}
|
|
return &TTYTablePrinter{
|
|
out: w,
|
|
IsTTY: tty,
|
|
maxWidth: ttyWidth,
|
|
colWidths: []int{},
|
|
colFuncs: make(map[int]func(string) string),
|
|
}
|
|
}
|
|
|
|
type TTYTablePrinter struct {
|
|
out io.Writer
|
|
IsTTY bool
|
|
maxWidth int
|
|
colWidths []int
|
|
colFuncs map[int]func(string) string
|
|
}
|
|
|
|
func (t *TTYTablePrinter) SetContentWidth(col, width int) {
|
|
if col == len(t.colWidths) {
|
|
t.colWidths = append(t.colWidths, 0)
|
|
}
|
|
if width > t.colWidths[col] {
|
|
t.colWidths[col] = width
|
|
}
|
|
}
|
|
|
|
func (t *TTYTablePrinter) SetColorFunc(col int, colorize func(string) string) {
|
|
t.colFuncs[col] = colorize
|
|
}
|
|
|
|
// FitColumns caps all but first column to fit available terminal width.
|
|
func (t *TTYTablePrinter) FitColumns() {
|
|
numCols := len(t.colWidths)
|
|
delimWidth := 2
|
|
availWidth := t.maxWidth - t.colWidths[0] - ((numCols - 1) * delimWidth)
|
|
// TODO: avoid widening columns that already fit
|
|
// TODO: support weighted instead of even redistribution
|
|
for col := 1; col < len(t.colWidths); col++ {
|
|
t.colWidths[col] = availWidth / (numCols - 1)
|
|
}
|
|
}
|
|
|
|
func (t *TTYTablePrinter) WriteRow(fields ...string) error {
|
|
lastCol := len(fields) - 1
|
|
delim := "\t"
|
|
if t.IsTTY {
|
|
delim = " "
|
|
}
|
|
|
|
for col, val := range fields {
|
|
if col > 0 {
|
|
_, err := fmt.Fprint(t.out, delim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if t.IsTTY {
|
|
truncVal := truncate(t.colWidths[col], val)
|
|
if col != lastCol {
|
|
truncVal = fmt.Sprintf("%-*s", t.colWidths[col], truncVal)
|
|
}
|
|
if t.colFuncs[col] != nil {
|
|
truncVal = t.colFuncs[col](truncVal)
|
|
}
|
|
_, err := fmt.Fprint(t.out, truncVal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err := fmt.Fprint(t.out, val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
_, err := fmt.Fprint(t.out, "\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func truncate(maxLength int, title string) string {
|
|
if len(title) > maxLength {
|
|
return title[0:maxLength-3] + "..."
|
|
}
|
|
return title
|
|
}
|