Skip to content

Commit 5abc279

Browse files
committed
table: support filtering
Support filtering of rows, useful with `StringWithTupleIndex` which will get the original tuple index. We also support relative-to-end indices in `HorizontalDividers`.
1 parent f275c62 commit 5abc279

File tree

3 files changed

+76
-45
lines changed

3 files changed

+76
-45
lines changed

internal/ascii/table/table.go

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,20 @@ func Define[T any](fields ...Element) Layout[T] {
6060
// A Layout defines the layout of a table.
6161
type Layout[T any] struct {
6262
fields []Element
63+
// FilterFn can be set to filter out rows. This is useful when using the tuple
64+
// index (e.g. StringWithTupleIndex), as the tuple index indicates the
65+
// original (pre-filter) index.
66+
FilterFn func(tupleIndex int, tuple T) (passed bool)
6367
}
6468

6569
// HorizontalDividers is a set of row indices before which a horizontal divider
66-
// is placed. If nil, the defult is to place a divider before the first row
70+
// is placed. If nil, the default is to place a divider before the first row
6771
// (i.e. HorizontalDividers{0}).
72+
//
73+
// A negative index corresponds to the end of the table, with -1 being the last
74+
// row (meaning there will be a divider before the last row).
75+
//
76+
// The row indices apply after any filtering is performed (see Layout.FilterFn).
6877
type HorizontalDividers map[int]struct{}
6978

7079
func MakeHorizontalDividers(rowIdx ...int) HorizontalDividers {
@@ -75,34 +84,45 @@ func MakeHorizontalDividers(rowIdx ...int) HorizontalDividers {
7584
return hd
7685
}
7786

78-
func (hd HorizontalDividers) Contains(rowIdx int) bool {
87+
func (hd HorizontalDividers) Contains(rowIdx int, numRows int) bool {
7988
if hd == nil {
8089
// Special case the nil value.
8190
return rowIdx == 0
8291
}
83-
_, ok := hd[rowIdx]
84-
return ok
92+
if _, ok := hd[rowIdx]; ok {
93+
return true
94+
}
95+
// Negative indices count from the end of the table (-1 is the last row).
96+
if _, ok := hd[rowIdx-numRows]; ok {
97+
return true
98+
}
99+
return false
85100
}
86101

87102
// RenderOptions specifies the options for rendering a table.
88103
type RenderOptions struct {
89104
HorizontalDividers HorizontalDividers
90105
}
91106

92-
// Render renders the given iterator of rows of a table into the given cursor,
93-
// returning the modified cursor.
94-
func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) ascii.Cursor {
107+
// Render renders the given tuples into the given cursor, returning the modified
108+
// cursor.
109+
func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, tuples ...T) ascii.Cursor {
95110
cur := start
96111

97-
tuples := rows
98-
vals := make([]string, len(tuples))
112+
rows := make([]int, 0, len(tuples))
113+
for i := range tuples {
114+
if d.FilterFn == nil || d.FilterFn(i, tuples[i]) {
115+
rows = append(rows, i)
116+
}
117+
}
118+
vals := make([]string, len(rows))
99119
for fieldIdx, c := range d.fields {
100120
if fieldIdx > 0 {
101121
// Each column is separated by a space from the previous column or
102122
// separator.
103123
rowCur := cur
104-
for rowIdx := range tuples {
105-
if opts.HorizontalDividers.Contains(rowIdx) {
124+
for rowIdx := range rows {
125+
if opts.HorizontalDividers.Contains(rowIdx, len(rows)) {
106126
rowCur = rowCur.Down(1)
107127
rowCur.WriteString("-")
108128
}
@@ -113,8 +133,8 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
113133
if _, ok := c.(divider); ok {
114134
rowCur := cur
115135
rowCur.WriteString("|")
116-
for rowIdx := range tuples {
117-
if opts.HorizontalDividers.Contains(rowIdx) {
136+
for rowIdx := range rows {
137+
if opts.HorizontalDividers.Contains(rowIdx, len(rows)) {
118138
rowCur = rowCur.Down(1)
119139
rowCur.WriteString("+")
120140
}
@@ -125,8 +145,8 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
125145
continue
126146
}
127147
f := c.(Field[T])
128-
for i, t := range tuples {
129-
vals[i] = f.renderValue(i, t)
148+
for rowIdx, tupleIdx := range rows {
149+
vals[rowIdx] = f.renderValue(tupleIdx, tuples[tupleIdx])
130150
}
131151

132152
width := c.width()
@@ -140,7 +160,7 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
140160
pad(cur, width, align, header)
141161
rowCur := cur
142162
for rowIdx := range vals {
143-
if opts.HorizontalDividers.Contains(rowIdx) {
163+
if opts.HorizontalDividers.Contains(rowIdx, len(vals)) {
144164
rowCur = rowCur.Down(1)
145165
rowCur.RepeatByte(width, '-')
146166
}
@@ -151,7 +171,7 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
151171
}
152172
rowCur := start.Down(len(vals) + 1)
153173
for rowIdx := range vals {
154-
if opts.HorizontalDividers.Contains(rowIdx) {
174+
if opts.HorizontalDividers.Contains(rowIdx, len(vals)) {
155175
rowCur = rowCur.Down(1)
156176
}
157177
}

internal/ascii/table/table_test.go

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package table
66

77
import (
88
"fmt"
9-
"slices"
109
"strconv"
1110
"testing"
1211

@@ -20,16 +19,6 @@ func TestTable(t *testing.T) {
2019
Age int
2120
Cuteness int
2221
}
23-
cats := []Cat{
24-
{Name: "Chicken", Age: 5, Cuteness: 10},
25-
{Name: "Heart", Age: 4, Cuteness: 10},
26-
{Name: "Mai", Age: 2, Cuteness: 10},
27-
{Name: "Poi", Age: 15, Cuteness: 10},
28-
{Name: "Pigeon", Age: 2, Cuteness: 10},
29-
{Name: "Sugar", Age: 8, Cuteness: 10},
30-
{Name: "Yaya", Age: 5, Cuteness: 10},
31-
{Name: "Yuumi", Age: 5, Cuteness: 10},
32-
}
3322

3423
wb := ascii.Make(1, 10)
3524
datadriven.RunTest(t, "testdata/table", func(t *testing.T, td *datadriven.TestData) string {
@@ -51,43 +40,55 @@ func TestTable(t *testing.T) {
5140
opts.HorizontalDividers[i] = struct{}{}
5241
}
5342
}
43+
var def Layout[Cat]
44+
cats := []Cat{
45+
{Name: "Chicken", Age: 5, Cuteness: 10},
46+
{Name: "Heart", Age: 4, Cuteness: 10},
47+
{Name: "Mai", Age: 2, Cuteness: 10},
48+
{Name: "Poi", Age: 15, Cuteness: 10},
49+
{Name: "Pigeon", Age: 2, Cuteness: 10},
50+
{Name: "Sugar", Age: 8, Cuteness: 10},
51+
{Name: "Yaya", Age: 5, Cuteness: 10},
52+
{Name: "Yuumi", Age: 5, Cuteness: 10},
53+
}
5454
switch td.Cmd {
5555
case "cats-tuple-index":
56-
def := Define[Cat](
56+
def = Define[Cat](
5757
StringWithTupleIndex("idx", 3, AlignLeft, func(tupleIndex int, r Cat) string {
5858
return fmt.Sprintf("%d", tupleIndex)
5959
}),
6060
Div(),
6161
String("name", 7, align, func(c Cat) string { return c.Name }),
6262
)
63-
wb.Reset(1)
64-
def.Render(wb.At(0, 0), opts, cats...)
65-
return wb.String()
63+
6664
case "cats-nodiv":
67-
def := Define[Cat](
65+
def = Define[Cat](
6866
String("name", 6, AlignLeft, func(c Cat) string { return c.Name }),
6967
Int("age", 3, align, func(c Cat) int { return c.Age }),
7068
Int("cuteness", 8, align, func(c Cat) int { return c.Cuteness }),
7169
)
72-
wb.Reset(1)
73-
def.Render(wb.At(0, 0), opts, cats...)
74-
return wb.String()
70+
7571
case "cats-column-too-wide":
76-
c := slices.Clone(cats)
77-
for i := range c {
78-
c[i].Age *= 1_000_000
72+
for i := range cats {
73+
cats[i].Age *= 1_000_000
7974
}
80-
def := Define[Cat](
75+
def = Define[Cat](
8176
String("name", 6, AlignLeft, func(c Cat) string { return c.Name }),
8277
Div(),
8378
Int("age", 3, align, func(c Cat) int { return c.Age }),
8479
Int("c", 1, align, func(c Cat) int { return c.Cuteness }),
8580
)
86-
wb.Reset(1)
87-
def.Render(wb.At(0, 0), opts, c...)
88-
return wb.String()
81+
8982
default:
9083
return fmt.Sprintf("unknown command: %s", td.Cmd)
9184
}
85+
wb.Reset(1)
86+
if td.HasArg("odd") {
87+
def.FilterFn = func(tupleIndex int, c Cat) bool {
88+
return c.Age%2 == 1
89+
}
90+
}
91+
def.Render(wb.At(0, 0), opts, cats...)
92+
return wb.String()
9293
})
9394
}

internal/ascii/table/testdata/table

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ idx | name
6464
6 | Yaya
6565
7 | Yuumi
6666

67-
cats-tuple-index horizontal-dividers=(0,1,2,3,6,7)
67+
cats-tuple-index horizontal-dividers=(0,1,2,3,-2,-1)
6868
----
6969
idx | name
7070
----+--------
@@ -82,6 +82,16 @@ idx | name
8282
----+--------
8383
7 | Yuumi
8484

85+
cats-tuple-index odd horizontal-dividers=(1,-1)
86+
----
87+
idx | name
88+
0 | Chicken
89+
----+--------
90+
3 | Poi
91+
6 | Yaya
92+
----+--------
93+
7 | Yuumi
94+
8595
cats-column-too-wide
8696
----
8797
name | age c
@@ -95,7 +105,7 @@ Sugar | 8000000 10
95105
Yaya | 5000000 10
96106
Yuumi | 5000000 10
97107

98-
cats-column-too-wide horizontal-dividers=(0,3,5)
108+
cats-column-too-wide horizontal-dividers=(0,3,-3)
99109
----
100110
name | age c
101111
--------+------------

0 commit comments

Comments
 (0)