Skip to content
113 changes: 36 additions & 77 deletions errors/error_taskfile_decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,28 @@ package errors

import (
"bytes"
"embed"
"cmp"
"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
Snippet string
Err error
}
TaskfileSnippet struct {
Lines []string
StartLine int
EndLine int
Padding int
}
)

func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
Expand Down Expand Up @@ -88,38 +64,44 @@ func (err *TaskfileDecodeError) Error() string {
}
}
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
fmt.Fprint(buf, err.Snippet)
return buf.String()
}

// 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
func (err *TaskfileDecodeError) Debug() string {
const indentWidth = 2
buf := &bytes.Buffer{}
fmt.Fprintln(buf, "TaskfileDecodeError:")

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)
// Recursively loop through the error chain and print any details
var debug func(error, int)
debug = func(err error, indent int) {
indentStr := strings.Repeat(" ", indent*indentWidth)

// Nothing left to unwrap
if err == nil {
fmt.Fprintf(buf, "%sEnd of chain\n", indentStr)
return
}

// If there are more lines to print, add a newline
if i < len(err.Snippet.Lines)-1 {
fmt.Fprintln(buf)
// Taskfile decode error
decodeErr := &TaskfileDecodeError{}
if errors.As(err, &decodeErr) {
fmt.Fprintf(buf, "%s%s (%s:%d:%d)\n",
indentStr,
cmp.Or(decodeErr.Message, "<no_message>"),
decodeErr.Location,
decodeErr.Line,
decodeErr.Column,
)
debug(errors.Unwrap(err), indent+1)
return
}
}

fmt.Fprintf(buf, "%s%s\n", indentStr, err)
debug(errors.Unwrap(err), indent+1)
}
debug(err, 0)
return buf.String()
}

Expand All @@ -141,23 +123,9 @@ func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
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)

func (err *TaskfileDecodeError) WithFileInfo(location string, snippet string) *TaskfileDecodeError {
err.Location = location
err.Snippet = TaskfileSnippet{
Lines: lines[start:end],
StartLine: start,
EndLine: end,
Padding: padding,
}

err.Snippet = snippet
return err
}

Expand All @@ -168,12 +136,3 @@ func extractTypeErrorMessage(message string) string {
}
return message
}

func digits(number int) int {
count := 0
for number != 0 {
number /= 10
count += 1
}
return count
}
78 changes: 37 additions & 41 deletions taskfile/ast/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,64 +51,60 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
return nil

case yaml.MappingNode:

// A command with additional options
var cmdStruct struct {
Cmd string
Task string
For *For
Silent bool
Set []string
Shopt []string
Vars *Vars
IgnoreError bool `yaml:"ignore_error"`
Defer *Defer
Platforms []*Platform
}
if err := node.Decode(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
c.Cmd = cmdStruct.Cmd
c.For = cmdStruct.For
c.Silent = cmdStruct.Silent
c.Set = cmdStruct.Set
c.Shopt = cmdStruct.Shopt
c.IgnoreError = cmdStruct.IgnoreError
c.Platforms = cmdStruct.Platforms
return nil
if err := node.Decode(&cmdStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
if cmdStruct.Defer != nil {

// A deferred command
var deferredCmd struct {
Defer string
Silent bool
}
if err := node.Decode(&deferredCmd); err == nil && deferredCmd.Defer != "" {
c.Defer = true
c.Cmd = deferredCmd.Defer
c.Silent = deferredCmd.Silent
return nil
}
// A deferred command
if cmdStruct.Defer.Cmd != "" {
c.Defer = true
c.Cmd = cmdStruct.Defer.Cmd
c.Silent = cmdStruct.Silent
return nil
}

// A deferred task call
var deferredCall struct {
Defer Call
}
if err := node.Decode(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
c.Defer = true
c.Task = deferredCall.Defer.Task
c.Vars = deferredCall.Defer.Vars
c.Silent = deferredCall.Defer.Silent
// A deferred task call
if cmdStruct.Defer.Task != "" {
c.Defer = true
c.Task = cmdStruct.Defer.Task
c.Vars = cmdStruct.Defer.Vars
c.Silent = cmdStruct.Defer.Silent
return nil
}
return nil
}

// A task call
var taskCall struct {
Task string
Vars *Vars
For *For
Silent bool
if cmdStruct.Task != "" {
c.Task = cmdStruct.Task
c.Vars = cmdStruct.Vars
c.For = cmdStruct.For
c.Silent = cmdStruct.Silent
return nil
}
if err := node.Decode(&taskCall); err == nil && taskCall.Task != "" {
c.Task = taskCall.Task
c.Vars = taskCall.Vars
c.For = taskCall.For
c.Silent = taskCall.Silent

// A command with additional options
if cmdStruct.Cmd != "" {
c.Cmd = cmdStruct.Cmd
c.For = cmdStruct.For
c.Silent = cmdStruct.Silent
c.Set = cmdStruct.Set
c.Shopt = cmdStruct.Shopt
c.IgnoreError = cmdStruct.IgnoreError
c.Platforms = cmdStruct.Platforms
return nil
}

Expand Down
45 changes: 45 additions & 0 deletions taskfile/ast/defer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ast

import (
"gopkg.in/yaml.v3"

"github.com/go-task/task/v3/errors"
)

type Defer struct {
Cmd string
Task string
Vars *Vars
Silent bool
}

func (d *Defer) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {

case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
d.Cmd = cmd
return nil

case yaml.MappingNode:
var deferStruct struct {
Defer string
Task string
Vars *Vars
Silent bool
}
if err := node.Decode(&deferStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
d.Cmd = deferStruct.Defer
d.Task = deferStruct.Task
d.Vars = deferStruct.Vars
d.Silent = deferStruct.Silent
return nil
}

return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("defer")
}
Loading
Loading