1
0
mirror of https://github.com/go-task/task.git synced 2025-04-07 07:09:55 +02:00

feat: colorize tasks in prefixed output ()

* feat: Colorize tasks in prefixed output

* chore: comment and style changes

* fix code tag has spaces in api reference

* fix: migrate to use logger for colors

* fix: Add bright colors to the color sequence

* fix: make colorized prefix logger standard
This commit is contained in:
Alexander Arvidsson 2024-06-03 11:37:24 +02:00 committed by GitHub
parent 0810ef01b0
commit 856ba3b8c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 114 additions and 11 deletions

@ -52,6 +52,30 @@ func Red() PrintFunc {
return color.New(envColor("TASK_COLOR_RED", color.FgRed)...).FprintfFunc() 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 { func envColor(env string, defaultColor color.Attribute) []color.Attribute {
if os.Getenv("FORCE_COLOR") != "" { if os.Getenv("FORCE_COLOR") != "" {
color.NoColor = false color.NoColor = false

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@ -15,7 +16,7 @@ type Output interface {
type CloseFunc func(err error) error type CloseFunc func(err error) error
// Build the Output for the requested ast.Output. // 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 { switch o.Name {
case "interleaved", "": case "interleaved", "":
if err := checkOutputGroupUnset(o); err != nil { if err := checkOutputGroupUnset(o); err != nil {
@ -32,7 +33,7 @@ func BuildFor(o *ast.Output) (Output, error) {
if err := checkOutputGroupUnset(o); err != nil { if err := checkOutputGroupUnset(o); err != nil {
return nil, err return nil, err
} }
return Prefixed{}, nil return NewPrefixed(logger), nil
default: default:
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name) return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
} }

@ -7,9 +7,11 @@ import (
"io" "io"
"testing" "testing"
"github.com/fatih/color"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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/omap"
"github.com/go-task/task/v3/internal/output" "github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/internal/templater"
@ -107,7 +109,11 @@ func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
func TestPrefixed(t *testing.T) { func TestPrefixed(t *testing.T) {
var b bytes.Buffer 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) w, _, cleanup := o.WrapWriter(&b, io.Discard, "prefix", nil)
t.Run("simple use cases", func(t *testing.T) { t.Run("simple use cases", func(t *testing.T) {
@ -132,3 +138,33 @@ func TestPrefixed(t *testing.T) {
assert.Equal(t, "[prefix] Test!\n", b.String()) 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())
}
})
}

@ -6,20 +6,36 @@ import (
"io" "io"
"strings" "strings"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/internal/templater"
) )
type Prefixed struct{} type Prefixed struct {
logger *logger.Logger
seen map[string]uint
counter *uint
}
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) { func NewPrefixed(logger *logger.Logger) Prefixed {
pw := &prefixWriter{writer: stdOut, prefix: prefix} var counter uint
return Prefixed{
seen: make(map[string]uint),
counter: &counter,
logger: logger,
}
}
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() } return pw, pw, func(error) error { return pw.close() }
} }
type prefixWriter struct { type prefixWriter struct {
writer io.Writer writer io.Writer
prefix string prefixed *Prefixed
buff bytes.Buffer prefix string
buff bytes.Buffer
} }
func (pw *prefixWriter) Write(p []byte) (int, error) { func (pw *prefixWriter) Write(p []byte) (int, error) {
@ -56,6 +72,11 @@ 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 { func (pw *prefixWriter) writeLine(line string) error {
if line == "" { if line == "" {
return nil return nil
@ -63,6 +84,27 @@ func (pw *prefixWriter) writeLine(line string) error {
if !strings.HasSuffix(line, "\n") { if !strings.HasSuffix(line, "\n") {
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 return err
} }

@ -155,7 +155,7 @@ func (e *Executor) setupOutput() error {
} }
var err error var err error
e.Output, err = output.BuildFor(&e.OutputStyle) e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
return err return err
} }