X Tutup
Skip to content

Commit 97a6dc4

Browse files
committed
Redesign TablePrinter to avoid SetContentWidth / FitColumns steps
The API is now: - AddField; - EndRow; - Render.
1 parent 2022f8e commit 97a6dc4

File tree

2 files changed

+137
-77
lines changed

2 files changed

+137
-77
lines changed

command/pr.go

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -167,33 +167,37 @@ func prList(cmd *cobra.Command, args []string) error {
167167
}
168168

169169
table := utils.NewTablePrinter(cmd.OutOrStdout())
170-
for _, pr := range prs {
171-
table.SetContentWidth(0, len(strconv.Itoa(pr.Number))+1)
172-
table.SetContentWidth(1, len(pr.Title))
173-
table.SetContentWidth(2, len(pr.HeadLabel()))
174-
}
175-
176-
table.FitColumns()
177-
table.SetColorFunc(2, utils.Cyan)
178-
179170
for _, pr := range prs {
180171
prNum := strconv.Itoa(pr.Number)
181-
if table.IsTTY {
172+
if table.IsTTY() {
182173
prNum = "#" + prNum
183174
}
184-
switch pr.State {
185-
case "OPEN":
186-
table.SetColorFunc(0, utils.Green)
187-
case "CLOSED":
188-
table.SetColorFunc(0, utils.Red)
189-
case "MERGED":
190-
table.SetColorFunc(0, utils.Magenta)
191-
}
192-
table.WriteRow(prNum, pr.Title, pr.HeadLabel())
175+
table.AddField(prNum, nil, colorFuncForState(pr.State))
176+
table.AddField(pr.Title, nil, nil)
177+
table.AddField(pr.HeadLabel(), nil, utils.Cyan)
178+
table.EndRow()
179+
}
180+
err = table.Render()
181+
if err != nil {
182+
return err
193183
}
184+
194185
return nil
195186
}
196187

