@@ -60,11 +60,20 @@ func Define[T any](fields ...Element) Layout[T] {
60
60
// A Layout defines the layout of a table.
61
61
type Layout [T any ] struct {
62
62
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 )
63
67
}
64
68
65
69
// 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
67
71
// (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).
68
77
type HorizontalDividers map [int ]struct {}
69
78
70
79
func MakeHorizontalDividers (rowIdx ... int ) HorizontalDividers {
@@ -75,34 +84,45 @@ func MakeHorizontalDividers(rowIdx ...int) HorizontalDividers {
75
84
return hd
76
85
}
77
86
78
- func (hd HorizontalDividers ) Contains (rowIdx int ) bool {
87
+ func (hd HorizontalDividers ) Contains (rowIdx int , numRows int ) bool {
79
88
if hd == nil {
80
89
// Special case the nil value.
81
90
return rowIdx == 0
82
91
}
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
85
100
}
86
101
87
102
// RenderOptions specifies the options for rendering a table.
88
103
type RenderOptions struct {
89
104
HorizontalDividers HorizontalDividers
90
105
}
91
106
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 {
95
110
cur := start
96
111
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 ))
99
119
for fieldIdx , c := range d .fields {
100
120
if fieldIdx > 0 {
101
121
// Each column is separated by a space from the previous column or
102
122
// separator.
103
123
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 ) ) {
106
126
rowCur = rowCur .Down (1 )
107
127
rowCur .WriteString ("-" )
108
128
}
@@ -113,8 +133,8 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
113
133
if _ , ok := c .(divider ); ok {
114
134
rowCur := cur
115
135
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 ) ) {
118
138
rowCur = rowCur .Down (1 )
119
139
rowCur .WriteString ("+" )
120
140
}
@@ -125,8 +145,8 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
125
145
continue
126
146
}
127
147
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 ] )
130
150
}
131
151
132
152
width := c .width ()
@@ -140,7 +160,7 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
140
160
pad (cur , width , align , header )
141
161
rowCur := cur
142
162
for rowIdx := range vals {
143
- if opts .HorizontalDividers .Contains (rowIdx ) {
163
+ if opts .HorizontalDividers .Contains (rowIdx , len ( vals ) ) {
144
164
rowCur = rowCur .Down (1 )
145
165
rowCur .RepeatByte (width , '-' )
146
166
}
@@ -151,7 +171,7 @@ func (d *Layout[T]) Render(start ascii.Cursor, opts RenderOptions, rows ...T) as
151
171
}
152
172
rowCur := start .Down (len (vals ) + 1 )
153
173
for rowIdx := range vals {
154
- if opts .HorizontalDividers .Contains (rowIdx ) {
174
+ if opts .HorizontalDividers .Contains (rowIdx , len ( vals ) ) {
155
175
rowCur = rowCur .Down (1 )
156
176
}
157
177
}
0 commit comments