mirror of
https://github.com/go-task/task.git
synced 2025-04-15 11:56:34 +02:00
Add support for begin/end messages with grouped output
Fixes #647 This allows CI systems that support grouping (such as with [GitHub Actions's `::group::` command](https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#grouping-log-lines) and [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands)) to collapse all of the logs for a single task, to improve readability of logs ## Example The following Taskfile ``` # Taskfile.yml version: 3 output: group: begin: "::group::{{ .TASK }}" end: "::endgroup::" tasks: default: cmds: - "echo 'Hello, World!'" ``` Results in the following output ```bash $ task task: [default] echo 'Hello, World!' ::group::default Hello, World! ::endgroup:: ``` See [this GitHub Actions job](https://github.com/janslow/task/runs/4811059609?check_suite_focus=true) for a full example <img width="771" alt="image" src="https://user-images.githubusercontent.com/1253367/149429832-6cb0c1b5-0758-442e-9375-c4daa65771bc.png"> <img width="394" alt="image" src="https://user-images.githubusercontent.com/1253367/149429851-1d5d2ab5-9095-4795-9b57-f91750720d40.png">
This commit is contained in:
parent
79f595d8d1
commit
74f5cf8f29
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -27,4 +27,4 @@ jobs:
|
|||||||
run: go build -o ./bin/task -v ./cmd/task
|
run: go build -o ./bin/task -v ./cmd/task
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./bin/task test
|
run: ./bin/task test --output=group --output-group-begin='::group::{{.TASK}}' --output-group-end='::endgroup::'
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
outputpkg "github.com/go-task/task/v3/internal/output"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ func main() {
|
|||||||
concurrency int
|
concurrency int
|
||||||
dir string
|
dir string
|
||||||
entrypoint string
|
entrypoint string
|
||||||
output string
|
output outputpkg.Style
|
||||||
color bool
|
color bool
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,7 +92,9 @@ func main() {
|
|||||||
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
|
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
|
||||||
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
||||||
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
|
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
|
||||||
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
pflag.StringVarP(&output.Name, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
||||||
|
pflag.StringVar(&output.Group.Begin, "output-group-begin", "", "message template to print before a task's grouped output")
|
||||||
|
pflag.StringVar(&output.Group.End, "output-group-end", "", "message template to print after a task's grouped output")
|
||||||
pflag.BoolVarP(&color, "color", "c", true, "colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable")
|
pflag.BoolVarP(&color, "color", "c", true, "colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable")
|
||||||
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
|
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
@ -126,6 +129,17 @@ func main() {
|
|||||||
entrypoint = filepath.Base(entrypoint)
|
entrypoint = filepath.Base(entrypoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if output.Name != "group" {
|
||||||
|
if output.Group.Begin != "" {
|
||||||
|
log.Fatal("task: You can't set --output-group-begin without --output=group")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if output.Group.End != "" {
|
||||||
|
log.Fatal("task: You can't set --output-group-end without --output=group")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Force: force,
|
Force: force,
|
||||||
Watch: watch,
|
Watch: watch,
|
||||||
|
@ -959,6 +959,33 @@ tasks:
|
|||||||
finishes, so you won't have live feedback for commands that take a long time
|
finishes, so you won't have live feedback for commands that take a long time
|
||||||
to run.
|
to run.
|
||||||
|
|
||||||
|
When using the `group` output, you can optionally provide a templated message
|
||||||
|
to print at the start of the group. This can be useful for instructing CI
|
||||||
|
systems to group all of the output for a given task, such as with [GitHub
|
||||||
|
Actions' `::group::` command](https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#grouping-log-lines).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
output:
|
||||||
|
group:
|
||||||
|
begin: '::begin::{{.TASK}}'
|
||||||
|
end: '::endgroup::'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo 'Hello, World!'
|
||||||
|
silent: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ task default
|
||||||
|
::begin::default
|
||||||
|
Hello, World!
|
||||||
|
::endgroup::
|
||||||
|
```
|
||||||
|
|
||||||
The `prefix` output will prefix every line printed by a command with
|
The `prefix` output will prefix every line printed by a command with
|
||||||
`[task-name] ` as the prefix, but you can customize the prefix for a command
|
`[task-name] ` as the prefix, but you can customize the prefix for a command
|
||||||
with the `prefix:` attribute:
|
with the `prefix:` attribute:
|
||||||
|
@ -5,15 +5,25 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct{}
|
type Group struct{
|
||||||
|
Begin, End string
|
||||||
|
}
|
||||||
|
|
||||||
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
|
func (g Group) WrapWriter(w io.Writer, _ string, tmpl Templater) io.Writer {
|
||||||
return &groupWriter{writer: w}
|
gw := &groupWriter{writer: w}
|
||||||
|
if g.Begin != "" {
|
||||||
|
gw.begin = tmpl.Replace(g.Begin) + "\n"
|
||||||
|
}
|
||||||
|
if g.End != "" {
|
||||||
|
gw.end = tmpl.Replace(g.End) + "\n"
|
||||||
|
}
|
||||||
|
return gw
|
||||||
}
|
}
|
||||||
|
|
||||||
type groupWriter struct {
|
type groupWriter struct {
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
buff bytes.Buffer
|
buff bytes.Buffer
|
||||||
|
begin, end string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gw *groupWriter) Write(p []byte) (int, error) {
|
func (gw *groupWriter) Write(p []byte) (int, error) {
|
||||||
@ -21,6 +31,14 @@ func (gw *groupWriter) Write(p []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gw *groupWriter) Close() error {
|
func (gw *groupWriter) Close() error {
|
||||||
|
if gw.buff.Len() == 0 {
|
||||||
|
// don't print begin/end messages if there's no buffered entries
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := io.WriteString(gw.writer, gw.begin); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gw.buff.WriteString(gw.end)
|
||||||
_, err := io.Copy(gw.writer, &gw.buff)
|
_, err := io.Copy(gw.writer, &gw.buff)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,6 @@ import (
|
|||||||
|
|
||||||
type Interleaved struct{}
|
type Interleaved struct{}
|
||||||
|
|
||||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
|
func (Interleaved) WrapWriter(w io.Writer, _ string, _ Templater) io.Writer {
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Output interface {
|
|
||||||
WrapWriter(w io.Writer, prefix string) io.Writer
|
// Templater executes a template engine.
|
||||||
|
// It is provided by the templater.Templater package.
|
||||||
|
type Templater interface {
|
||||||
|
// Replace replaces the provided template string with a rendered string.
|
||||||
|
Replace(tmpl string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Output interface {
|
||||||
|
WrapWriter(w io.Writer, prefix string, tmpl Templater) io.Writer
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
|
"github.com/go-task/task/v3/taskfile"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/output"
|
"github.com/go-task/task/v3/internal/output"
|
||||||
@ -14,7 +16,7 @@ import (
|
|||||||
func TestInterleaved(t *testing.T) {
|
func TestInterleaved(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Interleaved{}
|
var o output.Output = output.Interleaved{}
|
||||||
var w = o.WrapWriter(&b, "")
|
var w = o.WrapWriter(&b, "", nil)
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
fmt.Fprintln(w, "foo\nbar")
|
||||||
assert.Equal(t, "foo\nbar\n", b.String())
|
assert.Equal(t, "foo\nbar\n", b.String())
|
||||||
@ -25,7 +27,7 @@ func TestInterleaved(t *testing.T) {
|
|||||||
func TestGroup(t *testing.T) {
|
func TestGroup(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Group{}
|
var o output.Output = output.Group{}
|
||||||
var w = o.WrapWriter(&b, "").(io.WriteCloser)
|
var w = o.WrapWriter(&b, "", nil).(io.WriteCloser)
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
fmt.Fprintln(w, "foo\nbar")
|
||||||
assert.Equal(t, "", b.String())
|
assert.Equal(t, "", b.String())
|
||||||
@ -35,10 +37,43 @@ func TestGroup(t *testing.T) {
|
|||||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGroupWithBeginEnd(t *testing.T) {
|
||||||
|
tmpl := templater.Templater{
|
||||||
|
Vars: &taskfile.Vars{
|
||||||
|
Keys: []string{"VAR1"},
|
||||||
|
Mapping: map[string]taskfile.Var{
|
||||||
|
"VAR1": {Static: "example-value"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var o output.Output = output.Group{
|
||||||
|
Begin: "::group::{{ .VAR1 }}",
|
||||||
|
End: "::endgroup::",
|
||||||
|
}
|
||||||
|
t.Run("simple", func(t *testing.T) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "foo\nbar")
|
||||||
|
assert.Equal(t, "", b.String())
|
||||||
|
fmt.Fprintln(w, "baz")
|
||||||
|
assert.Equal(t, "", b.String())
|
||||||
|
assert.NoError(t, w.Close())
|
||||||
|
assert.Equal(t, "::group::example-value\nfoo\nbar\nbaz\n::endgroup::\n", b.String())
|
||||||
|
})
|
||||||
|
t.Run("no output", func(t *testing.T) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
|
||||||
|
assert.NoError(t, w.Close())
|
||||||
|
assert.Equal(t, "", b.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrefixed(t *testing.T) {
|
func TestPrefixed(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Prefixed{}
|
var o output.Output = output.Prefixed{}
|
||||||
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
|
var w = o.WrapWriter(&b, "prefix", nil).(io.WriteCloser)
|
||||||
|
|
||||||
t.Run("simple use cases", func(t *testing.T) {
|
t.Run("simple use cases", func(t *testing.T) {
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
type Prefixed struct{}
|
type Prefixed struct{}
|
||||||
|
|
||||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
|
func (Prefixed) WrapWriter(w io.Writer, prefix string, _ Templater) io.Writer {
|
||||||
return &prefixWriter{writer: w, prefix: prefix}
|
return &prefixWriter{writer: w, prefix: prefix}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
internal/output/style.go
Normal file
85
internal/output/style.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Style of the Task output
|
||||||
|
type Style struct {
|
||||||
|
// Name of the Style.
|
||||||
|
Name string `yaml:"-"`
|
||||||
|
// Group specific style
|
||||||
|
Group GroupStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the Output for the requested Style.
|
||||||
|
func (s *Style) Build() (Output, error) {
|
||||||
|
switch s.Name {
|
||||||
|
case "interleaved", "":
|
||||||
|
return Interleaved{}, s.ensureGroupStyleUnset()
|
||||||
|
case "group":
|
||||||
|
return Group{
|
||||||
|
Begin: s.Group.Begin,
|
||||||
|
End: s.Group.End,
|
||||||
|
}, nil
|
||||||
|
case "prefixed":
|
||||||
|
return Prefixed{}, s.ensureGroupStyleUnset()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(`task: output style %q not recognized`, s.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Style) ensureGroupStyleUnset() error {
|
||||||
|
if s.Group.IsSet() {
|
||||||
|
return fmt.Errorf("task: output style %q does not support the group begin/end parameter", s.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns true if and only if a custom output style is set.
|
||||||
|
func (s *Style) IsSet() bool {
|
||||||
|
return s.Name != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler
|
||||||
|
// It accepts a scalar node representing the Style.Name or a mapping node representing the GroupStyle.
|
||||||
|
func (s *Style) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var name string
|
||||||
|
if err := unmarshal(&name); err == nil {
|
||||||
|
return s.UnmarshalText([]byte(name))
|
||||||
|
}
|
||||||
|
var tmp struct {
|
||||||
|
Group *GroupStyle
|
||||||
|
}
|
||||||
|
if err := unmarshal(&tmp); err != nil {
|
||||||
|
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
|
||||||
|
}
|
||||||
|
if tmp.Group == nil {
|
||||||
|
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
|
||||||
|
}
|
||||||
|
*s = Style{
|
||||||
|
Name: "group",
|
||||||
|
Group: *tmp.Group,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler
|
||||||
|
// It accepts the Style.Node
|
||||||
|
func (s *Style) UnmarshalText(text []byte) error {
|
||||||
|
tmp := Style{Name: string(text)}
|
||||||
|
if _, err := tmp.Build(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupStyle is the style options specific to the Group style.
|
||||||
|
type GroupStyle struct{
|
||||||
|
Begin, End string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns true if and only if a custom output style is set.
|
||||||
|
func (g *GroupStyle) IsSet() bool {
|
||||||
|
return g != nil && *g != GroupStyle{}
|
||||||
|
}
|
44
task.go
44
task.go
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/output"
|
"github.com/go-task/task/v3/internal/output"
|
||||||
"github.com/go-task/task/v3/internal/summary"
|
"github.com/go-task/task/v3/internal/summary"
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
"github.com/go-task/task/v3/taskfile/read"
|
"github.com/go-task/task/v3/taskfile/read"
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ type Executor struct {
|
|||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Compiler compiler.Compiler
|
Compiler compiler.Compiler
|
||||||
Output output.Output
|
Output output.Output
|
||||||
OutputStyle string
|
OutputStyle output.Style
|
||||||
|
|
||||||
taskvars *taskfile.Vars
|
taskvars *taskfile.Vars
|
||||||
|
|
||||||
@ -148,11 +149,11 @@ func (e *Executor) Setup() error {
|
|||||||
v = 2.6
|
v = 2.6
|
||||||
}
|
}
|
||||||
if v == 3.0 {
|
if v == 3.0 {
|
||||||
v = 3.7
|
v = 3.8
|
||||||
}
|
}
|
||||||
|
|
||||||
if v > 3.7 {
|
if v > 3.8 {
|
||||||
return fmt.Errorf(`task: Taskfile versions greater than v3.7 not implemented in the version of Task`)
|
return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color available only on v3
|
// Color available only on v3
|
||||||
@ -194,7 +195,7 @@ func (e *Executor) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v < 2.1 && e.Taskfile.Output != "" {
|
if v < 2.1 && !e.Taskfile.Output.IsSet() {
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||||
}
|
}
|
||||||
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
|
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
|
||||||
@ -203,19 +204,17 @@ func (e *Executor) Setup() error {
|
|||||||
if v >= 3.0 && e.Taskfile.Expansions > 2 {
|
if v >= 3.0 && e.Taskfile.Expansions > 2 {
|
||||||
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
||||||
}
|
}
|
||||||
|
if v < 3.8 && e.Taskfile.Output.Group.IsSet() {
|
||||||
if e.OutputStyle != "" {
|
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
|
||||||
e.Taskfile.Output = e.OutputStyle
|
|
||||||
}
|
}
|
||||||
switch e.Taskfile.Output {
|
|
||||||
case "", "interleaved":
|
if !e.OutputStyle.IsSet() {
|
||||||
e.Output = output.Interleaved{}
|
e.OutputStyle = e.Taskfile.Output
|
||||||
case "group":
|
}
|
||||||
e.Output = output.Group{}
|
if o, err := e.OutputStyle.Build(); err != nil {
|
||||||
case "prefixed":
|
return err
|
||||||
e.Output = output.Prefixed{}
|
} else {
|
||||||
default:
|
e.Output = o
|
||||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Taskfile.Method == "" {
|
if e.Taskfile.Method == "" {
|
||||||
@ -435,8 +434,13 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
if t.Interactive {
|
if t.Interactive {
|
||||||
outputWrapper = output.Interleaved{}
|
outputWrapper = output.Interleaved{}
|
||||||
}
|
}
|
||||||
stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix)
|
vars, err := e.Compiler.FastGetVariables(t, call)
|
||||||
stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix)
|
outputTemplater := &templater.Templater{Vars: vars, RemoveNoValue: true}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("task: failed to get variables: %w", err)
|
||||||
|
}
|
||||||
|
stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix, outputTemplater)
|
||||||
|
stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix, outputTemplater)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if _, ok := stdOut.(*os.File); !ok {
|
if _, ok := stdOut.(*os.File); !ok {
|
||||||
@ -451,7 +455,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: cmd.Cmd,
|
Command: cmd.Cmd,
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Env: getEnviron(t),
|
Env: getEnviron(t),
|
||||||
|
25
task_test.go
25
task_test.go
@ -1171,3 +1171,28 @@ func TestIgnoreNilElements(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOutputGroup(t *testing.T) {
|
||||||
|
const dir = "testdata/output_group"
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
expectedOutputOrder := strings.TrimSpace(`
|
||||||
|
task: [hello] echo 'Hello!'
|
||||||
|
::group::hello
|
||||||
|
Hello!
|
||||||
|
::endgroup::
|
||||||
|
task: [bye] echo 'Bye!'
|
||||||
|
::group::bye
|
||||||
|
Bye!
|
||||||
|
::endgroup::
|
||||||
|
`)
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "bye"}))
|
||||||
|
t.Log(buff.String())
|
||||||
|
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
|||||||
if t2.Expansions != 0 && t2.Expansions != 2 {
|
if t2.Expansions != 0 && t2.Expansions != 2 {
|
||||||
t1.Expansions = t2.Expansions
|
t1.Expansions = t2.Expansions
|
||||||
}
|
}
|
||||||
if t2.Output != "" {
|
if t2.Output.IsSet() {
|
||||||
t1.Output = t2.Output
|
t1.Output = t2.Output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,13 +3,15 @@ package taskfile
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/output"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Taskfile represents a Taskfile.yml
|
// Taskfile represents a Taskfile.yml
|
||||||
type Taskfile struct {
|
type Taskfile struct {
|
||||||
Version string
|
Version string
|
||||||
Expansions int
|
Expansions int
|
||||||
Output string
|
Output output.Style
|
||||||
Method string
|
Method string
|
||||||
Includes *IncludedTaskfiles
|
Includes *IncludedTaskfiles
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
@ -25,7 +27,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
var taskfile struct {
|
var taskfile struct {
|
||||||
Version string
|
Version string
|
||||||
Expansions int
|
Expansions int
|
||||||
Output string
|
Output output.Style
|
||||||
Method string
|
Method string
|
||||||
Includes *IncludedTaskfiles
|
Includes *IncludedTaskfiles
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
|
16
testdata/output_group/Taskfile.yml
vendored
Normal file
16
testdata/output_group/Taskfile.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
output:
|
||||||
|
group:
|
||||||
|
begin: '::group::{{ .TASK }}'
|
||||||
|
end: '::endgroup::'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
hello:
|
||||||
|
cmds:
|
||||||
|
- echo 'Hello!'
|
||||||
|
bye:
|
||||||
|
deps:
|
||||||
|
- hello
|
||||||
|
cmds:
|
||||||
|
- echo 'Bye!'
|
Loading…
x
Reference in New Issue
Block a user