diff --git a/commands/api.go b/commands/api.go index 8e6bfb94714efcb4824688d59acdc4486e9f7c2a..db53af33fa4d447771f66e3c3947b1ee4a34d500 100644 --- a/commands/api.go +++ b/commands/api.go @@ -66,6 +66,10 @@ var cmdApi = &Command{ Parse response JSON and output the data in a line-based key-value format suitable for use in shell scripts. + --color[=] + Enable colored output even if stdout is not a terminal. can be one + of "always" (default for '--color'), "never", or "auto" (default). + --cache Cache successful responses to GET requests for seconds. @@ -205,7 +209,7 @@ func apiCommand(cmd *Command, args *Args) { args.NoForward() out := ui.Stdout - colorize := ui.IsTerminal(os.Stdout) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) success := response.StatusCode < 300 parseJSON := args.Flag.Bool("--flat") diff --git a/commands/ci_status.go b/commands/ci_status.go index f56761fc330d1f14dc43ad2a07d670b6840acc77..7399b6dd9aaa62feaefe17cb3ec7be1f61456453 100644 --- a/commands/ci_status.go +++ b/commands/ci_status.go @@ -33,6 +33,10 @@ var cmdCiStatus = &Command{ %t: name of the status check + --color[=] + Enable colored output even if stdout is not a terminal. can be one + of "always" (default for '--color'), "never", or "auto" (default). + A commit SHA or branch name (default: "HEAD"). @@ -122,7 +126,8 @@ func ciStatus(cmd *Command, args *Args) { verbose := args.Flag.Bool("--verbose") || args.Flag.HasReceived("--format") if verbose && len(response.Statuses) > 0 { - verboseFormat(response.Statuses, args.Flag.Value("--format")) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) + ciVerboseFormat(response.Statuses, args.Flag.Value("--format"), colorize) } else { if state != "" { ui.Println(state) @@ -135,9 +140,7 @@ func ciStatus(cmd *Command, args *Args) { } } -func verboseFormat(statuses []github.CIStatus, formatString string) { - colorize := ui.IsTerminal(os.Stdout) - +func ciVerboseFormat(statuses []github.CIStatus, formatString string, colorize bool) { contextWidth := 0 for _, status := range statuses { if len(status.Context) > contextWidth { diff --git a/commands/issue.go b/commands/issue.go index 7409f60dc04af2c29b58391106791502d6fe4c04..ea6e1c6f7cc68bf508da15ac5b755f6b5864f243 100644 --- a/commands/issue.go +++ b/commands/issue.go @@ -109,6 +109,10 @@ With no arguments, show a list of open issues. %%: a literal % + --color[=] + Enable colored output even if stdout is not a terminal. can be one + of "always" (default for '--color'), "never", or "auto" (default). + -m, --message The text up to the first blank line in is treated as the issue title, and the rest is used as issue description in Markdown format. @@ -173,6 +177,7 @@ hub-pr(1), hub(1) -^, --sort-ascending --include-pulls -L, --limit N + --color `, } @@ -196,6 +201,7 @@ hub-pr(1), hub(1) Run: showIssue, KnownFlags: ` -f, --format FMT + --color `, } @@ -285,7 +291,7 @@ func listIssues(cmd *Command, args *Args) { } } - colorize := ui.IsTerminal(os.Stdout) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) for _, issue := range issues { ui.Print(formatIssue(issue, flagIssueFormat, colorize)) } @@ -445,7 +451,7 @@ func showIssue(cmd *Command, args *Args) { args.NoForward() - colorize := ui.IsTerminal(os.Stdout) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) if args.Flag.HasReceived("--format") { flagShowIssueFormat := args.Flag.Value("--format") ui.Print(formatIssue(*issue, flagShowIssueFormat, colorize)) @@ -583,12 +589,22 @@ func listLabels(cmd *Command, args *Args) { labels, err := gh.FetchLabels(project) utils.Check(err) - flagLabelsColorize := args.Flag.Bool("--color") + flagLabelsColorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) for _, label := range labels { ui.Print(formatLabel(label, flagLabelsColorize)) } } +func colorizeOutput(colorSet bool, when string) bool { + if !colorSet || when == "auto" { + return ui.IsTerminal(os.Stdout) + } else if when == "never" { + return false + } else { + return true // "always" + } +} + func formatLabel(label github.IssueLabel, colorize bool) string { if colorize { if color, err := utils.NewColor(label.Color); err == nil { diff --git a/commands/pr.go b/commands/pr.go index 54c8e9fff77236c262392db55ca1d886ee981642..5cd4c27619d611209c7be62aa987808bc81d679b 100644 --- a/commands/pr.go +++ b/commands/pr.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os" "strconv" "strings" @@ -117,6 +116,10 @@ pr checkout [] %%: a literal % + --color[=] + Enable colored output even if stdout is not a terminal. can be one + of "always" (default for '--color'), "never", or "auto" (default). + -o, --sort Sort displayed issues by "created" (default), "updated", "popularity", or "long-running". @@ -133,8 +136,9 @@ hub-issue(1), hub-pull-request(1), hub(1) } cmdCheckoutPr = &Command{ - Key: "checkout", - Run: checkoutPr, + Key: "checkout", + Run: checkoutPr, + KnownFlags: "\n", } cmdListPulls = &Command{ @@ -210,7 +214,7 @@ func listPulls(cmd *Command, args *Args) { }) utils.Check(err) - colorize := ui.IsTerminal(os.Stdout) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) for _, pr := range pulls { ui.Print(formatPullRequest(pr, flagPullRequestFormat, colorize)) } diff --git a/commands/release.go b/commands/release.go index 7bdfbeabda541e03e7e0976e5cf5e405878b27a5..a3887c3a1196a662e066a68cefba857d16ecd28e 100644 --- a/commands/release.go +++ b/commands/release.go @@ -140,6 +140,10 @@ With '--exclude-prereleases', exclude non-stable releases from the listing. %%: a literal % + --color[=] + Enable colored output even if stdout is not a terminal. can be one + of "always" (default for '--color'), "never", or "auto" (default). + The git tag name for this release. @@ -152,6 +156,7 @@ hub(1), git-tag(1) -p, --exclude-prereleases -L, --limit N -f, --format FMT + --color `, } @@ -161,6 +166,7 @@ hub(1), git-tag(1) KnownFlags: ` -d, --show-downloads -f, --format FMT + --color `, } @@ -236,7 +242,7 @@ func listReleases(cmd *Command, args *Args) { }) utils.Check(err) - colorize := ui.IsTerminal(os.Stdout) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) for _, release := range releases { flagReleaseFormat := "%T%n" if args.Flag.HasReceived("--format") { @@ -331,7 +337,7 @@ func showRelease(cmd *Command, args *Args) { body := strings.TrimSpace(release.Body) - colorize := ui.IsTerminal(os.Stdout) + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) if flagShowReleaseFormat := args.Flag.Value("--format"); flagShowReleaseFormat != "" { ui.Print(formatRelease(*release, flagShowReleaseFormat, colorize)) return diff --git a/commands/sync.go b/commands/sync.go index f02c407355ebbf1fa6f5c35bf0a69dd555d6a476..9e02bc661173908b55dea2594d004b6ec562883c 100644 --- a/commands/sync.go +++ b/commands/sync.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os" "regexp" "strings" @@ -14,7 +13,7 @@ import ( var cmdSync = &Command{ Run: sync, - Usage: "sync", + Usage: "sync [--color]", Long: `Fetch git objects from upstream and update local branches. - If the local branch is outdated, fast-forward it; @@ -24,6 +23,11 @@ var cmdSync = &Command{ If a local branch does not have any upstream configuration, but has a same-named branch on the remote, treat that as its upstream branch. +## Options: + --color[=] + Enable colored output even if stdout is not a terminal. can be one + of "always" (default for '--color'), "never", or "auto" (default). + ## See also: hub(1), git-fetch(1) @@ -71,7 +75,8 @@ func sync(cmd *Command, args *Args) { lightRed, resetColor string - if ui.IsTerminal(os.Stdout) { + colorize := colorizeOutput(args.Flag.HasReceived("--color"), args.Flag.Value("--color")) + if colorize { green = "\033[32m" lightGreen = "\033[32;1m" red = "\033[31m" diff --git a/utils/args_parser.go b/utils/args_parser.go index 69fe5de1c23adf2c8c3108abccedd5a7e919cf55..d736ba4f73cafa13c40b51eee067cf9ab1be04c5 100644 --- a/utils/args_parser.go +++ b/utils/args_parser.go @@ -192,12 +192,12 @@ func NewArgsParser() *ArgsParser { func NewArgsParserWithUsage(usage string) *ArgsParser { p := NewArgsParser() - f := `(-[a-zA-Z0-9@^]|--[a-z][a-z0-9-]+)(?:[ =]([a-zA-Z_<>:=-]+))?` + f := `(-[a-zA-Z0-9@^]|--[a-z][a-z0-9-]+)(?:\[?[ =]([a-zA-Z_<>:=-]+\]?))?` re := regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%s(?:,\s*%s)?$`, f, f)) for _, match := range re.FindAllStringSubmatch(usage, -1) { n1 := match[1] n2 := match[3] - hasValue := match[2] != "" || match[4] != "" + hasValue := !(match[2] == "" || strings.HasSuffix(match[2], "]")) || match[4] != "" var aliases []string if len(n1) == 2 && len(n2) > 2 { aliases = []string{n1} diff --git a/utils/args_parser_test.go b/utils/args_parser_test.go index ec2c66e54c1f08afa6b0525e4fbdb64f49ad0a84..f38e9e9e6dd68b35d5dbe50e87e72f5b598569c1 100644 --- a/utils/args_parser_test.go +++ b/utils/args_parser_test.go @@ -91,8 +91,9 @@ func TestArgsParser_Values(t *testing.T) { func TestArgsParser_Bool(t *testing.T) { p := NewArgsParser() p.RegisterBool("--noop") + p.RegisterBool("--color") p.RegisterBool("--draft", "-d") - args := []string{"-d", "--draft=false"} + args := []string{"-d", "--draft=false", "--color=auto"} rest, err := p.Parse(args) equal(t, nil, err) equal(t, []string{}, rest) @@ -101,6 +102,8 @@ func TestArgsParser_Bool(t *testing.T) { equal(t, false, p.HasReceived("-d")) equal(t, false, p.HasReceived("--noop")) equal(t, false, p.Bool("--noop")) + equal(t, true, p.HasReceived("--color")) + equal(t, "auto", p.Value("--color")) } func TestArgsParser_BoolValue(t *testing.T) {