Skip to content

Commit 9dfb82e

Browse files
committed
table: RowPainterWithAttributes; addresses #345 (#346)
1 parent 5994546 commit 9dfb82e

File tree

10 files changed

+194
-95
lines changed

10 files changed

+194
-95
lines changed

.github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ jobs:
3232
run: |
3333
go install github.com/fzipp/gocyclo/cmd/[email protected]
3434
go install github.com/mattn/[email protected]
35+
go install github.com/rinchsan/gosimports/cmd/[email protected]
3536
3637
# Run all the unit-tests
3738
- name: Test

Makefile

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ all: test bench
66

77
tools:
88
go install github.com/fzipp/gocyclo/cmd/[email protected]
9+
go install github.com/rinchsan/gosimports/cmd/[email protected]
910

1011
bench:
1112
go test -bench=. -benchmem
@@ -23,16 +24,18 @@ demo-table:
2324
go run cmd/demo-table/demo.go
2425

2526
fmt:
26-
go fmt $(shell go list ./...)
27+
go fmt ./...
28+
gosimports -w .
2729

2830
profile:
2931
sh profile.sh
3032

3133
test: fmt vet cyclo
32-
go test -cover -coverprofile=.coverprofile $(shell go list ./...)
34+
go test -cover -coverprofile=.coverprofile ./...
3335

3436
test-race:
3537
go run -race ./cmd/demo-progress/demo.go
3638

3739
vet:
38-
go vet $(shell go list ./...)
40+
go vet ./...
41+

table/pager_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package table
22

33
import (
4-
"github.com/stretchr/testify/assert"
54
"strings"
65
"testing"
6+
7+
"github.com/stretchr/testify/assert"
78
)
89

