X Tutup
Skip to content

Commit f30e973

Browse files
committed
Extract generic row printer that adjusts itself for receiving terminal
This makes the approach from `pr list` reusable across other commands that may benefit from table-based output, e.g. `issue list` or `pr status` The idea is: instantiate a printer, connect it to stdout, feed it some data, and it does the rest: colored, truncated column output that fits into a terminal, or tab-delimited output (no color, no truncation) for scripts.
1 parent 624c44e commit f30e973

File tree

2 files changed

+126
-43
lines changed

2 files changed

+126
-43
lines changed

command/pr.go

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ package command
22

33
import (
44
"fmt"
5-
"os"
65
"strconv"
76

87
"github.com/github/gh-cli/api"
98
"github.com/github/gh-cli/utils"
109
"github.com/spf13/cobra"
11-
"golang.org/x/crypto/ssh/terminal"
1210
)
1311

1412
func init() {
@@ -158,53 +156,30 @@ func prList(cmd *cobra.Command, args []string) error {
158156
return err
159157
}
160158

161-
tty := false
162-
ttyWidth := 80
163-
out := cmd.OutOrStdout()
164-
if outFile, isFile := out.(*os.File); isFile {
165-
fd := int(outFile.Fd())
166-
tty = terminal.IsTerminal(fd)
167-
if w, _, err := terminal.GetSize(fd); err == nil {
168-
ttyWidth = w
169-
}
170-
}
171-
172-
numWidth := 0
173-
maxTitleWidth := 0
159+
table := utils.NewTablePrinter(cmd.OutOrStdout())
174160
for _, pr := range prs {
175-
numLen := len(strconv.Itoa(pr.Number)) + 1
176-
if numLen > numWidth {
177-
numWidth = numLen
178-
}
179-
if len(pr.Title) > maxTitleWidth {
180-
maxTitleWidth = len(pr.Title)
181-
}
161+
table.SetContentWidth(0, len(strconv.Itoa(pr.Number))+1)
162+
table.SetContentWidth(1, len(pr.Title))
163+
table.SetContentWidth(2, len(pr.HeadLabel()))
182164
}
183165

184-
branchWidth := 40
185-
titleWidth := ttyWidth - branchWidth - 2 - numWidth - 2
186-
187-
if maxTitleWidth < titleWidth {
188-
branchWidth += titleWidth - maxTitleWidth
189-
titleWidth = maxTitleWidth
190-
}
166+
table.FitColumns()
167+
table.SetColorFunc(2, utils.Cyan)
191168

192169
for _, pr := range prs {
193-
if tty {
194-
prNum := fmt.Sprintf("% *s", numWidth, fmt.Sprintf("#%d", pr.Number))
195-
switch pr.State {
196-
case "OPEN":
197-
prNum = utils.Green(prNum)
198-
case "CLOSED":
199-
prNum = utils.Red(prNum)
200-
case "MERGED":
201-
prNum = utils.Magenta(prNum)
202-
}
203-
prBranch := utils.Cyan(truncate(branchWidth, pr.HeadLabel()))
204-
fmt.Fprintf(out, "%s %-*s %s\n", prNum, titleWidth, truncate(titleWidth, pr.Title), prBranch)
205-
} else {
206-
fmt.Fprintf(out, "%d\t%s\t%s\n", pr.Number, pr.Title, pr.HeadLabel())
170+
prNum := strconv.Itoa(pr.Number)
171+
if table.IsTTY {
172+
prNum = "#" + prNum
173+
}
174+
switch pr.State {
175+
case "OPEN":
176+
table.SetColorFunc(0, utils.Green)
177+
case "CLOSED":
178+
table.SetColorFunc(0, utils.Red)
179+
case "MERGED":
180+
table.SetColorFunc(0, utils.Magenta)
207181
}
182+
table.WriteRow(prNum, pr.Title, pr.HeadLabel())
208183
}
209184
return nil
210185
}

utils/table_printer.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"golang.org/x/crypto/ssh/terminal"
9+
)
10+
11+
func NewTablePrinter(w io.Writer) *TTYTablePrinter {
12+
tty := false
13+
ttyWidth := 80
14+
if outFile, isFile := w.(*os.File); isFile {
15+
fd := int(outFile.Fd())
16+
tty = terminal.IsTerminal(fd)
17+
if w, _, err := terminal.GetSize(fd); err == nil {
18+
ttyWidth = w
19+
}
20+
}
21+
return &TTYTablePrinter{
22+
out: w,
23+
IsTTY: tty,
24+
maxWidth: ttyWidth,
25+
colWidths: []int{},
26+
colFuncs: make(map[int]func(string) string),
27+
}
28+
}
29+
30+
type TTYTablePrinter struct {
31+
out io.Writer
32+
IsTTY bool
33+
maxWidth int
34+
colWidths []int
35+
colFuncs map[int]func(string) string
36+
}
37+
38+
func (t *TTYTablePrinter) SetContentWidth(col, width int) {
39+
if col == len(t.colWidths) {
40+
t.colWidths = append(t.colWidths, 0)
41+
}
42+
if width > t.colWidths[col] {
43+
t.colWidths[col] = width
44+
}
45+
}
46+
47+
func (t *TTYTablePrinter) SetColorFunc(col int, colorize func(string) string) {
48+
t.colFuncs[col] = colorize
49+
}
50+
51+
// FitColumns caps all but first column to fit available terminal width.
52+
func (t *TTYTablePrinter) FitColumns() {
53+
numCols := len(t.colWidths)
54+
delimWidth := 2
55+
availWidth := t.maxWidth - t.colWidths[0] - ((numCols - 1) * delimWidth)
56+
// TODO: avoid widening columns that already fit
57+
// TODO: support weighted instead of even redistribution
58+
for col := 1; col < len(t.colWidths); col++ {
59+
t.colWidths[col] = availWidth / (numCols - 1)
60+
}
61+
}
62+
63+
func (t *TTYTablePrinter) WriteRow(fields ...string) error {
64+
lastCol := len(fields) - 1
65+
delim := "\t"
66+
if t.IsTTY {
67+
delim = " "
68+
}
69+
70+
for col, val := range fields {
71+
if col > 0 {
72+
_, err := fmt.Fprint(t.out, delim)
73+
if err != nil {
74+
return err
75+
}
76+
}
77+
if t.IsTTY {
78+
truncVal := truncate(t.colWidths[col], val)
79+
if col != lastCol {
80+
truncVal = fmt.Sprintf("%-*s", t.colWidths[col], truncVal)
81+
}
82+
if t.colFuncs[col] != nil {
83+
truncVal = t.colFuncs[col](truncVal)
84+
}
85+
_, err := fmt.Fprint(t.out, truncVal)
86+
if err != nil {
87+
return err
88+
}
89+
} else {
90+
_, err := fmt.Fprint(t.out, val)
91+
if err != nil {
92+
return err
93+
}
94+
}
95+
}
96+
_, err := fmt.Fprint(t.out, "\n")
97+
if err != nil {
98+
return err
99+
}
100+
return nil
101+
}
102+
103+
func truncate(maxLength int, title string) string {
104+
if len(title) > maxLength {
105+
return title[0:maxLength-3] + "..."
106+
}
107+
return title
108+
}

0 commit comments

Comments
 (0)
X Tutup