1
0
mirror of https://github.com/go-task/task.git synced 2025-06-27 00:51:05 +02:00

add custom Cmd and Dep types

This commit is contained in:
Andrey Nering
2017-07-02 15:30:50 -03:00
parent a3bfa13670
commit 196d3cb13d
10 changed files with 245 additions and 53 deletions

64
command.go Normal file
View File

@ -0,0 +1,64 @@
package task
import (
"errors"
"strings"
)
type Params map[string]string
type Cmd struct {
Cmd string
Task string
Params Params
}
type Dep struct {
Task string
Params Params
}
var (
ErrCantUnmarshalCmd = errors.New("task: can't unmarshal cmd value")
ErrCantUnmarshalDep = errors.New("task: can't unmarshal dep value")
)
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd string
if err := unmarshal(&cmd); err == nil {
if strings.HasPrefix(cmd, "^") {
c.Task = strings.TrimPrefix(cmd, "^")
} else {
c.Cmd = cmd
}
return nil
}
var taskCall struct {
Task string
Params Params
}
if err := unmarshal(&taskCall); err == nil {
c.Task = taskCall.Task
c.Params = taskCall.Params
return nil
}
return ErrCantUnmarshalCmd
}
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
var task string
if err := unmarshal(&task); err == nil {
d.Task = task
return nil
}
var taskCall struct {
Task string
Params Params
}
if err := unmarshal(&taskCall); err == nil {
d.Task = taskCall.Task
d.Params = taskCall.Params
return nil
}
return ErrCantUnmarshalDep
}

54
command_test.go Normal file
View File

@ -0,0 +1,54 @@
package task_test
import (
"testing"
"github.com/go-task/task"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
)
func TestCmdParse(t *testing.T) {
const (
yamlCmd = `echo "a string command"`
yamlDep = `"task-name"`
yamlTaskCall = `
task: another-task
params:
PARAM1: VALUE1
PARAM2: VALUE2
`
)
tests := []struct {
content string
v interface{}
expected interface{}
}{
{
yamlCmd,
&task.Cmd{},
&task.Cmd{Cmd: `echo "a string command"`},
},
{
yamlTaskCall,
&task.Cmd{},
&task.Cmd{Task: "another-task", Params: task.Params{"PARAM1": "VALUE1", "PARAM2": "VALUE2"}},
},
{
yamlDep,
&task.Dep{},
&task.Dep{Task: "task-name"},
},
{
yamlTaskCall,
&task.Dep{},
&task.Dep{Task: "another-task", Params: task.Params{"PARAM1": "VALUE1", "PARAM2": "VALUE2"}},
},
}
for _, test := range tests {
err := yaml.Unmarshal([]byte(test.content), test.v)
assert.NoError(t, err)
assert.Equal(t, test.expected, test.v)
}
}

View File

