X Tutup
Skip to content

Commit adfc014

Browse files
authored
Merge pull request cli#82 from github/table-output
Extract generic row printer that adjusts itself for receiving terminal
2 parents c7a38b6 + 97a6dc4 commit adfc014

File tree

2 files changed

+194
-44
lines changed

2 files changed

+194
-44
lines changed

command/pr.go

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/github/gh-cli/git"
1111
"github.com/github/gh-cli/utils"
1212
"github.com/spf13/cobra"
13-
"golang.org/x/crypto/ssh/terminal"
1413
)
1514

1615
func init() {
@@ -167,57 +166,38 @@ func prList(cmd *cobra.Command, args []string) error {
167166
return err
168167
}
169168

170-
tty := false
171-
ttyWidth := 80
172-
out := cmd.OutOrStdout()
173-
if outFile, isFile := out.(*os.File); isFile {
174-
fd := int(outFile.Fd())
175-
tty = terminal.IsTerminal(fd)
176-
if w, _, err := terminal.GetSize(fd); err == nil {
177-
ttyWidth = w
178-
}
179-
}
180-
181-
numWidth := 0
182-
maxTitleWidth := 0
169+
table := utils.NewTablePrinter(cmd.OutOrStdout())
183170
for _, pr := range prs {
184-
numLen := len(strconv.Itoa(pr.Number)) + 1
185-
if numLen > numWidth {
186-
numWidth = numLen
187-
}
188-
if len(pr.Title) > maxTitleWidth {
189-
maxTitleWidth = len(pr.Title)
171+
prNum := strconv.Itoa(pr.Number)
172+
if table.IsTTY() {
173+
prNum = "#" + prNum
190174
}
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()
191179
}
192-
193-
branchWidth := 40
194-
titleWidth := ttyWidth - branchWidth - 2 - numWidth - 2
195-
196-
if maxTitleWidth < titleWidth {
197-
branchWidth += titleWidth - maxTitleWidth
198-
titleWidth = maxTitleWidth
180+
err = table.Render()
181+
if err != nil {
182+
return err
199183
}
200184

201-
for _, pr := range prs {
202-
if tty {
203-
prNum := fmt.Sprintf("% *s", numWidth, fmt.Sprintf("#%d", pr.Number))
204-
switch pr.State {
205-
case "OPEN":
206-
prNum = utils.Green(prNum)
207-
case "CLOSED":
208-
prNum = utils.Red(prNum)
209-
case "MERGED":
210-
prNum = utils.Magenta(prNum)
211-
}
212-
prBranch := utils.Cyan(truncate(branchWidth, pr.HeadLabel()))
213-
fmt.Fprintf(out, "%s %-*s %s\n", prNum, titleWidth, truncate(titleWidth, pr.Title), prBranch)
214-
} else {
215-
fmt.Fprintf(out, "%d\t%s\t%s\n", pr.Number, pr.Title, pr.HeadLabel())
216-
}
217-
}
218185
return nil
219186
}
220187

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+
221201
func prView(cmd *cobra.Command, args []string) error {
222202
ctx := contextForCommand(cmd)
223203
baseRepo, err := ctx.BaseRepo()

utils/table_printer.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"golang.org/x/crypto/ssh/terminal"
9+
)
10+
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 {
19+
if outFile, isFile := w.(*os.File); isFile {
20+
fd := int(outFile.Fd())
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+
}
30+
}
31+
}
32+
return &tsvTablePrinter{
33+
out: w,
34+
}
35+
}
36+
37+
type tableField struct {
38+
Text string
39+
TruncateFunc func(int, string) string
40+
ColorFunc func(string) string
41+
}
42+
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{}}
59+
}
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)
67+
}
68+
69+
func (t *ttyTablePrinter) EndRow() {
70+
t.rows = append(t.rows, []tableField{})
71+
}
72+
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))
92+
// add extra space from columns that are already narrower than threshold
93+
for col := 1; col < numCols; col++ {
94+
availColWidth := availWidth / (numCols - 1)
95+
if extra := availColWidth - colWidths[col]; extra > 0 {
96+
availWidth += extra
97+
}
98+
}
99+
// cap all but first column to fit available terminal width
100+
// TODO: support weighted instead of even redistribution
101+
for col := 1; col < numCols; col++ {
102+
availColWidth := availWidth / (numCols - 1)
103+
if colWidths[col] > availColWidth {
104+
colWidths[col] = availColWidth
105+
}
106+
}
107+
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+
}
115+
}
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)
120+
}
121+
if field.ColorFunc != nil {
122+
truncVal = field.ColorFunc(truncVal)
123+
}
124+
_, err := fmt.Fprint(t.out, truncVal)
125+
if err != nil {
126+
return err
127+
}
128+
}
129+
if len(row) > 0 {
130+
_, err := fmt.Fprint(t.out, "\n")
131+
if err != nil {
132+
return err
133+
}
134+
}
135+
}
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")
151+
}
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 {
162+
return nil
163+
}
164+
165+
func truncate(maxLength int, title string) string {
166+
if len(title) > maxLength {
167+
return title[0:maxLength-3] + "..."
168+
}
169+
return title
170+
}

0 commit comments

Comments
 (0)
X Tutup