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

feat: colorize tasks in prefixed output #1572

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
24 changes: 24 additions & 0 deletions internal/logger/logger.go
Expand Up @@ -52,6 +52,30 @@ func Red() PrintFunc {
return color.New(envColor("TASK_COLOR_RED", color.FgRed)...).FprintfFunc()
}

func BrightBlue() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_BLUE", color.FgHiBlue)...).FprintfFunc()
}

func BrightGreen() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_GREEN", color.FgHiGreen)...).FprintfFunc()
}

func BrightCyan() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_CYAN", color.FgHiCyan)...).FprintfFunc()
}

func BrightYellow() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_YELLOW", color.FgHiYellow)...).FprintfFunc()
}

func BrightMagenta() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_MAGENTA", color.FgHiMagenta)...).FprintfFunc()
}

func BrightRed() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_RED", color.FgHiRed)...).FprintfFunc()
}

func envColor(env string, defaultColor color.Attribute) []color.Attribute {
if os.Getenv("FORCE_COLOR") != "" {
color.NoColor = false
Expand Down
5 changes: 3 additions & 2 deletions internal/output/output.go
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"

"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
)
Expand All @@ -15,7 +16,7 @@ type Output interface {
type CloseFunc func(err error) error

// Build the Output for the requested ast.Output.
func BuildFor(o *ast.Output) (Output, error) {
func BuildFor(o *ast.Output, logger *logger.Logger) (Output, error) {
switch o.Name {
case "interleaved", "":
if err := checkOutputGroupUnset(o); err != nil {
Expand All @@ -32,7 +33,7 @@ func BuildFor(o *ast.Output) (Output, error) {
if err := checkOutputGroupUnset(o); err != nil {
return nil, err
}
return Prefixed{}, nil
return NewPrefixed(logger), nil
default:
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
}
Expand Down
38 changes: 37 additions & 1 deletion internal/output/output_test.go
Expand Up @@ -7,9 +7,11 @@ import (
"io"
"testing"

"github.com/fatih/color"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/templater"
Expand Down Expand Up @@ -107,7 +109,11 @@ func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {

func TestPrefixed(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Prefixed{}
l := &logger.Logger{
Color: false,
}

var o output.Output = output.NewPrefixed(l)
w, _, cleanup := o.WrapWriter(&b, io.Discard, "prefix", nil)

t.Run("simple use cases", func(t *testing.T) {
Expand All @@ -132,3 +138,33 @@ func TestPrefixed(t *testing.T) {
assert.Equal(t, "[prefix] Test!\n", b.String())
})
}

func TestPrefixedWithColor(t *testing.T) {
color.NoColor = false

var b bytes.Buffer
l := &logger.Logger{
Color: true,
}

var o output.Output = output.NewPrefixed(l)

writers := make([]io.Writer, 16)
for i := range writers {
writers[i], _, _ = o.WrapWriter(&b, io.Discard, fmt.Sprintf("prefix-%d", i), nil)
}

t.Run("colors should loop", func(t *testing.T) {
for i, w := range writers {
b.Reset()

color := output.PrefixColorSequence[i%len(output.PrefixColorSequence)]

var prefix bytes.Buffer
l.FOutf(&prefix, color, fmt.Sprintf("prefix-%d", i))

fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, fmt.Sprintf("[%s] foo\n[%s] bar\n", prefix.String(), prefix.String()), b.String())
}
})
}
56 changes: 49 additions & 7 deletions internal/output/prefixed.go
Expand Up @@ -6,20 +6,36 @@ import (
"io"
"strings"

"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
)

type Prefixed struct{}
type Prefixed struct {
logger *logger.Logger
seen map[string]uint
counter *uint
}

func NewPrefixed(logger *logger.Logger) Prefixed {
var counter uint

return Prefixed{
seen: make(map[string]uint),
counter: &counter,
logger: logger,
}
}

func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
pw := &prefixWriter{writer: stdOut, prefix: prefix}
func (p Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
pw := &prefixWriter{writer: stdOut, prefix: prefix, prefixed: &p}
return pw, pw, func(error) error { return pw.close() }
}

type prefixWriter struct {
writer io.Writer
prefix string
buff bytes.Buffer
writer io.Writer
prefixed *Prefixed
prefix string
buff bytes.Buffer
}

func (pw *prefixWriter) Write(p []byte) (int, error) {
Expand Down Expand Up @@ -56,13 +72,39 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
}
}

var PrefixColorSequence = []logger.Color{
logger.Yellow, logger.Blue, logger.Magenta, logger.Cyan, logger.Green, logger.Red,
logger.BrightYellow, logger.BrightBlue, logger.BrightMagenta, logger.BrightCyan, logger.BrightGreen, logger.BrightRed,
}

func (pw *prefixWriter) writeLine(line string) error {
if line == "" {
return nil
}
if !strings.HasSuffix(line, "\n") {
line += "\n"
}
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)

idx, ok := pw.prefixed.seen[pw.prefix]

if !ok {
idx = *pw.prefixed.counter
pw.prefixed.seen[pw.prefix] = idx

*pw.prefixed.counter++
}

if _, err := fmt.Fprint(pw.writer, "["); err != nil {
return nil
}

color := PrefixColorSequence[idx%uint(len(PrefixColorSequence))]
pw.prefixed.logger.FOutf(pw.writer, color, pw.prefix)

if _, err := fmt.Fprint(pw.writer, "] "); err != nil {
return nil
}

_, err := fmt.Fprint(pw.writer, line)
return err
}
2 changes: 1 addition & 1 deletion setup.go
Expand Up @@ -155,7 +155,7 @@ func (e *Executor) setupOutput() error {
}

var err error
e.Output, err = output.BuildFor(&e.OutputStyle)
e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
return err
}

Expand Down