Migrate to tableprinter from go-gh (#6346)

This commit is contained in:
Mislav Marohnić 2022-10-17 15:15:39 +02:00 committed by GitHub
parent 4265b5b804
commit 3fe5026d39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 115 additions and 238 deletions

View file

@ -0,0 +1,52 @@
package tableprinter
import (
"strings"
"time"
"github.com/cli/cli/v2/internal/text"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/go-gh/pkg/tableprinter"
)
type TablePrinter struct {
tableprinter.TablePrinter
isTTY bool
}
func (t *TablePrinter) HeaderRow(columns ...string) {
if !t.isTTY {
return
}
for _, col := range columns {
t.AddField(strings.ToUpper(col))
}
t.EndRow()
}
func (tp *TablePrinter) AddTimeField(t time.Time, c func(string) string) {
tf := t.Format(time.RFC3339)
if tp.isTTY {
// TODO: use a static time.Now
tf = text.FuzzyAgo(time.Now(), t)
}
tp.AddField(tf, tableprinter.WithColor(c))
}
var (
WithTruncate = tableprinter.WithTruncate
WithColor = tableprinter.WithColor
)
func New(ios *iostreams.IOStreams) *TablePrinter {
maxWidth := 80
isTTY := ios.IsStdoutTTY()
if isTTY {
maxWidth = ios.TerminalWidth()
}
tp := tableprinter.New(ios.Out, isTTY, maxWidth)
return &TablePrinter{
TablePrinter: tp,
isTTY: isTTY,
}
}

View file

@ -54,6 +54,7 @@ func listRun(opts *ListOptions) error {
return cmdutil.NewNoResultsError("no aliases configured")
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
keys := []string{}
for alias := range aliasMap {

View file

@ -86,6 +86,7 @@ func (a *App) List(ctx context.Context, opts *listOptions, exporter cmdutil.Expo
return cmdutil.NewNoResultsError("no codespaces found")
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(a.io)
if tp.IsTTY() {
tp.AddField("NAME", nil, nil)

View file

@ -103,6 +103,7 @@ func (a *App) ListPorts(ctx context.Context, codespaceName string, exporter cmdu
}
cs := a.io.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(a.io)
if tp.IsTTY() {

View file

@ -50,6 +50,7 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
return cmdutil.NewNoResultsError("no installed extensions found")
}
cs := io.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
t := utils.NewTablePrinter(io)
for _, c := range cmds {
var repo string

View file

@ -95,6 +95,7 @@ func listRun(opts *ListOptions) error {
cs := opts.IO.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
for _, gist := range gists {

View file

@ -71,6 +71,7 @@ func listRun(opts *ListOptions) error {
return cmdutil.NewNoResultsError("no GPG keys present in the GitHub account")
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
t := utils.NewTablePrinter(opts.IO)
cs := opts.IO.ColorScheme()
now := time.Now()

View file

@ -15,6 +15,7 @@ import (
func PrintIssues(io *iostreams.IOStreams, now time.Time, prefix string, totalCount int, issues []api.Issue) {
cs := io.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
table := utils.NewTablePrinter(io)
for _, issue := range issues {
issueNum := strconv.Itoa(issue.Number)

View file

@ -134,6 +134,7 @@ func listRun(opts *listOptions) error {
func printLabels(io *iostreams.IOStreams, labels []label) error {
cs := io.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
table := utils.NewTablePrinter(io)
for _, label := range labels {

View file

@ -76,6 +76,7 @@ func printSummary(io *iostreams.IOStreams, counts checkCounts) {
}
func printTable(io *iostreams.IOStreams, checks []check) error {
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(io)
sort.Slice(checks, func(i, j int) bool {

View file

@ -198,6 +198,7 @@ func listRun(opts *ListOptions) error {
}
cs := opts.IO.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
table := utils.NewTablePrinter(opts.IO)
for _, pr := range listResult.PullRequests {
prNum := strconv.Itoa(pr.Number)

View file

@ -3,13 +3,12 @@ package list
import (
"fmt"
"net/http"
"time"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/tableprinter"
"github.com/cli/cli/v2/internal/text"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/utils"
"github.com/spf13/cobra"
)
@ -76,14 +75,15 @@ func listRun(opts *ListOptions) error {
fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
}
table := utils.NewTablePrinter(opts.IO)
table := tableprinter.New(opts.IO)
iofmt := opts.IO.ColorScheme()
table.HeaderRow("Title", "Type", "Tag name", "Published")
for _, rel := range releases {
title := text.RemoveExcessiveWhitespace(rel.Name)
if title == "" {
title = rel.TagName
}
table.AddField(title, nil, nil)
table.AddField(title)
badge := ""
var badgeColor func(string) string
@ -97,23 +97,15 @@ func listRun(opts *ListOptions) error {
badge = "Pre-release"
badgeColor = iofmt.Yellow
}
table.AddField(badge, nil, badgeColor)
table.AddField(badge, tableprinter.WithColor(badgeColor))
tagName := rel.TagName
if table.IsTTY() {
tagName = fmt.Sprintf("(%s)", tagName)
}
table.AddField(tagName, nil, nil)
table.AddField(rel.TagName, tableprinter.WithTruncate(nil))
pubDate := rel.PublishedAt
if rel.PublishedAt.IsZero() {
pubDate = rel.CreatedAt
}
publishedAt := pubDate.Format(time.RFC3339)
if table.IsTTY() {
publishedAt = text.FuzzyAgo(time.Now(), pubDate)
}
table.AddField(publishedAt, nil, iofmt.Gray)
table.AddTimeField(pubDate, iofmt.Gray)
table.EndRow()
}
err = table.Render()

View file

@ -103,10 +103,11 @@ func Test_listRun(t *testing.T) {
LimitResults: 30,
},
wantStdout: heredoc.Doc(`
v1.1.0 Draft (v1.1.0) about 1 day ago
The big 1.0 Latest (v1.0.0) about 1 day ago
1.0 release candidate Pre-release (v1.0.0-pre.2) about 1 day ago
New features (v0.9.2) about 1 day ago
TITLE TYPE TAG NAME PUBLISHED
v1.1.0 Draft v1.1.0 about 1 day ago
The big 1.0 Latest v1.0.0 about 1 day ago
1.0 release candidate Pre-release v1.0.0-pre.2 about 1 day ago
New features v0.9.2 about 1 day ago
`),
wantStderr: ``,
},

View file

@ -152,6 +152,7 @@ func renderReleaseTTY(io *iostreams.IOStreams, release *shared.Release) error {
if len(release.Assets) > 0 {
fmt.Fprintf(w, "%s\n", iofmt.Bold("Assets"))
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
table := utils.NewTablePrinter(io)
for _, a := range release.Assets {
table.AddField(a.Name, nil, nil)

View file

@ -64,6 +64,7 @@ func listRun(opts *ListOptions) error {
return cmdutil.NewNoResultsError(fmt.Sprintf("no deploy keys found in %s", ghrepo.FullName(repo)))
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
t := utils.NewTablePrinter(opts.IO)
cs := opts.IO.ColorScheme()
now := time.Now()

View file

@ -164,6 +164,7 @@ func listRun(opts *ListOptions) error {
}
cs := opts.IO.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
for _, repo := range listResult.Repositories {

View file

@ -119,6 +119,7 @@ func listRun(opts *ListOptions) error {
return opts.Exporter.Write(opts.IO, runs)
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
cs := opts.IO.ColorScheme()

View file

@ -158,6 +158,7 @@ func displayResults(io *iostreams.IOStreams, now time.Time, results search.Repos
now = time.Now()
}
cs := io.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(io)
for _, repo := range results.Items {
tags := []string{visibilityLabel(repo)}

View file

@ -96,6 +96,7 @@ func displayIssueResults(io *iostreams.IOStreams, now time.Time, et EntityType,
now = time.Now()
}
cs := io.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(io)
for _, issue := range results.Items {
if et == Both {

View file

@ -146,6 +146,7 @@ func listRun(opts *ListOptions) error {
fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
for _, secret := range secrets {
tp.AddField(secret.Name, nil, nil)

View file

@ -64,6 +64,7 @@ func listRun(opts *ListOptions) error {
return cmdutil.NewNoResultsError("no SSH keys present in the GitHub account")
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
t := utils.NewTablePrinter(opts.IO)
cs := opts.IO.ColorScheme()
now := time.Now()

View file

@ -664,6 +664,7 @@ func statusRun(opts *StatusOptions) error {
section := func(header string, items []StatusItem, width, rowLimit int) (string, error) {
tableOut := &bytes.Buffer{}
fmt.Fprintln(tableOut, cs.Bold(header))
//nolint:staticcheck // SA1019: utils.NewTablePrinterWithOptions is deprecated: use internal/tableprinter
tp := utils.NewTablePrinterWithOptions(opts.IO, utils.TablePrinterOptions{
IsTTY: opts.IO.IsStdoutTTY(),
MaxWidth: width,

View file

@ -92,6 +92,7 @@ func listRun(opts *ListOptions) error {
fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
}
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
cs := opts.IO.ColorScheme()

View file

@ -205,6 +205,7 @@ func viewWorkflowInfo(opts *ViewOptions, client *api.Client, repo ghrepo.Interfa
out := opts.IO.Out
cs := opts.IO.ColorScheme()
//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
tp := utils.NewTablePrinter(opts.IO)
// Header

View file

@ -1,13 +1,11 @@
package utils
import (
"fmt"
"io"
"sort"
"strings"
"github.com/cli/cli/v2/internal/text"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/go-gh/pkg/tableprinter"
)
type TablePrinter interface {
@ -23,12 +21,14 @@ type TablePrinterOptions struct {
Out io.Writer
}
// Deprecated: use internal/tableprinter
func NewTablePrinter(io *iostreams.IOStreams) TablePrinter {
return NewTablePrinterWithOptions(io, TablePrinterOptions{
IsTTY: io.IsStdoutTTY(),
})
}
// Deprecated: use internal/tableprinter
func NewTablePrinterWithOptions(ios *iostreams.IOStreams, opts TablePrinterOptions) TablePrinter {
var out io.Writer
if opts.Out != nil {
@ -36,8 +36,8 @@ func NewTablePrinterWithOptions(ios *iostreams.IOStreams, opts TablePrinterOptio
} else {
out = ios.Out
}
var maxWidth int
if opts.IsTTY {
var maxWidth int
if opts.MaxWidth > 0 {
maxWidth = opts.MaxWidth
} else if ios.IsStdoutTTY() {
@ -45,205 +45,47 @@ func NewTablePrinterWithOptions(ios *iostreams.IOStreams, opts TablePrinterOptio
} else {
maxWidth = ios.ProcessTerminalWidth()
}
return &ttyTablePrinter{
out: out,
maxWidth: maxWidth,
}
}
return &tsvTablePrinter{
out: out,
tp := tableprinter.New(out, opts.IsTTY, maxWidth)
return &printer{
tp: tp,
isTTY: opts.IsTTY,
}
}
type tableField struct {
Text string
TruncateFunc func(int, string) string
ColorFunc func(string) string
type printer struct {
tp tableprinter.TablePrinter
colIndex int
isTTY bool
}
func (f *tableField) DisplayWidth() int {
return text.DisplayWidth(f.Text)
func (p printer) IsTTY() bool {
return p.isTTY
}
type ttyTablePrinter struct {
out io.Writer
maxWidth int
rows [][]tableField
}
func (t ttyTablePrinter) IsTTY() bool {
return true
}
func (t *ttyTablePrinter) AddField(s string, truncateFunc func(int, string) string, colorFunc func(string) string) {
func (p *printer) AddField(s string, truncateFunc func(int, string) string, colorFunc func(string) string) {
if truncateFunc == nil {
truncateFunc = text.Truncate
}
if t.rows == nil {
t.rows = make([][]tableField, 1)
}
rowI := len(t.rows) - 1
field := tableField{
Text: s,
TruncateFunc: truncateFunc,
ColorFunc: colorFunc,
}
t.rows[rowI] = append(t.rows[rowI], field)
}
func (t *ttyTablePrinter) EndRow() {
t.rows = append(t.rows, []tableField{})
}
func (t *ttyTablePrinter) Render() error {
if len(t.rows) == 0 {
return nil
}
delim := " "
numCols := len(t.rows[0])
colWidths := t.calculateColumnWidths(len(delim))
for _, row := range t.rows {
for col, field := range row {
if col > 0 {
_, err := fmt.Fprint(t.out, delim)
if err != nil {
return err
}
}
truncVal := field.TruncateFunc(colWidths[col], field.Text)
if col < numCols-1 {
// pad value with spaces on the right
if padWidth := colWidths[col] - field.DisplayWidth(); padWidth > 0 {
truncVal += strings.Repeat(" ", padWidth)
}
}
if field.ColorFunc != nil {
truncVal = field.ColorFunc(truncVal)
}
_, err := fmt.Fprint(t.out, truncVal)
if err != nil {
return err
}
}
if len(row) > 0 {
_, err := fmt.Fprint(t.out, "\n")
if err != nil {
return err
}
// Disallow ever truncating the 1st colum or any URL value
if p.colIndex == 0 || isURL(s) {
p.tp.AddField(s, tableprinter.WithTruncate(nil), tableprinter.WithColor(colorFunc))
} else {
p.tp.AddField(s, tableprinter.WithColor(colorFunc))
}
} else {
p.tp.AddField(s, tableprinter.WithTruncate(truncateFunc), tableprinter.WithColor(colorFunc))
}
return nil
p.colIndex++
}
func (t *ttyTablePrinter) calculateColumnWidths(delimSize int) []int {
numCols := len(t.rows[0])
allColWidths := make([][]int, numCols)
for _, row := range t.rows {
for col, field := range row {
allColWidths[col] = append(allColWidths[col], field.DisplayWidth())
}
}
// calculate max & median content width per column
maxColWidths := make([]int, numCols)
// medianColWidth := make([]int, numCols)
for col := 0; col < numCols; col++ {
widths := allColWidths[col]
sort.Ints(widths)
maxColWidths[col] = widths[len(widths)-1]
// medianColWidth[col] = widths[(len(widths)+1)/2]
}
colWidths := make([]int, numCols)
// never truncate the first column
colWidths[0] = maxColWidths[0]
// never truncate the last column if it contains URLs
if strings.HasPrefix(t.rows[0][numCols-1].Text, "https://") {
colWidths[numCols-1] = maxColWidths[numCols-1]
}
availWidth := func() int {
setWidths := 0
for col := 0; col < numCols; col++ {
setWidths += colWidths[col]
}
return t.maxWidth - delimSize*(numCols-1) - setWidths
}
numFixedCols := func() int {
fixedCols := 0
for col := 0; col < numCols; col++ {
if colWidths[col] > 0 {
fixedCols++
}
}
return fixedCols
}
// set the widths of short columns
if w := availWidth(); w > 0 {
if numFlexColumns := numCols - numFixedCols(); numFlexColumns > 0 {
perColumn := w / numFlexColumns
for col := 0; col < numCols; col++ {
if max := maxColWidths[col]; max < perColumn {
colWidths[col] = max
}
}
}
}
firstFlexCol := -1
// truncate long columns to the remaining available width
if numFlexColumns := numCols - numFixedCols(); numFlexColumns > 0 {
perColumn := availWidth() / numFlexColumns
for col := 0; col < numCols; col++ {
if colWidths[col] == 0 {
if firstFlexCol == -1 {
firstFlexCol = col
}
if max := maxColWidths[col]; max < perColumn {
colWidths[col] = max
} else if perColumn > 0 {
colWidths[col] = perColumn
}
}
}
}
// add remainder to the first flex column
if w := availWidth(); w > 0 && firstFlexCol > -1 {
colWidths[firstFlexCol] += w
if max := maxColWidths[firstFlexCol]; max < colWidths[firstFlexCol] {
colWidths[firstFlexCol] = max
}
}
return colWidths
func (p *printer) EndRow() {
p.tp.EndRow()
p.colIndex = 0
}
type tsvTablePrinter struct {
out io.Writer
currentCol int
func (p *printer) Render() error {
return p.tp.Render()
}
func (t tsvTablePrinter) IsTTY() bool {
return false
}
func (t *tsvTablePrinter) AddField(text string, _ func(int, string) string, _ func(string) string) {
if t.currentCol > 0 {
fmt.Fprint(t.out, "\t")
}
fmt.Fprint(t.out, text)
t.currentCol++
}
func (t *tsvTablePrinter) EndRow() {
fmt.Fprint(t.out, "\n")
t.currentCol = 0
}
func (t *tsvTablePrinter) Render() error {
return nil
func isURL(s string) bool {
return strings.HasPrefix(s, "https://") || strings.HasPrefix(s, "http://")
}

View file

@ -1,31 +0,0 @@
package utils
import (
"bytes"
"testing"
)
func Test_ttyTablePrinter_truncate(t *testing.T) {
buf := bytes.Buffer{}
tp := &ttyTablePrinter{
out: &buf,
maxWidth: 5,
}
tp.AddField("1", nil, nil)
tp.AddField("hello", nil, nil)
tp.EndRow()
tp.AddField("2", nil, nil)
tp.AddField("world", nil, nil)
tp.EndRow()
err := tp.Render()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := "1 he\n2 wo\n"
if buf.String() != expected {
t.Errorf("expected: %q, got: %q", expected, buf.String())
}
}