Skip to content

Commit ec0b7a0

Browse files
committed
Added rudimentary text table output
1 parent 6393734 commit ec0b7a0

File tree

6 files changed

+93
-46
lines changed

6 files changed

+93
-46
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/djthorpe/go-errors v1.0.3
77
github.com/pkg/errors v0.9.1
88
github.com/stretchr/testify v1.8.4
9+
golang.org/x/exp v0.0.0-20231127185646-65229373498e
910
golang.org/x/term v0.15.0
1011
)
1112

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
88
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
99
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
1010
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
11+
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
12+
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
1113
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
1214
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1315
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=

pkg/writer/iterator.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func (i *iterator) String() string {
5858
///////////////////////////////////////////////////////////////////////////////
5959
// PUBLIC METHODS
6060

61+
// Return the number of elements
62+
func (i *iterator) Reset() {
63+
i.index = 0
64+
}
65+
6166
// Return the number of elements
6267
func (i *iterator) Len() int {
6368
return i.slice.Len()

pkg/writer/table.go

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
// Packages
11+
"golang.org/x/exp/slices"
1112

1213
// Namespace imports
1314
. "github.com/djthorpe/go-errors"
@@ -35,17 +36,14 @@ type TableMeta struct {
3536
}
3637

3738
type ColumnMeta struct {
38-
Key string // the unique key of the field
39-
Name string // the name of the field
40-
Index []int // the index of the field
41-
Tuples []string // the tuples from the tag
42-
NonZero bool // true if there is a non-zero value in this column
43-
Width int // the maximum we column
44-
Flags FormatFlag // the formatting for the column
39+
Key string // the unique key of the field
40+
Name string // the name of the field
41+
Index []int // the index of the field
42+
Tuples []string // the tuples from the tag
43+
NonZero bool // true if there is a non-zero value in this column
44+
Width int // the maximum we column
4545
}
4646

47-
type FormatFlag uint
48-
4947
///////////////////////////////////////////////////////////////////////////////
5048
// GLBOALS
5149

@@ -54,10 +52,7 @@ const (
5452
tagWriter = "writer"
5553
nilValue = "<nil>"
5654
defaultTextWidth = 70
57-
)
58-
59-
const (
60-
FormatAlignLeft FormatFlag = 1 << iota
55+
tagAlignRight = "alignright"
6156
)
6257

6358
///////////////////////////////////////////////////////////////////////////////
@@ -144,13 +139,9 @@ func (t *TableMeta) Header() []string {
144139
return names
145140
}
146141

147-
// Returns a header for Text output
148-
func (t *TableMeta) HeaderAny() []any {
149-
names := make([]any, len(t.Columns))
150-
for i, col := range t.Columns {
151-
names[i] = col.Name
152-
}
153-
return names
142+
// Reset the iterator
143+
func (t *TableMeta) Reset() {
144+
t.Iterator.Reset()
154145
}
155146

156147
// Returns the next row of values, or nil if there are no more rows
@@ -174,6 +165,10 @@ func (t *TableMeta) NextRow() []any {
174165
return t.row
175166
}
176167

168+
func (m ColumnMeta) IsAlignRight() bool {
169+
return slices.Contains(m.Tuples, tagAlignRight)
170+
}
171+
177172
///////////////////////////////////////////////////////////////////////////////
178173
// PRIVATE METHODS
179174

pkg/writer/text.go

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,93 @@ import (
44
"fmt"
55
"io"
66
"strings"
7+
"unicode/utf8"
78
)
89

910
///////////////////////////////////////////////////////////////////////////////
1011
// TYPES
1112

1213
type TextWriter struct {
13-
format string
14+
row []any
15+
meta []ColumnMeta
1416
}
1517

1618
///////////////////////////////////////////////////////////////////////////////
1719
// CONSTRUCTOR
1820

1921
// Write outputs the table to a writer
20-
func NewTextWriter(columns []ColumnMeta, delim rune) *TextWriter {
22+
func NewTextWriter(columns []ColumnMeta) *TextWriter {
2123
self := new(TextWriter)
24+
self.row = make([]any, len(columns))
25+
self.meta = make([]ColumnMeta, len(columns))
26+
copy(self.meta, columns)
2227

28+
// Return success
29+
return self
30+
}
31+
32+
///////////////////////////////////////////////////////////////////////////////
33+
// PUBLIC METHODS
34+
35+
// Create a format string for the writer
36+
func (self *TextWriter) Formatln(delim rune) string {
2337
// Create the format from the column metadata
2438
f := new(strings.Builder)
2539
f.WriteRune(delim)
26-
for _, column := range columns {
40+
for _, column := range self.meta {
2741
f.WriteRune('%')
28-
if column.Flags&FormatAlignLeft != 0 {
42+
if !column.IsAlignRight() {
2943
f.WriteRune('-')
3044
}
3145
if column.Width > 0 {
3246
f.WriteString(fmt.Sprint(column.Width))
47+
} else if column.Width < 0 {
48+
f.WriteString(fmt.Sprint(-column.Width))
3349
}
3450
f.WriteRune('s')
3551
f.WriteRune(delim)
3652
}
37-
self.format = f.String()
38-
return self
53+
fmt.Println(f.String())
54+
return f.String()
3955
}
4056

41-
func (self *TextWriter) Writeln(w io.Writer, elems []any) error {
42-
if _, err := fmt.Fprintf(w, self.format, elems...); err != nil {
57+
// Determine the maximum width of each column
58+
func (self *TextWriter) Sizeln(elems []string) {
59+
for i, elem := range elems {
60+
w, _ := textSize(elem)
61+
if self.meta[i].Width == 0 {
62+
self.meta[i].Width = w
63+
} else if w > 0 && w > -self.meta[i].Width {
64+
self.meta[i].Width = -w
65+
}
66+
}
67+
}
68+
69+
// Write a row to the writer
70+
func (self *TextWriter) Writeln(w io.Writer, format string, elems []string) error {
71+
for i, elem := range elems {
72+
self.row[i] = elem
73+
}
74+
if _, err := fmt.Fprintf(w, format, self.row...); err != nil {
4375
return err
4476
}
4577
if _, err := fmt.Fprintln(w); err != nil {
4678
return err
4779
}
4880
return nil
4981
}
82+
83+
///////////////////////////////////////////////////////////////////////////////
84+
// PRIVATE METHODS
85+
86+
// Returns the maximum width of a line in runes, and the height
87+
func textSize(elem string) (int, int) {
88+
lines := strings.Split(elem, "\n")
89+
max := 0
90+
for _, line := range lines {
91+
if runes := utf8.RuneCountInString(line); runes > max {
92+
max = runes
93+
}
94+
}
95+
return max, len(lines)
96+
}

pkg/writer/writer.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,6 @@ func (meta *TableMeta) toString(elems []any, quote bool) ([]string, error) {
3838
return meta.rowstr, nil
3939
}
4040

41-
// Create an array of strings representing the row
42-
func (meta *TableMeta) toStringAny(elems []any, quote bool) ([]any, error) {
43-
for i, elem := range elems {
44-
if bytes, err := Marshal(elem, quote); err != nil {
45-
return nil, err
46-
} else {
47-
meta.row[i] = string(bytes)
48-
}
49-
}
50-
return meta.row, nil
51-
}
52-
5341
func (self *TableWriter) writeCSV(meta *TableMeta, w io.Writer) error {
5442
csv := csv.NewWriter(w)
5543
csv.Comma = meta.delim
@@ -78,27 +66,36 @@ func (self *TableWriter) writeCSV(meta *TableMeta, w io.Writer) error {
7866
}
7967

8068
func (self *TableWriter) writeText(meta *TableMeta, w io.Writer) error {
81-
text := NewTextWriter(meta.Columns, meta.delim)
69+
text := NewTextWriter(meta.Columns)
8270

71+
var format string
8372
for pass := int(0); pass < 2; pass++ {
73+
// Set the format string
74+
if pass > 0 {
75+
format = text.Formatln('|')
76+
}
8477
// Write header
8578
if meta.header {
79+
header := meta.Header()
8680
if pass == 0 {
87-
// TODO: Adjust the column widths
88-
} else if err := text.Writeln(w, meta.HeaderAny()); err != nil {
81+
// Set maximal widths
82+
text.Sizeln(header)
83+
} else if err := text.Writeln(w, format, header); err != nil {
8984
return err
9085
}
9186
}
9287

9388
// Write rows
89+
meta.Reset()
9490
for elems := meta.NextRow(); elems != nil; elems = meta.NextRow() {
95-
row, err := meta.toStringAny(elems, false)
91+
row, err := meta.toString(elems, false)
9692
if err != nil {
9793
return err
9894
}
9995
if pass == 0 {
100-
// TODO: Adjust the column widths
101-
} else if err := text.Writeln(w, row); err != nil {
96+
// Set maximal widths
97+
text.Sizeln(row)
98+
} else if err := text.Writeln(w, format, row); err != nil {
10299
return err
103100
}
104101
}

0 commit comments

Comments
 (0)