Ensure that commands print to a colorable output
If a command does `fmt.Print(...)` for output that contains ANSI color codes, this not safe on Windows. We have to ensure that we always use the `fmt.Fprint*` family of functions with a writer that was transformed using `utils.NewColorable()`.
This commit is contained in:
parent
4148cf76b9
commit
b6fa88337d
5 changed files with 69 additions and 43 deletions
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -94,12 +95,15 @@ func issueList(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
colorOut := colorableOut(cmd)
|
||||
|
||||
if len(issues) == 0 {
|
||||
printMessage("There are no open issues")
|
||||
printMessage(colorOut, "There are no open issues")
|
||||
return nil
|
||||
}
|
||||
|
||||
table := utils.NewTablePrinter(cmd.OutOrStdout())
|
||||
table := utils.NewTablePrinter(out)
|
||||
for _, issue := range issues {
|
||||
issueNum := strconv.Itoa(issue.Number)
|
||||
if table.IsTTY() {
|
||||
|
|
@ -141,30 +145,32 @@ func issueStatus(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
printHeader("Issues assigned to you")
|
||||
out := colorableOut(cmd)
|
||||
|
||||
printHeader(out, "Issues assigned to you")
|
||||
if issuePayload.Assigned != nil {
|
||||
printIssues(" ", issuePayload.Assigned...)
|
||||
printIssues(out, " ", issuePayload.Assigned...)
|
||||
} else {
|
||||
message := fmt.Sprintf(" There are no issues assgined to you")
|
||||
printMessage(message)
|
||||
printMessage(out, message)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Issues mentioning you")
|
||||
printHeader(out, "Issues mentioning you")
|
||||
if len(issuePayload.Mentioned) > 0 {
|
||||
printIssues(" ", issuePayload.Mentioned...)
|
||||
printIssues(out, " ", issuePayload.Mentioned...)
|
||||
} else {
|
||||
printMessage(" There are no issues mentioning you")
|
||||
printMessage(out, " There are no issues mentioning you")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Issues opened by you")
|
||||
printHeader(out, "Issues opened by you")
|
||||
if len(issuePayload.Authored) > 0 {
|
||||
printIssues(" ", issuePayload.Authored...)
|
||||
printIssues(out, " ", issuePayload.Authored...)
|
||||
} else {
|
||||
printMessage(" There are no issues opened by you")
|
||||
printMessage(out, " There are no issues opened by you")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -255,14 +261,14 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printIssues(prefix string, issues ...api.Issue) {
|
||||
func printIssues(w io.Writer, prefix string, issues ...api.Issue) {
|
||||
for _, issue := range issues {
|
||||
number := utils.Green("#" + strconv.Itoa(issue.Number))
|
||||
coloredLabels := labelList(issue)
|
||||
if coloredLabels != "" {
|
||||
coloredLabels = utils.Gray(fmt.Sprintf(" (%s)", coloredLabels))
|
||||
}
|
||||
fmt.Printf("%s%s %s%s\n", prefix, number, truncate(70, issue.Title), coloredLabels)
|
||||
fmt.Fprintf(w, "%s%s %s%s\n", prefix, number, truncate(70, issue.Title), coloredLabels)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
|
@ -78,30 +79,32 @@ func prStatus(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
printHeader("Current branch")
|
||||
out := colorableOut(cmd)
|
||||
|
||||
printHeader(out, "Current branch")
|
||||
if prPayload.CurrentPR != nil {
|
||||
printPrs(*prPayload.CurrentPR)
|
||||
printPrs(out, *prPayload.CurrentPR)
|
||||
} else {
|
||||
message := fmt.Sprintf(" There is no pull request associated with %s", utils.Cyan("["+currentBranch+"]"))
|
||||
printMessage(message)
|
||||
printMessage(out, message)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Created by you")
|
||||
printHeader(out, "Created by you")
|
||||
if len(prPayload.ViewerCreated) > 0 {
|
||||
printPrs(prPayload.ViewerCreated...)
|
||||
printPrs(out, prPayload.ViewerCreated...)
|
||||
} else {
|
||||
printMessage(" You have no open pull requests")
|
||||
printMessage(out, " You have no open pull requests")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
printHeader("Requesting a code review from you")
|
||||
printHeader(out, "Requesting a code review from you")
|
||||
if len(prPayload.ReviewRequested) > 0 {
|
||||
printPrs(prPayload.ReviewRequested...)
|
||||
printPrs(out, prPayload.ReviewRequested...)
|
||||
} else {
|
||||
printMessage(" You have no pull requests to review")
|
||||
printMessage(out, " You have no pull requests to review")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Fprintln(out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -330,15 +333,15 @@ func prCheckout(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printPrs(prs ...api.PullRequest) {
|
||||
func printPrs(w io.Writer, prs ...api.PullRequest) {
|
||||
for _, pr := range prs {
|
||||
prNumber := fmt.Sprintf("#%d", pr.Number)
|
||||
fmt.Printf(" %s %s %s", utils.Yellow(prNumber), truncate(50, pr.Title), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
fmt.Fprintf(w, " %s %s %s", utils.Yellow(prNumber), truncate(50, pr.Title), utils.Cyan("["+pr.HeadLabel()+"]"))
|
||||
|
||||
checks := pr.ChecksStatus()
|
||||
reviews := pr.ReviewStatus()
|
||||
if checks.Total > 0 || reviews.ChangesRequested || reviews.Approved {
|
||||
fmt.Printf("\n ")
|
||||
fmt.Fprintf(w, "\n ")
|
||||
}
|
||||
|
||||
if checks.Total > 0 {
|
||||
|
|
@ -354,27 +357,27 @@ func printPrs(prs ...api.PullRequest) {
|
|||
} else if checks.Passing == checks.Total {
|
||||
summary = utils.Green("Checks passing")
|
||||
}
|
||||
fmt.Printf(" - %s", summary)
|
||||
fmt.Fprintf(w, " - %s", summary)
|
||||
}
|
||||
|
||||
if reviews.ChangesRequested {
|
||||
fmt.Printf(" - %s", utils.Red("changes requested"))
|
||||
fmt.Fprintf(w, " - %s", utils.Red("changes requested"))
|
||||
} else if reviews.ReviewRequired {
|
||||
fmt.Printf(" - %s", utils.Yellow("review required"))
|
||||
fmt.Fprintf(w, " - %s", utils.Yellow("review required"))
|
||||
} else if reviews.Approved {
|
||||
fmt.Printf(" - %s", utils.Green("approved"))
|
||||
fmt.Fprintf(w, " - %s", utils.Green("approved"))
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func printHeader(s string) {
|
||||
fmt.Println(utils.Bold(s))
|
||||
func printHeader(w io.Writer, s string) {
|
||||
fmt.Fprintln(w, utils.Bold(s))
|
||||
}
|
||||
|
||||
func printMessage(s string) {
|
||||
fmt.Println(utils.Gray(s))
|
||||
func printMessage(w io.Writer, s string) {
|
||||
fmt.Fprintln(w, utils.Gray(s))
|
||||
}
|
||||
|
||||
func truncate(maxLength int, title string) string {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/github/gh-cli/api"
|
||||
"github.com/github/gh-cli/context"
|
||||
"github.com/github/gh-cli/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -93,3 +95,11 @@ var apiClientForContext = func(ctx context.Context) (*api.Client, error) {
|
|||
}
|
||||
return api.NewClient(opts...), nil
|
||||
}
|
||||
|
||||
func colorableOut(cmd *cobra.Command) io.Writer {
|
||||
out := cmd.OutOrStdout()
|
||||
if outFile, isFile := out.(*os.File); isFile {
|
||||
return utils.NewColorable(outFile)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/mgutz/ansi"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewColorable returns an output stream that handles ANSI color sequences on Windows
|
||||
func NewColorable(f *os.File) io.Writer {
|
||||
return colorable.NewColorable(f)
|
||||
}
|
||||
|
||||
func makeColorFunc(color string) func(string) string {
|
||||
return func(arg string) string {
|
||||
output := arg
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
|
@ -37,7 +36,7 @@ func NewTablePrinter(w io.Writer) TablePrinter {
|
|||
}
|
||||
}
|
||||
return &ttyTablePrinter{
|
||||
out: colorable.NewColorable(outFile),
|
||||
out: NewColorable(outFile),
|
||||
maxWidth: ttyWidth,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue