Skip to content

Instantly share code, notes, and snippets.

@matteopic
Created June 5, 2024 04:51
Show Gist options
  • Save matteopic/1fccdbe9818ed8c823db441c80f31eca to your computer and use it in GitHub Desktop.
Save matteopic/1fccdbe9818ed8c823db441c80f31eca to your computer and use it in GitHub Desktop.
It allows building a table in ASCII text format, adding rows of data, and writing the resulting table to a writer, with a well-formatted appearance and borders defined using Unicode characters.
package export
import (
"bytes"
"io"
"strings"
"unicode/utf8"
)
type ASCIITable struct {
widths []int
rows [][]string
}
func (mkt *ASCIITable) AddRow(data ...string) {
if len(data) > len(mkt.widths) {
newWidths := make([]int, len(data))
copy(newWidths, mkt.widths)
mkt.widths = newWidths
}
for cell_index, cell := range data {
max_width := mkt.widths[cell_index]
width := utf8.RuneCountInString(cell)
if width > max_width {
mkt.widths[cell_index] = width
}
}
newRows := append(mkt.rows, data)
mkt.rows = newRows
}
func (mkt ASCIITable) WriteTo(w io.Writer) (int64, error) {
buffer := new(bytes.Buffer)
var total int64
var err error
for row_index, row := range mkt.rows {
if row_index == 0 {
mkt.writeTableTop(buffer)
if total, err = mkt.writeBuffer(w, buffer, total); err != nil {
return total, err
}
}
mkt.writeTableCells(buffer, row)
if total, err = mkt.writeBuffer(w, buffer, total); err != nil {
return total, err
}
if row_index == len(mkt.rows)-1 {
mkt.writeTableBottom(buffer)
} else {
mkt.writeRowSeparator(buffer)
}
if total, err = mkt.writeBuffer(w, buffer, total); err != nil {
return total, err
}
}
return total, nil
}
func (mkt ASCIITable) writeBuffer(w io.Writer, buffer *bytes.Buffer, total int64) (totalOut int64, err error) {
var written int64
written, err = buffer.WriteTo(w)
buffer.Reset()
totalOut = written + total
return
}
func (mkt ASCIITable) writeTableCells(buffer *bytes.Buffer, row []string) {
sep := []byte("\u2502")
padding := []byte(" ")
eol := []byte("\n")
for cell_index, cell := range row {
if cell_index == 0 {
buffer.Write([]byte("\u2502"))
buffer.Write(padding)
} else {
buffer.Write(padding)
buffer.Write(sep)
buffer.Write(padding)
}
txtLenght := utf8.RuneCountInString(cell)
cellLength := mkt.widths[cell_index]
buffer.WriteString(cell)
fillLength := cellLength - txtLenght
fill := strings.Repeat(" ", fillLength)
buffer.WriteString(fill)
}
buffer.Write(padding)
buffer.Write(sep)
buffer.Write(eol)
}
func (mkt ASCIITable) writeTableTop(buffer *bytes.Buffer) {
mkt.writeTable(buffer, "\u250C", "\u252C", "\u2510")
}
func (mkt ASCIITable) writeTableBottom(buffer *bytes.Buffer) {
mkt.writeTable(buffer, "\u2514", "\u2534", "\u2518")
}
func (mkt ASCIITable) writeRowSeparator(buffer *bytes.Buffer) {
mkt.writeTable(buffer, "\u251C", "\u253C", "\u2524")
}
func (mkt ASCIITable) writeTable(buffer *bytes.Buffer, left string, middle string, right string) {
for index, width := range mkt.widths {
if index == 0 {
buffer.Write([]byte(left))
}
// +2 is padding left and right
fill := strings.Repeat("\u2500", width+2)
buffer.Write([]byte(fill))
if index == len(mkt.widths)-1 {
buffer.Write([]byte(right))
} else {
buffer.Write([]byte(middle))
}
}
buffer.Write([]byte("\n"))
}
func NewASCIITable() *ASCIITable {
widths := make([]int, 0)
rows := make([][]string, 0)
at := ASCIITable{rows: rows, widths: widths}
return &at
}
package export
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBasicAsciiTable(t *testing.T) {
mkt := NewASCIITable()
mkt.AddRow("Lorem", "ipsum", "dolor", "sit", "amet")
mkt.AddRow("consectetur", "adipiscing", "elit", "", "")
bb := new(bytes.Buffer)
written, err := mkt.WriteTo(bb)
txt := bb.String()
expected := "" +
"┌─────────────┬────────────┬───────┬─────┬──────┐\n" +
"│ Lorem │ ipsum │ dolor │ sit │ amet │\n" +
"├─────────────┼────────────┼───────┼─────┼──────┤\n" +
"│ consectetur │ adipiscing │ elit │ │ │\n" +
"└─────────────┴────────────┴───────┴─────┴──────┘\n"
assert.Equal(t, expected, txt)
assert.EqualValues(t, len([]byte(expected)), written)
assert.NoError(t, err)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment