mirror of
https://github.com/go-task/task.git
synced 2025-04-07 07:09:55 +02:00
feat: colorize tasks in prefixed output (#1572)
* 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:
parent
0810ef01b0
commit
856ba3b8c2
@ -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
|
||||||
}
|
}
|
||||||
|
2
setup.go
2
setup.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user