blob: 1d947d93f24e68de2af9bf2697fa0fbeffad3888 [file] [log] [blame]
Serge Bazanski504ea312023-03-22 17:47:48 +01001package main
2
3import (
4 "fmt"
5 "io"
6 "strings"
7)
8
9// table is a list of entries that form a table with sparse columns. Each entry
10// defines its own columns.
11type table struct {
12 entries []entry
13}
14
15// add an entry to the table.
16func (t *table) add(e entry) {
17 t.entries = append(t.entries, e)
18}
19
20// An entry is made up of column key -> value pairs.
21type entry struct {
22 columns []entryColumn
23}
24
25// add a key/value pair to the entry.
26func (e *entry) add(key, value string) {
27 e.columns = append(e.columns, entryColumn{
28 key: key,
29 value: value,
30 })
31}
32
33// get a value from a given key, returning zero string if not set.
34func (e *entry) get(key string) string {
35 for _, col := range e.columns {
36 if col.key == key {
37 return col.value
38 }
39 }
40 return ""
41}
42
43// An entryColumn is a pair for table column key and entry column value.
44type entryColumn struct {
45 key string
46 value string
47}
48
49// columns returns the keys and widths of columns that are present in the table's
50// entries.
51func (t *table) columns() columns {
52 var res columns
53 for _, e := range t.entries {
54 for _, c := range e.columns {
55 tc := res.upsert(c.key)
56 if len(c.value) > tc.width {
57 tc.width = len(c.value)
58 }
59 }
60 }
61 return res
62}
63
64type columns []*column
65
66// A column in a table, not containing all entries that make up this table, but
67// containing their maximum width (for layout purposes).
68type column struct {
69 // key is the column key.
70 key string
71 // width is the maximum width (in runes) of all the entries' data in this column.
72 width int
73}
74
75// upsert a key into a list of columns, returning the upserted column.
76func (c *columns) upsert(key string) *column {
77 for _, col := range *c {
78 if col.key == key {
79 return col
80 }
81 }
82 col := &column{
83 key: key,
84 width: len(key),
85 }
86 *c = append(*c, col)
87 return col
88}
89
90// filter returns a copy of columns where the only columns present are the ones
91// whose onlyColumns values are true. If only columns is nil, no filtering takes
92// place (all columns are returned).
93func (c columns) filter(onlyColumns map[string]bool) columns {
94 var res []*column
95 for _, cc := range c {
96 if onlyColumns != nil && !onlyColumns[cc.key] {
97 continue
98 }
99 res = append(res, cc)
100 }
101 return res
102}
103
104// printHeader writes a table-like header to the given file, keeping margin
105// spaces between columns.
106func (c columns) printHeader(f io.Writer, margin int) {
107 for _, cc := range c {
108 fmt.Fprintf(f, "%-*s", cc.width+margin, strings.ToUpper(cc.key))
109 }
110 fmt.Fprintf(f, "\n")
111}
112
113// print writes a table-like representation of this table to the given file,
114// first filtering the columns by onlyColumns (if not set, no filtering takes
115// place).
116func (t *table) print(f io.Writer, onlyColumns map[string]bool) {
117 margin := 3
118 cols := t.columns().filter(onlyColumns)
119 cols.printHeader(f, margin)
120
121 for _, e := range t.entries {
122 for _, c := range cols {
123 v := e.get(c.key)
124 fmt.Fprintf(f, "%-*s", c.width+margin, v)
125 }
126 fmt.Fprintf(f, "\n")
127 }
128}