Merge pull request #1631 from cli/color-env
Add support for CLICOLOR standard
This commit is contained in:
commit
6d0da077b6
6 changed files with 229 additions and 9 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
|
||||
surveyCore "github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/command"
|
||||
"github.com/cli/cli/internal/config"
|
||||
|
|
@ -43,6 +44,23 @@ func main() {
|
|||
|
||||
cmdFactory := factory.New(command.Version)
|
||||
stderr := cmdFactory.IOStreams.ErrOut
|
||||
if !cmdFactory.IOStreams.ColorEnabled() {
|
||||
surveyCore.DisableColor = true
|
||||
} else {
|
||||
// override survey's poor choice of color
|
||||
surveyCore.TemplateFuncsWithColor["color"] = func(style string) string {
|
||||
switch style {
|
||||
case "white":
|
||||
if cmdFactory.IOStreams.ColorSupport256() {
|
||||
return fmt.Sprintf("\x1b[%d;5;%dm", 38, 242)
|
||||
}
|
||||
return ansi.ColorCode("default")
|
||||
default:
|
||||
return ansi.ColorCode(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootCmd := root.NewCmdRoot(cmdFactory, command.Version, command.BuildDate)
|
||||
|
||||
cfg, err := cmdFactory.Config()
|
||||
|
|
|
|||
|
|
@ -28,12 +28,17 @@ func NewHelpTopic(topic string) *cobra.Command {
|
|||
DEBUG: set to any value to enable verbose output to standard error. Include values "api"
|
||||
or "oauth" to print detailed information about HTTP requests or authentication flow.
|
||||
|
||||
PAGER: a paging program to send standard output to, e.g. "less".
|
||||
PAGER: a terminal paging program to send standard output to, e.g. "less".
|
||||
|
||||
GLAMOUR_STYLE: the style to use for rendering Markdown. See
|
||||
https://github.com/charmbracelet/glamour#styles
|
||||
|
||||
NO_COLOR: avoid printing ANSI escape sequences for color output.
|
||||
NO_COLOR: set to any value to avoid printing ANSI escape sequences for color output.
|
||||
|
||||
CLICOLOR: set to "0" to disable printing ANSI colors in output.
|
||||
|
||||
CLICOLOR_FORCE: set to a value other than "0" to keep ANSI colors in output
|
||||
even when the output is piped.
|
||||
`)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
package iostreams
|
||||
|
||||
import "github.com/mgutz/ansi"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mgutz/ansi"
|
||||
)
|
||||
|
||||
var (
|
||||
magenta = ansi.ColorFunc("magenta")
|
||||
|
|
@ -11,14 +17,42 @@ var (
|
|||
green = ansi.ColorFunc("green")
|
||||
gray = ansi.ColorFunc("black+h")
|
||||
bold = ansi.ColorFunc("default+b")
|
||||
|
||||
gray256 = func(t string) string {
|
||||
return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t)
|
||||
}
|
||||
)
|
||||
|
||||
func NewColorScheme(enabled bool) *ColorScheme {
|
||||
return &ColorScheme{enabled: enabled}
|
||||
func EnvColorDisabled() bool {
|
||||
return os.Getenv("NO_COLOR") != "" || os.Getenv("CLICOLOR") == "0"
|
||||
}
|
||||
|
||||
func EnvColorForced() bool {
|
||||
return os.Getenv("CLICOLOR_FORCE") != "" && os.Getenv("CLICOLOR_FORCE") != "0"
|
||||
}
|
||||
|
||||
func Is256ColorSupported() bool {
|
||||
term := os.Getenv("TERM")
|
||||
colorterm := os.Getenv("COLORTERM")
|
||||
|
||||
return strings.Contains(term, "256") ||
|
||||
strings.Contains(term, "24bit") ||
|
||||
strings.Contains(term, "truecolor") ||
|
||||
strings.Contains(colorterm, "256") ||
|
||||
strings.Contains(colorterm, "24bit") ||
|
||||
strings.Contains(colorterm, "truecolor")
|
||||
}
|
||||
|
||||
func NewColorScheme(enabled, is256enabled bool) *ColorScheme {
|
||||
return &ColorScheme{
|
||||
enabled: enabled,
|
||||
is256enabled: is256enabled,
|
||||
}
|
||||
}
|
||||
|
||||
type ColorScheme struct {
|
||||
enabled bool
|
||||
enabled bool
|
||||
is256enabled bool
|
||||
}
|
||||
|
||||
func (c *ColorScheme) Bold(t string) string {
|
||||
|
|
@ -53,6 +87,9 @@ func (c *ColorScheme) Gray(t string) string {
|
|||
if !c.enabled {
|
||||
return t
|
||||
}
|
||||
if c.is256enabled {
|
||||
return gray256(t)
|
||||
}
|
||||
return gray(t)
|
||||
}
|
||||
|
||||
|
|
|
|||
145
pkg/iostreams/color_test.go
Normal file
145
pkg/iostreams/color_test.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package iostreams
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnvColorDisabled(t *testing.T) {
|
||||
orig_NO_COLOR := os.Getenv("NO_COLOR")
|
||||
orig_CLICOLOR := os.Getenv("CLICOLOR")
|
||||
orig_CLICOLOR_FORCE := os.Getenv("CLICOLOR_FORCE")
|
||||
t.Cleanup(func() {
|
||||
os.Setenv("NO_COLOR", orig_NO_COLOR)
|
||||
os.Setenv("CLICOLOR", orig_CLICOLOR)
|
||||
os.Setenv("CLICOLOR_FORCE", orig_CLICOLOR_FORCE)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
NO_COLOR string
|
||||
CLICOLOR string
|
||||
CLICOLOR_FORCE string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "pristine env",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "NO_COLOR enabled",
|
||||
NO_COLOR: "1",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR disabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "0",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR enabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "1",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR_FORCE has no effect",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "1",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.Setenv("NO_COLOR", tt.NO_COLOR)
|
||||
os.Setenv("CLICOLOR", tt.CLICOLOR)
|
||||
os.Setenv("CLICOLOR_FORCE", tt.CLICOLOR_FORCE)
|
||||
|
||||
if got := EnvColorDisabled(); got != tt.want {
|
||||
t.Errorf("EnvColorDisabled(): want %v, got %v", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvColorForced(t *testing.T) {
|
||||
orig_NO_COLOR := os.Getenv("NO_COLOR")
|
||||
orig_CLICOLOR := os.Getenv("CLICOLOR")
|
||||
orig_CLICOLOR_FORCE := os.Getenv("CLICOLOR_FORCE")
|
||||
t.Cleanup(func() {
|
||||
os.Setenv("NO_COLOR", orig_NO_COLOR)
|
||||
os.Setenv("CLICOLOR", orig_CLICOLOR)
|
||||
os.Setenv("CLICOLOR_FORCE", orig_CLICOLOR_FORCE)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
NO_COLOR string
|
||||
CLICOLOR string
|
||||
CLICOLOR_FORCE string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "pristine env",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "NO_COLOR enabled",
|
||||
NO_COLOR: "1",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR disabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "0",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR enabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "1",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR_FORCE enabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR_FORCE disabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "0",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
os.Setenv("NO_COLOR", tt.NO_COLOR)
|
||||
os.Setenv("CLICOLOR", tt.CLICOLOR)
|
||||
os.Setenv("CLICOLOR_FORCE", tt.CLICOLOR_FORCE)
|
||||
|
||||
if got := EnvColorForced(); got != tt.want {
|
||||
t.Errorf("EnvColorForced(): want %v, got %v", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ type IOStreams struct {
|
|||
// the original (non-colorable) output stream
|
||||
originalOut io.Writer
|
||||
colorEnabled bool
|
||||
is256enabled bool
|
||||
|
||||
progressIndicatorEnabled bool
|
||||
progressIndicator *spinner.Spinner
|
||||
|
|
@ -47,6 +48,10 @@ func (s *IOStreams) ColorEnabled() bool {
|
|||
return s.colorEnabled
|
||||
}
|
||||
|
||||
func (s *IOStreams) ColorSupport256() bool {
|
||||
return s.is256enabled
|
||||
}
|
||||
|
||||
func (s *IOStreams) SetStdinTTY(isTTY bool) {
|
||||
s.stdinTTYOverride = true
|
||||
s.stdinIsTTY = isTTY
|
||||
|
|
@ -200,7 +205,7 @@ func (s *IOStreams) TerminalWidth() int {
|
|||
}
|
||||
|
||||
func (s *IOStreams) ColorScheme() *ColorScheme {
|
||||
return NewColorScheme(s.ColorEnabled())
|
||||
return NewColorScheme(s.ColorEnabled(), s.ColorSupport256())
|
||||
}
|
||||
|
||||
func System() *IOStreams {
|
||||
|
|
@ -212,7 +217,8 @@ func System() *IOStreams {
|
|||
originalOut: os.Stdout,
|
||||
Out: colorable.NewColorable(os.Stdout),
|
||||
ErrOut: colorable.NewColorable(os.Stderr),
|
||||
colorEnabled: os.Getenv("NO_COLOR") == "" && stdoutIsTTY,
|
||||
colorEnabled: EnvColorForced() || (!EnvColorDisabled() && stdoutIsTTY),
|
||||
is256enabled: Is256ColorSupported(),
|
||||
pagerCommand: os.Getenv("PAGER"),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mgutz/ansi"
|
||||
)
|
||||
|
|
@ -32,6 +34,9 @@ func makeColorFunc(color string) func(string) string {
|
|||
cf := ansi.ColorFunc(color)
|
||||
return func(arg string) string {
|
||||
if isColorEnabled() {
|
||||
if color == "black+h" && iostreams.Is256ColorSupported() {
|
||||
return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, arg)
|
||||
}
|
||||
return cf(arg)
|
||||
}
|
||||
return arg
|
||||
|
|
@ -39,7 +44,11 @@ func makeColorFunc(color string) func(string) string {
|
|||
}
|
||||
|
||||
func isColorEnabled() bool {
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
if iostreams.EnvColorForced() {
|
||||
return true
|
||||
}
|
||||
|
||||
if iostreams.EnvColorDisabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue