From 37af1a73b4e9c4f840638464623ab2cde432bd12 Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:58:48 -0800 Subject: [PATCH 1/4] prefix output --- internal/task/task.go | 11 +++++++++-- internal/task/util.go | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/internal/task/task.go b/internal/task/task.go index 2fe09dc..8337b21 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -133,10 +133,17 @@ func (exec *Executor) RunTasks(config *Config, tasks *[]string) error { return err } + taskExec := &Executor{ + Stdout: newPrefixWriter(exec.Stdout, task+":: "), + Stderr: newPrefixWriter(exec.Stderr, task+":: "), + Stdin: exec.Stdin, + Config: exec.Config, + } + // if a task contains cmds, run them if len(taskConfig.Cmds) > 0 { for _, cmd := range taskConfig.Cmds { - err := exec.runCommand(cmd, taskConfig.Dir, env) + err := taskExec.runCommand(cmd, taskConfig.Dir, env) if err != nil { fmt.Println(err.Error()) // if the cmd exited with an error, bail immediately @@ -146,7 +153,7 @@ func (exec *Executor) RunTasks(config *Config, tasks *[]string) error { } else { // if there are no cmds then we intend to run a script with the name name as the task script := fmt.Sprintf("%s/%s", exec.Config.ScriptDir, task) - err := exec.runCommand(script, taskConfig.Dir, env) + err := taskExec.runCommand(script, taskConfig.Dir, env) if err != nil { fmt.Println(err.Error()) // if the cmd exited with an error, bail immediately diff --git a/internal/task/util.go b/internal/task/util.go index 94426ca..01e06d3 100644 --- a/internal/task/util.go +++ b/internal/task/util.go @@ -3,12 +3,39 @@ package task import ( "bytes" "fmt" + "io" "sort" "text/template" "github.com/joho/godotenv" ) +type prefixWriter struct { + writer io.Writer + prefix string + buf []byte +} + +func newPrefixWriter(w io.Writer, prefix string) *prefixWriter { + return &prefixWriter{writer: w, prefix: prefix} +} + +func (p *prefixWriter) Write(b []byte) (int, error) { + p.buf = append(p.buf, b...) + for { + idx := bytes.IndexByte(p.buf, '\n') + if idx < 0 { + break + } + line := p.buf[:idx+1] + if _, err := fmt.Fprintf(p.writer, "%s%s", p.prefix, line); err != nil { + return 0, err + } + p.buf = p.buf[idx+1:] + } + return len(b), nil +} + type Vals struct { CLI_ARGS string } From 0b9f973db8f9d2d20e08329f3f527022ece0ca96 Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:16:41 -0800 Subject: [PATCH 2/4] add --prefix for output prefixing --- cmd/tsk/tsk.go | 3 +++ internal/task/task.go | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/cmd/tsk/tsk.go b/cmd/tsk/tsk.go index 8daef3b..5167756 100644 --- a/cmd/tsk/tsk.go +++ b/cmd/tsk/tsk.go @@ -26,6 +26,7 @@ type Options struct { init bool listTasks bool output string + prefix bool pure bool taskFile string tasks []string @@ -46,6 +47,7 @@ func main() { flag.BoolVar(&opts.init, "init", false, "create a tasks.toml file in $PWD") flag.BoolVarP(&opts.listTasks, "list", "l", false, "list tasks") flag.StringVarP(&opts.output, "output", "o", "text", fmt.Sprintf("output format (applies only to --list) (one of: %s)", output.String())) + flag.BoolVar(&opts.prefix, "prefix", false, "prefix task output with the task name") flag.BoolVarP(&opts.pure, "pure", "", false, "don't inherit the parent env") flag.StringVarP(&opts.taskFile, "file", "f", "", "taskfile to use") flag.BoolVar(&opts.which, "which", false, "print the path to the found tasks.toml, or an error") @@ -91,6 +93,7 @@ func main() { Stdin: os.Stdin, Stderr: os.Stderr, Config: cfg, + Prefix: opts.prefix, } if opts.listTasks { diff --git a/internal/task/task.go b/internal/task/task.go index 8337b21..7bf56d7 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -47,6 +47,7 @@ type Executor struct { Stdin io.Reader Stderr io.Writer Config *Config + Prefix bool } // sets the top-level env @@ -90,6 +91,13 @@ func (t *Task) CompileEnv(env []string) ([]string, error) { return env, nil } +func (exec *Executor) prefixedWriter(w io.Writer, prefix string) io.Writer { + if exec.Prefix { + return newPrefixWriter(w, prefix) + } + return w +} + func (exec *Executor) RunTasks(config *Config, tasks *[]string) error { // top-level env env, err := config.CompileEnv() @@ -138,6 +146,7 @@ func (exec *Executor) RunTasks(config *Config, tasks *[]string) error { Stderr: newPrefixWriter(exec.Stderr, task+":: "), Stdin: exec.Stdin, Config: exec.Config, + Prefix: exec.Prefix, } // if a task contains cmds, run them From 10b706c660b2ebbfa106b52cce901aead049e1cd Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:16:58 -0800 Subject: [PATCH 3/4] make the prefix ::this:: --- internal/task/task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/task/task.go b/internal/task/task.go index 7bf56d7..a5be68d 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -142,8 +142,8 @@ func (exec *Executor) RunTasks(config *Config, tasks *[]string) error { } taskExec := &Executor{ - Stdout: newPrefixWriter(exec.Stdout, task+":: "), - Stderr: newPrefixWriter(exec.Stderr, task+":: "), + Stdout: exec.prefixedWriter(exec.Stdout, "::"+task+":: "), + Stderr: exec.prefixedWriter(exec.Stderr, "::"+task+":: "), Stdin: exec.Stdin, Config: exec.Config, Prefix: exec.Prefix, From 83f09db8474fed91f76ac7bce0330abe72e78a29 Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:37:03 -0800 Subject: [PATCH 4/4] colored prefix --- internal/task/task.go | 33 ++++++++++++++++++++++++--------- internal/task/util.go | 23 +++++++++++++++++++++-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/internal/task/task.go b/internal/task/task.go index a5be68d..33ec385 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -43,11 +43,13 @@ type Task struct { } type Executor struct { - Stdout io.Writer - Stdin io.Reader - Stderr io.Writer - Config *Config - Prefix bool + Stdout io.Writer + Stdin io.Reader + Stderr io.Writer + Config *Config + Prefix bool + colorIndex int + taskColors map[string]string } // sets the top-level env @@ -91,9 +93,22 @@ func (t *Task) CompileEnv(env []string) ([]string, error) { return env, nil } -func (exec *Executor) prefixedWriter(w io.Writer, prefix string) io.Writer { +func (exec *Executor) colorForTask(task string) string { + if exec.taskColors == nil { + exec.taskColors = make(map[string]string) + } + if color, ok := exec.taskColors[task]; ok { + return color + } + color := prefixColors[exec.colorIndex%len(prefixColors)] + exec.colorIndex++ + exec.taskColors[task] = color + return color +} + +func (exec *Executor) prefixedWriter(w io.Writer, prefix string, task string) io.Writer { if exec.Prefix { - return newPrefixWriter(w, prefix) + return newPrefixWriter(w, prefix, exec.colorForTask(task)) } return w } @@ -142,8 +157,8 @@ func (exec *Executor) RunTasks(config *Config, tasks *[]string) error { } taskExec := &Executor{ - Stdout: exec.prefixedWriter(exec.Stdout, "::"+task+":: "), - Stderr: exec.prefixedWriter(exec.Stderr, "::"+task+":: "), + Stdout: exec.prefixedWriter(exec.Stdout, "::"+task+":: ", task), + Stderr: exec.prefixedWriter(exec.Stderr, "::"+task+":: ", task), Stdin: exec.Stdin, Config: exec.Config, Prefix: exec.Prefix, diff --git a/internal/task/util.go b/internal/task/util.go index 01e06d3..4728978 100644 --- a/internal/task/util.go +++ b/internal/task/util.go @@ -10,14 +10,33 @@ import ( "github.com/joho/godotenv" ) +// ANSI color codes for prefix coloring. These are chosen for visual distinction. +var prefixColors = []string{ + "\033[36m", // cyan + "\033[33m", // yellow + "\033[35m", // magenta + "\033[32m", // green + "\033[34m", // blue + "\033[31m", // red + "\033[96m", // bright cyan + "\033[93m", // bright yellow + "\033[95m", // bright magenta + "\033[92m", // bright green + "\033[94m", // bright blue + "\033[91m", // bright red +} + +const colorReset = "\033[0m" + type prefixWriter struct { writer io.Writer prefix string buf []byte } -func newPrefixWriter(w io.Writer, prefix string) *prefixWriter { - return &prefixWriter{writer: w, prefix: prefix} +func newPrefixWriter(w io.Writer, prefix string, color string) *prefixWriter { + coloredPrefix := color + prefix + colorReset + return &prefixWriter{writer: w, prefix: coloredPrefix} } func (p *prefixWriter) Write(b []byte) (int, error) {