2020-08-04 17:07:17 +00:00
|
|
|
package tablewriter
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/acarl005/stripansi"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Column struct {
|
|
|
|
Name string
|
|
|
|
SeparateLine bool
|
2020-09-30 11:33:42 +00:00
|
|
|
Lines int
|
2020-08-04 17:07:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type TableWriter struct {
|
|
|
|
cols []Column
|
|
|
|
rows []map[int]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func Col(name string) Column {
|
|
|
|
return Column{
|
|
|
|
Name: name,
|
|
|
|
SeparateLine: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewLineCol(name string) Column {
|
|
|
|
return Column{
|
|
|
|
Name: name,
|
|
|
|
SeparateLine: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlike text/tabwriter, this works with CLI escape codes, and allows for info
|
2022-08-29 14:25:30 +00:00
|
|
|
//
|
|
|
|
// in separate lines
|
2020-08-04 17:07:17 +00:00
|
|
|
func New(cols ...Column) *TableWriter {
|
|
|
|
return &TableWriter{
|
|
|
|
cols: cols,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *TableWriter) Write(r map[string]interface{}) {
|
|
|
|
// this can cause columns to be out of order, but will at least work
|
|
|
|
byColID := map[int]string{}
|
|
|
|
|
2020-08-04 17:07:31 +00:00
|
|
|
cloop:
|
2020-08-04 17:07:17 +00:00
|
|
|
for col, val := range r {
|
|
|
|
for i, column := range w.cols {
|
|
|
|
if column.Name == col {
|
|
|
|
byColID[i] = fmt.Sprint(val)
|
2020-09-30 11:33:42 +00:00
|
|
|
w.cols[i].Lines++
|
2020-08-04 17:07:17 +00:00
|
|
|
continue cloop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
byColID[len(w.cols)] = fmt.Sprint(val)
|
|
|
|
w.cols = append(w.cols, Column{
|
|
|
|
Name: col,
|
|
|
|
SeparateLine: false,
|
2020-09-30 11:33:42 +00:00
|
|
|
Lines: 1,
|
2020-08-04 17:07:17 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
w.rows = append(w.rows, byColID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *TableWriter) Flush(out io.Writer) error {
|
|
|
|
colLengths := make([]int, len(w.cols))
|
|
|
|
|
|
|
|
header := map[int]string{}
|
|
|
|
for i, col := range w.cols {
|
|
|
|
if col.SeparateLine {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
header[i] = col.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
w.rows = append([]map[int]string{header}, w.rows...)
|
|
|
|
|
2020-09-30 11:33:42 +00:00
|
|
|
for col, c := range w.cols {
|
|
|
|
if c.Lines == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-08-04 17:07:17 +00:00
|
|
|
for _, row := range w.rows {
|
|
|
|
val, found := row[col]
|
|
|
|
if !found {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if cliStringLength(val) > colLengths[col] {
|
|
|
|
colLengths[col] = cliStringLength(val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, row := range w.rows {
|
|
|
|
cols := make([]string, len(w.cols))
|
|
|
|
|
|
|
|
for ci, col := range w.cols {
|
2020-09-30 11:33:42 +00:00
|
|
|
if col.Lines == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-08-04 17:07:17 +00:00
|
|
|
e, _ := row[ci]
|
|
|
|
pad := colLengths[ci] - cliStringLength(e) + 2
|
2020-09-30 11:33:42 +00:00
|
|
|
if !col.SeparateLine && col.Lines > 0 {
|
2020-08-04 17:07:17 +00:00
|
|
|
e = e + strings.Repeat(" ", pad)
|
|
|
|
if _, err := fmt.Fprint(out, e); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cols[ci] = e
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := fmt.Fprintln(out); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for ci, col := range w.cols {
|
|
|
|
if !col.SeparateLine || len(cols[ci]) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := fmt.Fprintf(out, " %s: %s\n", col.Name, cols[ci]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func cliStringLength(s string) (n int) {
|
|
|
|
return utf8.RuneCountInString(stripansi.Strip(s))
|
|
|
|
}
|