188+
func colorFuncForState(state string) func(string) string {
189+
switch state {
190+
case "OPEN":
191+
return utils.Green
192+
case "CLOSED":
193+
return utils.Red
194+
case "MERGED":
195+
return utils.Magenta
196+
default:
197+
return nil
198+
}
199+
}
200+
197201
func prView(cmd *cobra.Command, args []string) error {
198202
ctx := contextForCommand(cmd)
199203
baseRepo, err := ctx.BaseRepo()

utils/table_printer.go

Lines changed: 114 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -8,101 +8,157 @@ import (
88
"golang.org/x/crypto/ssh/terminal"
99
)
1010

11-
func NewTablePrinter(w io.Writer) *TTYTablePrinter {
12-
tty := false
13-
ttyWidth := 80
11+
type TablePrinter interface {
12+
IsTTY() bool
13+
AddField(string, func(int, string) string, func(string) string)
14+
EndRow()
15+
Render() error
16+
}
17+
18+
func NewTablePrinter(w io.Writer) TablePrinter {
1419
if outFile, isFile := w.(*os.File); isFile {
1520
fd := int(outFile.Fd())
16-
tty = terminal.IsTerminal(fd)
17-
if w, _, err := terminal.GetSize(fd); err == nil {
18-
ttyWidth = w
21+
if terminal.IsTerminal(fd) {
22+
ttyWidth := 80
23+
if w, _, err := terminal.GetSize(fd); err == nil {
24+
ttyWidth = w
25+
}
26+
return &ttyTablePrinter{
27+
out: w,
28+
maxWidth: ttyWidth,
29+
}
1930
}
2031
}
21-
return &TTYTablePrinter{
22-
out: w,
23-
IsTTY: tty,
24-
maxWidth: ttyWidth,
25-
colWidths: make(map[int]int),
26-
colFuncs: make(map[int]func(string) string),
32+
return &tsvTablePrinter{
33+
out: w,
2734
}
2835
}
2936

30-
type TTYTablePrinter struct {
31-
out io.Writer
32-
IsTTY bool
33-
maxWidth int
34-
colWidths map[int]int
35-
colFuncs map[int]func(string) string
37+
type tableField struct {
38+
Text string
39+
TruncateFunc func(int, string) string
40+
ColorFunc func(string) string
3641
}
3742

38-
func (t *TTYTablePrinter) SetContentWidth(col, width int) {
39-
if width > t.colWidths[col] {
40-
t.colWidths[col] = width
43+
type ttyTablePrinter struct {
44+
out io.Writer
45+
maxWidth int
46+
rows [][]tableField
47+
}
48+
49+
func (t ttyTablePrinter) IsTTY() bool {
50+
return true
51+
}
52+
53+
func (t *ttyTablePrinter) AddField(text string, truncateFunc func(int, string) string, colorFunc func(string) string) {
54+
if truncateFunc == nil {
55+
truncateFunc = truncate
56+
}
57+
if t.rows == nil {
58+
t.rows = [][]tableField{[]tableField{}}
4159
}
60+
rowI := len(t.rows) - 1
61+
field := tableField{
62+
Text: text,
63+
TruncateFunc: truncateFunc,
64+
ColorFunc: colorFunc,
65+
}
66+
t.rows[rowI] = append(t.rows[rowI], field)
4267
}
4368

44-
func (t *TTYTablePrinter) SetColorFunc(col int, colorize func(string) string) {
45-
t.colFuncs[col] = colorize
69+
func (t *ttyTablePrinter) EndRow() {
70+
t.rows = append(t.rows, []tableField{})
4671
}
4772

48-
// FitColumns caps all but first column to fit available terminal width.
49-
func (t *TTYTablePrinter) FitColumns() {
50-
numCols := len(t.colWidths)
51-
delimWidth := 2
52-
availWidth := t.maxWidth - t.colWidths[0] - ((numCols - 1) * delimWidth)
73+
func (t *ttyTablePrinter) Render() error {
74+
if len(t.rows) == 0 {
75+
return nil
76+
}
77+
78+
numCols := len(t.rows[0])
79+
colWidths := make([]int, numCols)
80+
// measure maximum content width per column
81+
for _, row := range t.rows {
82+
for col, field := range row {
83+
textLen := len(field.Text)
84+
if textLen > colWidths[col] {
85+
colWidths[col] = textLen
86+
}
87+
}
88+
}
89+
90+
delim := " "
91+
availWidth := t.maxWidth - colWidths[0] - ((numCols - 1) * len(delim))
5392
// add extra space from columns that are already narrower than threshold
54-
for col := 1; col < len(t.colWidths); col++ {
93+
for col := 1; col < numCols; col++ {
5594
availColWidth := availWidth / (numCols - 1)
56-
if extra := availColWidth - t.colWidths[col]; extra > 0 {
95+
if extra := availColWidth - colWidths[col]; extra > 0 {
5796
availWidth += extra
5897
}
5998
}
99+
// cap all but first column to fit available terminal width
60100
// TODO: support weighted instead of even redistribution
61-
for col := 1; col < len(t.colWidths); col++ {
101+
for col := 1; col < numCols; col++ {
62102
availColWidth := availWidth / (numCols - 1)
63-
if t.colWidths[col] > availColWidth {
64-
t.colWidths[col] = availColWidth
103+
if colWidths[col] > availColWidth {
104+
colWidths[col] = availColWidth
65105
}
66106
}
67-
}
68-
69-
func (t *TTYTablePrinter) WriteRow(fields ...string) error {
70-
lastCol := len(fields) - 1
71-
delim := "\t"
72-
if t.IsTTY {
73-
delim = " "
74-
}
75107

76-
for col, val := range fields {
77-
if col > 0 {
78-
_, err := fmt.Fprint(t.out, delim)
79-
if err != nil {
80-
return err
108+
for _, row := range t.rows {
109+
for col, field := range row {
110+
if col > 0 {
111+
_, err := fmt.Fprint(t.out, delim)
112+
if err != nil {
113+
return err
114+
}
81115
}
82-
}
83-
if t.IsTTY {
84-
truncVal := truncate(t.colWidths[col], val)
85-
if col != lastCol {
86-
truncVal = fmt.Sprintf("%-*s", t.colWidths[col], truncVal)
116+
truncVal := field.TruncateFunc(colWidths[col], field.Text)
117+
if col < numCols-1 {
118+
// pad value with spaces on the right
119+
truncVal = fmt.Sprintf("%-*s", colWidths[col], truncVal)
87120
}
88-
if t.colFuncs[col] != nil {
89-
truncVal = t.colFuncs[col](truncVal)
121+
if field.ColorFunc != nil {
122+
truncVal = field.ColorFunc(truncVal)
90123
}
91124
_, err := fmt.Fprint(t.out, truncVal)
92125
if err != nil {
93126
return err
94127
}
95-
} else {
96-
_, err := fmt.Fprint(t.out, val)
128+
}
129+
if len(row) > 0 {
130+
_, err := fmt.Fprint(t.out, "\n")
97131
if err != nil {
98132
return err
99133
}
100134
}
101135
}
102-
_, err := fmt.Fprint(t.out, "\n")
103-
if err != nil {
104-
return err
136+
return nil
137+
}
138+
139+
type tsvTablePrinter struct {
140+
out io.Writer
141+
currentCol int
142+
}
143+
144+
func (t tsvTablePrinter) IsTTY() bool {
145+
return false
146+
}
147+
148+
func (t *tsvTablePrinter) AddField(text string, _ func(int, string) string, _ func(string) string) {
149+
if t.currentCol > 0 {
150+
fmt.Fprint(t.out, "\t")
105151
}
152+
fmt.Fprint(t.out, text)
153+
t.currentCol++
154+
}
155+
156+
func (t *tsvTablePrinter) EndRow() {
157+
fmt.Fprint(t.out, "\n")
158+
t.currentCol = 0
159+
}
160+
161+
func (t *tsvTablePrinter) Render() error {
106162
return nil
107163
}
108164

0 commit comments

Comments
 (0)
X Tutup