910
func TestPager(t *testing.T) {

table/render_init.go

+36-17
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,16 @@ func (t *Table) initForRenderRows() {
223223
t.autoIndexVIndexMaxLength = len(fmt.Sprint(len(t.rowsRaw)))
224224

225225
// stringify all the rows to make it easy to render
226-
if t.rowPainter != nil {
227-
t.rowsColors = make([]text.Colors, len(t.rowsRaw))
228-
}
229226
t.rows = t.initForRenderRowsStringify(t.rowsRaw, renderHint{})
230227
t.rowsFooter = t.initForRenderRowsStringify(t.rowsFooterRaw, renderHint{isFooterRow: true})
231228
t.rowsHeader = t.initForRenderRowsStringify(t.rowsHeaderRaw, renderHint{isHeaderRow: true})
232229

233230
// sort the rows as requested
234231
t.initForRenderSortRows()
235232

233+
// find the row colors (if any)
234+
t.initForRenderRowPainterColors()
235+
236236
// suppress columns without any content
237237
t.initForRenderSuppressColumns()
238238

@@ -243,14 +243,42 @@ func (t *Table) initForRenderRows() {
243243
func (t *Table) initForRenderRowsStringify(rows []Row, hint renderHint) []rowStr {
244244
rowsStr := make([]rowStr, len(rows))
245245
for idx, row := range rows {
246-
if t.rowPainter != nil && hint.isRegularRow() {
247-
t.rowsColors[idx] = t.rowPainter(row)
248-
}
246+
hint.rowNumber = idx + 1
249247
rowsStr[idx] = t.analyzeAndStringify(row, hint)
250248
}
251249
return rowsStr
252250
}
253251

252+
func (t *Table) initForRenderRowPainterColors() {
253+
if !t.hasRowPainter() {
254+
return
255+
}
256+
257+
// generate the colors
258+
t.rowsColors = make([]text.Colors, len(t.rowsRaw))
259+
for idx, row := range t.rowsRaw {
260+
idxColors := idx
261+
if len(t.sortedRowIndices) > 0 {
262+
// override with the sorted row index
263+
for j := 0; j < len(t.sortedRowIndices); j++ {
264+
if t.sortedRowIndices[j] == idx {
265+
idxColors = j
266+
break
267+
}
268+
}
269+
}
270+
271+
if t.rowPainter != nil {
272+
t.rowsColors[idxColors] = t.rowPainter(row)
273+
} else if t.rowPainterWithAttributes != nil {
274+
t.rowsColors[idxColors] = t.rowPainterWithAttributes(row, RowAttributes{
275+
Number: idx + 1,
276+
NumberSorted: idxColors + 1,
277+
})
278+
}
279+
}
280+
}
281+
254282
func (t *Table) initForRenderRowSeparator() {
255283
t.rowSeparator = make(rowStr, t.numColumns)
256284
for colIdx, maxColumnLength := range t.maxColumnLengths {
@@ -265,21 +293,12 @@ func (t *Table) initForRenderSortRows() {
265293
}
266294

267295
// sort the rows
268-
sortedRowIndices := t.getSortedRowIndices()
296+
t.sortedRowIndices = t.getSortedRowIndices()
269297
sortedRows := make([]rowStr, len(t.rows))
270298
for idx := range t.rows {
271-
sortedRows[idx] = t.rows[sortedRowIndices[idx]]
299+
sortedRows[idx] = t.rows[t.sortedRowIndices[idx]]
272300
}
273301
t.rows = sortedRows
274-
275-
// sort the rowsColors
276-
if len(t.rowsColors) > 0 {
277-
sortedRowsColors := make([]text.Colors, len(t.rows))
278-
for idx := range t.rows {
279-
sortedRowsColors[idx] = t.rowsColors[sortedRowIndices[idx]]
280-
}
281-
t.rowsColors = sortedRowsColors
282-
}
283302
}
284303

285304
func (t *Table) initForRenderSuppressColumns() {

table/render_test.go

+61-39
Original file line numberDiff line numberDiff line change
@@ -878,13 +878,51 @@ func TestTable_Render_Reset(t *testing.T) {
878878
}
879879

880880
func TestTable_Render_RowPainter(t *testing.T) {
881-
tw := NewWriter()
882-
tw.AppendHeader(testHeader)
883-
tw.AppendRows(testRows)
884-
tw.AppendRow(testRowMultiLine)
885-
tw.AppendFooter(testFooter)
886-
tw.SetIndexColumn(1)
887-
tw.SetRowPainter(func(row Row) text.Colors {
881+
runTestWithRowPainter := func(t *testing.T, rowPainter interface{}) {
882+
tw := NewWriter()
883+
tw.AppendHeader(testHeader)
884+
tw.AppendRows(testRows)
885+
tw.AppendRow(testRowMultiLine)
886+
tw.AppendFooter(testFooter)
887+
tw.SetIndexColumn(1)
888+
tw.SetRowPainter(rowPainter)
889+
tw.SetStyle(StyleLight)
890+
tw.SortBy([]SortBy{{Name: "Salary", Mode: AscNumeric}})
891+
892+
expectedOutLines := []string{
893+
"┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐",
894+
"│ # │ FIRST NAME │ LAST NAME │ SALARY │ │",
895+
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
896+
"│ 0 │\x1b[41;30m Winter \x1b[0m│\x1b[41;30m Is \x1b[0m│\x1b[41;30m 0 \x1b[0m│\x1b[41;30m Coming. \x1b[0m│",
897+
"│ │\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m The North Remembers! \x1b[0m│",
898+
"│ │\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m This is known. \x1b[0m│",
899+
"│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │",
900+
"│ 1 │ Arya │ Stark │ 3000 │ │",
901+
"│ 300 │\x1b[43;30m Tyrion \x1b[0m│\x1b[43;30m Lannister \x1b[0m│\x1b[43;30m 5000 \x1b[0m│\x1b[43;30m \x1b[0m│",
902+
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
903+
"│ │ │ TOTAL │ 10000 │ │",
904+
"└─────┴────────────┴───────────┴────────┴─────────────────────────────┘",
905+
}
906+
expectedOut := strings.Join(expectedOutLines, "\n")
907+
assert.Equal(t, expectedOut, tw.Render())
908+
909+
tw.SetStyle(StyleColoredBright)
910+
tw.Style().Color.RowAlternate = tw.Style().Color.Row
911+
expectedOutLines = []string{
912+
"\x1b[106;30m # \x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m \x1b[0m",
913+
"\x1b[106;30m 0 \x1b[0m\x1b[41;30m Winter \x1b[0m\x1b[41;30m Is \x1b[0m\x1b[41;30m 0 \x1b[0m\x1b[41;30m Coming. \x1b[0m",
914+
"\x1b[106;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m The North Remembers! \x1b[0m",
915+
"\x1b[106;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m This is known. \x1b[0m",
916+
"\x1b[106;30m 20 \x1b[0m\x1b[107;30m Jon \x1b[0m\x1b[107;30m Snow \x1b[0m\x1b[107;30m 2000 \x1b[0m\x1b[107;30m You know nothing, Jon Snow! \x1b[0m",
917+
"\x1b[106;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m",
918+
"\x1b[106;30m 300 \x1b[0m\x1b[43;30m Tyrion \x1b[0m\x1b[43;30m Lannister \x1b[0m\x1b[43;30m 5000 \x1b[0m\x1b[43;30m \x1b[0m",
919+
"\x1b[46;30m \x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m \x1b[0m",
920+
}
921+
expectedOut = strings.Join(expectedOutLines, "\n")
922+
assert.Equal(t, expectedOut, tw.Render())
923+
}
924+
925+
rowPainter := func(row Row) text.Colors {
888926
if salary, ok := row[3].(int); ok {
889927
if salary > 3000 {
890928
return text.Colors{text.BgYellow, text.FgBlack}
@@ -893,41 +931,25 @@ func TestTable_Render_RowPainter(t *testing.T) {
893931
}
894932
}
895933
return nil
934+
}
935+
t.Run("RowPainter 1", func(t *testing.T) {
936+
runTestWithRowPainter(t, rowPainter)
937+
})
938+
t.Run("RowPainter 2", func(t *testing.T) {
939+
runTestWithRowPainter(t, RowPainter(rowPainter))
896940
})
897-
tw.SetStyle(StyleLight)
898-
tw.SortBy([]SortBy{{Name: "Salary", Mode: AscNumeric}})
899941

900-
expectedOutLines := []string{
901-
"┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐",
902-
"│ # │ FIRST NAME │ LAST NAME │ SALARY │ │",
903-
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
904-
"│ 0 │\x1b[41;30m Winter \x1b[0m│\x1b[41;30m Is \x1b[0m│\x1b[41;30m 0 \x1b[0m│\x1b[41;30m Coming. \x1b[0m│",
905-
"│ │\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m The North Remembers! \x1b[0m│",
906-
"│ │\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m This is known. \x1b[0m│",
907-
"│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │",
908-
"│ 1 │ Arya │ Stark │ 3000 │ │",
909-
"│ 300 │\x1b[43;30m Tyrion \x1b[0m│\x1b[43;30m Lannister \x1b[0m│\x1b[43;30m 5000 \x1b[0m│\x1b[43;30m \x1b[0m│",
910-
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
911-
"│ │ │ TOTAL │ 10000 │ │",
912-
"└─────┴────────────┴───────────┴────────┴─────────────────────────────┘",
942+
rowPainterWithAttributes := func(row Row, attr RowAttributes) text.Colors {
943+
assert.NotZero(t, attr.Number)
944+
assert.NotZero(t, attr.NumberSorted)
945+
return rowPainter(row)
913946
}
914-
expectedOut := strings.Join(expectedOutLines, "\n")
915-
assert.Equal(t, expectedOut, tw.Render())
916-
917-
tw.SetStyle(StyleColoredBright)
918-
tw.Style().Color.RowAlternate = tw.Style().Color.Row
919-
expectedOutLines = []string{
920-
"\x1b[106;30m # \x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m \x1b[0m",
921-
"\x1b[106;30m 0 \x1b[0m\x1b[41;30m Winter \x1b[0m\x1b[41;30m Is \x1b[0m\x1b[41;30m 0 \x1b[0m\x1b[41;30m Coming. \x1b[0m",
922-
"\x1b[106;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m The North Remembers! \x1b[0m",
923-
"\x1b[106;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m This is known. \x1b[0m",
924-
"\x1b[106;30m 20 \x1b[0m\x1b[107;30m Jon \x1b[0m\x1b[107;30m Snow \x1b[0m\x1b[107;30m 2000 \x1b[0m\x1b[107;30m You know nothing, Jon Snow! \x1b[0m",
925-
"\x1b[106;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m",
926-
"\x1b[106;30m 300 \x1b[0m\x1b[43;30m Tyrion \x1b[0m\x1b[43;30m Lannister \x1b[0m\x1b[43;30m 5000 \x1b[0m\x1b[43;30m \x1b[0m",
927-
"\x1b[46;30m \x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m \x1b[0m",
928-
}
929-
expectedOut = strings.Join(expectedOutLines, "\n")
930-
assert.Equal(t, expectedOut, tw.Render())
947+
t.Run("RowPainterWithAttributes 1", func(t *testing.T) {
948+
runTestWithRowPainter(t, rowPainterWithAttributes)
949+
})
950+
t.Run("RowPainterWithAttributes 2", func(t *testing.T) {
951+
runTestWithRowPainter(t, RowPainterWithAttributes(rowPainterWithAttributes))
952+
})
931953
}
932954

933955
func TestTable_Render_Sorted(t *testing.T) {

table/row.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package table
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/jedib0t/go-pretty/v6/text"
7+
)
8+
9+
// Row defines a single row in the Table.
10+
type Row []interface{}
11+
12+
func (r Row) findColumnNumber(colName string) int {
13+
for colIdx, col := range r {
14+
if fmt.Sprint(col) == colName {
15+
return colIdx + 1
16+
}
17+
}
18+
return 0
19+
}
20+
21+
// RowAttributes contains properties about the Row during the render.
22+
type RowAttributes struct {
23+
Number int // Row Number (1-indexed) as appended
24+
NumberSorted int // Row number (1-indexed) after sorting
25+
}
26+
27+
// RowPainter is a custom function that takes a Row as input and returns the
28+
// text.Colors{} to use on the entire row
29+
type RowPainter func(row Row) text.Colors
30+
31+
// RowPainterWithAttributes is the same as RowPainter but passes in additional
32+
// attributes from render time
33+
type RowPainterWithAttributes func(row Row, attr RowAttributes) text.Colors
34+
35+
// rowStr defines a single row in the Table comprised of just string objects.
36+
type rowStr []string
37+
38+
// areEqual returns true if the contents of the 2 given columns are the same
39+
func (row rowStr) areEqual(colIdx1 int, colIdx2 int) bool {
40+
return colIdx1 >= 0 && colIdx1 < len(row) &&
41+
colIdx2 >= 0 && colIdx2 < len(row) &&
42+
row[colIdx1] == row[colIdx2]
43+
}

0 commit comments

Comments
 (0)