diff --git a/internal/colour/colour.go b/internal/colour/colour.go index ba326d0..01d0862 100644 --- a/internal/colour/colour.go +++ b/internal/colour/colour.go @@ -18,23 +18,36 @@ const ( // // If $NO_COLOR is set, text will be returned unmodified. func Title(text string) string { - if noColour() { - return text - } - return CodeTitle + text + CodeReset + return sprint(CodeTitle, text) } // Bold returns the given text in bold white. // // If $NO_COLOR is set, text will be returned unmodified. func Bold(text string) string { - if noColour() { + return sprint(CodeBold, text) +} + +// sprint returns a string with a given colour and the reset code. +// +// It handles checking for NO_COLOR and FORCE_COLOR. +func sprint(code, text string) string { + // TODO(@FollowTheProcess): I don't like checking *every* time but doing it + // via e.g. sync.Once means that tests are annoying unless we ensure env vars are + // set at the process level + noColor := os.Getenv("NO_COLOR") != "" + forceColor := os.Getenv("FORCE_COLOR") != "" + + // $FORCE_COLOR overrides $NO_COLOR + if forceColor { + return code + text + CodeReset + } + + // $NO_COLOR is next + if noColor { return text } - return CodeBold + text + CodeReset -} -// noColour returns whether the $NO_COLOR env var was set. -func noColour() bool { - return os.Getenv("NO_COLOR") != "" + // Normal + return code + text + CodeReset } diff --git a/internal/colour/colour_test.go b/internal/colour/colour_test.go index 0883e0b..7c85ec0 100644 --- a/internal/colour/colour_test.go +++ b/internal/colour/colour_test.go @@ -9,11 +9,12 @@ import ( func TestColour(t *testing.T) { tests := []struct { - name string // Name of the test case - text string // Text to colour - fn func(text string) string // Printer function to return the coloured version of text - want string // Expected result containing ANSI escape codes - noColor bool // Whether to set the $NO_COLOR env var + name string // Name of the test case + text string // Text to colour + fn func(text string) string // Printer function to return the coloured version of text + want string // Expected result containing ANSI escape codes + noColor bool // Whether to set the $NO_COLOR env var + forceColor bool // Whether to set the $FORCE_COLOR env var }{ { name: "bold", @@ -28,6 +29,21 @@ func TestColour(t *testing.T) { noColor: true, want: "hello bold", }, + { + name: "bold force color", + text: "hello bold", + fn: colour.Bold, + want: colour.CodeBold + "hello bold" + colour.CodeReset, + forceColor: true, + }, + { + name: "bold force color and no color", + text: "hello bold", + fn: colour.Bold, + want: colour.CodeBold + "hello bold" + colour.CodeReset, + forceColor: true, // force should override no + noColor: true, + }, { name: "title", text: "Section", @@ -41,6 +57,21 @@ func TestColour(t *testing.T) { noColor: true, want: "Section", }, + { + name: "title force color", + text: "Section", + fn: colour.Title, + want: colour.CodeTitle + "Section" + colour.CodeReset, + forceColor: true, + }, + { + name: "title force color and no color", + text: "Section", + fn: colour.Title, + want: colour.CodeTitle + "Section" + colour.CodeReset, + forceColor: true, // force should override no + noColor: true, + }, } for _, tt := range tests { @@ -48,6 +79,9 @@ func TestColour(t *testing.T) { if tt.noColor { t.Setenv("NO_COLOR", "true") } + if tt.forceColor { + t.Setenv("FORCE_COLOR", "true") + } got := tt.fn(tt.text) test.Equal(t, got, tt.want) })