1
0
mirror of https://github.com/go-task/task.git synced 2024-12-04 10:24:45 +02:00

Add ability to set error_only: true on the group output mode

This commit is contained in:
Dennis Jekubczyk 2023-03-09 02:34:52 +01:00 committed by GitHub
parent 4b97d4f7f5
commit 88d644a7e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 135 additions and 16 deletions

View File

@ -94,6 +94,7 @@ func main() {
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.BoolVar(&output.Group.ErrorOnly, "output-group-error-only", false, "swallow output from successful tasks")
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.DurationVarP(&interval, "interval", "I", 0, "interval to watch for changes")
@ -138,6 +139,10 @@ func main() {
log.Fatal("task: You can't set --output-group-end without --output=group")
return
}
if output.Group.ErrorOnly {
log.Fatal("task: You can't set --output-group-error-only without --output=group")
return
}
}
e := task.Executor{

View File

@ -36,6 +36,7 @@ variable
| `-o` | `--output` | `string` | Default set in the Taskfile or `intervealed` | Sets output style: [`interleaved`/`group`/`prefixed`]. |
| | `--output-group-begin` | `string` | | Message template to print before a task's grouped output. |
| | `--output-group-end` | `string` | | Message template to print after a task's grouped output. |
| | `--output-group-error-only` | `bool` | `false` | Swallow command output on zero exit code. |
| `-p` | `--parallel` | `bool` | `false` | Executes tasks provided on command line in parallel. |
| `-s` | `--silent` | `bool` | `false` | Disables echoing. |
| | `--status` | `bool` | `false` | Exits with non-zero exit code if any of the given tasks is not up-to-date. |

View File

@ -1342,6 +1342,30 @@ Hello, World!
::endgroup::
```
When using the `group` output, you may swallow the output of the executed command
on standard output and standard error if it does not fail (zero exit code).
```yaml
version: '3'
silent: true
output:
group:
error_only: true
tasks:
passes: echo 'output-of-passes'
errors: echo 'output-of-errors' && exit 1
```
```bash
$ task passes
$ task errors
output-of-errors
task: Failed to run task "errors": exit status 1
```
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
with the `prefix:` attribute:

View File

@ -331,6 +331,11 @@
},
"end": {
"type": "string"
},
"error_only": {
"description": "Swallows command output on zero exit code",
"type": "boolean",
"default": false
}
}
}

View File

@ -7,6 +7,7 @@ import (
type Group struct {
Begin, End string
ErrorOnly bool
}
func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, tmpl Templater) (io.Writer, io.Writer, CloseFunc) {
@ -17,7 +18,13 @@ func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, tmpl Templater) (io.Wri
if g.End != "" {
gw.end = tmpl.Replace(g.End) + "\n"
}
return gw, gw, func() error { return gw.close() }
return gw, gw, func(err error) error {
if g.ErrorOnly && err == nil {
return nil
}
return gw.close()
}
}
type groupWriter struct {

View File

@ -7,5 +7,5 @@ import (
type Interleaved struct{}
func (Interleaved) WrapWriter(stdOut, stdErr io.Writer, _ string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
return stdOut, stdErr, func() error { return nil }
return stdOut, stdErr, func(error) error { return nil }
}

View File

@ -18,7 +18,7 @@ type Output interface {
WrapWriter(stdOut, stdErr io.Writer, prefix string, tmpl Templater) (io.Writer, io.Writer, CloseFunc)
}
type CloseFunc func() error
type CloseFunc func(err error) error
// Build the Output for the requested taskfile.Output.
func BuildFor(o *taskfile.Output) (Output, error) {
@ -30,8 +30,9 @@ func BuildFor(o *taskfile.Output) (Output, error) {
return Interleaved{}, nil
case "group":
return Group{
Begin: o.Group.Begin,
End: o.Group.End,
Begin: o.Group.Begin,
End: o.Group.End,
ErrorOnly: o.Group.ErrorOnly,
}, nil
case "prefixed":
if err := checkOutputGroupUnset(o); err != nil {

View File

@ -2,6 +2,7 @@ package output_test
import (
"bytes"
"errors"
"fmt"
"io"
"testing"
@ -38,7 +39,7 @@ func TestGroup(t *testing.T) {
fmt.Fprintln(stdErr, "err")
assert.Equal(t, "", b.String())
assert.NoError(t, cleanup())
assert.NoError(t, cleanup(nil))
assert.Equal(t, "out\nout\nerr\nerr\nout\nerr\n", b.String())
}
@ -64,17 +65,44 @@ func TestGroupWithBeginEnd(t *testing.T) {
assert.Equal(t, "", b.String())
fmt.Fprintln(w, "baz")
assert.Equal(t, "", b.String())
assert.NoError(t, cleanup())
assert.NoError(t, cleanup(nil))
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 _, _, cleanup = o.WrapWriter(&b, io.Discard, "", &tmpl)
assert.NoError(t, cleanup())
assert.NoError(t, cleanup(nil))
assert.Equal(t, "", b.String())
})
}
func TestGroupErrorOnlySwallowsOutputOnNoError(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Group{
ErrorOnly: true,
}
var stdOut, stdErr, cleanup = o.WrapWriter(&b, io.Discard, "", nil)
_, _ = fmt.Fprintln(stdOut, "std-out")
_, _ = fmt.Fprintln(stdErr, "std-err")
assert.NoError(t, cleanup(nil))
assert.Empty(t, b.String())
}
func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Group{
ErrorOnly: true,
}
var stdOut, stdErr, cleanup = o.WrapWriter(&b, io.Discard, "", nil)
_, _ = fmt.Fprintln(stdOut, "std-out")
_, _ = fmt.Fprintln(stdErr, "std-err")
assert.NoError(t, cleanup(errors.New("any-error")))
assert.Equal(t, "std-out\nstd-err\n", b.String())
}
func TestPrefixed(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Prefixed{}
@ -87,7 +115,7 @@ func TestPrefixed(t *testing.T) {
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
fmt.Fprintln(w, "baz")
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
assert.NoError(t, cleanup())
assert.NoError(t, cleanup(nil))
})
t.Run("multiple writes for a single line", func(t *testing.T) {
@ -98,7 +126,7 @@ func TestPrefixed(t *testing.T) {
assert.Equal(t, "", b.String())
}
assert.NoError(t, cleanup())
assert.NoError(t, cleanup(nil))
assert.Equal(t, "[prefix] Test!\n", b.String())
})
}

View File

@ -11,7 +11,7 @@ type Prefixed struct{}
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
pw := &prefixWriter{writer: stdOut, prefix: prefix}
return pw, pw, func() error { return pw.close() }
return pw, pw, func(error) error { return pw.close() }
}
type prefixWriter struct {

10
task.go
View File

@ -282,11 +282,6 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
return fmt.Errorf("task: failed to get variables: %w", err)
}
stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater)
defer func() {
if err := close(); err != nil {
e.Logger.Errf(logger.Red, "task: unable to close writer: %v", err)
}
}()
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: cmd.Cmd,
@ -298,6 +293,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
Stdout: stdOut,
Stderr: stdErr,
})
defer func() {
if err := close(err); err != nil {
e.Logger.Errf(logger.Red, "task: unable to close writer: %v", err)
}
}()
if execext.IsExitError(err) && cmd.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
return nil

View File

@ -1576,6 +1576,36 @@ Bye!
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
func TestOutputGroupErrorOnlySwallowsOutputOnSuccess(t *testing.T) {
const dir = "testdata/output_group_error_only"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "passing"}))
t.Log(buff.String())
assert.Empty(t, buff.String())
}
func TestOutputGroupErrorOnlyShowsOutputOnFailure(t *testing.T) {
const dir = "testdata/output_group_error_only"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "failing"}))
t.Log(buff.String())
assert.Contains(t, "failing-output", strings.TrimSpace(buff.String()))
assert.NotContains(t, "passing", strings.TrimSpace(buff.String()))
}
func TestIncludedVars(t *testing.T) {
const dir = "testdata/include_with_vars"

View File

@ -53,6 +53,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
// OutputGroup is the style options specific to the Group style.
type OutputGroup struct {
Begin, End string
ErrorOnly bool `yaml:"error_only"`
}
// IsSet returns true if and only if a custom output style is set.

View File

@ -0,0 +1,17 @@
version: '3'
silent: true
output:
group:
error_only: true
tasks:
passing: echo 'passing-output'
failing:
cmds:
- task: passing
- echo 'passing-output-2'
- echo 'passing-output-3'
- echo 'failing-output' && exit 1