From e46f4608ff55d56918903122104b66e28656f323 Mon Sep 17 00:00:00 2001 From: Antoine Niek Date: Fri, 14 Feb 2020 15:53:05 -0500 Subject: [PATCH 1/2] Allow force color --- command/issue.go | 60 +++++++++++++++++------------ command/pr.go | 89 ++++++++++++++++++++++++++------------------ command/pr_create.go | 10 ++++- command/root.go | 1 + utils/color.go | 53 ++++++++++++++------------ 5 files changed, 129 insertions(+), 84 deletions(-) diff --git a/command/issue.go b/command/issue.go index 6caf81c1200..c8a23970d1d 100644 --- a/command/issue.go +++ b/command/issue.go @@ -78,6 +78,11 @@ var issueViewCmd = &cobra.Command{ func issueList(cmd *cobra.Command, args []string) error { ctx := contextForCommand(cmd) + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } + apiClient, err := apiClientForContext(ctx) if err != nil { return err @@ -126,7 +131,7 @@ func issueList(cmd *cobra.Command, args []string) error { if userSetFlags { msg = "No issues match your search" } - printMessage(colorErr, msg) + printMessage(colorErr, palette, msg) return nil } @@ -141,9 +146,9 @@ func issueList(cmd *cobra.Command, args []string) error { if labels != "" && table.IsTTY() { labels = fmt.Sprintf("(%s)", labels) } - table.AddField(issueNum, nil, colorFuncForState(issue.State)) + table.AddField(issueNum, nil, colorFuncForState(issue.State, palette)) table.AddField(replaceExcessiveWhitespace(issue.Title), nil, nil) - table.AddField(labels, nil, utils.Gray) + table.AddField(labels, nil, palette.Gray) table.EndRow() } table.Render() @@ -153,6 +158,11 @@ func issueList(cmd *cobra.Command, args []string) error { func issueStatus(cmd *cobra.Command, args []string) error { ctx := contextForCommand(cmd) + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } + apiClient, err := apiClientForContext(ctx) if err != nil { return err @@ -179,28 +189,28 @@ func issueStatus(cmd *cobra.Command, args []string) error { fmt.Fprintf(out, "Relevant issues in %s\n", ghrepo.FullName(*baseRepo)) fmt.Fprintln(out, "") - printHeader(out, "Issues assigned to you") + printHeader(out, palette, "Issues assigned to you") if issuePayload.Assigned.TotalCount > 0 { - printIssues(out, " ", issuePayload.Assigned.TotalCount, issuePayload.Assigned.Issues) + printIssues(out, palette, " ", issuePayload.Assigned.TotalCount, issuePayload.Assigned.Issues) } else { message := fmt.Sprintf(" There are no issues assigned to you") - printMessage(out, message) + printMessage(out, palette, message) } fmt.Fprintln(out) - printHeader(out, "Issues mentioning you") + printHeader(out, palette, "Issues mentioning you") if issuePayload.Mentioned.TotalCount > 0 { - printIssues(out, " ", issuePayload.Mentioned.TotalCount, issuePayload.Mentioned.Issues) + printIssues(out, palette, " ", issuePayload.Mentioned.TotalCount, issuePayload.Mentioned.Issues) } else { - printMessage(out, " There are no issues mentioning you") + printMessage(out, palette, " There are no issues mentioning you") } fmt.Fprintln(out) - printHeader(out, "Issues opened by you") + printHeader(out, palette, "Issues opened by you") if issuePayload.Authored.TotalCount > 0 { - printIssues(out, " ", issuePayload.Authored.TotalCount, issuePayload.Authored.Issues) + printIssues(out, palette, " ", issuePayload.Authored.TotalCount, issuePayload.Authored.Issues) } else { - printMessage(out, " There are no issues opened by you") + printMessage(out, palette, " There are no issues opened by you") } fmt.Fprintln(out) @@ -209,6 +219,10 @@ func issueStatus(cmd *cobra.Command, args []string) error { func issueView(cmd *cobra.Command, args []string) error { ctx := contextForCommand(cmd) + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } apiClient, err := apiClientForContext(ctx) if err != nil { @@ -233,7 +247,7 @@ func issueView(cmd *cobra.Command, args []string) error { if preview { out := colorableOut(cmd) - printIssuePreview(out, issue) + printIssuePreview(out, palette, issue) return nil } else { fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", openURL) @@ -242,14 +256,14 @@ func issueView(cmd *cobra.Command, args []string) error { } -func printIssuePreview(out io.Writer, issue *api.Issue) { +func printIssuePreview(out io.Writer, palette *utils.Palette, issue *api.Issue) { coloredLabels := labelList(*issue) if coloredLabels != "" { - coloredLabels = utils.Gray(fmt.Sprintf("(%s)", coloredLabels)) + coloredLabels = palette.Gray(fmt.Sprintf("(%s)", coloredLabels)) } - fmt.Fprintln(out, utils.Bold(issue.Title)) - fmt.Fprintln(out, utils.Gray(fmt.Sprintf( + fmt.Fprintln(out, palette.Bold(issue.Title)) + fmt.Fprintln(out, palette.Gray(fmt.Sprintf( "opened by %s. %s. %s", issue.Author.Login, utils.Pluralize(issue.Comments.TotalCount, "comment"), @@ -260,7 +274,7 @@ func printIssuePreview(out io.Writer, issue *api.Issue) { fmt.Fprintln(out, utils.RenderMarkdown(issue.Body)) fmt.Fprintln(out) } - fmt.Fprintf(out, utils.Gray("View this issue on GitHub: %s\n"), issue.URL) + fmt.Fprintf(out, palette.Gray("View this issue on GitHub: %s\n"), issue.URL) } var issueURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/issues/(\d+)`) @@ -389,12 +403,12 @@ func issueCreate(cmd *cobra.Command, args []string) error { return nil } -func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) { +func printIssues(w io.Writer, palette *utils.Palette, prefix string, totalCount int, issues []api.Issue) { for _, issue := range issues { - number := utils.Green("#" + strconv.Itoa(issue.Number)) + number := palette.Green("#" + strconv.Itoa(issue.Number)) coloredLabels := labelList(issue) if coloredLabels != "" { - coloredLabels = utils.Gray(fmt.Sprintf(" (%s)", coloredLabels)) + coloredLabels = palette.Gray(fmt.Sprintf(" (%s)", coloredLabels)) } now := time.Now() @@ -403,11 +417,11 @@ func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) fmt.Fprintf(w, "%s%s %s%s %s\n", prefix, number, truncate(70, replaceExcessiveWhitespace(issue.Title)), coloredLabels, - utils.Gray(utils.FuzzyAgo(ago))) + palette.Gray(utils.FuzzyAgo(ago))) } remaining := totalCount - len(issues) if remaining > 0 { - fmt.Fprintf(w, utils.Gray("%sAnd %d more\n"), prefix, remaining) + fmt.Fprintf(w, palette.Gray("%sAnd %d more\n"), prefix, remaining) } } diff --git a/command/pr.go b/command/pr.go index d1cb7e5be0d..21038360c8c 100644 --- a/command/pr.go +++ b/command/pr.go @@ -65,6 +65,12 @@ branch is opened.`, func prStatus(cmd *cobra.Command, args []string) error { ctx := contextForCommand(cmd) + + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } + apiClient, err := apiClientForContext(ctx) if err != nil { return err @@ -95,28 +101,28 @@ func prStatus(cmd *cobra.Command, args []string) error { fmt.Fprintf(out, "Relevant pull requests in %s\n", ghrepo.FullName(*baseRepo)) fmt.Fprintln(out, "") - printHeader(out, "Current branch") + printHeader(out, palette, "Current branch") if prPayload.CurrentPR != nil { - printPrs(out, 0, *prPayload.CurrentPR) + printPrs(out, palette, 0, *prPayload.CurrentPR) } else { - message := fmt.Sprintf(" There is no pull request associated with %s", utils.Cyan("["+currentPRHeadRef+"]")) - printMessage(out, message) + message := fmt.Sprintf(" There is no pull request associated with %s", palette.Cyan("["+currentPRHeadRef+"]")) + printMessage(out, palette, message) } fmt.Fprintln(out) - printHeader(out, "Created by you") + printHeader(out, palette, "Created by you") if prPayload.ViewerCreated.TotalCount > 0 { - printPrs(out, prPayload.ViewerCreated.TotalCount, prPayload.ViewerCreated.PullRequests...) + printPrs(out, palette, prPayload.ViewerCreated.TotalCount, prPayload.ViewerCreated.PullRequests...) } else { - printMessage(out, " You have no open pull requests") + printMessage(out, palette, " You have no open pull requests") } fmt.Fprintln(out) - printHeader(out, "Requesting a code review from you") + printHeader(out, palette, "Requesting a code review from you") if prPayload.ReviewRequested.TotalCount > 0 { - printPrs(out, prPayload.ReviewRequested.TotalCount, prPayload.ReviewRequested.PullRequests...) + printPrs(out, palette, prPayload.ReviewRequested.TotalCount, prPayload.ReviewRequested.PullRequests...) } else { - printMessage(out, " You have no pull requests to review") + printMessage(out, palette, " You have no pull requests to review") } fmt.Fprintln(out) @@ -125,6 +131,12 @@ func prStatus(cmd *cobra.Command, args []string) error { func prList(cmd *cobra.Command, args []string) error { ctx := contextForCommand(cmd) + + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } + apiClient, err := apiClientForContext(ctx) if err != nil { return err @@ -203,7 +215,7 @@ func prList(cmd *cobra.Command, args []string) error { if userSetFlags { msg = "No pull requests match your search" } - printMessage(colorErr, msg) + printMessage(colorErr, palette, msg) return nil } @@ -213,9 +225,9 @@ func prList(cmd *cobra.Command, args []string) error { if table.IsTTY() { prNum = "#" + prNum } - table.AddField(prNum, nil, colorFuncForState(pr.State)) + table.AddField(prNum, nil, colorFuncForState(pr.State, palette)) table.AddField(replaceExcessiveWhitespace(pr.Title), nil, nil) - table.AddField(pr.HeadLabel(), nil, utils.Cyan) + table.AddField(pr.HeadLabel(), nil, palette.Cyan) table.EndRow() } err = table.Render() @@ -226,14 +238,14 @@ func prList(cmd *cobra.Command, args []string) error { return nil } -func colorFuncForState(state string) func(string) string { +func colorFuncForState(state string, palette *utils.Palette) func(string) string { switch state { case "OPEN": - return utils.Green + return palette.Green case "CLOSED": - return utils.Red + return palette.Red case "MERGED": - return utils.Magenta + return palette.Magenta default: return nil } @@ -242,6 +254,11 @@ func colorFuncForState(state string) func(string) string { func prView(cmd *cobra.Command, args []string) error { ctx := contextForCommand(cmd) + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } + apiClient, err := apiClientForContext(ctx) if err != nil { return err @@ -291,7 +308,7 @@ func prView(cmd *cobra.Command, args []string) error { if preview { out := colorableOut(cmd) - printPrPreview(out, pr) + printPrPreview(out, palette, pr) return nil } else { fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", openURL) @@ -299,9 +316,9 @@ func prView(cmd *cobra.Command, args []string) error { } } -func printPrPreview(out io.Writer, pr *api.PullRequest) { - fmt.Fprintln(out, utils.Bold(pr.Title)) - fmt.Fprintln(out, utils.Gray(fmt.Sprintf( +func printPrPreview(out io.Writer, palette *utils.Palette, pr *api.PullRequest) { + fmt.Fprintln(out, palette.Bold(pr.Title)) + fmt.Fprintln(out, palette.Gray(fmt.Sprintf( "%s wants to merge %s into %s from %s", pr.Author.Login, utils.Pluralize(pr.Commits.TotalCount, "commit"), @@ -313,7 +330,7 @@ func printPrPreview(out io.Writer, pr *api.PullRequest) { fmt.Fprintln(out, utils.RenderMarkdown(pr.Body)) fmt.Fprintln(out) } - fmt.Fprintf(out, utils.Gray("View this pull request on GitHub: %s\n"), pr.URL) + fmt.Fprintf(out, palette.Gray("View this pull request on GitHub: %s\n"), pr.URL) } var prURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/pull/(\d+)`) @@ -376,10 +393,10 @@ func prSelectorForCurrentBranch(ctx context.Context) (prNumber int, prHeadRef st return } -func printPrs(w io.Writer, totalCount int, prs ...api.PullRequest) { +func printPrs(w io.Writer, palette *utils.Palette, totalCount int, prs ...api.PullRequest) { for _, pr := range prs { prNumber := fmt.Sprintf("#%d", pr.Number) - fmt.Fprintf(w, " %s %s %s", utils.Green(prNumber), truncate(50, replaceExcessiveWhitespace(pr.Title)), utils.Cyan("["+pr.HeadLabel()+"]")) + fmt.Fprintf(w, " %s %s %s", palette.Green(prNumber), truncate(50, replaceExcessiveWhitespace(pr.Title)), palette.Cyan("["+pr.HeadLabel()+"]")) checks := pr.ChecksStatus() reviews := pr.ReviewStatus() @@ -391,40 +408,40 @@ func printPrs(w io.Writer, totalCount int, prs ...api.PullRequest) { var summary string if checks.Failing > 0 { if checks.Failing == checks.Total { - summary = utils.Red("All checks failing") + summary = palette.Red("All checks failing") } else { - summary = utils.Red(fmt.Sprintf("%d/%d checks failing", checks.Failing, checks.Total)) + summary = palette.Red(fmt.Sprintf("%d/%d checks failing", checks.Failing, checks.Total)) } } else if checks.Pending > 0 { - summary = utils.Yellow("Checks pending") + summary = palette.Yellow("Checks pending") } else if checks.Passing == checks.Total { - summary = utils.Green("Checks passing") + summary = palette.Green("Checks passing") } fmt.Fprintf(w, " - %s", summary) } if reviews.ChangesRequested { - fmt.Fprintf(w, " - %s", utils.Red("Changes requested")) + fmt.Fprintf(w, " - %s", palette.Red("Changes requested")) } else if reviews.ReviewRequired { - fmt.Fprintf(w, " - %s", utils.Yellow("Review required")) + fmt.Fprintf(w, " - %s", palette.Yellow("Review required")) } else if reviews.Approved { - fmt.Fprintf(w, " - %s", utils.Green("Approved")) + fmt.Fprintf(w, " - %s", palette.Green("Approved")) } fmt.Fprint(w, "\n") } remaining := totalCount - len(prs) if remaining > 0 { - fmt.Fprintf(w, utils.Gray(" And %d more\n"), remaining) + fmt.Fprintf(w, palette.Gray(" And %d more\n"), remaining) } } -func printHeader(w io.Writer, s string) { - fmt.Fprintln(w, utils.Bold(s)) +func printHeader(w io.Writer, palette *utils.Palette, s string) { + fmt.Fprintln(w, palette.Bold(s)) } -func printMessage(w io.Writer, s string) { - fmt.Fprintln(w, utils.Gray(s)) +func printMessage(w io.Writer, palette *utils.Palette, s string) { + fmt.Fprintln(w, palette.Gray(s)) } func truncate(maxLength int, title string) string { diff --git a/command/pr_create.go b/command/pr_create.go index 7c1109b0916..cdc7e2eed00 100644 --- a/command/pr_create.go +++ b/command/pr_create.go @@ -16,6 +16,12 @@ import ( func prCreate(cmd *cobra.Command, _ []string) error { ctx := contextForCommand(cmd) + + palette, err := utils.NewPalette(cmd) + if err != nil { + return err + } + remotes, err := ctx.Remotes() if err != nil { return err @@ -122,8 +128,8 @@ func prCreate(cmd *cobra.Command, _ []string) error { headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch) } fmt.Fprintf(colorableErr(cmd), "\nCreating pull request for %s into %s in %s\n\n", - utils.Cyan(headBranchLabel), - utils.Cyan(baseBranch), + palette.Cyan(headBranchLabel), + palette.Cyan(baseBranch), ghrepo.FullName(baseRepo)) title, err := cmd.Flags().GetString("title") diff --git a/command/root.go b/command/root.go index 0864bfca7c1..f9bfee68be9 100644 --- a/command/root.go +++ b/command/root.go @@ -30,6 +30,7 @@ func init() { RootCmd.SetVersionTemplate(versionOutput) RootCmd.PersistentFlags().StringP("repo", "R", "", "Select another repository using the `OWNER/REPO` format") + RootCmd.PersistentFlags().Bool("force-color", false, "Override coloring detection") RootCmd.PersistentFlags().Bool("help", false, "Show help for command") RootCmd.Flags().Bool("version", false, "Show gh version") // TODO: diff --git a/utils/color.go b/utils/color.go index ed22bc3e966..347a7d3e475 100644 --- a/utils/color.go +++ b/utils/color.go @@ -3,10 +3,12 @@ package utils import ( "io" "os" + "fmt" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "github.com/mgutz/ansi" + "github.com/spf13/cobra" ) var _isStdoutTerminal = false @@ -26,36 +28,41 @@ func NewColorable(f *os.File) io.Writer { return colorable.NewColorable(f) } -func makeColorFunc(color string) func(string) string { +func makeColorFunc(color string, forceColor bool) func(string) string { cf := ansi.ColorFunc(color) return func(arg string) string { - if isStdoutTerminal() { + if isStdoutTerminal() || forceColor { return cf(arg) } return arg } } -// Magenta outputs ANSI color if stdout is a tty -var Magenta = makeColorFunc("magenta") - -// Cyan outputs ANSI color if stdout is a tty -var Cyan = makeColorFunc("cyan") - -// Red outputs ANSI color if stdout is a tty -var Red = makeColorFunc("red") - -// Yellow outputs ANSI color if stdout is a tty -var Yellow = makeColorFunc("yellow") - -// Blue outputs ANSI color if stdout is a tty -var Blue = makeColorFunc("blue") - -// Green outputs ANSI color if stdout is a tty -var Green = makeColorFunc("green") +type Palette struct { + Magenta func(string) string + Cyan func(string) string + Red func(string) string + Yellow func(string) string + Blue func(string) string + Green func(string) string + Gray func(string) string + Bold func(string) string +} -// Gray outputs ANSI color if stdout is a tty -var Gray = makeColorFunc("black+h") +func NewPalette(cmd *cobra.Command) (*Palette, error) { + forceColor, err := cmd.Flags().GetBool("force-color") + if err != nil { + return nil, fmt.Errorf("could not parse force-color: %w", err) + } -// Bold outputs ANSI color if stdout is a tty -var Bold = makeColorFunc("default+b") + return &Palette{ + Magenta: makeColorFunc("magenta", forceColor), + Cyan: makeColorFunc("cyan", forceColor), + Red: makeColorFunc("red", forceColor), + Yellow: makeColorFunc("yellow", forceColor), + Blue: makeColorFunc("blue", forceColor), + Green: makeColorFunc("green", forceColor), + Gray: makeColorFunc("black+h", forceColor), + Bold: makeColorFunc("default+b", forceColor), + }, nil +} From b5405d98d621485fe0cca7b2209a5c53cf730b61 Mon Sep 17 00:00:00 2001 From: Antoine Niek Date: Wed, 19 Feb 2020 10:00:13 -0500 Subject: [PATCH 2/2] Evaluate cheap forceColor expression first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Mislav Marohnić --- utils/color.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/color.go b/utils/color.go index 347a7d3e475..1b0659ef24e 100644 --- a/utils/color.go +++ b/utils/color.go @@ -31,7 +31,7 @@ func NewColorable(f *os.File) io.Writer { func makeColorFunc(color string, forceColor bool) func(string) string { cf := ansi.ColorFunc(color) return func(arg string) string { - if isStdoutTerminal() || forceColor { + if forceColor || isStdoutTerminal() { return cf(arg) } return arg