blob: 1d947d93f24e68de2af9bf2697fa0fbeffad3888 [file] [log] [blame]
package main
import (
"fmt"
"io"
"strings"
)
// table is a list of entries that form a table with sparse columns. Each entry
// defines its own columns.
type table struct {
entries []entry
}
// add an entry to the table.
func (t *table) add(e entry) {
t.entries = append(t.entries, e)
}
// An entry is made up of column key -> value pairs.
type entry struct {
columns []entryColumn
}
// add a key/value pair to the entry.
func (e *entry) add(key, value string) {
e.columns = append(e.columns, entryColumn{
key: key,
value: value,
})
}
// get a value from a given key, returning zero string if not set.
func (e *entry) get(key string) string {
for _, col := range e.columns {
if col.key == key {
return col.value
}
}
return ""
}
// An entryColumn is a pair for table column key and entry column value.
type entryColumn struct {
key string
value string
}
// columns returns the keys and widths of columns that are present in the table's
// entries.
func (t *table) columns() columns {
var res columns
for _, e := range t.entries {
for _, c := range e.columns {
tc := res.upsert(c.key)
if len(c.value) > tc.width {
tc.width = len(c.value)
}
}
}
return res
}
type columns []*column
// A column in a table, not containing all entries that make up this table, but
// containing their maximum width (for layout purposes).
type column struct {
// key is the column key.
key string
// width is the maximum width (in runes) of all the entries' data in this column.
width int
}
// upsert a key into a list of columns, returning the upserted column.
func (c *columns) upsert(key string) *column {
for _, col := range *c {
if col.key == key {
return col
}
}
col := &column{
key: key,
width: len(key),
}
*c = append(*c, col)
return col
}
// filter returns a copy of columns where the only columns present are the ones
// whose onlyColumns values are true. If only columns is nil, no filtering takes
// place (all columns are returned).
func (c columns) filter(onlyColumns map[string]bool) columns {
var res []*column
for _, cc := range c {
if onlyColumns != nil && !onlyColumns[cc.key] {
continue
}
res = append(res, cc)
}
return res
}
// printHeader writes a table-like header to the given file, keeping margin
// spaces between columns.
func (c columns) printHeader(f io.Writer, margin int) {
for _, cc := range c {
fmt.Fprintf(f, "%-*s", cc.width+margin, strings.ToUpper(cc.key))
}
fmt.Fprintf(f, "\n")
}
// print writes a table-like representation of this table to the given file,
// first filtering the columns by onlyColumns (if not set, no filtering takes
// place).
func (t *table) print(f io.Writer, onlyColumns map[string]bool) {
margin := 3
cols := t.columns().filter(onlyColumns)
cols.printHeader(f, margin)
for _, e := range t.entries {
for _, c := range cols {
v := e.get(c.key)
fmt.Fprintf(f, "%-*s", c.width+margin, v)
}
fmt.Fprintf(f, "\n")
}
}