Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎁 Add FORCE_COLOR support #156

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
Expand Up @@ -130,7 +130,9 @@ There might be a case where you want to explicitly disable/enable color output.
(for example if the output were piped directly to `less`).

The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment
variable is set (regardless of its value).
variable is set (regardless of its value). Setting the `FORCE_COLOR`
environment variable to value different from `0` will force enable color
output, even for non-tty output streams.

`Color` has support to disable/enable colors programatically both globally and
for single color definitions. For example suppose you have a CLI app and a
Expand Down Expand Up @@ -160,7 +162,16 @@ c.Println("This prints again cyan...")

## GitHub Actions

To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
To output color in GitHub Actions (or other CI systems that support ANSI
colors), make sure to set `color.NoColor = false` so that it bypasses the check
for non-tty output streams.

Easiest way to do it can be leveraging the `FORCE_COLOR` environment variable:

```yaml
env:
FORCE_COLOR: true
```

## Todo

Expand Down
48 changes: 46 additions & 2 deletions color.go
Expand Up @@ -19,8 +19,7 @@ var (
// set (regardless of its value). This is a global option and affects all
// colors. For more control over each color block use the methods
// DisableColor() individually.
NoColor = noColorExists() || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
NoColor = calculateNoColor()

// Output defines the standard output of the print functions. By default
// os.Stdout is used.
Expand All @@ -35,6 +34,47 @@ var (
colorsCacheMu sync.Mutex // protects colorsCache
)

func calculateNoColor() bool {
mode := resolveColorMode()
if mode.set() {
return !mode.colored()
}
return noColorExists() || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
}

type colorMode int

const notForcedColor colorMode = -1
const (
noColor colorMode = iota
color16
color256
color16M
)

func (c colorMode) set() bool {
return c != notForcedColor
}

func (c colorMode) colored() bool {
return c > noColor
}

func resolveColorMode() colorMode {
if ev, set := os.LookupEnv("FORCE_COLOR"); set {
ev = strings.ToLower(ev)
if mode, err := strconv.Atoi(ev); err == nil {
return colorMode(mode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undocumented functionality (nor in README.md nor in doc comments)

}
if ev == "yes" || ev == "true" {
return color256
}
return noColor
}
return notForcedColor
}

// noColorExists returns true if the environment variable NO_COLOR exists.
func noColorExists() bool {
_, exists := os.LookupEnv("NO_COLOR")
Expand Down Expand Up @@ -124,6 +164,10 @@ func New(value ...Attribute) *Color {
c.noColor = boolPtr(true)
}

if mode := resolveColorMode(); mode.set() {
c.noColor = boolPtr(!mode.colored())
}

c.Add(value...)
return c
}
Expand Down
19 changes: 19 additions & 0 deletions color_test.go
Expand Up @@ -200,6 +200,25 @@ func TestNoColor_Env(t *testing.T) {

}

func TestForceColorEnv(t *testing.T) {
_ = os.Setenv("FORCE_COLOR", "1")
t.Cleanup(func() {
_ = os.Unsetenv("FORCE_COLOR")
})

rb := new(bytes.Buffer)
Output = rb

p := New(FgHiCyan)
text := "text to be colored"
_, _ = p.Print(text)

line, _ := rb.ReadString('\n')
if line == text {
t.Errorf("Expecting '%s' be colored\n", line)
}
}

func TestColorVisual(t *testing.T) {
// First Visual Test
Output = colorable.NewColorableStdout()
Expand Down