Skip to content

Commit df30706

Browse files
authored
Use charmbracelet/lipgloss for tables and remove tablewriter (#136)
1 parent 4464f5e commit df30706

File tree

9 files changed

+132
-115
lines changed

9 files changed

+132
-115
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
99

1010
- Add a `-follow-output` flag to allow writing go test output directly into a file. This will be
1111
useful (especially in CI jobs) for outputting overly verbose testing output into a file instead of
12-
the standard stream. (#133)
12+
the standard stream. (#134)
1313

1414
| flag combination | `go test` output destination |
1515
| ------------------------ | ---------------------------- |
@@ -18,6 +18,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1818
| `-follow-output` | Write to file |
1919
| `-follow -follow-output` | Write to file |
2020

21+
- Use [charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) for table rendering.
22+
- This will allow for more control over the output and potentially more features in the future.
23+
(#136)
24+
- Minor changes to the output format are expected, but the overall content should remain the same.
25+
If you have any feedback, please let me know.
26+
2127
## [v0.15.0]
2228

2329
- Add `-trimpath` flag, which removes the path prefix from package names in the output, simplifying

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module github.com/mfridman/tparse
22

3-
go 1.18
3+
go 1.21
4+
5+
toolchain go1.23.2
46

57
require (
68
github.com/charmbracelet/lipgloss v1.0.0
79
github.com/mfridman/buildversion v0.3.0
8-
github.com/muesli/termenv v0.15.2
9-
github.com/olekukonko/tablewriter v0.0.5
10+
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a
1011
github.com/stretchr/testify v1.9.0
1112
)
1213

@@ -16,7 +17,6 @@ require (
1617
github.com/davecgh/go-spew v1.1.1 // indirect
1718
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
1819
github.com/mattn/go-isatty v0.0.20 // indirect
19-
github.com/mattn/go-runewidth v0.0.16 // indirect
2020
github.com/pmezard/go-difflib v1.0.0 // indirect
2121
github.com/rivo/uniseg v0.4.7 // indirect
2222
golang.org/x/sys v0.26.0 // indirect

go.sum

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
22
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
3+
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
4+
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
35
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
46
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
57
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk=
68
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
9+
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
10+
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
711
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
812
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
913
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
1014
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
1115
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
1216
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
13-
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
14-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
15-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
1617
github.com/mfridman/buildversion v0.3.0 h1:hehEX3IbBZJBqquXctUEOWJfIM46P0ku9naK9h1BGuY=
1718
github.com/mfridman/buildversion v0.3.0/go.mod h1:sfXvYxwfmLvkklTJLv9xJ0Wffw57z9ZFOK4KOGJYafU=
18-
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
19-
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
20-
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
21-
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
19+
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
20+
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
2221
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2322
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
24-
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
2523
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
2624
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
2725
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=

internal/app/table.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package app
2+
3+
import (
4+
"github.com/charmbracelet/lipgloss"
5+
"github.com/charmbracelet/lipgloss/table"
6+
)
7+
8+
func newTable(
9+
format OutputFormat,
10+
override func(style lipgloss.Style, row, col int) lipgloss.Style,
11+
) *table.Table {
12+
tbl := table.New()
13+
switch format {
14+
case OutputFormatPlain:
15+
tbl.Border(lipgloss.HiddenBorder()).BorderTop(false).BorderBottom(false)
16+
case OutputFormatMarkdown:
17+
tbl.Border(markdownBorder).BorderBottom(false).BorderTop(false)
18+
case OutputFormatBasic:
19+
tbl.Border(lipgloss.RoundedBorder())
20+
}
21+
return tbl.StyleFunc(func(row, col int) lipgloss.Style {
22+
// Default style, may be overridden.
23+
style := lipgloss.NewStyle().PaddingLeft(1).PaddingRight(1).Align(lipgloss.Center)
24+
if override != nil {
25+
style = override(style, row, col)
26+
}
27+
return style
28+
})
29+
}
30+
31+
var markdownBorder = lipgloss.Border{
32+
Top: "-",
33+
Bottom: "-",
34+
Left: "|",
35+
Right: "|",
36+
TopLeft: "", // empty for markdown
37+
TopRight: "", // empty for markdown
38+
BottomLeft: "", // empty for markdown
39+
BottomRight: "", // empty for markdown
40+
MiddleLeft: "|",
41+
MiddleRight: "|",
42+
Middle: "|",
43+
MiddleTop: "|",
44+
MiddleBottom: "|",
45+
}

internal/app/table_summary.go

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88

99
"github.com/charmbracelet/lipgloss"
10-
"github.com/olekukonko/tablewriter"
10+
"github.com/charmbracelet/lipgloss/table"
1111

1212
"github.com/mfridman/tparse/internal/utils"
1313
"github.com/mfridman/tparse/parse"
@@ -33,16 +33,16 @@ func (c *consoleWriter) summaryTable(
3333
options SummaryTableOptions,
3434
against *parse.GoTestSummary,
3535
) {
36-
var tableString strings.Builder
37-
tbl := newTableWriter(&tableString, c.format)
38-
tbl.SetColumnAlignment([]int{
39-
tablewriter.ALIGN_LEFT,
40-
tablewriter.ALIGN_CENTER,
41-
tablewriter.ALIGN_LEFT,
42-
tablewriter.ALIGN_CENTER,
43-
tablewriter.ALIGN_CENTER,
44-
tablewriter.ALIGN_CENTER,
45-
tablewriter.ALIGN_CENTER,
36+
tbl := newTable(c.format, func(style lipgloss.Style, row, col int) lipgloss.Style {
37+
switch row {
38+
case table.HeaderRow:
39+
default:
40+
if col == 2 {
41+
// Package name
42+
style = style.Align(lipgloss.Left)
43+
}
44+
}
45+
return style
4646
})
4747
header := summaryRow{
4848
status: "Status",
@@ -53,7 +53,8 @@ func (c *consoleWriter) summaryTable(
5353
fail: "Fail",
5454
skip: "Skip",
5555
}
56-
tbl.SetHeader(header.toRow())
56+
tbl.Headers(header.toRow()...)
57+
data := table.NewStringData()
5758

5859
// Capture as separate slices because notests are optional when passed tests are available.
5960
// The only exception is if passed=0 and notests=1, then we display them regardless. This
@@ -80,7 +81,7 @@ func (c *consoleWriter) summaryTable(
8081
packageName: packageName,
8182
cover: "--", pass: "--", fail: "--", skip: "--",
8283
}
83-
tbl.Append(row.toRow())
84+
data.Append(row.toRow())
8485
continue
8586
}
8687
if pkg.HasFailedBuildOrSetup {
@@ -90,7 +91,7 @@ func (c *consoleWriter) summaryTable(
9091
packageName: packageName + "\n[" + pkg.Summary.Output + "]",
9192
cover: "--", pass: "--", fail: "--", skip: "--",
9293
}
93-
tbl.Append(row.toRow())
94+
data.Append(row.toRow())
9495
continue
9596
}
9697
if pkg.NoTestFiles {
@@ -187,32 +188,24 @@ func (c *consoleWriter) summaryTable(
187188
passed = append(passed, row)
188189
}
189190

190-
if tbl.NumLines() == 0 && len(passed) == 0 && len(notests) == 0 {
191+
if data.Rows() == 0 && len(passed) == 0 && len(notests) == 0 {
191192
return
192193
}
193-
194-
for _, p := range passed {
195-
tbl.Append(p.toRow())
194+
for _, r := range passed {
195+
data.Append(r.toRow())
196196
}
197+
197198
// Only display the "no tests to run" cases if users want to see them when passed
198199
// tests are available.
199200
// An exception is made if there are no passed tests and only a single no test files
200201
// package. This is almost always because the user forgot to match one or more packages.
201202
if showNoTests || (len(passed) == 0 && len(notests) == 1) {
202-
for _, p := range notests {
203-
tbl.Append(p.toRow())
203+
for _, r := range notests {
204+
data.Append(r.toRow())
204205
}
205206
}
206-
// The table gets written to a strings builder so we can further modify the output
207-
// with lipgloss.
208-
tbl.Render()
209-
output := tableString.String()
210-
if c.format == OutputFormatBasic {
211-
output = lipgloss.NewStyle().
212-
Border(lipgloss.NormalBorder()).
213-
Render(strings.TrimSuffix(tableString.String(), "\n"))
214-
}
215-
fmt.Fprintln(c.w, output)
207+
208+
fmt.Fprintln(c.w, tbl.Data(data).Render())
216209
}
217210

218211
type summaryRow struct {

internal/app/table_tests.go

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

1010
"github.com/charmbracelet/lipgloss"
11+
"github.com/charmbracelet/lipgloss/table"
1112

1213
"github.com/mfridman/tparse/internal/utils"
1314
"github.com/mfridman/tparse/parse"
@@ -44,16 +45,25 @@ type packageTests struct {
4445

4546
func (c *consoleWriter) testsTable(packages []*parse.Package, option TestTableOptions) {
4647
// Print passed tests, sorted by elapsed DESC. Grouped by alphabetically sorted packages.
47-
var tableString strings.Builder
48-
tbl := newTableWriter(&tableString, c.format)
49-
48+
tbl := newTable(c.format, func(style lipgloss.Style, row, col int) lipgloss.Style {
49+
switch row {
50+
case table.HeaderRow:
51+
default:
52+
if col == 2 || col == 3 {
53+
// Test name and package name
54+
style = style.Align(lipgloss.Left)
55+
}
56+
}
57+
return style
58+
})
5059
header := testRow{
5160
status: "Status",
5261
elapsed: "Elapsed",
5362
testName: "Test",
5463
packageName: "Package",
5564
}
56-
tbl.SetHeader(header.toRow())
65+
tbl.Headers(header.toRow()...)
66+
data := table.NewStringData()
5767

5868
names := make([]string, 0, len(packages))
5969
for _, pkg := range packages {
@@ -95,40 +105,40 @@ func (c *consoleWriter) testsTable(packages []*parse.Package, option TestTableOp
95105
testName: testName,
96106
packageName: packageName,
97107
}
98-
tbl.Append(row.toRow())
108+
data.Append(row.toRow())
99109
}
100110
if i != (len(packages) - 1) {
101-
// TODO(mf): is it possible to add a custom separator with tablewriter instead of empty space?
102-
tbl.Append(testRow{}.toRow())
111+
// Add a blank row between packages.
112+
data.Append(testRow{}.toRow())
103113
}
104114
}
105115

106-
if tbl.NumLines() > 0 {
107-
// The table gets written to a strings builder so we can further modify the output
108-
// with lipgloss.
109-
tbl.Render()
110-
output := tableString.String()
111-
if c.format == OutputFormatBasic {
112-
output = lipgloss.NewStyle().
113-
Border(lipgloss.NormalBorder()).
114-
Render(strings.TrimSuffix(output, "\n"))
115-
}
116-
fmt.Fprintln(c.w, output)
116+
if data.Rows() > 0 {
117+
fmt.Fprintln(c.w, tbl.Data(data).Render())
117118
}
118119
}
119120

120121
func (c *consoleWriter) testsTableMarkdown(packages []*parse.Package, option TestTableOptions) {
121122
for _, pkg := range packages {
122123
// Print passed tests, sorted by elapsed DESC. Grouped by alphabetically sorted packages.
123-
var tableString strings.Builder
124-
tbl := newTableWriter(&tableString, c.format)
125-
124+
tbl := newTable(c.format, func(style lipgloss.Style, row, col int) lipgloss.Style {
125+
switch row {
126+
case table.HeaderRow:
127+
default:
128+
if col == 2 {
129+
// Test name
130+
style = style.Align(lipgloss.Left)
131+
}
132+
}
133+
return style
134+
})
126135
header := []string{
127136
"Status",
128137
"Elapsed",
129138
"Test",
130139
}
131-
tbl.SetHeader(header)
140+
tbl.Headers(header...)
141+
data := table.NewStringData()
132142

133143
// Discard packages where we cannot generate a sensible test summary.
134144
if pkg.NoTestFiles || pkg.NoTests || pkg.HasPanic {
@@ -154,30 +164,34 @@ func (c *consoleWriter) testsTableMarkdown(packages []*parse.Package, option Tes
154164
case parse.ActionFail:
155165
status = c.red(status)
156166
}
157-
tbl.Append([]string{
167+
data.Append([]string{
158168
status,
159169
strconv.FormatFloat(t.Elapsed(), 'f', 2, 64),
160170
testName,
161171
})
162172
}
163-
if tbl.NumLines() > 0 {
164-
tbl.Render()
165-
173+
if data.Rows() > 0 {
166174
fmt.Fprintf(c.w, "## 📦 Package **`%s`**\n", pkg.Summary.Package)
167175
fmt.Fprintln(c.w)
168-
fmt.Fprintf(c.w,
169-
"**%d passed** tests (out of %d) | **%d skipped** tests (out of %d)\n",
170-
len(pkgTests.passed),
176+
177+
msg := fmt.Sprintf("Tests: ✓ %d passed | %d skipped\n",
171178
pkgTests.passedCount,
172-
len(pkgTests.skipped),
173179
pkgTests.skippedCount,
174180
)
181+
if option.Slow > 0 && option.Slow < pkgTests.passedCount {
182+
msg += fmt.Sprintf("↓ Slowest %d passed tests shown (of %d)\n",
183+
option.Slow,
184+
pkgTests.passedCount,
185+
)
186+
}
187+
fmt.Fprint(c.w, msg)
188+
175189
fmt.Fprintln(c.w)
176190
fmt.Fprintln(c.w, "<details>")
177191
fmt.Fprintln(c.w)
178192
fmt.Fprintln(c.w, "<summary>Click for test summary</summary>")
179193
fmt.Fprintln(c.w)
180-
fmt.Fprintln(c.w, tableString.String())
194+
fmt.Fprintln(c.w, tbl.Data(data).Render())
181195
fmt.Fprintln(c.w, "</details>")
182196
fmt.Fprintln(c.w)
183197
}

0 commit comments

Comments
 (0)