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:
parent
4b97d4f7f5
commit
88d644a7e9
@ -94,6 +94,7 @@ func main() {
|
|||||||
pflag.StringVarP(&output.Name, "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.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.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.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.DurationVarP(&interval, "interval", "I", 0, "interval to watch for changes")
|
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")
|
log.Fatal("task: You can't set --output-group-end without --output=group")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if output.Group.ErrorOnly {
|
||||||
|
log.Fatal("task: You can't set --output-group-error-only without --output=group")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
|
@ -36,6 +36,7 @@ variable
|
|||||||
| `-o` | `--output` | `string` | Default set in the Taskfile or `intervealed` | Sets output style: [`interleaved`/`group`/`prefixed`]. |
|
| `-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-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-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. |
|
| `-p` | `--parallel` | `bool` | `false` | Executes tasks provided on command line in parallel. |
|
||||||
| `-s` | `--silent` | `bool` | `false` | Disables echoing. |
|
| `-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. |
|
| | `--status` | `bool` | `false` | Exits with non-zero exit code if any of the given tasks is not up-to-date. |
|
||||||
|
@ -1342,6 +1342,30 @@ Hello, World!
|
|||||||
::endgroup::
|
::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
|
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
docs/static/schema.json
vendored
5
docs/static/schema.json
vendored
@ -331,6 +331,11 @@
|
|||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"error_only": {
|
||||||
|
"description": "Swallows command output on zero exit code",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
Begin, End string
|
Begin, End string
|
||||||
|
ErrorOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, tmpl Templater) (io.Writer, io.Writer, CloseFunc) {
|
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 != "" {
|
if g.End != "" {
|
||||||
gw.end = tmpl.Replace(g.End) + "\n"
|
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 {
|
type groupWriter struct {
|
||||||
|
@ -7,5 +7,5 @@ import (
|
|||||||
type Interleaved struct{}
|
type Interleaved struct{}
|
||||||
|
|
||||||
func (Interleaved) WrapWriter(stdOut, stdErr io.Writer, _ string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
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 }
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ type Output interface {
|
|||||||
WrapWriter(stdOut, stdErr io.Writer, prefix string, tmpl Templater) (io.Writer, io.Writer, CloseFunc)
|
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.
|
// Build the Output for the requested taskfile.Output.
|
||||||
func BuildFor(o *taskfile.Output) (Output, error) {
|
func BuildFor(o *taskfile.Output) (Output, error) {
|
||||||
@ -30,8 +30,9 @@ func BuildFor(o *taskfile.Output) (Output, error) {
|
|||||||
return Interleaved{}, nil
|
return Interleaved{}, nil
|
||||||
case "group":
|
case "group":
|
||||||
return Group{
|
return Group{
|
||||||
Begin: o.Group.Begin,
|
Begin: o.Group.Begin,
|
||||||
End: o.Group.End,
|
End: o.Group.End,
|
||||||
|
ErrorOnly: o.Group.ErrorOnly,
|
||||||
}, nil
|
}, nil
|
||||||
case "prefixed":
|
case "prefixed":
|
||||||
if err := checkOutputGroupUnset(o); err != nil {
|
if err := checkOutputGroupUnset(o); err != nil {
|
||||||
|
@ -2,6 +2,7 @@ package output_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
@ -38,7 +39,7 @@ func TestGroup(t *testing.T) {
|
|||||||
fmt.Fprintln(stdErr, "err")
|
fmt.Fprintln(stdErr, "err")
|
||||||
assert.Equal(t, "", b.String())
|
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())
|
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())
|
assert.Equal(t, "", b.String())
|
||||||
fmt.Fprintln(w, "baz")
|
fmt.Fprintln(w, "baz")
|
||||||
assert.Equal(t, "", b.String())
|
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())
|
assert.Equal(t, "::group::example-value\nfoo\nbar\nbaz\n::endgroup::\n", b.String())
|
||||||
})
|
})
|
||||||
t.Run("no output", func(t *testing.T) {
|
t.Run("no output", func(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var _, _, cleanup = o.WrapWriter(&b, io.Discard, "", &tmpl)
|
var _, _, cleanup = o.WrapWriter(&b, io.Discard, "", &tmpl)
|
||||||
assert.NoError(t, cleanup())
|
assert.NoError(t, cleanup(nil))
|
||||||
assert.Equal(t, "", b.String())
|
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) {
|
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{}
|
||||||
@ -87,7 +115,7 @@ func TestPrefixed(t *testing.T) {
|
|||||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
|
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
|
||||||
fmt.Fprintln(w, "baz")
|
fmt.Fprintln(w, "baz")
|
||||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
|
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) {
|
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.Equal(t, "", b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, cleanup())
|
assert.NoError(t, cleanup(nil))
|
||||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
assert.Equal(t, "[prefix] Test!\n", b.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ type Prefixed struct{}
|
|||||||
|
|
||||||
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
||||||
pw := &prefixWriter{writer: stdOut, prefix: prefix}
|
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 {
|
type prefixWriter struct {
|
||||||
|
10
task.go
10
task.go
@ -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)
|
return fmt.Errorf("task: failed to get variables: %w", err)
|
||||||
}
|
}
|
||||||
stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater)
|
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{
|
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: cmd.Cmd,
|
Command: cmd.Cmd,
|
||||||
@ -298,6 +293,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
Stdout: stdOut,
|
Stdout: stdOut,
|
||||||
Stderr: stdErr,
|
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 {
|
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v", t.Name(), err)
|
||||||
return nil
|
return nil
|
||||||
|
30
task_test.go
30
task_test.go
@ -1576,6 +1576,36 @@ Bye!
|
|||||||
t.Log(buff.String())
|
t.Log(buff.String())
|
||||||
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
|
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) {
|
func TestIncludedVars(t *testing.T) {
|
||||||
const dir = "testdata/include_with_vars"
|
const dir = "testdata/include_with_vars"
|
||||||
|
@ -53,6 +53,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
// OutputGroup is the style options specific to the Group style.
|
// OutputGroup is the style options specific to the Group style.
|
||||||
type OutputGroup struct {
|
type OutputGroup struct {
|
||||||
Begin, End string
|
Begin, End string
|
||||||
|
ErrorOnly bool `yaml:"error_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet returns true if and only if a custom output style is set.
|
// IsSet returns true if and only if a custom output style is set.
|
||||||
|
17
testdata/output_group_error_only/Taskfile.yml
vendored
Normal file
17
testdata/output_group_error_only/Taskfile.yml
vendored
Normal 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
|
Loading…
Reference in New Issue
Block a user