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

feat: better yaml parsing and error handling (#1619)

This commit is contained in:
Pete Davison 2024-05-16 02:24:02 +01:00 committed by GitHub
parent 635e3f4e7d
commit 8d138a5eea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 299 additions and 77 deletions

View File

@ -131,7 +131,8 @@ func run() error {
return err
}
if err := e.Setup(); err != nil {
err := e.Setup()
if err != nil {
return err
}

View File

@ -0,0 +1,179 @@
package errors
import (
"bytes"
"embed"
"errors"
"fmt"
"regexp"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/quick"
"github.com/alecthomas/chroma/v2/styles"
"github.com/fatih/color"
"gopkg.in/yaml.v3"
)
//go:embed themes/*.xml
var embedded embed.FS
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
func init() {
r, err := embedded.Open("themes/task.xml")
if err != nil {
panic(err)
}
style, err := chroma.NewXMLStyle(r)
if err != nil {
panic(err)
}
styles.Register(style)
}
type (
TaskfileDecodeError struct {
Message string
Location string
Line int
Column int
Tag string
Snippet TaskfileSnippet
Err error
}
TaskfileSnippet struct {
Lines []string
StartLine int
EndLine int
Padding int
}
)
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
// If the error is already a DecodeError, return it
taskfileInvalidErr := &TaskfileDecodeError{}
if errors.As(err, &taskfileInvalidErr) {
return taskfileInvalidErr
}
return &TaskfileDecodeError{
Line: node.Line,
Column: node.Column,
Tag: node.ShortTag(),
Err: err,
}
}
func (err *TaskfileDecodeError) Error() string {
buf := &bytes.Buffer{}
// Print the error message
if err.Message != "" {
fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
} else {
// Extract the errors from the TypeError
te := &yaml.TypeError{}
if errors.As(err.Err, &te) {
if len(te.Errors) > 1 {
fmt.Fprintln(buf, color.RedString("errs:"))
for _, message := range te.Errors {
fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
}
} else {
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
}
} else {
// Otherwise print the error message normally
fmt.Fprintln(buf, color.RedString("err: %s", err.Err))
}
}
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
// Print the snippet
maxLineNumberDigits := digits(err.Snippet.EndLine)
lineNumberSpacer := strings.Repeat(" ", maxLineNumberDigits)
columnSpacer := strings.Repeat(" ", err.Column-1)
for i, line := range err.Snippet.Lines {
currentLine := err.Snippet.StartLine + i + 1
lineIndicator := " "
if currentLine == err.Line {
lineIndicator = ">"
}
columnIndicator := "^"
// Print each line
lineIndicator = color.RedString(lineIndicator)
columnIndicator = color.RedString(columnIndicator)
lineNumberFormat := fmt.Sprintf("%%%dd", maxLineNumberDigits)
lineNumber := fmt.Sprintf(lineNumberFormat, currentLine)
fmt.Fprintf(buf, "%s %s | %s", lineIndicator, lineNumber, line)
// Print the column indicator
if currentLine == err.Line {
fmt.Fprintf(buf, "\n %s | %s%s", lineNumberSpacer, columnSpacer, columnIndicator)
}
// If there are more lines to print, add a newline
if i < len(err.Snippet.Lines)-1 {
fmt.Fprintln(buf)
}
}
return buf.String()
}
func (err *TaskfileDecodeError) Unwrap() error {
return err.Err
}
func (err *TaskfileDecodeError) Code() int {
return CodeTaskfileDecode
}
func (err *TaskfileDecodeError) WithMessage(format string, a ...any) *TaskfileDecodeError {
err.Message = fmt.Sprintf(format, a...)
return err
}
func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
err.Message = fmt.Sprintf("cannot unmarshal %s into %s", err.Tag, t)
return err
}
func (err *TaskfileDecodeError) WithFileInfo(location string, b []byte, padding int) *TaskfileDecodeError {
buf := &bytes.Buffer{}
if err := quick.Highlight(buf, string(b), "yaml", "terminal", "task"); err != nil {
buf.WriteString(string(b))
}
lines := strings.Split(buf.String(), "\n")
start := max(err.Line-1-padding, 0)
end := min(err.Line+padding, len(lines)-1)
err.Location = location
err.Snippet = TaskfileSnippet{
Lines: lines[start:end],
StartLine: start,
EndLine: end,
Padding: padding,
}
return err
}
func extractTypeErrorMessage(message string) string {
matches := typeErrorRegex.FindStringSubmatch(message)
if len(matches) == 2 {
return matches[1]
}
return message
}
func digits(number int) int {
count := 0
for number != 0 {
number /= 10
count += 1
}
return count
}

