Skip to content

Commit 7eecb18

Browse files
committed
feat: port to tablewriter 1.0.4
We got a Dependabot bump to this version. Previously we were on a pre-1.0 version, but 1.0 came out this month. There were some minor API breaks - renamed fields and slightly changed concepts. The main difference that I can see, though, is that direct handling of colours was removed. In the `ci list` subcommand, we output a table containing the check runs and statuses for a pull request or commit. As a bonus feature on a terminal we'll colour those ("Passed" is green, etc). We also pass them through a [`caser`] to make them into Title Case. The removal of the colour support isn't too bad in itself. We already had a function to map check statuses to colours. All we needed to do there was to move this over to the type's `String()` method, using the [`color`] library which we already use. The problem comes when we then try to title case these strings. The title caser has no understanding of terminal colour codes at all. So here we make a wrapper [`transformer`] that strips the colour codes before passing the plain text to the underlying `caser`, before reassembling back to keep the colours. This ended up being quite a bit of work due to the way that transformers work - they can be called partially in a sort of streaming mode. But it's all in here and working now. The wrapper has an extensive testsuite to help build confidence that it works properly, since it's challenging code to follow and the concepts involved are a bit difficult to understand too. Why is it OK to include colour codes in the string results? That's because the `fatih/color` library auto-detects if the output is going to a terminal or not before emitting the codes. In fact, we already did this for the case of job names. [`caser`]: https://pkg.go.dev/golang.org/x/text/cases#Title [`color`]: https://pkg.go.dev/github.com/fatih/color [`transformer`]: https://pkg.go.dev/golang.org/x/text/transform#Transformer
1 parent d55c8e6 commit 7eecb18

File tree

7 files changed

+1102
-137
lines changed

7 files changed

+1102
-137
lines changed

cmd/wait-for-github/ci_list.go

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,94 +18,107 @@ package main
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"io"
2223
"os"
2324

25+
"github.com/fatih/color"
26+
"github.com/grafana/wait-for-github/internal/ansi"
2427
"github.com/grafana/wait-for-github/internal/github"
25-
"github.com/mattn/go-isatty"
2628
"github.com/olekukonko/tablewriter"
29+
"github.com/olekukonko/tablewriter/renderer"
30+
"github.com/olekukonko/tablewriter/tw"
2731
"github.com/urfave/cli/v2"
2832
"golang.org/x/text/cases"
2933
"golang.org/x/text/language"
34+
"golang.org/x/text/transform"
3035
)
3136

3237
type checkListConfig struct {
3338
ciConfig
3439
githubClient github.GetDetailedCIStatus
3540
}
3641

37-
var caser = cases.Title(language.English)
38-
39-
func statusColor(check github.CICheckStatus) tablewriter.Colors {
40-
switch check.Outcome() {
41-
case github.CIStatusPassed:
42-
return tablewriter.Colors{tablewriter.FgGreenColor}
43-
case github.CIStatusFailed:
44-
return tablewriter.Colors{tablewriter.FgRedColor}
45-
case github.CIStatusPending:
46-
return tablewriter.Colors{tablewriter.FgYellowColor}
47-
case github.CIStatusSkipped:
48-
return tablewriter.Colors{tablewriter.FgHiBlackColor}
49-
default:
50-
return tablewriter.Colors{tablewriter.FgWhiteColor}
51-
}
52-
}
42+
var caser = ansi.NewANSITransformer(cases.Title(language.English))
5343

5444
// tableWriter is a wrapper around the tablewriter library, provided so that
5545
// tests can mock the table writing process
5646
type tableWriter interface {
57-
SetHeader(headers []string)
58-
Rich(row []string, colors []tablewriter.Colors)
59-
Render()
47+
Header(headers ...any)
48+
Bulk(data any) error
49+
Render() error
6050
}
6151

52+
type realTableWriter = tablewriter.Table
53+
54+
var _ tableWriter = (*realTableWriter)(nil)
55+
6256
func newTableWriter(w io.Writer) (tableWriter, error) {
63-
tw := tablewriter.NewWriter(w)
64-
tw.SetAutoWrapText(false)
57+
colorConfig := renderer.ColorizedConfig{
58+
Header: renderer.Tint{
59+
FG: renderer.Colors{color.FgBlack, color.Bold},
60+
},
61+
Border: renderer.Tint{FG: renderer.Colors{color.FgWhite}},
62+
Separator: renderer.Tint{FG: renderer.Colors{color.FgWhite}},
6563

66-
if err := tw.SetUnicodeHV(tablewriter.Double, tablewriter.Regular); err != nil {
67-
return nil, err
64+
Column: renderer.Tint{BG: renderer.Colors{color.Reset}},
6865
}
6966

70-
return tw, nil
67+
table := tablewriter.NewTable(w,
68+
tablewriter.WithRenderer(renderer.NewColorized(
69+
colorConfig,
70+
)),
71+
tablewriter.WithConfig(tablewriter.Config{
72+
Row: tw.CellConfig{
73+
Formatting: tw.CellFormatting{
74+
AutoWrap: tw.WrapNone,
75+
},
76+
},
77+
}),
78+
)
79+
80+
return table, nil
7181
}
7282

73-
func listChecks(ctx context.Context, cfg *checkListConfig, table tableWriter, useColors bool) error {
83+
func listChecks(ctx context.Context, cfg *checkListConfig, table tableWriter) error {
7484
checks, err := cfg.githubClient.GetDetailedCIStatus(ctx, cfg.owner, cfg.repo, cfg.ref)
7585
if err != nil {
7686
return err
7787
}
7888

7989
if len(checks) == 0 {
8090
// For empty results, still use the table but with a single message row
81-
table.SetHeader([]string{"Status"})
82-
table.Rich([]string{"No CI checks found"}, nil)
83-
table.Render()
91+
table.Header([]string{"Status"})
92+
93+
if err := table.Bulk([]string{"No CI checks found"}); err != nil {
94+
return fmt.Errorf("failed to write table: %w", err)
95+
}
96+
97+
if err := table.Render(); err != nil {
98+
return fmt.Errorf("failed to render table: %w", err)
99+
}
84100

85101
return nil
86102
}
87103

88-
table.SetHeader([]string{"Name", "Type", "Status"})
104+
table.Header([]string{"Name", "Type", "Status"})
89105

106+
var data [][]string
90107
for _, check := range checks {
91-
var colors []tablewriter.Colors
92-
if useColors {
93-
colors = []tablewriter.Colors{
94-
{},
95-
{},
96-
statusColor(check),
97-
}
98-
}
108+
checkOutcomeString, _, _ := transform.String(caser, check.Outcome().String())
99109

100-
table.Rich([]string{
110+
data = append(data, []string{
101111
check.String(),
102112
check.Type(),
103-
caser.String(check.Outcome().String()),
104-
}, colors)
113+
checkOutcomeString,
114+
})
105115
}
106116

107-
table.Render()
108-
return nil
117+
if err := table.Bulk(data); err != nil {
118+
return fmt.Errorf("failed to write table: %w", err)
119+
}
120+
121+
return table.Render()
109122
}
110123

111124
func ciListCommand(cfg *config) *cli.Command {
@@ -130,12 +143,10 @@ func ciListCommand(cfg *config) *cli.Command {
130143
return err
131144
}
132145

133-
useColor := isatty.IsTerminal(w.Fd()) && os.Getenv("NO_COLOR") == ""
134-
135146
return listChecks(c.Context, &checkListConfig{
136147
ciConfig: ciConf,
137148
githubClient: githubClient,
138-
}, table, useColor)
149+
}, table)
139150
},
140151
}
141152
}

0 commit comments

Comments
 (0)