package tablewriter import ( "fmt" "io" "strings" "unicode/utf8" "github.com/acarl005/stripansi" ) type Column struct { Name string SeparateLine bool } 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 // in separate lines 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{} cloop: for col, val := range r { for i, column := range w.cols { if column.Name == col { byColID[i] = fmt.Sprint(val) continue cloop } } byColID[len(w.cols)] = fmt.Sprint(val) w.cols = append(w.cols, Column{ Name: col, SeparateLine: false, }) } 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...) for col := range w.cols { 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 { e, _ := row[ci] pad := colLengths[ci] - cliStringLength(e) + 2 if !col.SeparateLine { 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)) }