@ -13,7 +13,7 @@ func (e *Executor) HasCyclicDep() bool {
defer delete(visits, name) defer delete(visits, name)
for _, d := range t.Deps { for _, d := range t.Deps {
if !checkCyclicDep(d, e.Tasks[d]) { if !checkCyclicDep(d.Task, e.Tasks[d.Task]) {
return false return false
} }
} }

View File

@ -10,10 +10,10 @@ func TestCyclicDepCheck(t *testing.T) {
isCyclic := &task.Executor{ isCyclic := &task.Executor{
Tasks: task.Tasks{ Tasks: task.Tasks{
"task-a": &task.Task{ "task-a": &task.Task{
Deps: []string{"task-b"}, Deps: []*task.Dep{&task.Dep{Task: "task-b"}},
}, },
"task-b": &task.Task{ "task-b": &task.Task{
Deps: []string{"task-a"}, Deps: []*task.Dep{&task.Dep{Task: "task-a"}},
}, },
}, },
} }
@ -25,10 +25,10 @@ func TestCyclicDepCheck(t *testing.T) {
isNotCyclic := &task.Executor{ isNotCyclic := &task.Executor{
Tasks: task.Tasks{ Tasks: task.Tasks{
"task-a": &task.Task{ "task-a": &task.Task{
Deps: []string{"task-c"}, Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
}, },
"task-b": &task.Task{ "task-b": &task.Task{
Deps: []string{"task-c"}, Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
}, },
"task-c": &task.Task{}, "task-c": &task.Task{},
}, },

81
task.go
View File

@ -38,8 +38,8 @@ type Tasks map[string]*Task
// Task represents a task // Task represents a task
type Task struct { type Task struct {
Cmds []string Cmds []*Cmd
Deps []string Deps []*Dep
Desc string Desc string
Sources []string Sources []string
Generates []string Generates []string
@ -83,7 +83,7 @@ func (e *Executor) Run(args ...string) error {
} }
for _, a := range args { for _, a := range args {
if err := e.RunTask(context.Background(), a); err != nil { if err := e.RunTask(context.Background(), a, nil); err != nil {
return err return err
} }
} }
@ -91,18 +91,18 @@ func (e *Executor) Run(args ...string) error {
} }
// RunTask runs a task by its name // RunTask runs a task by its name
func (e *Executor) RunTask(ctx context.Context, name string) error { func (e *Executor) RunTask(ctx context.Context, name string, params Params) error {
t, ok := e.Tasks[name] t, ok := e.Tasks[name]
if !ok { if !ok {
return &taskNotFoundError{name} return &taskNotFoundError{name}
} }
if err := e.runDeps(ctx, name); err != nil { if err := e.runDeps(ctx, name, params); err != nil {
return err return err
} }
if !e.Force { if !e.Force {
upToDate, err := e.isTaskUpToDate(ctx, name) upToDate, err := e.isTaskUpToDate(ctx, name, params)
if err != nil { if err != nil {
return err return err
} }
@ -113,27 +113,27 @@ func (e *Executor) RunTask(ctx context.Context, name string) error {
} }
for i := range t.Cmds { for i := range t.Cmds {
if err := e.runCommand(ctx, name, i); err != nil { if err := e.runCommand(ctx, name, i, params); err != nil {
return &taskRunError{name, err} return &taskRunError{name, err}
} }
} }
return nil return nil
} }
func (e *Executor) runDeps(ctx context.Context, task string) error { func (e *Executor) runDeps(ctx context.Context, task string, params Params) error {
g, ctx := errgroup.WithContext(ctx) g, ctx := errgroup.WithContext(ctx)
t := e.Tasks[task] t := e.Tasks[task]
for _, d := range t.Deps { for _, d := range t.Deps {
dep := d d := d
g.Go(func() error { g.Go(func() error {
dep, err := e.ReplaceVariables(task, dep) dep, err := e.ReplaceVariables(d.Task, task, params)
if err != nil { if err != nil {
return err return err
} }
if err = e.RunTask(ctx, dep); err != nil { if err = e.RunTask(ctx, dep, d.Params); err != nil {
return err return err
} }
return nil return nil
@ -146,28 +146,32 @@ func (e *Executor) runDeps(ctx context.Context, task string) error {
return nil return nil
} }
func (e *Executor) isTaskUpToDate(ctx context.Context, task string) (bool, error) { func (e *Executor) isTaskUpToDate(ctx context.Context, task string, params Params) (bool, error) {
t := e.Tasks[task] t := e.Tasks[task]
if len(t.Status) > 0 { if len(t.Status) > 0 {
return e.isUpToDateStatus(ctx, task) return e.isUpToDateStatus(ctx, task, params)
} }
return e.isUpToDateTimestamp(ctx, task) return e.isUpToDateTimestamp(ctx, task, params)
} }
func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, error) { func (e *Executor) isUpToDateStatus(ctx context.Context, task string, params Params) (bool, error) {
t := e.Tasks[task] t := e.Tasks[task]
environ, err := e.getEnviron(task) environ, err := e.getEnviron(task, params)
if err != nil { if err != nil {
return false, err return false, err
} }
dir, err := e.getTaskDir(task) dir, err := e.getTaskDir(task, params)
if err != nil {
return false, err
}
status, err := e.ReplaceSliceVariables(t.Status, task, params)
if err != nil { if err != nil {
return false, err return false, err
} }
for _, s := range t.Status { for _, s := range status {
err = execext.RunCommand(&execext.RunCommandOptions{ err = execext.RunCommand(&execext.RunCommandOptions{
Context: ctx, Context: ctx,
Command: s, Command: s,
@ -181,23 +185,23 @@ func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, err
return true, nil return true, nil
} }
func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string) (bool, error) { func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string, params Params) (bool, error) {
t := e.Tasks[task] t := e.Tasks[task]
if len(t.Sources) == 0 || len(t.Generates) == 0 { if len(t.Sources) == 0 || len(t.Generates) == 0 {
return false, nil return false, nil
} }
dir, err := e.getTaskDir(task) dir, err := e.getTaskDir(task, params)
if err != nil { if err != nil {
return false, err return false, err
} }
sources, err := e.ReplaceSliceVariables(task, t.Sources) sources, err := e.ReplaceSliceVariables(t.Sources, task, params)
if err != nil { if err != nil {
return false, err return false, err
} }
generates, err := e.ReplaceSliceVariables(task, t.Generates) generates, err := e.ReplaceSliceVariables(t.Generates, task, params)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -215,28 +219,25 @@ func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string) (bool,
return generatesMinTime.After(sourcesMaxTime), nil return generatesMinTime.After(sourcesMaxTime), nil
} }
func (e *Executor) runCommand(ctx context.Context, task string, i int) error { func (e *Executor) runCommand(ctx context.Context, task string, i int, params Params) error {
t := e.Tasks[task] t := e.Tasks[task]
cmd := t.Cmds[i]
c, err := e.ReplaceVariables(task, t.Cmds[i]) if cmd.Cmd == "" {
return e.RunTask(ctx, cmd.Task, cmd.Params)
}
c, err := e.ReplaceVariables(cmd.Cmd, task, params)
if err != nil { if err != nil {
return err return err
} }
if strings.HasPrefix(c, "^") { dir, err := e.getTaskDir(task, params)
c = strings.TrimPrefix(c, "^")
if err = e.RunTask(ctx, c); err != nil {
return err
}
return nil
}
dir, err := e.getTaskDir(task)
if err != nil { if err != nil {
return err return err
} }
envs, err := e.getEnviron(task) envs, err := e.getEnviron(task, params)
if err != nil { if err != nil {
return err return err
} }
@ -266,14 +267,14 @@ func (e *Executor) runCommand(ctx context.Context, task string, i int) error {
return nil return nil
} }
func (e *Executor) getTaskDir(name string) (string, error) { func (e *Executor) getTaskDir(task string, params Params) (string, error) {
t := e.Tasks[name] t := e.Tasks[task]
exeDir, err := e.ReplaceVariables(name, e.Dir) exeDir, err := e.ReplaceVariables(e.Dir, task, params)
if err != nil { if err != nil {
return "", err return "", err
} }
taskDir, err := e.ReplaceVariables(name, t.Dir) taskDir, err := e.ReplaceVariables(t.Dir, task, params)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -281,7 +282,7 @@ func (e *Executor) getTaskDir(name string) (string, error) {
return filepath.Join(exeDir, taskDir), nil return filepath.Join(exeDir, taskDir), nil
} }
func (e *Executor) getEnviron(task string) ([]string, error) { func (e *Executor) getEnviron(task string, params Params) ([]string, error) {
t := e.Tasks[task] t := e.Tasks[task]
if t.Env == nil { if t.Env == nil {
@ -291,7 +292,7 @@ func (e *Executor) getEnviron(task string) ([]string, error) {
envs := os.Environ() envs := os.Environ()
for k, v := range t.Env { for k, v := range t.Env {
env, err := e.ReplaceVariables(task, fmt.Sprintf("%s=%s", k, v)) env, err := e.ReplaceVariables(fmt.Sprintf("%s=%s", k, v), task, params)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -166,3 +166,35 @@ func TestInit(t *testing.T) {
t.Errorf("Taskfile.yml should exists") t.Errorf("Taskfile.yml should exists")
} }
} }
func TestParams(t *testing.T) {
const dir = "testdata/params"
var files = []struct {
file string
content string
}{
{"hello.txt", "Hello\n"},
{"world.txt", "World\n"},
{"exclamation.txt", "!\n"},
{"dep1.txt", "Dependence1\n"},
{"dep2.txt", "Dependence2\n"},
}
for _, f := range files {
_ = os.Remove(filepath.Join(dir, f.file))
}
e := task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.ReadTaskfile())
assert.NoError(t, e.Run("default"))
for _, f := range files {
content, err := ioutil.ReadFile(filepath.Join(dir, f.file))
assert.NoError(t, err)
assert.Equal(t, f.content, string(content))
}
}

1
testdata/params/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.txt

27
testdata/params/Taskfile.yml vendored Normal file
View File

@ -0,0 +1,27 @@
default:
deps:
- task: write-file
params:
CONTENT: Dependence1
FILE: dep1.txt
- task: write-file
params:
CONTENT: Dependence2
FILE: dep2.txt
cmds:
- task: write-file
params:
CONTENT: Hello
FILE: hello.txt
- task: write-file
params:
CONTENT: World
FILE: world.txt
- task: write-file
params:
CONTENT: "!"
FILE: exclamation.txt
write-file:
cmds:
- echo {{.CONTENT}} > {{.FILE}}

View File

@ -52,7 +52,7 @@ func (e *Executor) handleDynamicVariableContent(value string) (string, error) {
return result, nil return result, nil
} }
func (e *Executor) getVariables(task string) (map[string]string, error) { func (e *Executor) getVariables(task string, params Params) (map[string]string, error) {
t := e.Tasks[task] t := e.Tasks[task]
localVariables := make(map[string]string) localVariables := make(map[string]string)
@ -77,6 +77,15 @@ func (e *Executor) getVariables(task string) (map[string]string, error) {
for key, value := range getEnvironmentVariables() { for key, value := range getEnvironmentVariables() {
localVariables[key] = value localVariables[key] = value
} }
if params != nil {
for k, v := range params {
val, err := e.handleDynamicVariableContent(v)
if err != nil {
return nil, err
}
localVariables[k] = val
}
}
return localVariables, nil return localVariables, nil
} }
@ -109,11 +118,11 @@ func init() {
} }
// ReplaceSliceVariables writes vars into initial string slice // ReplaceSliceVariables writes vars into initial string slice
func (e *Executor) ReplaceSliceVariables(task string, initials []string) ([]string, error) { func (e *Executor) ReplaceSliceVariables(initials []string, task string, params Params) ([]string, error) {
result := make([]string, len(initials)) result := make([]string, len(initials))
for i, s := range initials { for i, s := range initials {
var err error var err error
result[i], err = e.ReplaceVariables(task, s) result[i], err = e.ReplaceVariables(s, task, params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -122,8 +131,8 @@ func (e *Executor) ReplaceSliceVariables(task string, initials []string) ([]stri
} }
// ReplaceVariables writes vars into initial string // ReplaceVariables writes vars into initial string
func (e *Executor) ReplaceVariables(task, initial string) (string, error) { func (e *Executor) ReplaceVariables(initial, task string, params Params) (string, error) {
vars, err := e.getVariables(task) vars, err := e.getVariables(task, params)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -15,7 +15,7 @@ func (e *Executor) watchTasks(args ...string) error {
// run tasks on init // run tasks on init
for _, a := range args { for _, a := range args {
if err := e.RunTask(context.Background(), a); err != nil { if err := e.RunTask(context.Background(), a, nil); err != nil {
e.println(err) e.println(err)
break break
} }
@ -41,7 +41,7 @@ loop:
select { select {
case <-watcher.Events: case <-watcher.Events:
for _, a := range args { for _, a := range args {
if err := e.RunTask(context.Background(), a); err != nil { if err := e.RunTask(context.Background(), a, nil); err != nil {
e.println(err) e.println(err)
continue loop continue loop
} }
@ -68,7 +68,11 @@ func (e *Executor) registerWatchedFiles(w *fsnotify.Watcher, args []string) erro
if !ok { if !ok {
return &taskNotFoundError{a} return &taskNotFoundError{a}
} }
if err := e.registerWatchedFiles(w, task.Deps); err != nil { deps := make([]string, len(task.Deps))
for i, d := range task.Deps {
deps[i] = d.Task
}
if err := e.registerWatchedFiles(w, deps); err != nil {
return err return err
} }
for _, s := range task.Sources { for _, s := range task.Sources {