View File

@ -12,14 +12,14 @@ const (
const (
CodeTaskfileNotFound int = iota + 100
CodeTaskfileAlreadyExists
CodeTaskfileInvalid
CodeTaskfileDecode
CodeTaskfileFetchFailed
CodeTaskfileNotTrusted
CodeTaskfileNotSecure
CodeTaskfileCacheNotFound
CodeTaskfileVersionCheckError
CodeTaskfileNetworkTimeout
_ // CodeTaskfileDuplicateInclude
CodeTaskfileInvalid
CodeTaskfileCycle
)
@ -58,3 +58,8 @@ func Is(err, target error) bool {
func As(err error, target any) bool {
return errors.As(err, target)
}
// Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package.
func Unwrap(err error) error {
return errors.Unwrap(err)
}

17
errors/themes/task.xml Normal file
View File

@ -0,0 +1,17 @@
<style name="task">
<entry type="Background" style="bg:#eee8d5"/>
<entry type="Keyword" style="#859900"/>
<entry type="KeywordConstant" style=""/>
<entry type="KeywordNamespace" style="#dc322f"/>
<entry type="KeywordType" style=""/>
<entry type="Name" style="#268bd2"/>
<entry type="NameBuiltin" style="#cb4b16"/>
<entry type="NameClass" style="#cb4b16"/>
<entry type="NameTag" style=""/>
<entry type="Literal" style="#2aa198"/>
<entry type="LiteralNumber" style=""/>
<entry type="OperatorWord" style="#859900"/>
<entry type="Comment" style="italic #93a1a1"/>
<entry type="Generic" style="#d33682"/>
<entry type="Text" style="#586e75"/>
</style>

2
go.mod
View File

@ -4,6 +4,7 @@ go 1.21.0
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/alecthomas/chroma/v2 v2.13.0
github.com/davecgh/go-spew v1.1.1
github.com/dominikbraun/graph v0.23.0
github.com/fatih/color v1.16.0
@ -25,6 +26,7 @@ require (
)
require (
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect

10
go.sum
View File

@ -1,9 +1,17 @@
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
@ -16,6 +24,8 @@ github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90 h1:JBbiZ2CXIZ9Upe
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=

View File

@ -1042,7 +1042,7 @@ func TestIncludesIncorrect(t *testing.T) {
err := e.Setup()
require.Error(t, err)
assert.Contains(t, err.Error(), "task: Failed to parse testdata/includes_incorrect/incomplete.yml:")
assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error())
}
func TestIncludesEmptyMain(t *testing.T) {

View File

@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@ -46,7 +45,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
c.Cmd = cmd
return nil
@ -110,8 +109,8 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in command")
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("command")
}

View File

@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// Dep is a task dependency
@ -32,7 +32,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var task string
if err := node.Decode(&task); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
d.Task = task
return nil
@ -45,7 +45,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
Silent bool
}
if err := node.Decode(&taskCall); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
d.Task = taskCall.Task
d.For = taskCall.For
@ -54,5 +54,5 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("dependency")
}

View File

@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@ -22,7 +21,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var from string
if err := node.Decode(&from); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
f.From = from
return nil
@ -30,7 +29,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.SequenceNode:
var list []any
if err := node.Decode(&list); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
f.List = list
return nil
@ -41,17 +40,19 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
Split string
As string
}
if err := node.Decode(&forStruct); err == nil && forStruct.Var != "" {
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
return nil
if err := node.Decode(&forStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
return fmt.Errorf("yaml: line %d: invalid keys in for", node.Line)
if forStruct.Var == "" {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
}
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into for", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("for")
}
func (f *For) DeepCopy() *For {

View File

@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
type Glob struct {
@ -13,20 +13,22 @@ type Glob struct {
func (g *Glob) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
g.Glob = node.Value
return nil
case yaml.MappingNode:
var glob struct {
Exclude string
}
if err := node.Decode(&glob); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
g.Glob = glob.Exclude
g.Negate = true
return nil
default:
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
}
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("glob")
}

View File

@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
omap "github.com/go-task/task/v3/internal/omap"
)
@ -38,7 +37,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
var v Include
if err := valueNode.Decode(&v); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Namespace = keyNode.Value
includes.Set(keyNode.Value, &v)
@ -46,7 +45,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("includes")
}
// Len returns the length of the map
@ -71,7 +70,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var str string
if err := node.Decode(&str); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
include.Taskfile = str
return nil
@ -86,7 +85,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
Vars *Vars
}
if err := node.Decode(&includedTaskfile); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
include.Taskfile = includedTaskfile.Taskfile
include.Dir = includedTaskfile.Dir
@ -98,7 +97,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("include")
}
// DeepCopy creates a new instance of IncludedTaskfile and copies

