1
0
mirror of https://github.com/go-task/task.git synced 2025-01-12 04:34:11 +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 {
var cmd string
if err := unmarshal(&cmd); err == nil {
func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var cmd string
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 := 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 := 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 := 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 := node.Decode(&taskCall); err == nil && taskCall.Task != "" {
c.Task = taskCall.Task
c.Vars = taskCall.Vars
return nil
}
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
}
var cmdStruct struct {
Cmd string
Silent bool
IgnoreError bool `yaml:"ignore_error"`
}
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
c.Cmd = cmdStruct.Cmd
c.Silent = cmdStruct.Silent
c.IgnoreError = cmdStruct.IgnoreError
return nil
}
var deferredCmd struct {
Defer string
}
if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" {
c.Defer = true
c.Cmd = deferredCmd.Defer
return nil
}
var deferredCall struct {
Defer Call
}
if err := unmarshal(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
c.Defer = true
c.Task = deferredCall.Defer.Task
c.Vars = deferredCall.Defer.Vars
return nil
}
var taskCall struct {
Task string
Vars *Vars
}
if err := unmarshal(&taskCall); err != nil {
return err
}
c.Task = taskCall.Task
c.Vars = taskCall.Vars
return nil
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
var task string
if err := unmarshal(&task); err == nil {
func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var task string
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 := node.Decode(&taskCall); err != nil {
return err
}
d.Task = taskCall.Task
d.Vars = taskCall.Vars
return nil
}
var taskCall struct {
Task string
Vars *Vars
}
if err := unmarshal(&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,24 +31,26 @@ 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,
// even number contains the keys, while odd numbers contains
// the values.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
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 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
var v IncludedTaskfile
if err := valueNode.Decode(&v); err != nil {
return err
var v IncludedTaskfile
if err := valueNode.Decode(&v); err != nil {
return err
}
tfs.Set(keyNode.Value, v)
}
tfs.Set(keyNode.Value, v)
return nil
}
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
@ -92,33 +93,40 @@ 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 {
var str string
if err := unmarshal(&str); err == nil {
func (it *IncludedTaskfile) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var str string
if err := node.Decode(&str); err != nil {
return err
}
it.Taskfile = str
return nil
case yaml.MappingNode:
var includedTaskfile struct {
Taskfile string
Dir string
Optional bool
Internal bool
Aliases []string
Vars *Vars
}
if err := node.Decode(&includedTaskfile); err != nil {
return err
}
it.Taskfile = includedTaskfile.Taskfile
it.Dir = includedTaskfile.Dir
it.Optional = includedTaskfile.Optional
it.Internal = includedTaskfile.Internal
it.Aliases = includedTaskfile.Aliases
it.AdvancedImport = true
it.Vars = includedTaskfile.Vars
return nil
}
var includedTaskfile struct {
Taskfile string
Dir string
Optional bool
Internal bool
Aliases []string
Vars *Vars
}
if err := unmarshal(&includedTaskfile); err != nil {
return err
}
it.Taskfile = includedTaskfile.Taskfile
it.Dir = includedTaskfile.Dir
it.Optional = includedTaskfile.Optional
it.Internal = includedTaskfile.Internal
it.Aliases = includedTaskfile.Aliases
it.AdvancedImport = true
it.Vars = includedTaskfile.Vars
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

View File

@ -2,6 +2,8 @@ package taskfile
import (
"fmt"
"gopkg.in/yaml.v3"
)
// Output of the Task output
@ -17,28 +19,35 @@ 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 {
var name string
if err := unmarshal(&name); err == nil {
func (s *Output) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var name string
if err := node.Decode(&name); err != nil {
return err
}
s.Name = name
return nil
case yaml.MappingNode:
var tmp struct {
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)
}
if tmp.Group == nil {
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
}
*s = Output{
Name: "group",
Group: *tmp.Group,
}
return nil
}
var tmp struct {
Group *OutputGroup
}
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 = Output{
Name: "group",
Group: *tmp.Group,
}
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.

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 := 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
}
var sh struct {
Sh string
Msg string
}
if err := unmarshal(&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,67 +45,80 @@ func (t *Task) Name() string {
return t.Task
}
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd Cmd
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
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 := node.Decode(&cmd); err != nil {
return err
}
t.Cmds = append(t.Cmds, &cmd)
return nil
}
var cmds []*Cmd
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
// Shortcut syntax for a simple task with a list of commands
case yaml.SequenceNode:
var cmds []*Cmd
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
Label string
Desc string
Summary string
Aliases []string
Sources []string
Generates []string
Status []string
Preconditions []*Precondition
Dir string
Vars *Vars
Env *Vars
Dotenv []string
Silent bool
Interactive bool
Internal bool
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
Run string
}
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.Summary = task.Summary
t.Aliases = task.Aliases
t.Sources = task.Sources
t.Generates = task.Generates
t.Status = task.Status
t.Preconditions = task.Preconditions
t.Dir = task.Dir
t.Vars = task.Vars
t.Env = task.Env
t.Dotenv = task.Dotenv
t.Silent = task.Silent
t.Interactive = task.Interactive
t.Internal = task.Internal
t.Method = task.Method
t.Prefix = task.Prefix
t.IgnoreError = task.IgnoreError
t.Run = task.Run
return nil
}
var task struct {
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Summary string
Aliases []string
Sources []string
Generates []string
Status []string
Preconditions []*Precondition
Dir string
Vars *Vars
Env *Vars
Dotenv []string
Silent bool
Interactive bool
Internal bool
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
Run string
}
if err := unmarshal(&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.Sources = task.Sources
t.Generates = task.Generates
t.Status = task.Status
t.Preconditions = task.Preconditions
t.Dir = task.Dir
t.Vars = task.Vars
t.Env = task.Env
t.Dotenv = task.Dotenv
t.Silent = task.Silent
t.Interactive = task.Interactive
t.Internal = task.Internal
t.Method = task.Method
t.Prefix = task.Prefix
t.IgnoreError = task.IgnoreError
t.Run = task.Run
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

View File

@ -3,6 +3,8 @@ package taskfile
import (
"fmt"
"strconv"
"gopkg.in/yaml.v3"
)
// Taskfile represents a Taskfile.yml
@ -21,50 +23,52 @@ type Taskfile struct {
Interval string
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
var taskfile struct {
Version string
Expansions int
Output Output
Method string
Includes *IncludedTaskfiles
Vars *Vars
Env *Vars
Tasks Tasks
Silent bool
Dotenv []string
Run string
Interval string
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
var taskfile struct {
Version string
Expansions int
Output Output
Method string
Includes *IncludedTaskfiles
Vars *Vars
Env *Vars
Tasks Tasks
Silent bool
Dotenv []string
Run string
Interval string
}
if err := node.Decode(&taskfile); err != nil {
return err
}
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Method = taskfile.Method
tf.Includes = taskfile.Includes
tf.Vars = taskfile.Vars
tf.Env = taskfile.Env
tf.Tasks = taskfile.Tasks
tf.Silent = taskfile.Silent
tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run
tf.Interval = taskfile.Interval
if tf.Expansions <= 0 {
tf.Expansions = 2
}
if tf.Vars == nil {
tf.Vars = &Vars{}
}
if tf.Env == nil {
tf.Env = &Vars{}
}
return nil
}
if err := unmarshal(&taskfile); err != nil {
return err
}
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Method = taskfile.Method
tf.Includes = taskfile.Includes
tf.Vars = taskfile.Vars
tf.Env = taskfile.Env
tf.Tasks = taskfile.Tasks
tf.Silent = taskfile.Silent
tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run
tf.Interval = taskfile.Interval
if tf.Expansions <= 0 {
tf.Expansions = 2
}
if tf.Vars == nil {
tf.Vars = &Vars{}
}
if tf.Env == nil {
tf.Env = &Vars{}
}
return nil
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
}
// ParsedVersion returns the version as a float64

View File

@ -1,7 +1,7 @@
package taskfile
import (
"errors"
"fmt"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
@ -13,26 +13,27 @@ 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,
// even number contains the keys, while odd numbers contains
// the values.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
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 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
var v Var
if err := valueNode.Decode(&v); err != nil {
return err
var v Var
if err := valueNode.Decode(&v); err != nil {
return err
}
vs.Set(keyNode.Value, v)
}
vs.Set(keyNode.Value, v)
return nil
}
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
@ -116,20 +117,27 @@ type Var struct {
Dir string
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var str string
if err := node.Decode(&str); err != nil {
return err
}
v.Static = str
return nil
case yaml.MappingNode:
var sh struct {
Sh string
}
if err := node.Decode(&sh); err != nil {
return err
}
v.Sh = sh.Sh
return nil
}
var sh struct {
Sh string
}
if err := unmarshal(&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())
}