mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +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:
		
							
								
								
									
										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 | ||||
|  | ||||
|       - 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" | ||||
| 	"syscall" | ||||
|  | ||||
| 	outputpkg "github.com/go-task/task/v3/internal/output" | ||||
| 	"github.com/spf13/pflag" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
|  | ||||
| @@ -72,7 +73,7 @@ func main() { | ||||
| 		concurrency int | ||||
| 		dir         string | ||||
| 		entrypoint  string | ||||
| 		output      string | ||||
| 		output      outputpkg.Style | ||||
| 		color       bool | ||||
| 	) | ||||
|  | ||||
| @@ -91,7 +92,9 @@ func main() { | ||||
| 	pflag.BoolVar(&summary, "summary", false, "show summary about a task") | ||||
| 	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(&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.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently") | ||||
| 	pflag.Parse() | ||||
| @@ -126,6 +129,17 @@ func main() { | ||||
| 		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{ | ||||
| 		Force:       force, | ||||
| 		Watch:       watch, | ||||
|   | ||||
| @@ -959,6 +959,33 @@ tasks: | ||||
|  finishes, so you won't have live feedback for commands that take a long time | ||||
|  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 | ||||
|  `[task-name] ` as the prefix, but you can customize the prefix for a command | ||||
|  with the `prefix:` attribute: | ||||
|   | ||||
| @@ -5,15 +5,25 @@ import ( | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type Group struct{} | ||||
| type Group struct{ | ||||
| 	Begin, End string | ||||
| } | ||||
|  | ||||
| func (Group) WrapWriter(w io.Writer, _ string) io.Writer { | ||||
| 	return &groupWriter{writer: w} | ||||
| func (g Group) WrapWriter(w io.Writer, _ string, tmpl Templater) io.Writer { | ||||
| 	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 { | ||||
| 	writer io.Writer | ||||
| 	buff   bytes.Buffer | ||||
| 	begin, end string | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	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) | ||||
| 	return err | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,6 @@ import ( | ||||
|  | ||||
| type Interleaved struct{} | ||||
|  | ||||
| func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer { | ||||
| func (Interleaved) WrapWriter(w io.Writer, _ string, _ Templater) io.Writer { | ||||
| 	return w | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,14 @@ import ( | ||||
| 	"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" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-task/task/v3/internal/templater" | ||||
| 	"github.com/go-task/task/v3/taskfile" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"github.com/go-task/task/v3/internal/output" | ||||
| @@ -14,7 +16,7 @@ import ( | ||||
| func TestInterleaved(t *testing.T) { | ||||
| 	var b bytes.Buffer | ||||
| 	var o output.Output = output.Interleaved{} | ||||
| 	var w = o.WrapWriter(&b, "") | ||||
| 	var w = o.WrapWriter(&b, "", nil) | ||||
|  | ||||
| 	fmt.Fprintln(w, "foo\nbar") | ||||
| 	assert.Equal(t, "foo\nbar\n", b.String()) | ||||
| @@ -25,7 +27,7 @@ func TestInterleaved(t *testing.T) { | ||||
| func TestGroup(t *testing.T) { | ||||
| 	var b bytes.Buffer | ||||
| 	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") | ||||
| 	assert.Equal(t, "", b.String()) | ||||
| @@ -35,10 +37,43 @@ func TestGroup(t *testing.T) { | ||||
| 	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) { | ||||
| 	var b bytes.Buffer | ||||
| 	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) { | ||||
| 		b.Reset() | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
|  | ||||
| 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} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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/output" | ||||
| 	"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/read" | ||||
|  | ||||
| @@ -51,7 +52,7 @@ type Executor struct { | ||||
| 	Logger      *logger.Logger | ||||
| 	Compiler    compiler.Compiler | ||||
| 	Output      output.Output | ||||
| 	OutputStyle string | ||||
| 	OutputStyle output.Style | ||||
|  | ||||
| 	taskvars *taskfile.Vars | ||||
|  | ||||
| @@ -148,11 +149,11 @@ func (e *Executor) Setup() error { | ||||
| 		v = 2.6 | ||||
| 	} | ||||
| 	if v == 3.0 { | ||||
| 		v = 3.7 | ||||
| 		v = 3.8 | ||||
| 	} | ||||
|  | ||||
| 	if v > 3.7 { | ||||
| 		return fmt.Errorf(`task: Taskfile versions greater than v3.7 not implemented in the version of Task`) | ||||
| 	if v > 3.8 { | ||||
| 		return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`) | ||||
| 	} | ||||
|  | ||||
| 	// 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`) | ||||
| 	} | ||||
| 	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 { | ||||
| 		return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`) | ||||
| 	} | ||||
|  | ||||
| 	if e.OutputStyle != "" { | ||||
| 		e.Taskfile.Output = e.OutputStyle | ||||
| 	if v < 3.8 && e.Taskfile.Output.Group.IsSet() { | ||||
| 		return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`) | ||||
| 	} | ||||
| 	switch e.Taskfile.Output { | ||||
| 	case "", "interleaved": | ||||
| 		e.Output = output.Interleaved{} | ||||
| 	case "group": | ||||
| 		e.Output = output.Group{} | ||||
| 	case "prefixed": | ||||
| 		e.Output = output.Prefixed{} | ||||
| 	default: | ||||
| 		return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output) | ||||
|  | ||||
| 	if !e.OutputStyle.IsSet() { | ||||
| 		e.OutputStyle = e.Taskfile.Output | ||||
| 	} | ||||
| 	if o, err := e.OutputStyle.Build(); err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		e.Output = o | ||||
| 	} | ||||
|  | ||||
| 	if e.Taskfile.Method == "" { | ||||
| @@ -435,8 +434,13 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi | ||||
| 		if t.Interactive { | ||||
| 			outputWrapper = output.Interleaved{} | ||||
| 		} | ||||
| 		stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix) | ||||
| 		stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix) | ||||
| 		vars, err := e.Compiler.FastGetVariables(t, call) | ||||
| 		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() { | ||||
| 			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, | ||||
| 			Dir:     t.Dir, | ||||
| 			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 { | ||||
| 		t1.Expansions = t2.Expansions | ||||
| 	} | ||||
| 	if t2.Output != "" { | ||||
| 	if t2.Output.IsSet() { | ||||
| 		t1.Output = t2.Output | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -3,13 +3,15 @@ package taskfile | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/go-task/task/v3/internal/output" | ||||
| ) | ||||
|  | ||||
| // Taskfile represents a Taskfile.yml | ||||
| type Taskfile struct { | ||||
| 	Version    string | ||||
| 	Expansions int | ||||
| 	Output     string | ||||
| 	Output     output.Style | ||||
| 	Method     string | ||||
| 	Includes   *IncludedTaskfiles | ||||
| 	Vars       *Vars | ||||
| @@ -25,7 +27,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||
| 	var taskfile struct { | ||||
| 		Version    string | ||||
| 		Expansions int | ||||
| 		Output     string | ||||
| 		Output     output.Style | ||||
| 		Method     string | ||||
| 		Includes   *IncludedTaskfiles | ||||
| 		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!' | ||||
		Reference in New Issue
	
	Block a user