1
0
mirror of https://github.com/go-task/task.git synced 2025-04-15 11:56:34 +02:00

feat: improve unmarshal error handling and use v3 yaml interface everywhere (#959)

This commit is contained in:
Pete Davison 2022-12-19 01:11:31 +00:00 committed by GitHub
parent e4158dc5e4
commit 491888f6c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 341 additions and 256 deletions

View File

@ -1,5 +1,11 @@
package taskfile
import (
"fmt"
"gopkg.in/yaml.v3"
)
// Cmd is a task command
type Cmd struct {
Cmd string
@ -16,68 +22,93 @@ type Dep struct {
Vars *Vars
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var cmd string
if err := unmarshal(&cmd); err == nil {
if err := node.Decode(&cmd); err != nil {
return err
}
c.Cmd = cmd
return nil
}
case yaml.MappingNode:
// A command with additional options
var cmdStruct struct {
Cmd string
Silent bool
IgnoreError bool `yaml:"ignore_error"`
}
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
if err := node.Decode(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
c.Cmd = cmdStruct.Cmd
c.Silent = cmdStruct.Silent
c.IgnoreError = cmdStruct.IgnoreError
return nil
}
// A deferred command
var deferredCmd struct {
Defer string
}
if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" {
if err := node.Decode(&deferredCmd); err == nil && deferredCmd.Defer != "" {
c.Defer = true
c.Cmd = deferredCmd.Defer
return nil
}
// A deferred task call
var deferredCall struct {
Defer Call
}
if err := unmarshal(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
if err := node.Decode(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
c.Defer = true
c.Task = deferredCall.Defer.Task
c.Vars = deferredCall.Defer.Vars
return nil
}
// A task call
var taskCall struct {
Task string
Vars *Vars
}
if err := unmarshal(&taskCall); err != nil {
return err
}
if err := node.Decode(&taskCall); err == nil && taskCall.Task != "" {
c.Task = taskCall.Task
c.Vars = taskCall.Vars
return nil
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
}
func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var task string
if err := unmarshal(&task); err == nil {
if err := node.Decode(&task); err != nil {
return err
}
d.Task = task
return nil
}
case yaml.MappingNode:
var taskCall struct {
Task string
Vars *Vars
}
if err := unmarshal(&taskCall); err != nil {
if err := node.Decode(&taskCall); err != nil {
return err
}
d.Task = taskCall.Task
d.Vars = taskCall.Vars
return nil
}
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
}

View File

@ -1,7 +1,6 @@
package taskfile
import (
"errors"
"fmt"
"path/filepath"
@ -32,11 +31,10 @@ type IncludedTaskfiles struct {
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
if node.Kind != yaml.MappingNode {
return errors.New("task: includes is not a map")
}
switch node.Kind {
// NOTE(@andreynering): on this style of custom unmarsheling,
case yaml.MappingNode:
// NOTE(@andreynering): on this style of custom unmarshalling,
// even number contains the keys, while odd numbers contains
// the values.
for i := 0; i < len(node.Content); i += 2 {
@ -52,6 +50,9 @@ func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
}
// Len returns the length of the map
func (tfs *IncludedTaskfiles) Len() int {
if tfs == nil {
@ -92,14 +93,18 @@ func (tfs *IncludedTaskfiles) Range(yield func(key string, includedTaskfile Incl
return nil
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (it *IncludedTaskfile) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var str string
if err := unmarshal(&str); err == nil {
if err := node.Decode(&str); err != nil {
return err
}
it.Taskfile = str
return nil
}
case yaml.MappingNode:
var includedTaskfile struct {
Taskfile string
Dir string
@ -108,7 +113,7 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
Aliases []string
Vars *Vars
}
if err := unmarshal(&includedTaskfile); err != nil {
if err := node.Decode(&includedTaskfile); err != nil {
return err
}
it.Taskfile = includedTaskfile.Taskfile
@ -121,6 +126,9 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
}
// DeepCopy creates a new instance of IncludedTaskfile and copies
// data by value from the source struct.
func (it *IncludedTaskfile) DeepCopy() *IncludedTaskfile {

View File

@ -2,6 +2,8 @@ package taskfile
import (
"fmt"
"gopkg.in/yaml.v3"
)
// Output of the Task output
@ -17,18 +19,22 @@ func (s *Output) IsSet() bool {
return s.Name != ""
}
// UnmarshalYAML implements yaml.Unmarshaler
// It accepts a scalar node representing the Output.Name or a mapping node representing the OutputGroup.
func (s *Output) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (s *Output) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var name string
if err := unmarshal(&name); err == nil {
if err := node.Decode(&name); err != nil {
return err
}
s.Name = name
return nil
}
case yaml.MappingNode:
var tmp struct {
Group *OutputGroup
}
if err := unmarshal(&tmp); err != nil {
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)
}
if tmp.Group == nil {
@ -41,6 +47,9 @@ func (s *Output) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
}
// OutputGroup is the style options specific to the Group style.
type OutputGroup struct {
Begin, End string

View File

@ -3,6 +3,8 @@ package taskfile
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
)
var (
@ -17,29 +19,33 @@ type Precondition struct {
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd string
func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
if err := unmarshal(&cmd); err == nil {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
}
p.Sh = cmd
p.Msg = fmt.Sprintf("`%s` failed", cmd)
return nil
}
case yaml.MappingNode:
var sh struct {
Sh string
Msg string
}
if err := unmarshal(&sh); err != nil {
if err := node.Decode(&sh); err != nil {
return err
}
p.Sh = sh.Sh
p.Msg = sh.Msg
if p.Msg == "" {
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
}
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
}

View File

@ -1,5 +1,11 @@
package taskfile
import (
"fmt"
"gopkg.in/yaml.v3"
)
// Tasks represents a group of tasks
type Tasks map[string]*Task
@ -39,19 +45,29 @@ func (t *Task) Name() string {
return t.Task
}
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
// Shortcut syntax for a task with a single command
case yaml.ScalarNode:
var cmd Cmd
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
if err := node.Decode(&cmd); err != nil {
return err
}
t.Cmds = append(t.Cmds, &cmd)
return nil
}
// Shortcut syntax for a simple task with a list of commands
case yaml.SequenceNode:
var cmds []*Cmd
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
if err := node.Decode(&cmds); err != nil {
return err
}
t.Cmds = cmds
return nil
}
// Full task object
case yaml.MappingNode:
var task struct {
Cmds []*Cmd
Deps []*Dep
@ -75,15 +91,15 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
IgnoreError bool `yaml:"ignore_error"`
Run string
}
if err := unmarshal(&task); err != nil {
if err := node.Decode(&task); err != nil {
return err
}
t.Cmds = task.Cmds
t.Deps = task.Deps
t.Label = task.Label
t.Desc = task.Desc
t.Aliases = task.Aliases
t.Summary = task.Summary
t.Aliases = task.Aliases
t.Sources = task.Sources
t.Generates = task.Generates
t.Status = task.Status
@ -102,6 +118,9 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
}
// DeepCopy creates a new instance of Task and copies
// data by value from the source struct.
func (t *Task) DeepCopy() *Task {

View File

@ -3,6 +3,8 @@ package taskfile
import (
"fmt"
"strconv"
"gopkg.in/yaml.v3"
)
// Taskfile represents a Taskfile.yml
@ -21,8 +23,10 @@ type Taskfile struct {
Interval string
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
var taskfile struct {
Version string
Expansions int
@ -37,11 +41,9 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
Run string
Interval string
}
if err := unmarshal(&taskfile); err != nil {
if err := node.Decode(&taskfile); err != nil {
return err
}
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
@ -54,7 +56,6 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run
tf.Interval = taskfile.Interval
if tf.Expansions <= 0 {
tf.Expansions = 2
}
@ -67,6 +68,9 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
}
// ParsedVersion returns the version as a float64
func (tf *Taskfile) ParsedVersion() (float64, error) {
v, err := strconv.ParseFloat(tf.Version, 64)

View File

@ -1,7 +1,7 @@
package taskfile
import (
"errors"
"fmt"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
@ -13,13 +13,11 @@ type Vars struct {
Mapping map[string]Var
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
if node.Kind != yaml.MappingNode {
return errors.New("task: vars is not a map")
}
switch node.Kind {
// NOTE(@andreynering): on this style of custom unmarsheling,
case yaml.MappingNode:
// NOTE(@andreynering): on this style of custom unmarshalling,
// even number contains the keys, while odd numbers contains
// the values.
for i := 0; i < len(node.Content); i += 2 {
@ -35,6 +33,9 @@ func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variables", node.Line, node.ShortTag())
}
// DeepCopy creates a new instance of Vars and copies
// data by value from the source struct.
func (vs *Vars) DeepCopy() *Vars {
@ -116,20 +117,27 @@ type Var struct {
Dir string
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var str string
if err := unmarshal(&str); err == nil {
if err := node.Decode(&str); err != nil {
return err
}
v.Static = str
return nil
}
case yaml.MappingNode:
var sh struct {
Sh string
}
if err := unmarshal(&sh); err != nil {
if err := node.Decode(&sh); err != nil {
return err
}
v.Sh = sh.Sh
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variable", node.Line, node.ShortTag())
}