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.
This commit is contained in:
parent
624c44efda
commit
f30e973b9d
2 changed files with 126 additions and 43 deletions
|
|
@ -2,13 +2,11 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/github/gh-cli/api"
|
||||
"github.com/github/gh-cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -158,53 +156,30 @@ func prList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tty := false
|
||||
ttyWidth := 80
|
||||
out := cmd.OutOrStdout()
|
||||
if outFile, isFile := out.(*os.File); isFile {
|
||||
fd := int(outFile.Fd())
|
||||
tty = terminal.IsTerminal(fd)
|
||||
if w, _, err := terminal.GetSize(fd); err == nil {
|
||||
ttyWidth = w
|
||||
}
|
||||
}
|
||||
|
||||
numWidth := 0
|
||||
maxTitleWidth := 0
|
||||
table := utils.NewTablePrinter(cmd.OutOrStdout())
|
||||
for _, pr := range prs {
|
||||
numLen := len(strconv.Itoa(pr.Number)) + 1
|
||||
if numLen > numWidth {
|
||||
numWidth = numLen
|
||||
}
|
||||
if len(pr.Title) > maxTitleWidth {
|
||||
maxTitleWidth = len(pr.Title)
|
||||
}
|
||||
table.SetContentWidth(0, len(strconv.Itoa(pr.Number))+1)
|
||||
table.SetContentWidth(1, len(pr.Title))
|
||||
table.SetContentWidth(2, len(pr.HeadLabel()))
|
||||
}
|
||||
|
||||
branchWidth := 40
|
||||
titleWidth := ttyWidth - branchWidth - 2 - numWidth - 2
|
||||
|
||||
if maxTitleWidth < titleWidth {
|
||||
branchWidth += titleWidth - maxTitleWidth
|
||||
titleWidth = maxTitleWidth
|
||||
}
|
||||
table.FitColumns()
|
||||
table.SetColorFunc(2, utils.Cyan)
|
||||
|
||||
for _, pr := range prs {
|
||||
if tty {
|
||||
prNum := fmt.Sprintf("% *s", numWidth, fmt.Sprintf("#%d", pr.Number))
|
||||
switch pr.State {
|
||||
case "OPEN":
|
||||
prNum = utils.Green(prNum)
|
||||
case "CLOSED":
|
||||
prNum = utils.Red(prNum)
|
||||
case "MERGED":
|
||||
prNum = utils.Magenta(prNum)
|
||||
}
|
||||
prBranch := utils.Cyan(truncate(branchWidth, pr.HeadLabel()))
|
||||
fmt.Fprintf(out, "%s %-*s %s\n", prNum, titleWidth, truncate(titleWidth, pr.Title), prBranch)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%d\t%s\t%s\n", pr.Number, pr.Title, pr.HeadLabel())
|
||||
prNum := strconv.Itoa(pr.Number)
|
||||
if table.IsTTY {
|
||||
prNum = "#" + prNum
|
||||
}
|
||||
switch pr.State {
|
||||
case "OPEN":
|
||||
table.SetColorFunc(0, utils.Green)
|
||||
case "CLOSED":
|
||||
table.SetColorFunc(0, utils.Red)
|
||||
case "MERGED":
|
||||
table.SetColorFunc(0, utils.Magenta)
|
||||
}
|
||||
table.WriteRow(prNum, pr.Title, pr.HeadLabel())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
108
utils/table_printer.go
Normal file
108
utils/table_printer.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func NewTablePrinter(w io.Writer) *TTYTablePrinter {
|
||||
tty := false
|
||||
ttyWidth := 80
|
||||
if outFile, isFile := w.(*os.File); isFile {
|
||||
fd := int(outFile.Fd())
|
||||
tty = terminal.IsTerminal(fd)
|
||||
if w, _, err := terminal.GetSize(fd); err == nil {
|
||||
ttyWidth = w
|
||||
}
|
||||
}
|
||||
return &TTYTablePrinter{
|
||||
out: w,
|
||||
IsTTY: tty,
|
||||
maxWidth: ttyWidth,
|
||||
colWidths: []int{},
|
||||
colFuncs: make(map[int]func(string) string),
|
||||
}
|
||||
}
|
||||
|
||||
type TTYTablePrinter struct {
|
||||
out io.Writer
|
||||
IsTTY bool
|
||||
maxWidth int
|
||||
colWidths []int
|
||||
colFuncs map[int]func(string) string
|
||||
}
|
||||
|
||||
func (t *TTYTablePrinter) SetContentWidth(col, width int) {
|
||||
if col == len(t.colWidths) {
|
||||
t.colWidths = append(t.colWidths, 0)
|
||||
}
|
||||
if width > t.colWidths[col] {
|
||||
t.colWidths[col] = width
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TTYTablePrinter) SetColorFunc(col int, colorize func(string) string) {
|
||||
t.colFuncs[col] = colorize
|
||||
}
|
||||
|
||||
// FitColumns caps all but first column to fit available terminal width.
|
||||
func (t *TTYTablePrinter) FitColumns() {
|
||||
numCols := len(t.colWidths)
|
||||
delimWidth := 2
|
||||
availWidth := t.maxWidth - t.colWidths[0] - ((numCols - 1) * delimWidth)
|
||||
// TODO: avoid widening columns that already fit
|
||||
// TODO: support weighted instead of even redistribution
|
||||
for col := 1; col < len(t.colWidths); col++ {
|
||||
t.colWidths[col] = availWidth / (numCols - 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TTYTablePrinter) WriteRow(fields ...string) error {
|
||||
lastCol := len(fields) - 1
|
||||
delim := "\t"
|
||||
if t.IsTTY {
|
||||
delim = " "
|
||||
}
|
||||
|
||||
for col, val := range fields {
|
||||
if col > 0 {
|
||||
_, err := fmt.Fprint(t.out, delim)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if t.IsTTY {
|
||||
truncVal := truncate(t.colWidths[col], val)
|
||||
if col != lastCol {
|
||||
truncVal = fmt.Sprintf("%-*s", t.colWidths[col], truncVal)
|
||||
}
|
||||
if t.colFuncs[col] != nil {
|
||||
truncVal = t.colFuncs[col](truncVal)
|
||||
}
|
||||
_, err := fmt.Fprint(t.out, truncVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := fmt.Fprint(t.out, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err := fmt.Fprint(t.out, "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func truncate(maxLength int, title string) string {
|
||||
if len(title) > maxLength {
|
||||
return title[0:maxLength-3] + "..."
|
||||
}
|
||||
return title
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue