mirror of
https://github.com/go-task/task.git
synced 2025-04-23 12:18:57 +02:00
* refactor: moved/simplified snippets into its own file with tests * refactor: move snippet to taskfile package * feat: support snippets with line/col = 0 * feat: functional options for snippets * feat: added option to hide snippet indicators * feat: store raw lines for length calculations * feat: add debug function for TaskfileDecodeError * fix: decode errors from commands * fix: schema for defer cmd calls * fix: linting issues * refactor: split var and vars into different files like other structures
139 lines
3.2 KiB
Go
139 lines
3.2 KiB
Go
package errors
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/fatih/color"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
|
|
|
|
type (
|
|
TaskfileDecodeError struct {
|
|
Message string
|
|
Location string
|
|
Line int
|
|
Column int
|
|
Tag string
|
|
Snippet string
|
|
Err error
|
|
}
|
|
)
|
|
|
|
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))
|
|
fmt.Fprint(buf, err.Snippet)
|
|
return buf.String()
|
|
}
|
|
|
|
func (err *TaskfileDecodeError) Debug() string {
|
|
const indentWidth = 2
|
|
buf := &bytes.Buffer{}
|
|
fmt.Fprintln(buf, "TaskfileDecodeError:")
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
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, snippet string) *TaskfileDecodeError {
|
|
err.Location = location
|
|
err.Snippet = snippet
|
|
return err
|
|
}
|
|
|
|
func extractTypeErrorMessage(message string) string {
|
|
matches := typeErrorRegex.FindStringSubmatch(message)
|
|
if len(matches) == 2 {
|
|
return matches[1]
|
|
}
|
|
return message
|
|
}
|