diff --git a/go.mod b/go.mod index 252405f72..c26451600 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/lipgloss v0.5.0 - github.com/cli/go-gh/v2 v2.3.0 + github.com/cli/go-gh/v2 v2.4.1-0.20231019124728-ec1e1cd3e0cb github.com/cli/oauth v1.0.1 github.com/cli/safeexec v1.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.3 @@ -51,8 +51,8 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/cli/browser v1.2.0 // indirect - github.com/cli/shurcooL-graphql v0.0.3 // indirect + github.com/cli/browser v1.3.0 // indirect + github.com/cli/shurcooL-graphql v0.0.4 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect diff --git a/go.sum b/go.sum index 00876d819..4e177e4d7 100644 --- a/go.sum +++ b/go.sum @@ -21,17 +21,17 @@ github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8o github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= -github.com/cli/browser v1.2.0 h1:yvU7e9qf97kZqGFX6n2zJPHsmSObY9ske+iCvKelvXg= -github.com/cli/browser v1.2.0/go.mod h1:xFFnXLVcAyW9ni0cuo6NnrbCP75JxJ0RO7VtCBiH/oI= -github.com/cli/go-gh/v2 v2.3.0 h1:FAQAP4PaWSAJf4VSxFEIYDQ1oBIs+bKB4GXQAiRr2sQ= -github.com/cli/go-gh/v2 v2.3.0/go.mod h1:6WBUuf7LUVAc+eXYYX/nYTYURRc6M03K9cJNwBKvwT0= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/cli/go-gh/v2 v2.4.1-0.20231019124728-ec1e1cd3e0cb h1:HeIpiv5Jf09GQA0AyABbjC7Zq55eyIxpv4/BU6ujHRk= +github.com/cli/go-gh/v2 v2.4.1-0.20231019124728-ec1e1cd3e0cb/go.mod h1:h3salfqqooVpzKmHp6aUdeNx62UmxQRpLbagFSHTJGQ= github.com/cli/oauth v1.0.1 h1:pXnTFl/qUegXHK531Dv0LNjW4mLx626eS42gnzfXJPA= github.com/cli/oauth v1.0.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= -github.com/cli/shurcooL-graphql v0.0.3 h1:CtpPxyGDs136/+ZeyAfUKYmcQBjDlq5aqnrDCW5Ghh8= -github.com/cli/shurcooL-graphql v0.0.3/go.mod h1:tlrLmw/n5Q/+4qSvosT+9/W5zc8ZMjnJeYBxSdb4nWA= +github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= +github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -183,7 +183,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= @@ -199,7 +198,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/tableprinter/table_printer.go b/internal/tableprinter/table_printer.go index 1cd7c7d6e..ed734cbad 100644 --- a/internal/tableprinter/table_printer.go +++ b/internal/tableprinter/table_printer.go @@ -1,6 +1,7 @@ package tableprinter import ( + "io" "strings" "time" @@ -12,24 +13,22 @@ import ( type TablePrinter struct { tableprinter.TablePrinter isTTY bool + cs *iostreams.ColorScheme } -func (t *TablePrinter) HeaderRow(columns ...string) { - if !t.isTTY { - return - } - for _, col := range columns { - t.AddField(strings.ToUpper(col)) - } - t.EndRow() +// IsTTY gets wether the TablePrinter will render to a terminal. +func (t *TablePrinter) IsTTY() bool { + return t.isTTY } -// In tty mode display the fuzzy time difference between now and t. -// In nontty mode just display t with the time.RFC3339 format. +// AddTimeField in TTY mode displays the fuzzy time difference between now and t. +// In non-TTY mode it just displays t with the time.RFC3339 format. func (tp *TablePrinter) AddTimeField(now, t time.Time, c func(string) string) { - tf := t.Format(time.RFC3339) + var tf string if tp.isTTY { tf = text.FuzzyAgo(now, t) + } else { + tf = t.Format(time.RFC3339) } tp.AddField(tf, tableprinter.WithColor(c)) } @@ -39,15 +38,63 @@ var ( WithColor = tableprinter.WithColor ) -func New(ios *iostreams.IOStreams) *TablePrinter { +type headerOption struct { + columns []string +} + +// New creates a TablePrinter from an IOStreams. +func New(ios *iostreams.IOStreams, headers headerOption) *TablePrinter { maxWidth := 80 isTTY := ios.IsStdoutTTY() if isTTY { maxWidth = ios.TerminalWidth() } - tp := tableprinter.New(ios.Out, isTTY, maxWidth) - return &TablePrinter{ - TablePrinter: tp, - isTTY: isTTY, - } + + return NewWithWriter(ios.Out, isTTY, maxWidth, ios.ColorScheme(), headers) } + +// NewWithWriter creates a TablePrinter from a Writer, whether the output is a terminal, the terminal width, and more. +func NewWithWriter(w io.Writer, isTTY bool, maxWidth int, cs *iostreams.ColorScheme, headers headerOption) *TablePrinter { + tp := &TablePrinter{ + TablePrinter: tableprinter.New(w, isTTY, maxWidth), + isTTY: isTTY, + cs: cs, + } + + if isTTY && len(headers.columns) > 0 { + // Make sure all headers are uppercase. + for i := range headers.columns { + headers.columns[i] = strings.ToUpper(headers.columns[i]) + } + + // Make sure all header columns are padded - even the last one. Previously, the last header column + // was not padded. In tests cs.Enabled() is false which allows us to avoid having to fix up + // numerous tests that verify header padding. + var paddingFunc func(int, string) string + if cs.Enabled() { + paddingFunc = text.PadRight + } + + tp.AddHeader( + headers.columns, + tableprinter.WithPadding(paddingFunc), + tableprinter.WithColor(cs.LightGrayUnderline), + ) + } + + return tp +} + +// WithHeader defines the column names for a table. +// Panics if columns is nil or empty. +func WithHeader(columns ...string) headerOption { + if len(columns) == 0 { + panic("must define header columns") + } + return headerOption{columns} +} + +// NoHeader disable printing or checking for a table header. +// +// Deprecated: use WithHeader unless required otherwise. +var NoHeader = headerOption{} diff --git a/internal/text/text.go b/internal/text/text.go index 6eb541584..9ecfad93b 100644 --- a/internal/text/text.go +++ b/internal/text/text.go @@ -77,3 +77,7 @@ func DisplayURL(urlStr string) string { func RemoveDiacritics(value string) string { return text.RemoveDiacritics(value) } + +func PadRight(maxWidth int, s string) string { + return text.PadRight(maxWidth, s) +} diff --git a/pkg/cmd/cache/list/list.go b/pkg/cmd/cache/list/list.go index 85ab68f6b..ab5d662b5 100644 --- a/pkg/cmd/cache/list/list.go +++ b/pkg/cmd/cache/list/list.go @@ -115,8 +115,7 @@ func listRun(opts *ListOptions) error { opts.Now = time.Now() } - tp := tableprinter.New(opts.IO) - tp.HeaderRow("ID", "KEY", "SIZE", "CREATED", "ACCESSED") + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "KEY", "SIZE", "CREATED", "ACCESSED")) for _, cache := range result.ActionsCaches { tp.AddField(opts.IO.ColorScheme().Cyan(fmt.Sprintf("%d", cache.Id))) tp.AddField(cache.Key) diff --git a/pkg/cmd/codespace/list.go b/pkg/cmd/codespace/list.go index 4775e3db5..086334e29 100644 --- a/pkg/cmd/codespace/list.go +++ b/pkg/cmd/codespace/list.go @@ -7,9 +7,9 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/codespaces/api" + "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/utils" "github.com/spf13/cobra" ) @@ -116,25 +116,23 @@ func (a *App) List(ctx context.Context, opts *listOptions, exporter cmdutil.Expo return a.browser.Browse(fmt.Sprintf("%s/codespaces?repository_id=%d", a.apiClient.ServerURL(), codespaces[0].Repository.ID)) } - //nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter - tp := utils.NewTablePrinter(a.io) - if tp.IsTTY() { - tp.AddField("NAME", nil, nil) - tp.AddField("DISPLAY NAME", nil, nil) - if opts.orgName != "" { - tp.AddField("OWNER", nil, nil) - } - tp.AddField("REPOSITORY", nil, nil) - tp.AddField("BRANCH", nil, nil) - tp.AddField("STATE", nil, nil) - tp.AddField("CREATED AT", nil, nil) - - if hasNonProdVSCSTarget { - tp.AddField("VSCS TARGET", nil, nil) - } - - tp.EndRow() + headers := []string{ + "NAME", + "DISPLAY NAME", } + if opts.orgName != "" { + headers = append(headers, "OWNER") + } + headers = append(headers, + "REPOSITORY", + "BRANCH", + "STATE", + "CREATED AT", + ) + if hasNonProdVSCSTarget { + headers = append(headers, "VSCS TARGET") + } + tp := tableprinter.New(a.io, tableprinter.WithHeader(headers...)) cs := a.io.ColorScheme() for _, apiCodespace := range codespaces { @@ -158,17 +156,17 @@ func (a *App) List(ctx context.Context, opts *listOptions, exporter cmdutil.Expo nameColor = cs.Gray } - tp.AddField(formattedName, nil, nameColor) - tp.AddField(c.DisplayName, nil, nil) + tp.AddField(formattedName, tableprinter.WithColor(nameColor)) + tp.AddField(c.DisplayName) if opts.orgName != "" { - tp.AddField(c.Owner.Login, nil, nil) + tp.AddField(c.Owner.Login) } - tp.AddField(c.Repository.FullName, nil, nil) - tp.AddField(c.branchWithGitStatus(), nil, cs.Cyan) + tp.AddField(c.Repository.FullName) + tp.AddField(c.branchWithGitStatus(), tableprinter.WithColor(cs.Cyan)) if c.PendingOperation { - tp.AddField(c.PendingOperationDisabledReason, nil, nameColor) + tp.AddField(c.PendingOperationDisabledReason, tableprinter.WithColor(nameColor)) } else { - tp.AddField(c.State, nil, stateColor) + tp.AddField(c.State, tableprinter.WithColor(stateColor)) } if tp.IsTTY() { @@ -176,13 +174,13 @@ func (a *App) List(ctx context.Context, opts *listOptions, exporter cmdutil.Expo if err != nil { return fmt.Errorf("error parsing date %q: %w", c.CreatedAt, err) } - tp.AddField(text.FuzzyAgoAbbr(time.Now(), ct), nil, cs.Gray) + tp.AddField(text.FuzzyAgoAbbr(time.Now(), ct), tableprinter.WithColor(cs.Gray)) } else { - tp.AddField(c.CreatedAt, nil, nil) + tp.AddField(c.CreatedAt) } if hasNonProdVSCSTarget { - tp.AddField(c.VSCSTarget, nil, nil) + tp.AddField(c.VSCSTarget) } tp.EndRow() diff --git a/pkg/cmd/codespace/ports.go b/pkg/cmd/codespace/ports.go index 38688cd3a..3903da6cb 100644 --- a/pkg/cmd/codespace/ports.go +++ b/pkg/cmd/codespace/ports.go @@ -107,15 +107,7 @@ func (a *App) ListPorts(ctx context.Context, selector *CodespaceSelector, export } cs := a.io.ColorScheme() - tp := tableprinter.New(a.io) - - if a.io.IsStdoutTTY() { - tp.AddField("LABEL") - tp.AddField("PORT") - tp.AddField("VISIBILITY") - tp.AddField("BROWSE URL") - tp.EndRow() - } + tp := tableprinter.New(a.io, tableprinter.WithHeader("LABEL", "PORT", "VISIBILITY", "BROWSE URL")) for _, port := range portInfos { // Convert the ACE to a friendly visibility string (private, org, public) diff --git a/pkg/cmd/codespace/view.go b/pkg/cmd/codespace/view.go index 7fe4ae313..e212f0820 100644 --- a/pkg/cmd/codespace/view.go +++ b/pkg/cmd/codespace/view.go @@ -73,7 +73,8 @@ func (a *App) ViewCodespace(ctx context.Context, opts *viewOptions) error { return opts.exporter.Write(a.io, selectedCodespace) } - tp := tableprinter.New(a.io) + //nolint:staticcheck // SA1019: Showing NAME|VALUE headers adds nothing to table. + tp := tableprinter.New(a.io, tableprinter.NoHeader) c := codespace{selectedCodespace} formattedName := formatNameForVSCSTarget(c.Name, c.VSCSTarget) diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index 4348e2cd3..c2ed18985 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -18,7 +18,6 @@ import ( "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/extensions" "github.com/cli/cli/v2/pkg/search" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -217,9 +216,7 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { return false } - tp := tableprinter.New(io) - tp.HeaderRow("", "REPO", "DESCRIPTION") - + tp := tableprinter.New(io, tableprinter.WithHeader("", "REPO", "DESCRIPTION")) for _, repo := range result.Items { if !strings.HasPrefix(repo.Name, "gh-") { continue @@ -270,8 +267,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) + t := tableprinter.New(io, tableprinter.WithHeader("NAME", "REPO", "VERSION")) for _, c := range cmds { // TODO consider a Repo() on Extension interface var repo string @@ -281,13 +277,13 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { } } - t.AddField(fmt.Sprintf("gh %s", c.Name()), nil, nil) - t.AddField(repo, nil, nil) + t.AddField(fmt.Sprintf("gh %s", c.Name())) + t.AddField(repo) version := displayExtensionVersion(c, c.CurrentVersion()) if c.IsPinned() { - t.AddField(version, nil, cs.Cyan) + t.AddField(version, tableprinter.WithColor(cs.Cyan)) } else { - t.AddField(version, nil, nil) + t.AddField(version) } t.EndRow() diff --git a/pkg/cmd/gist/list/list.go b/pkg/cmd/gist/list/list.go index c49f1fffd..ee9fb2539 100644 --- a/pkg/cmd/gist/list/list.go +++ b/pkg/cmd/gist/list/list.go @@ -7,11 +7,11 @@ import ( "time" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/tableprinter" "github.com/cli/cli/v2/internal/text" "github.com/cli/cli/v2/pkg/cmd/gist/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -94,9 +94,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) + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "DESCRIPTION", "FILES", "VISIBILITY", "UPDATED")) for _, gist := range gists { fileCount := len(gist.Files) @@ -118,16 +116,14 @@ func listRun(opts *ListOptions) error { } } - gistTime := gist.UpdatedAt.Format(time.RFC3339) - if tp.IsTTY() { - gistTime = text.FuzzyAgo(time.Now(), gist.UpdatedAt) - } - - tp.AddField(gist.ID, nil, nil) - tp.AddField(text.RemoveExcessiveWhitespace(description), nil, cs.Bold) - tp.AddField(text.Pluralize(fileCount, "file"), nil, nil) - tp.AddField(visibility, nil, visColor) - tp.AddField(gistTime, nil, cs.Gray) + tp.AddField(gist.ID) + tp.AddField( + text.RemoveExcessiveWhitespace(description), + tableprinter.WithColor(cs.Bold), + ) + tp.AddField(text.Pluralize(fileCount, "file")) + tp.AddField(visibility, tableprinter.WithColor(visColor)) + tp.AddTimeField(time.Now(), gist.UpdatedAt, cs.Gray) tp.EndRow() } diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go index 54bb52eb1..682ebe8dc 100644 --- a/pkg/cmd/gist/list/list_test.go +++ b/pkg/cmd/gist/list/list_test.go @@ -180,10 +180,11 @@ func Test_listRun(t *testing.T) { ) }, wantOut: heredoc.Doc(` - 1234567890 cool.txt 1 file public about 6 hours ago - 4567890123 1 file public about 6 hours ago - 2345678901 tea leaves thwart those who ... 2 files secret about 6 hours ago - 3456789012 short desc 11 files secret about 6 hours ago + ID DESCRIPTION FILES VISIBILITY UPDATED + 1234567890 cool.txt 1 file public about 6 hours ago + 4567890123 1 file public about 6 hours ago + 2345678901 tea leaves thwart those ... 2 files secret about 6 hours ago + 3456789012 short desc 11 files secret about 6 hours ago `), }, { @@ -214,8 +215,9 @@ func Test_listRun(t *testing.T) { ) }, wantOut: heredoc.Doc(` - 1234567890 cool.txt 1 file public about 6 hours ago - 4567890123 1 file public about 6 hours ago + ID DESCRIPTION FILES VISIBILITY UPDATED + 1234567890 cool.txt 1 file public about 6 hours ago + 4567890123 1 file public about 6 hours ago `), }, { @@ -261,8 +263,9 @@ func Test_listRun(t *testing.T) { ) }, wantOut: heredoc.Doc(` - 2345678901 tea leaves thwart those who ... 2 files secret about 6 hours ago - 3456789012 short desc 11 files secret about 6 hours ago + ID DESCRIPTION FILES VISIBILITY UPDATED + 2345678901 tea leaves thwart those ... 2 files secret about 6 hours ago + 3456789012 short desc 11 files secret about 6 hours ago `), }, { @@ -285,7 +288,10 @@ func Test_listRun(t *testing.T) { )), ) }, - wantOut: "1234567890 cool.txt 1 file public about 6 hours ago\n", + wantOut: heredoc.Doc(` + ID DESCRIPTION FILES VISIBILITY UPDATED + 1234567890 cool.txt 1 file public about 6 hours ago + `), }, { name: "nontty output", diff --git a/pkg/cmd/gpg-key/list/list.go b/pkg/cmd/gpg-key/list/list.go index 623cc63e6..1ecc88b85 100644 --- a/pkg/cmd/gpg-key/list/list.go +++ b/pkg/cmd/gpg-key/list/list.go @@ -7,10 +7,10 @@ import ( "time" "github.com/cli/cli/v2/internal/config" + "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" ) @@ -71,30 +71,20 @@ 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) + t := tableprinter.New(opts.IO, tableprinter.WithHeader("EMAIL", "KEY ID", "PUBLIC KEY", "ADDED", "EXPIRES")) cs := opts.IO.ColorScheme() now := time.Now() - if t.IsTTY() { - t.AddField("EMAIL", nil, nil) - t.AddField("KEY ID", nil, nil) - t.AddField("PUBLIC KEY", nil, nil) - t.AddField("ADDED", nil, nil) - t.AddField("EXPIRES", nil, nil) - t.EndRow() - } - for _, gpgKey := range gpgKeys { - t.AddField(gpgKey.Emails.String(), nil, nil) - t.AddField(gpgKey.KeyID, nil, nil) - t.AddField(gpgKey.PublicKey, truncateMiddle, nil) + t.AddField(gpgKey.Emails.String()) + t.AddField(gpgKey.KeyID) + t.AddField(gpgKey.PublicKey, tableprinter.WithTruncate(truncateMiddle)) createdAt := gpgKey.CreatedAt.Format(time.RFC3339) if t.IsTTY() { createdAt = text.FuzzyAgoAbbr(now, gpgKey.CreatedAt) } - t.AddField(createdAt, nil, cs.Gray) + t.AddField(createdAt, tableprinter.WithColor(cs.Gray)) expiresAt := gpgKey.ExpiresAt.Format(time.RFC3339) if t.IsTTY() { @@ -104,7 +94,7 @@ func listRun(opts *ListOptions) error { expiresAt = gpgKey.ExpiresAt.Format("2006-01-02") } } - t.AddField(expiresAt, nil, cs.Gray) + t.AddField(expiresAt, tableprinter.WithColor(cs.Gray)) t.EndRow() } diff --git a/pkg/cmd/issue/develop/develop.go b/pkg/cmd/issue/develop/develop.go index 1a100c9a1..20149c6e3 100644 --- a/pkg/cmd/issue/develop/develop.go +++ b/pkg/cmd/issue/develop/develop.go @@ -199,7 +199,7 @@ func developRunList(opts *DevelopOptions, apiClient *api.Client, issueRepo ghrep func printLinkedBranches(io *iostreams.IOStreams, branches []api.LinkedBranch) { cs := io.ColorScheme() - table := tableprinter.New(io) + table := tableprinter.New(io, tableprinter.WithHeader("BRANCH", "URL")) for _, branch := range branches { table.AddField(branch.BranchName, tableprinter.WithColor(cs.ColorFromString("cyan"))) table.AddField(branch.URL) diff --git a/pkg/cmd/issue/develop/develop_test.go b/pkg/cmd/issue/develop/develop_test.go index d4f8bce2d..b834237ab 100644 --- a/pkg/cmd/issue/develop/develop_test.go +++ b/pkg/cmd/issue/develop/develop_test.go @@ -6,6 +6,7 @@ import ( "net/http" "testing" + "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/context" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/ghrepo" @@ -245,7 +246,14 @@ func TestDevelopRun(t *testing.T) { assert.Equal(t, "REPO", inputs["name"]) })) }, - expectedOut: "\nShowing linked branches for OWNER/REPO#42\n\nfoo https://github.com/OWNER/REPO/tree/foo\nbar https://github.com/OWNER/OTHER-REPO/tree/bar\n", + expectedOut: heredoc.Doc(` + + Showing linked branches for OWNER/REPO#42 + + BRANCH URL + foo https://github.com/OWNER/REPO/tree/foo + bar https://github.com/OWNER/OTHER-REPO/tree/bar + `), }, { name: "list branches for an issue providing an issue url", diff --git a/pkg/cmd/issue/list/list_test.go b/pkg/cmd/issue/list/list_test.go index cf0624c87..e816500c2 100644 --- a/pkg/cmd/issue/list/list_test.go +++ b/pkg/cmd/issue/list/list_test.go @@ -104,9 +104,10 @@ func TestIssueList_tty(t *testing.T) { Showing 3 of 3 open issues in OWNER/REPO - #1 number won label about 1 day ago - #2 number too label about 1 month ago - #4 number fore label about 2 years ago + ID TITLE LABELS UPDATED + #1 number won label about 1 day ago + #2 number too label about 1 month ago + #4 number fore label about 2 years ago `), output.String()) assert.Equal(t, ``, output.Stderr()) } diff --git a/pkg/cmd/issue/shared/display.go b/pkg/cmd/issue/shared/display.go index efb8398ce..0c56ffd2c 100644 --- a/pkg/cmd/issue/shared/display.go +++ b/pkg/cmd/issue/shared/display.go @@ -7,33 +7,38 @@ import ( "time" "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/tableprinter" "github.com/cli/cli/v2/internal/text" prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/utils" ) 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) + isTTY := io.IsStdoutTTY() + headers := []string{"ID"} + if !isTTY { + headers = append(headers, "STATE") + } + headers = append(headers, + "TITLE", + "LABELS", + "UPDATED", + ) + table := tableprinter.New(io, tableprinter.WithHeader(headers...)) for _, issue := range issues { issueNum := strconv.Itoa(issue.Number) - if table.IsTTY() { + if isTTY { issueNum = "#" + issueNum } issueNum = prefix + issueNum - table.AddField(issueNum, nil, cs.ColorFromString(prShared.ColorForIssueState(issue))) - if !table.IsTTY() { - table.AddField(issue.State, nil, nil) - } - table.AddField(text.RemoveExcessiveWhitespace(issue.Title), nil, nil) - table.AddField(issueLabelList(&issue, cs, table.IsTTY()), nil, nil) - if table.IsTTY() { - table.AddField(text.FuzzyAgo(now, issue.UpdatedAt), nil, cs.Gray) - } else { - table.AddField(issue.UpdatedAt.String(), nil, nil) + table.AddField(issueNum, tableprinter.WithColor(cs.ColorFromString(prShared.ColorForIssueState(issue)))) + if !isTTY { + table.AddField(issue.State) } + table.AddField(text.RemoveExcessiveWhitespace(issue.Title)) + table.AddField(issueLabelList(&issue, cs, isTTY)) + table.AddTimeField(now, issue.UpdatedAt, cs.Gray) table.EndRow() } _ = table.Render() diff --git a/pkg/cmd/label/list.go b/pkg/cmd/label/list.go index 5ad056115..7b4c95ed1 100644 --- a/pkg/cmd/label/list.go +++ b/pkg/cmd/label/list.go @@ -7,10 +7,10 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/browser" "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" ) @@ -134,13 +134,12 @@ 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) + table := tableprinter.New(io, tableprinter.WithHeader("NAME", "DESCRIPTION", "COLOR")) for _, label := range labels { - table.AddField(label.Name, nil, cs.ColorFromRGB(label.Color)) - table.AddField(label.Description, text.Truncate, nil) - table.AddField("#"+label.Color, nil, nil) + table.AddField(label.Name, tableprinter.WithColor(cs.ColorFromRGB(label.Color))) + table.AddField(label.Description) + table.AddField("#" + label.Color) table.EndRow() } diff --git a/pkg/cmd/label/list_test.go b/pkg/cmd/label/list_test.go index 7e7a9a93d..37f458439 100644 --- a/pkg/cmd/label/list_test.go +++ b/pkg/cmd/label/list_test.go @@ -215,7 +215,14 @@ func TestListRun(t *testing.T) { ), ) }, - wantStdout: "\nShowing 2 of 2 labels in OWNER/REPO\n\nbug This is a bug label #d73a4a\ndocs This is a docs label #ffa8da\n", + wantStdout: heredoc.Doc(` + + Showing 2 of 2 labels in OWNER/REPO + + NAME DESCRIPTION COLOR + bug This is a bug label #d73a4a + docs This is a docs label #ffa8da + `), }, { name: "lists labels notty", @@ -343,6 +350,7 @@ func TestListRun(t *testing.T) { Showing 2 of 2 labels in OWNER/REPO + NAME DESCRIPTION COLOR bug This is a bug label #d73a4a docs This is a docs label #ffa8da `), diff --git a/pkg/cmd/org/list/list.go b/pkg/cmd/org/list/list.go index 54e5daf5a..888d76c74 100644 --- a/pkg/cmd/org/list/list.go +++ b/pkg/cmd/org/list/list.go @@ -6,7 +6,6 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/internal/config" - "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" @@ -85,16 +84,8 @@ func listRun(opts *ListOptions) error { fmt.Fprintf(opts.IO.Out, "\n%s\n\n", header) } - table := tableprinter.New(opts.IO) - for _, org := range listResult.Organizations { - table.AddField(org.Login) - table.EndRow() - } - - err = table.Render() - if err != nil { - return err + fmt.Fprintln(opts.IO.Out, org.Login) } return nil diff --git a/pkg/cmd/pr/checks/checks_test.go b/pkg/cmd/pr/checks/checks_test.go index dcab36117..cc21043ba 100644 --- a/pkg/cmd/pr/checks/checks_test.go +++ b/pkg/cmd/pr/checks/checks_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/browser" fd "github.com/cli/cli/v2/internal/featuredetection" @@ -167,7 +168,15 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/someFailing.json"), ) }, - wantOut: "Some checks were not successful\n0 cancelled, 1 failing, 1 successful, 0 skipped, and 1 pending checks\n\nX sad tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n* slow tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + Some checks were not successful + 0 cancelled, 1 failing, 1 successful, 0 skipped, and 1 pending checks + + NAME DESCRIPTION ELAPSED URL + X sad tests 1m26s sweet link + ✓ cool tests 1m26s sweet link + * slow tests 1m26s sweet link + `), wantErr: "SilentError", }, { @@ -179,7 +188,15 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/someCancelled.json"), ) }, - wantOut: "Some checks were cancelled\n1 cancelled, 0 failing, 2 successful, 0 skipped, and 0 pending checks\n\n✓ cool tests 1m26s sweet link\n- sad tests 1m26s sweet link\n✓ awesome tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + Some checks were cancelled + 1 cancelled, 0 failing, 2 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ cool tests 1m26s sweet link + - sad tests 1m26s sweet link + ✓ awesome tests 1m26s sweet link + `), wantErr: "", }, { @@ -191,7 +208,16 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/somePending.json"), ) }, - wantOut: "Some checks are still pending\n1 cancelled, 0 failing, 2 successful, 0 skipped, and 1 pending checks\n\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n* slow tests 1m26s sweet link\n- sad tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + Some checks are still pending + 1 cancelled, 0 failing, 2 successful, 0 skipped, and 1 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ cool tests 1m26s sweet link + ✓ rad tests 1m26s sweet link + * slow tests 1m26s sweet link + - sad tests 1m26s sweet link + `), wantErr: "PendingError", }, { @@ -203,7 +229,15 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/allPassing.json"), ) }, - wantOut: "All checks were successful\n0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + All checks were successful + 0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ awesome tests 1m26s sweet link + ✓ cool tests 1m26s sweet link + ✓ rad tests 1m26s sweet link + `), wantErr: "", }, { @@ -216,7 +250,22 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/allPassing.json"), ) }, - wantOut: "\x1b[?1049hAll checks were successful\n0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n\x1b[?1049lAll checks were successful\n0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n", + wantOut: heredoc.Docf(` + %[1]s[?1049hAll checks were successful + 0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ awesome tests 1m26s sweet link + ✓ cool tests 1m26s sweet link + ✓ rad tests 1m26s sweet link + %[1]s[?1049lAll checks were successful + 0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ awesome tests 1m26s sweet link + ✓ cool tests 1m26s sweet link + ✓ rad tests 1m26s sweet link + `, "\x1b"), wantErr: "", }, { @@ -230,7 +279,24 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/someFailing.json"), ) }, - wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing checks status every 0 seconds. Press Ctrl+C to quit.\n\nSome checks were not successful\n0 cancelled, 1 failing, 1 successful, 0 skipped, and 1 pending checks\n\nX sad tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n* slow tests 1m26s sweet link\n\x1b[?1049lSome checks were not successful\n0 cancelled, 1 failing, 1 successful, 0 skipped, and 1 pending checks\n\nX sad tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n* slow tests 1m26s sweet link\n", + wantOut: heredoc.Docf(` + %[1]s[?1049h%[1]s[0;0H%[1]s[JRefreshing checks status every 0 seconds. Press Ctrl+C to quit. + + Some checks were not successful + 0 cancelled, 1 failing, 1 successful, 0 skipped, and 1 pending checks + + NAME DESCRIPTION ELAPSED URL + X sad tests 1m26s sweet link + ✓ cool tests 1m26s sweet link + * slow tests 1m26s sweet link + %[1]s[?1049lSome checks were not successful + 0 cancelled, 1 failing, 1 successful, 0 skipped, and 1 pending checks + + NAME DESCRIPTION ELAPSED URL + X sad tests 1m26s sweet link + ✓ cool tests 1m26s sweet link + * slow tests 1m26s sweet link + `, "\x1b"), wantErr: "SilentError", }, { @@ -242,7 +308,15 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/withStatuses.json"), ) }, - wantOut: "Some checks were not successful\n0 cancelled, 1 failing, 2 successful, 0 skipped, and 0 pending checks\n\nX a status sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + Some checks were not successful + 0 cancelled, 1 failing, 2 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + X a status sweet link + ✓ cool tests 1m26s sweet link + ✓ rad tests 1m26s sweet link + `), wantErr: "SilentError", }, { @@ -320,7 +394,15 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/someSkipping.json"), ) }, - wantOut: "All checks were successful\n0 cancelled, 0 failing, 1 successful, 2 skipped, and 0 pending checks\n\n✓ cool tests 1m26s sweet link\n- rad tests 1m26s sweet link\n- skip tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + All checks were successful + 0 cancelled, 0 failing, 1 successful, 2 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ cool tests 1m26s sweet link + - rad tests 1m26s sweet link + - skip tests 1m26s sweet link + `), wantErr: "", }, { @@ -344,7 +426,13 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/onlyRequired.json"), ) }, - wantOut: "All checks were successful\n0 cancelled, 0 failing, 1 successful, 0 skipped, and 0 pending checks\n\n✓ cool tests 1m26s sweet link\n", + wantOut: heredoc.Doc(` + All checks were successful + 0 cancelled, 0 failing, 1 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ cool tests 1m26s sweet link + `), wantErr: "", }, { @@ -393,7 +481,15 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/withDescriptions.json"), ) }, - wantOut: "All checks were successful\n0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests awesome description 1m26s sweet link\n✓ cool tests cool description 1m26s sweet link\n✓ rad tests rad description 1m26s sweet link\n", + wantOut: heredoc.Doc(` + All checks were successful + 0 cancelled, 0 failing, 3 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ awesome tests awesome description 1m26s sweet link + ✓ cool tests cool description 1m26s sweet link + ✓ rad tests rad description 1m26s sweet link + `), wantErr: "", }, { @@ -416,7 +512,14 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/withEvents.json"), ) }, - wantOut: "All checks were successful\n0 cancelled, 0 failing, 2 successful, 0 skipped, and 0 pending checks\n\n✓ tests/cool tests (pull_request) cool description 1m26s sweet link\n✓ tests/cool tests (push) cool description 1m26s sweet link\n", + wantOut: heredoc.Doc(` + All checks were successful + 0 cancelled, 0 failing, 2 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ tests/cool tests (pull_request) cool description 1m26s sweet link + ✓ tests/cool tests (push) cool description 1m26s sweet link + `), wantErr: "", }, { @@ -429,7 +532,13 @@ func Test_checksRun(t *testing.T) { httpmock.FileResponse("./fixtures/withoutEvents.json"), ) }, - wantOut: "All checks were successful\n0 cancelled, 0 failing, 1 successful, 0 skipped, and 0 pending checks\n\n✓ tests/cool tests cool description 1m26s sweet link\n", + wantOut: heredoc.Doc(` + All checks were successful + 0 cancelled, 0 failing, 1 successful, 0 skipped, and 0 pending checks + + NAME DESCRIPTION ELAPSED URL + ✓ tests/cool tests cool description 1m26s sweet link + `), wantErr: "", }, { diff --git a/pkg/cmd/pr/checks/output.go b/pkg/cmd/pr/checks/output.go index 9efee29cf..5d2f080c6 100644 --- a/pkg/cmd/pr/checks/output.go +++ b/pkg/cmd/pr/checks/output.go @@ -92,7 +92,14 @@ func printSummary(io *iostreams.IOStreams, counts checkCounts) { } func printTable(io *iostreams.IOStreams, checks []check) error { - tp := tableprinter.New(io) + var headers []string + if io.IsStdoutTTY() { + headers = []string{"", "NAME", "DESCRIPTION", "ELAPSED", "URL"} + } else { + headers = []string{"NAME", "STATUS", "ELAPSED", "URL", "DESCRIPTION"} + } + + tp := tableprinter.New(io, tableprinter.WithHeader(headers...)) sort.Slice(checks, func(i, j int) bool { b0 := checks[i].Bucket diff --git a/pkg/cmd/pr/list/list.go b/pkg/cmd/pr/list/list.go index cea7c2b19..e900a5d7b 100644 --- a/pkg/cmd/pr/list/list.go +++ b/pkg/cmd/pr/list/list.go @@ -11,11 +11,11 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/browser" "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/cmd/pr/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -199,24 +199,35 @@ func listRun(opts *ListOptions) error { } cs := opts.IO.ColorScheme() - //nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter - table := utils.NewTablePrinter(opts.IO) + isTTY := opts.IO.IsStdoutTTY() + + headers := []string{ + "ID", + "TITLE", + "BRANCH", + } + if !isTTY { + headers = append(headers, "STATE") + } + headers = append(headers, "CREATED AT") + + table := tableprinter.New(opts.IO, tableprinter.WithHeader(headers...)) for _, pr := range listResult.PullRequests { prNum := strconv.Itoa(pr.Number) - if table.IsTTY() { + if isTTY { prNum = "#" + prNum } - table.AddField(prNum, nil, cs.ColorFromString(shared.ColorForPRState(pr))) - table.AddField(text.RemoveExcessiveWhitespace(pr.Title), nil, nil) - table.AddField(pr.HeadLabel(), nil, cs.Cyan) - if !table.IsTTY() { - table.AddField(prStateWithDraft(&pr), nil, nil) + table.AddField(prNum, tableprinter.WithColor(cs.ColorFromString(shared.ColorForPRState(pr)))) + table.AddField(text.RemoveExcessiveWhitespace(pr.Title)) + table.AddField(pr.HeadLabel(), tableprinter.WithColor(cs.Cyan)) + if !isTTY { + table.AddField(prStateWithDraft(&pr)) } - if table.IsTTY() { - table.AddField(text.FuzzyAgo(opts.Now(), pr.CreatedAt), nil, cs.Gray) + if isTTY { + table.AddField(text.FuzzyAgo(opts.Now(), pr.CreatedAt), tableprinter.WithColor(cs.Gray)) } else { - table.AddField(pr.CreatedAt.String(), nil, nil) + table.AddField(pr.CreatedAt.String()) } table.EndRow() } diff --git a/pkg/cmd/pr/list/list_test.go b/pkg/cmd/pr/list/list_test.go index bc661f104..d4a658a81 100644 --- a/pkg/cmd/pr/list/list_test.go +++ b/pkg/cmd/pr/list/list_test.go @@ -84,6 +84,7 @@ func TestPRList(t *testing.T) { Showing 3 of 3 open pull requests in OWNER/REPO + ID TITLE BRANCH CREATED AT #32 New feature feature about 3 hours ago #29 Fixed bad bug hubot:bug-fix about 1 month ago #28 Improve documentation docs about 2 years ago diff --git a/pkg/cmd/project/field-list/field_list.go b/pkg/cmd/project/field-list/field_list.go index 152d90fcd..3e36e2a86 100644 --- a/pkg/cmd/project/field-list/field_list.go +++ b/pkg/cmd/project/field-list/field_list.go @@ -52,7 +52,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C opts.number = int32(num) } - t := tableprinter.New(f.IOStreams) + t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Name", "Data type", "ID")) config := listConfig{ io: f.IOStreams, tp: t, @@ -109,8 +109,6 @@ func printResults(config listConfig, fields []queries.ProjectField, login string return cmdutil.NewNoResultsError(fmt.Sprintf("Project %d for owner %s has no fields", config.opts.number, login)) } - config.tp.HeaderRow("Name", "Data type", "ID") - for _, f := range fields { config.tp.AddField(f.Name()) config.tp.AddField(f.Type()) diff --git a/pkg/cmd/project/field-list/field_list_test.go b/pkg/cmd/project/field-list/field_list_test.go index c6c2f97c6..85e67451f 100644 --- a/pkg/cmd/project/field-list/field_list_test.go +++ b/pkg/cmd/project/field-list/field_list_test.go @@ -163,7 +163,8 @@ func TestRunList_User(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "monalisa", @@ -256,7 +257,8 @@ func TestRunList_Org(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "github", @@ -339,7 +341,8 @@ func TestRunList_Me(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "@me", @@ -406,7 +409,8 @@ func TestRunList_Empty(t *testing.T) { ios, _, _, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "@me", diff --git a/pkg/cmd/project/item-list/item_list.go b/pkg/cmd/project/item-list/item_list.go index 4acc95dcb..f2e9bf8a6 100644 --- a/pkg/cmd/project/item-list/item_list.go +++ b/pkg/cmd/project/item-list/item_list.go @@ -52,7 +52,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C opts.number = int32(num) } - t := tableprinter.New(f.IOStreams) + t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Type", "Title", "Number", "Repository", "ID")) config := listConfig{ io: f.IOStreams, tp: t, @@ -108,8 +108,6 @@ func printResults(config listConfig, items []queries.ProjectItem, login string) return cmdutil.NewNoResultsError(fmt.Sprintf("Project %d for owner %s has no items", config.opts.number, login)) } - config.tp.HeaderRow("Type", "Title", "Number", "Repository", "ID") - for _, i := range items { config.tp.AddField(i.Type()) config.tp.AddField(i.Title()) diff --git a/pkg/cmd/project/item-list/item_list_test.go b/pkg/cmd/project/item-list/item_list_test.go index 490473691..c36dfc589 100644 --- a/pkg/cmd/project/item-list/item_list_test.go +++ b/pkg/cmd/project/item-list/item_list_test.go @@ -178,7 +178,8 @@ func TestRunList_User(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "monalisa", @@ -285,7 +286,8 @@ func TestRunList_Org(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "github", @@ -382,7 +384,8 @@ func TestRunList_Me(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ number: 1, owner: "@me", diff --git a/pkg/cmd/project/list/list.go b/pkg/cmd/project/list/list.go index c92434522..d3168d7eb 100644 --- a/pkg/cmd/project/list/list.go +++ b/pkg/cmd/project/list/list.go @@ -52,7 +52,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C URLOpener := func(url string) error { return f.Browser.Browse(url) } - t := tableprinter.New(f.IOStreams) + t := tableprinter.New(f.IOStreams, tableprinter.WithHeader("Number", "Title", "State", "ID")) config := listConfig{ tp: t, client: client, @@ -157,8 +157,6 @@ func printResults(config listConfig, projects []queries.Project, owner string) e return cmdutil.NewNoResultsError(fmt.Sprintf("No projects found for %s", owner)) } - config.tp.HeaderRow("Number", "Title", "State", "ID") - for _, p := range projects { config.tp.AddField(strconv.Itoa(int(p.Number)), tableprinter.WithTruncate(nil)) config.tp.AddField(p.Title) diff --git a/pkg/cmd/project/list/list_test.go b/pkg/cmd/project/list/list_test.go index 492d224b0..bafdf8e93 100644 --- a/pkg/cmd/project/list/list_test.go +++ b/pkg/cmd/project/list/list_test.go @@ -154,7 +154,8 @@ func TestRunList(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "monalisa", }, @@ -225,7 +226,8 @@ func TestRunList_Me(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "@me", }, @@ -296,7 +298,8 @@ func TestRunListViewer(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{}, client: client, io: ios, @@ -374,7 +377,8 @@ func TestRunListOrg(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "github", }, @@ -428,7 +432,8 @@ func TestRunListEmpty(t *testing.T) { ios, _, _, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{}, client: client, io: ios, @@ -504,7 +509,8 @@ func TestRunListWithClosed(t *testing.T) { ios, _, stdout, _ := iostreams.Test() config := listConfig{ - tp: tableprinter.New(ios), + //nolint:staticcheck // SA1019: tableprinter.NoHeaders temporarily allowed in project tests. + tp: tableprinter.New(ios, tableprinter.NoHeader), opts: listOpts{ owner: "monalisa", closed: true, diff --git a/pkg/cmd/release/list/list.go b/pkg/cmd/release/list/list.go index f2258a933..e6efc6062 100644 --- a/pkg/cmd/release/list/list.go +++ b/pkg/cmd/release/list/list.go @@ -78,9 +78,8 @@ func listRun(opts *ListOptions) error { fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err) } - table := tableprinter.New(opts.IO) + table := tableprinter.New(opts.IO, tableprinter.WithHeader("Title", "Type", "Tag name", "Published")) iofmt := opts.IO.ColorScheme() - table.HeaderRow("Title", "Type", "Tag name", "Published") for _, rel := range releases { title := text.RemoveExcessiveWhitespace(rel.Name) if title == "" { diff --git a/pkg/cmd/release/view/view.go b/pkg/cmd/release/view/view.go index 1f6fb6a34..a32482e65 100644 --- a/pkg/cmd/release/view/view.go +++ b/pkg/cmd/release/view/view.go @@ -10,12 +10,12 @@ import ( "github.com/MakeNowJust/heredoc" "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/cmd/release/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/markdown" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -154,11 +154,11 @@ 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) + //nolint:staticcheck // SA1019: Showing NAME|SIZE headers adds nothing to table. + table := tableprinter.New(io, tableprinter.NoHeader) for _, a := range release.Assets { - table.AddField(a.Name, nil, nil) - table.AddField(humanFileSize(a.Size), nil, nil) + table.AddField(a.Name) + table.AddField(humanFileSize(a.Size)) table.EndRow() } err := table.Render() diff --git a/pkg/cmd/repo/deploy-key/list/list.go b/pkg/cmd/repo/deploy-key/list/list.go index 537599fe5..3df78212c 100644 --- a/pkg/cmd/repo/deploy-key/list/list.go +++ b/pkg/cmd/repo/deploy-key/list/list.go @@ -7,10 +7,10 @@ import ( "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" ) @@ -77,28 +77,28 @@ func listRun(opts *ListOptions) error { return opts.Exporter.Write(opts.IO, deployKeys) } - //nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter - t := utils.NewTablePrinter(opts.IO) + t := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "TITLE", "TYPE", "KEY", "CREATED AT")) cs := opts.IO.ColorScheme() now := time.Now() for _, deployKey := range deployKeys { sshID := strconv.Itoa(deployKey.ID) - t.AddField(sshID, nil, nil) - t.AddField(deployKey.Title, nil, nil) + t.AddField(sshID) + t.AddField(deployKey.Title) sshType := "read-only" if !deployKey.ReadOnly { sshType = "read-write" } - t.AddField(sshType, nil, nil) - t.AddField(deployKey.Key, truncateMiddle, nil) + t.AddField(sshType) + t.AddField(deployKey.Key, tableprinter.WithTruncate(truncateMiddle)) + // TODO: Modify AddTimeField, add AddAbbrTimeField, or something else. createdAt := deployKey.CreatedAt.Format(time.RFC3339) if t.IsTTY() { createdAt = text.FuzzyAgoAbbr(now, deployKey.CreatedAt) } - t.AddField(createdAt, nil, cs.Gray) + t.AddField(createdAt, tableprinter.WithColor(cs.Gray)) t.EndRow() } diff --git a/pkg/cmd/repo/deploy-key/list/list_test.go b/pkg/cmd/repo/deploy-key/list/list_test.go index 0cefd0eb2..4e5a57f11 100644 --- a/pkg/cmd/repo/deploy-key/list/list_test.go +++ b/pkg/cmd/repo/deploy-key/list/list_test.go @@ -48,6 +48,7 @@ func TestListRun(t *testing.T) { ) }, wantStdout: heredoc.Doc(` + ID TITLE TYPE KEY CREATED AT 1234 Mac read-only ssh-rsa AAAABbBB123 1d 5678 hubot@Windows read-write ssh-rsa EEEEEEEK247 1d `), diff --git a/pkg/cmd/repo/list/list.go b/pkg/cmd/repo/list/list.go index 07729b481..f91b847a7 100644 --- a/pkg/cmd/repo/list/list.go +++ b/pkg/cmd/repo/list/list.go @@ -12,10 +12,10 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/config" fd "github.com/cli/cli/v2/internal/featuredetection" + "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" ) type ListOptions struct { @@ -176,9 +176,9 @@ func listRun(opts *ListOptions) error { } cs := opts.IO.ColorScheme() - //nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter - tp := utils.NewTablePrinter(opts.IO) + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("NAME", "DESCRIPTION", "INFO", "UPDATED")) + totalMatchCount := len(listResult.Repositories) for _, repo := range listResult.Repositories { info := repoInfo(repo) infoColor := cs.Gray @@ -192,13 +192,13 @@ func listRun(opts *ListOptions) error { t = &repo.CreatedAt } - tp.AddField(repo.NameWithOwner, nil, cs.Bold) - tp.AddField(text.RemoveExcessiveWhitespace(repo.Description), nil, nil) - tp.AddField(info, nil, infoColor) + tp.AddField(repo.NameWithOwner, tableprinter.WithColor(cs.Bold)) + tp.AddField(text.RemoveExcessiveWhitespace(repo.Description)) + tp.AddField(info, tableprinter.WithColor(infoColor)) if tp.IsTTY() { - tp.AddField(text.FuzzyAgoAbbr(opts.Now(), *t), nil, cs.Gray) + tp.AddField(text.FuzzyAgoAbbr(opts.Now(), *t), tableprinter.WithColor(cs.Gray)) } else { - tp.AddField(t.Format(time.RFC3339), nil, nil) + tp.AddField(t.Format(time.RFC3339)) } tp.EndRow() } @@ -208,11 +208,15 @@ func listRun(opts *ListOptions) error { } if opts.IO.IsStdoutTTY() { hasFilters := filter.Visibility != "" || filter.Fork || filter.Source || filter.Language != "" || len(filter.Topic) > 0 - title := listHeader(listResult.Owner, len(listResult.Repositories), listResult.TotalCount, hasFilters) + title := listHeader(listResult.Owner, totalMatchCount, listResult.TotalCount, hasFilters) fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title) } - return tp.Render() + if totalMatchCount > 0 { + return tp.Render() + } + + return nil } func listHeader(owner string, matchCount, totalMatchCount int, hasFilters bool) string { diff --git a/pkg/cmd/repo/list/list_test.go b/pkg/cmd/repo/list/list_test.go index 3a02cbb22..55e4da83d 100644 --- a/pkg/cmd/repo/list/list_test.go +++ b/pkg/cmd/repo/list/list_test.go @@ -385,6 +385,7 @@ func TestRepoList_tty(t *testing.T) { Showing 3 of 3 repositories in @octocat + NAME DESCRIPTION INFO UPDATED octocat/hello-world My first repository public 8h octocat/cli GitHub CLI public, fork 8h octocat/testing private 7d diff --git a/pkg/cmd/ruleset/list/list.go b/pkg/cmd/ruleset/list/list.go index c88f2d1e0..a4b52073d 100644 --- a/pkg/cmd/ruleset/list/list.go +++ b/pkg/cmd/ruleset/list/list.go @@ -161,8 +161,7 @@ func listRun(opts *ListOptions) error { fmt.Fprintf(opts.IO.Out, "\nShowing %d of %d rulesets in %s\n\n", len(result.Rulesets), result.TotalCount, inMsg) } - tp := tableprinter.New(opts.IO) - tp.HeaderRow("ID", "NAME", "SOURCE", "STATUS", "RULES") + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "NAME", "SOURCE", "STATUS", "RULES")) for _, rs := range result.Rulesets { tp.AddField(strconv.Itoa(rs.DatabaseId), tableprinter.WithColor(cs.Cyan)) diff --git a/pkg/cmd/run/list/list.go b/pkg/cmd/run/list/list.go index 394af5dcb..39e14442a 100644 --- a/pkg/cmd/run/list/list.go +++ b/pkg/cmd/run/list/list.go @@ -7,12 +7,12 @@ import ( "github.com/cli/cli/v2/api" "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/cmd/run/shared" workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -138,41 +138,28 @@ 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) + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("STATUS", "TITLE", "WORKFLOW", "BRANCH", "EVENT", "ID", "ELAPSED", "AGE")) cs := opts.IO.ColorScheme() - if tp.IsTTY() { - tp.AddField("STATUS", nil, nil) - tp.AddField("TITLE", nil, nil) - tp.AddField("WORKFLOW", nil, nil) - tp.AddField("BRANCH", nil, nil) - tp.AddField("EVENT", nil, nil) - tp.AddField("ID", nil, nil) - tp.AddField("ELAPSED", nil, nil) - tp.AddField("AGE", nil, nil) - tp.EndRow() - } - for _, run := range runs { if tp.IsTTY() { symbol, symbolColor := shared.Symbol(cs, run.Status, run.Conclusion) - tp.AddField(symbol, nil, symbolColor) + tp.AddField(symbol, tableprinter.WithColor(symbolColor)) } else { - tp.AddField(string(run.Status), nil, nil) - tp.AddField(string(run.Conclusion), nil, nil) + tp.AddField(string(run.Status)) + tp.AddField(string(run.Conclusion)) } - tp.AddField(run.Title(), nil, cs.Bold) + tp.AddField(run.Title(), tableprinter.WithColor(cs.Bold)) - tp.AddField(run.WorkflowName(), nil, nil) - tp.AddField(run.HeadBranch, nil, cs.Bold) - tp.AddField(string(run.Event), nil, nil) - tp.AddField(fmt.Sprintf("%d", run.ID), nil, cs.Cyan) + tp.AddField(run.WorkflowName()) + tp.AddField(run.HeadBranch, tableprinter.WithColor(cs.Bold)) + tp.AddField(string(run.Event)) + tp.AddField(fmt.Sprintf("%d", run.ID), tableprinter.WithColor(cs.Cyan)) - tp.AddField(run.Duration(opts.now).String(), nil, nil) - tp.AddField(text.FuzzyAgoAbbr(time.Now(), run.StartedTime()), nil, nil) + tp.AddField(run.Duration(opts.now).String()) + tp.AddField(text.FuzzyAgoAbbr(time.Now(), run.StartedTime())) tp.EndRow() } diff --git a/pkg/cmd/search/commits/commits.go b/pkg/cmd/search/commits/commits.go index 7f74c51d8..698e933bb 100644 --- a/pkg/cmd/search/commits/commits.go +++ b/pkg/cmd/search/commits/commits.go @@ -155,8 +155,7 @@ func displayResults(io *iostreams.IOStreams, now time.Time, results search.Commi now = time.Now() } cs := io.ColorScheme() - tp := tableprinter.New(io) - tp.HeaderRow("Repo", "SHA", "Message", "Author", "Created") + tp := tableprinter.New(io, tableprinter.WithHeader("Repo", "SHA", "Message", "Author", "Created")) for _, commit := range results.Items { tp.AddField(commit.Repo.FullName) tp.AddField(commit.Sha) diff --git a/pkg/cmd/search/repos/repos.go b/pkg/cmd/search/repos/repos.go index 759b3d120..bf88edbe8 100644 --- a/pkg/cmd/search/repos/repos.go +++ b/pkg/cmd/search/repos/repos.go @@ -158,8 +158,7 @@ func displayResults(io *iostreams.IOStreams, now time.Time, results search.Repos now = time.Now() } cs := io.ColorScheme() - tp := tableprinter.New(io) - tp.HeaderRow("Name", "Description", "Visibility", "Updated") + tp := tableprinter.New(io, tableprinter.WithHeader("Name", "Description", "Visibility", "Updated")) for _, repo := range results.Items { tags := []string{visibilityLabel(repo)} if repo.IsFork { diff --git a/pkg/cmd/search/shared/shared.go b/pkg/cmd/search/shared/shared.go index 141ab5421..f0a346fc8 100644 --- a/pkg/cmd/search/shared/shared.go +++ b/pkg/cmd/search/shared/shared.go @@ -95,14 +95,16 @@ func displayIssueResults(io *iostreams.IOStreams, now time.Time, et EntityType, if now.IsZero() { now = time.Now() } - isTTY := io.IsStdoutTTY() - cs := io.ColorScheme() - tp := tableprinter.New(io) + + var headers []string if et == Both { - tp.HeaderRow("Kind", "Repo", "ID", "Title", "Labels", "Updated") + headers = []string{"Kind", "Repo", "ID", "Title", "Labels", "Updated"} } else { - tp.HeaderRow("Repo", "ID", "Title", "Labels", "Updated") + headers = []string{"Repo", "ID", "Title", "Labels", "Updated"} } + + cs := io.ColorScheme() + tp := tableprinter.New(io, tableprinter.WithHeader(headers...)) for _, issue := range results.Items { if et == Both { kind := "issue" @@ -115,7 +117,7 @@ func displayIssueResults(io *iostreams.IOStreams, now time.Time, et EntityType, name := comp[len(comp)-2:] tp.AddField(strings.Join(name, "/")) issueNum := strconv.Itoa(issue.Number) - if isTTY { + if tp.IsTTY() { issueNum = "#" + issueNum } if issue.IsPullRequest() { @@ -125,16 +127,16 @@ func displayIssueResults(io *iostreams.IOStreams, now time.Time, et EntityType, color := tableprinter.WithColor(cs.ColorFromString(colorForIssueState(issue.State(), issue.StateReason))) tp.AddField(issueNum, color) } - if !isTTY { + if !tp.IsTTY() { tp.AddField(issue.State()) } tp.AddField(text.RemoveExcessiveWhitespace(issue.Title)) - tp.AddField(listIssueLabels(&issue, cs, isTTY)) + tp.AddField(listIssueLabels(&issue, cs, tp.IsTTY())) tp.AddTimeField(now, issue.UpdatedAt, cs.Gray) tp.EndRow() } - if isTTY { + if tp.IsTTY() { var header string switch et { case Both: diff --git a/pkg/cmd/secret/list/list.go b/pkg/cmd/secret/list/list.go index dae097fce..53582412d 100644 --- a/pkg/cmd/secret/list/list.go +++ b/pkg/cmd/secret/list/list.go @@ -158,12 +158,14 @@ func listRun(opts *ListOptions) error { return opts.Exporter.Write(opts.IO, secrets) } - table := tableprinter.New(opts.IO) + var headers []string if secretEntity == shared.Organization || secretEntity == shared.User { - table.HeaderRow("Name", "Updated", "Visibility") + headers = []string{"Name", "Updated", "Visibility"} } else { - table.HeaderRow("Name", "Updated") + headers = []string{"Name", "Updated"} } + + table := tableprinter.New(opts.IO, tableprinter.WithHeader(headers...)) for _, secret := range secrets { table.AddField(secret.Name) table.AddTimeField(opts.Now(), secret.UpdatedAt, nil) diff --git a/pkg/cmd/ssh-key/list/list.go b/pkg/cmd/ssh-key/list/list.go index 12ee6d635..828fc0b31 100644 --- a/pkg/cmd/ssh-key/list/list.go +++ b/pkg/cmd/ssh-key/list/list.go @@ -10,11 +10,11 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/tableprinter" "github.com/cli/cli/v2/internal/text" "github.com/cli/cli/v2/pkg/cmd/ssh-key/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -79,36 +79,26 @@ 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) + t := tableprinter.New(opts.IO, tableprinter.WithHeader("TITLE", "ID", "KEY", "TYPE", "ADDED")) cs := opts.IO.ColorScheme() now := time.Now() - if t.IsTTY() { - t.AddField("TITLE", nil, nil) - t.AddField("ID", nil, nil) - t.AddField("KEY", nil, nil) - t.AddField("TYPE", nil, nil) - t.AddField("ADDED", nil, nil) - t.EndRow() - } - for _, sshKey := range sshKeys { id := strconv.Itoa(sshKey.ID) createdAt := sshKey.CreatedAt.Format(time.RFC3339) if t.IsTTY() { - t.AddField(sshKey.Title, nil, nil) - t.AddField(id, nil, nil) - t.AddField(sshKey.Key, truncateMiddle, nil) - t.AddField(sshKey.Type, nil, nil) - t.AddField(text.FuzzyAgoAbbr(now, sshKey.CreatedAt), nil, cs.Gray) + t.AddField(sshKey.Title) + t.AddField(id) + t.AddField(sshKey.Key, tableprinter.WithTruncate(truncateMiddle)) + t.AddField(sshKey.Type) + t.AddField(text.FuzzyAgoAbbr(now, sshKey.CreatedAt), tableprinter.WithColor(cs.Gray)) } else { - t.AddField(sshKey.Title, nil, nil) - t.AddField(sshKey.Key, nil, nil) - t.AddField(createdAt, nil, nil) - t.AddField(id, nil, nil) - t.AddField(sshKey.Type, nil, nil) + t.AddField(sshKey.Title) + t.AddField(sshKey.Key) + t.AddField(createdAt) + t.AddField(id) + t.AddField(sshKey.Type) } t.EndRow() diff --git a/pkg/cmd/status/status.go b/pkg/cmd/status/status.go index 4e6bd3781..0ed7dd3a7 100644 --- a/pkg/cmd/status/status.go +++ b/pkg/cmd/status/status.go @@ -15,11 +15,11 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/charmbracelet/lipgloss" "github.com/cli/cli/v2/api" + "github.com/cli/cli/v2/internal/tableprinter" "github.com/cli/cli/v2/pkg/cmd/factory" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/set" - "github.com/cli/cli/v2/utils" ghAPI "github.com/cli/go-gh/v2/pkg/api" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -677,32 +677,27 @@ 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, - Out: tableOut, - }) if len(items) == 0 { - tp.AddField("Nothing here ^_^", nil, nil) - tp.EndRow() + fmt.Fprintln(tableOut, "Nothing here ^_^") } else { + //nolint:staticcheck // SA1019: Headers distract from numerous nested tables. + tp := tableprinter.NewWithWriter(tableOut, opts.IO.IsStdoutTTY(), width, cs, tableprinter.NoHeader) for i, si := range items { if i == rowLimit { break } - tp.AddField(si.Identifier, nil, idStyle) + tp.AddField(si.Identifier, tableprinter.WithColor(idStyle), tableprinter.WithTruncate(nil)) if si.Reason != "" { - tp.AddField(si.Reason, nil, nil) + tp.AddField(si.Reason) } - tp.AddField(si.Preview(), nil, nil) + tp.AddField(si.Preview()) tp.EndRow() } - } - err := tp.Render() - if err != nil { - return "", err + err := tp.Render() + if err != nil { + return "", err + } } return tableOut.String(), nil diff --git a/pkg/cmd/variable/list/list.go b/pkg/cmd/variable/list/list.go index 7925b0f22..86dd99500 100644 --- a/pkg/cmd/variable/list/list.go +++ b/pkg/cmd/variable/list/list.go @@ -124,12 +124,14 @@ func listRun(opts *ListOptions) error { fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err) } - table := tableprinter.New(opts.IO) + var headers []string if variableEntity == shared.Organization { - table.HeaderRow("Name", "Value", "Updated", "Visibility") + headers = []string{"Name", "Value", "Updated", "Visibility"} } else { - table.HeaderRow("Name", "Value", "Updated") + headers = []string{"Name", "Value", "Updated"} } + + table := tableprinter.New(opts.IO, tableprinter.WithHeader(headers...)) for _, variable := range variables { table.AddField(variable.Name) table.AddField(variable.Value) diff --git a/pkg/cmd/workflow/list/list.go b/pkg/cmd/workflow/list/list.go index acfbe7d55..efedc731e 100644 --- a/pkg/cmd/workflow/list/list.go +++ b/pkg/cmd/workflow/list/list.go @@ -111,8 +111,7 @@ func listRun(opts *ListOptions) error { } cs := opts.IO.ColorScheme() - tp := tableprinter.New(opts.IO) - tp.HeaderRow("Name", "State", "ID") + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("Name", "State", "ID")) for _, workflow := range filteredWorkflows { tp.AddField(workflow.Name) diff --git a/pkg/cmd/workflow/view/view.go b/pkg/cmd/workflow/view/view.go index 23b148942..28e561766 100644 --- a/pkg/cmd/workflow/view/view.go +++ b/pkg/cmd/workflow/view/view.go @@ -11,13 +11,13 @@ import ( "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/browser" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/tableprinter" "github.com/cli/cli/v2/internal/text" runShared "github.com/cli/cli/v2/pkg/cmd/run/shared" "github.com/cli/cli/v2/pkg/cmd/workflow/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/cli/v2/pkg/markdown" - "github.com/cli/cli/v2/utils" "github.com/spf13/cobra" ) @@ -213,8 +213,6 @@ 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 filename := workflow.Base() @@ -228,26 +226,39 @@ func viewWorkflowInfo(opts *ViewOptions, client *api.Client, repo ghrepo.Interfa fmt.Fprintln(out, "Recent runs") } + headers := make([]string, 0, 8) + if opts.Raw { + headers = append(headers, "STATUS", "CONCLUSION") + } else { + headers = append(headers, "") + } + headers = append(headers, "TITLE", "WORKFLOW", "BRANCH", "EVENT") + if opts.Raw { + headers = append(headers, "ELAPSED") + } + headers = append(headers, "ID") + + tp := tableprinter.New(opts.IO, tableprinter.WithHeader(headers...)) for _, run := range wr.WorkflowRuns { if opts.Raw { - tp.AddField(string(run.Status), nil, nil) - tp.AddField(string(run.Conclusion), nil, nil) + tp.AddField(string(run.Status)) + tp.AddField(string(run.Conclusion)) } else { symbol, symbolColor := runShared.Symbol(cs, run.Status, run.Conclusion) - tp.AddField(symbol, nil, symbolColor) + tp.AddField(symbol, tableprinter.WithColor(symbolColor)) } - tp.AddField(run.Title(), nil, cs.Bold) + tp.AddField(run.Title(), tableprinter.WithColor(cs.Bold)) - tp.AddField(run.WorkflowName(), nil, nil) - tp.AddField(run.HeadBranch, nil, cs.Bold) - tp.AddField(string(run.Event), nil, nil) + tp.AddField(run.WorkflowName()) + tp.AddField(run.HeadBranch, tableprinter.WithColor(cs.Bold)) + tp.AddField(string(run.Event)) if opts.Raw { - tp.AddField(run.Duration(opts.now).String(), nil, nil) + tp.AddField(run.Duration(opts.now).String()) } - tp.AddField(fmt.Sprintf("%d", run.ID), nil, cs.Cyan) + tp.AddField(fmt.Sprintf("%d", run.ID), tableprinter.WithColor(cs.Cyan)) tp.EndRow() } diff --git a/pkg/cmd/workflow/view/view_test.go b/pkg/cmd/workflow/view/view_test.go index 9be264d92..c706da48f 100644 --- a/pkg/cmd/workflow/view/view_test.go +++ b/pkg/cmd/workflow/view/view_test.go @@ -176,10 +176,11 @@ func TestViewRun(t *testing.T) { Total runs 10 Recent runs - X cool commit a workflow trunk push 1 - * cool commit a workflow trunk push 2 - ✓ cool commit a workflow trunk push 3 - X cool commit a workflow trunk push 4 + TITLE WORKFLOW BRANCH EVENT ID + X cool commit a workflow trunk push 1 + * cool commit a workflow trunk push 2 + ✓ cool commit a workflow trunk push 3 + X cool commit a workflow trunk push 4 To see more runs for this workflow, try: gh run list --workflow flow.yml To see the YAML for this workflow, try: gh workflow view flow.yml --yaml diff --git a/pkg/iostreams/color.go b/pkg/iostreams/color.go index 6cb5a607a..804b9a275 100644 --- a/pkg/iostreams/color.go +++ b/pkg/iostreams/color.go @@ -9,16 +9,17 @@ import ( ) var ( - magenta = ansi.ColorFunc("magenta") - cyan = ansi.ColorFunc("cyan") - red = ansi.ColorFunc("red") - yellow = ansi.ColorFunc("yellow") - blue = ansi.ColorFunc("blue") - green = ansi.ColorFunc("green") - gray = ansi.ColorFunc("black+h") - bold = ansi.ColorFunc("default+b") - cyanBold = ansi.ColorFunc("cyan+b") - greenBold = ansi.ColorFunc("green+b") + magenta = ansi.ColorFunc("magenta") + cyan = ansi.ColorFunc("cyan") + red = ansi.ColorFunc("red") + yellow = ansi.ColorFunc("yellow") + blue = ansi.ColorFunc("blue") + green = ansi.ColorFunc("green") + gray = ansi.ColorFunc("black+h") + lightGrayUnderline = ansi.ColorFunc("white+du") + bold = ansi.ColorFunc("default+b") + cyanBold = ansi.ColorFunc("cyan+b") + greenBold = ansi.ColorFunc("green+b") gray256 = func(t string) string { return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t) @@ -39,6 +40,10 @@ type ColorScheme struct { hasTrueColor bool } +func (c *ColorScheme) Enabled() bool { + return c.enabled +} + func (c *ColorScheme) Bold(t string) string { if !c.enabled { return t @@ -104,6 +109,13 @@ func (c *ColorScheme) Grayf(t string, args ...interface{}) string { return c.Gray(fmt.Sprintf(t, args...)) } +func (c *ColorScheme) LightGrayUnderline(t string) string { + if !c.enabled { + return t + } + return lightGrayUnderline(t) +} + func (c *ColorScheme) Magenta(t string) string { if !c.enabled { return t diff --git a/utils/table_printer.go b/utils/table_printer.go deleted file mode 100644 index 1bf9a955b..000000000 --- a/utils/table_printer.go +++ /dev/null @@ -1,89 +0,0 @@ -package utils - -import ( - "io" - "strings" - - "github.com/cli/cli/v2/pkg/iostreams" - "github.com/cli/go-gh/v2/pkg/tableprinter" -) - -type TablePrinter interface { - IsTTY() bool - AddField(string, func(int, string) string, func(string) string) - EndRow() - Render() error -} - -type TablePrinterOptions struct { - IsTTY bool - MaxWidth int - 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 { - out = opts.Out - } else { - out = ios.Out - } - var maxWidth int - if opts.IsTTY { - if opts.MaxWidth > 0 { - maxWidth = opts.MaxWidth - } else { - maxWidth = ios.TerminalWidth() - } - } - tp := tableprinter.New(out, opts.IsTTY, maxWidth) - return &printer{ - tp: tp, - isTTY: opts.IsTTY, - } -} - -type printer struct { - tp tableprinter.TablePrinter - colIndex int - isTTY bool -} - -func (p printer) IsTTY() bool { - return p.isTTY -} - -func (p *printer) AddField(s string, truncateFunc func(int, string) string, colorFunc func(string) string) { - if truncateFunc == nil { - // Disallow ever truncating the 1st column 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)) - } - p.colIndex++ -} - -func (p *printer) EndRow() { - p.tp.EndRow() - p.colIndex = 0 -} - -func (p *printer) Render() error { - return p.tp.Render() -} - -func isURL(s string) bool { - return strings.HasPrefix(s, "https://") || strings.HasPrefix(s, "http://") -}