View File

@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// Output of the Task output
@ -25,7 +25,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var name string
if err := node.Decode(&name); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
s.Name = name
return nil
@ -35,10 +35,10 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
Group *OutputGroup
}
if err := node.Decode(&tmp); err != nil {
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
return errors.NewTaskfileDecodeError(err, node)
}
if tmp.Group == nil {
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" key when in mapping form`)
}
*s = Output{
Name: "group",
@ -47,7 +47,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("output")
}
// OutputGroup is the style options specific to the Group style.

View File

@ -6,6 +6,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/goext"
)
@ -30,7 +31,7 @@ type ErrInvalidPlatform struct {
}
func (err *ErrInvalidPlatform) Error() string {
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
return fmt.Sprintf(`invalid platform "%s"`, err.Platform)
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
@ -39,14 +40,14 @@ func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var platform string
if err := node.Decode(&platform); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
if err := p.parsePlatform(platform); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("platform")
}
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)

View File

@ -26,10 +26,10 @@ func TestPlatformParsing(t *testing.T) {
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
{Input: "invalid", Error: `invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `invalid platform "invalid/amd64"`},
}
for _, test := range tests {

View File

@ -1,14 +1,12 @@
package ast
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
)
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
var ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
"github.com/go-task/task/v3/errors"
)
// Precondition represents a precondition necessary for a task to run
type Precondition struct {
@ -33,7 +31,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
p.Sh = cmd
p.Msg = fmt.Sprintf("`%s` failed", cmd)
@ -45,7 +43,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
Msg string
}
if err := node.Decode(&sh); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
p.Sh = sh.Sh
p.Msg = sh.Msg
@ -55,5 +53,5 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("precondition")
}

View File

@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@ -83,7 +84,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd Cmd
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
t.Cmds = append(t.Cmds, &cmd)
return nil
@ -92,7 +93,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
case yaml.SequenceNode:
var cmds []*Cmd
if err := node.Decode(&cmds); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
t.Cmds = cmds
return nil
@ -130,11 +131,11 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
Watch bool
}
if err := node.Decode(&task); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
if task.Cmd != nil {
if task.Cmds != nil {
return fmt.Errorf("yaml: line %d: task cannot have both cmd and cmds", node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("task cannot have both cmd and cmds")
}
t.Cmds = []*Cmd{task.Cmd}
} else {
@ -169,7 +170,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("task")
}
// DeepCopy creates a new instance of Task and copies

View File

@ -1,12 +1,13 @@
package ast
import (
"errors"
"fmt"
"time"
"github.com/Masterminds/semver/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// NamespaceSeparator contains the character that separates namespaces
@ -77,7 +78,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
Interval time.Duration
}
if err := node.Decode(&taskfile); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
tf.Version = taskfile.Version
tf.Output = taskfile.Output
@ -101,5 +102,5 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("taskfile")
}

View File

@ -6,6 +6,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/omap"
)
@ -118,7 +119,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
tasks := omap.New[string, *Task]()
if err := node.Decode(&tasks); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
// nolint: errcheck
@ -150,7 +151,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into tasks", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("tasks")
}
func taskNameWithNamespace(taskName string, namespace string) string {

View File

@ -1,11 +1,11 @@
package ast
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/omap"
)
@ -95,7 +95,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
if experiments.MapVariables.Value == "1" {
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
// If the value is a string and it starts with $, then it's a shell command
if str, ok := value.(string); ok {
@ -123,7 +123,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
Yaml string
}
if err := node.Decode(&m); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Sh = m.Sh
v.Ref = m.Ref
@ -132,12 +132,12 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
v.Yaml = m.Yaml
return nil
default:
return fmt.Errorf(`yaml: line %d: %q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, node.Line, key)
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, key)
}
default:
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Value = value
return nil
@ -149,13 +149,13 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
if len(node.Content) > 2 || node.Content[0].Value != "sh" {
return fmt.Errorf(`task: line %d: maps cannot be assigned to variables`, node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
}
var sh struct {
Sh string
}
if err := node.Decode(&sh); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Sh = sh.Sh
return nil
@ -163,7 +163,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
default:
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Value = value
return nil

View File

@ -265,6 +265,11 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
var tf ast.Taskfile
if err := yaml.Unmarshal(b, &tf); err != nil {
// Decode the taskfile and add the file info the any errors
taskfileInvalidErr := &errors.TaskfileDecodeError{}
if errors.As(err, &taskfileInvalidErr) {
return nil, taskfileInvalidErr.WithFileInfo(node.Location(), b, 2)
}
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
}