Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add task parameters #32

Merged
merged 7 commits into from
Jul 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ your `PATH`. DEB and RPM packages are also available.
## Usage

Create a file called `Taskfile.yml` in the root of the project.
(`Taskfile.toml` and `Taskfile.json` are also supported, but YAML is used in
the documentation). The `cmds` attribute should contains the commands of a
task:
The `cmds` attribute should contains the commands of a task:

```yml
build:
Expand Down Expand Up @@ -167,22 +165,53 @@ The above will fail with the message: "cyclic dependency detected".

When a task has many dependencies, they are executed concurrently. This will
often result in a faster build pipeline. But in some situations you may need
to call other tasks serially. For this just prefix a command with `^`:
to call other tasks serially. In this case, just use the following syntax:

```yml
a-task:
main-task:
cmds:
- ^another-task
- ^even-another-task
- task: task-to-be-called
- task: another-task
- echo "Both done"

task-to-be-called:
cmds:
- echo "Task to be called"

another-task:
cmds:
- ...
- echo "Another task"
```

Overriding variables in the called task is as simple as informing `vars`
attribute:

```yml
main-task:
cmds:
- task: write-file
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
- task: write-file
vars: {FILE: "world.txt", CONTENT: "World!"}

write-file:
cmds:
- echo "{{.CONTENT}}" > {{.FILE}}
```

The above syntax is also supported in `deps`.

> NOTE: It's also possible to call a task without any param prefixing it
with `^`, but this syntax is deprecaded:

```yml
a-task:
cmds:
- ^another-task

even-another-task:
another-task:
cmds:
- ...
- echo "Another task"
```

### Prevent unnecessary work
Expand Down Expand Up @@ -256,7 +285,7 @@ setvar:
The above sample saves the path into a new variable which is then again echoed.

You can use environment variables, task level variables and a file called
`Taskvars.yml` (or `Taskvars.toml` or `Taskvars.json`) as source of variables.
`Taskvars.yml` as source of variables.

They are evaluated in the following order:

Expand Down
68 changes: 68 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package task

import (
"errors"
"strings"
)

// Cmd is a task command
type Cmd struct {
Cmd string
Task string
Vars Vars
}

// Dep is a task dependency
type Dep struct {
Task string
Vars Vars
}

var (
// ErrCantUnmarshalCmd is returned for invalid command YAML
ErrCantUnmarshalCmd = errors.New("task: can't unmarshal cmd value")
// ErrCantUnmarshalDep is returned for invalid dependency YAML
ErrCantUnmarshalDep = errors.New("task: can't unmarshal dep value")
)

// UnmarshalYAML implements yaml.Unmarshaler interface
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
Vars Vars
}
if err := unmarshal(&taskCall); err == nil {
c.Task = taskCall.Task
c.Vars = taskCall.Vars
return nil
}
return ErrCantUnmarshalCmd
}

// UnmarshalYAML implements yaml.Unmarshaler interface
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
Vars Vars
}
if err := unmarshal(&taskCall); err == nil {
d.Task = taskCall.Task
d.Vars = taskCall.Vars
return nil
}
return ErrCantUnmarshalDep
}
54 changes: 54 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
@@ -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
vars:
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", Vars: task.Vars{"PARAM1": "VALUE1", "PARAM2": "VALUE2"}},
},
{
yamlDep,
&task.Dep{},
&task.Dep{Task: "task-name"},
},
{
yamlTaskCall,
&task.Dep{},
&task.Dep{Task: "another-task", Vars: task.Vars{"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)
}
}
2 changes: 1 addition & 1 deletion cyclic.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func (e *Executor) HasCyclicDep() bool {
defer delete(visits, name)

for _, d := range t.Deps {
if !checkCyclicDep(d, e.Tasks[d]) {
if !checkCyclicDep(d.Task, e.Tasks[d.Task]) {
return false
}
}
Expand Down
8 changes: 4 additions & 4 deletions cyclic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ func TestCyclicDepCheck(t *testing.T) {
isCyclic := &task.Executor{
Tasks: task.Tasks{
"task-a": &task.Task{
Deps: []string{"task-b"},
Deps: []*task.Dep{&task.Dep{Task: "task-b"}},
},
"task-b": &task.Task{
Deps: []string{"task-a"},
Deps: []*task.Dep{&task.Dep{Task: "task-a"}},
},
},
}
Expand All @@ -25,10 +25,10 @@ func TestCyclicDepCheck(t *testing.T) {
isNotCyclic := &task.Executor{
Tasks: task.Tasks{
"task-a": &task.Task{
Deps: []string{"task-c"},
Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
},
"task-b": &task.Task{
Deps: []string{"task-c"},
Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
},
"task-c": &task.Task{},
},
Expand Down
11 changes: 0 additions & 11 deletions example/Taskfile.json

This file was deleted.

6 changes: 0 additions & 6 deletions example/Taskfile.toml

This file was deleted.

23 changes: 14 additions & 9 deletions read_taskfile.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package task

import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"runtime"

"github.com/BurntSushi/toml"
"github.com/imdario/mergo"
"gopkg.in/yaml.v2"
)
Expand All @@ -26,26 +24,33 @@ func (e *Executor) ReadTaskfile() error {
if err != nil {
switch err.(type) {
case taskFileNotFound:
return nil
default:
return err
}
}
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
return err
}
if err := e.readTaskvarsFile(); err != nil {
return err
}
return nil
}

func (e *Executor) readTaskfileData(path string) (tasks map[string]*Task, err error) {
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
return tasks, yaml.Unmarshal(b, &tasks)
}
if b, err := ioutil.ReadFile(path + ".json"); err == nil {
return tasks, json.Unmarshal(b, &tasks)
}
if b, err := ioutil.ReadFile(path + ".toml"); err == nil {
return tasks, toml.Unmarshal(b, &tasks)
}
return nil, taskFileNotFound{path}
}

func (e *Executor) readTaskvarsFile() error {
file := filepath.Join(e.Dir, TaskvarsFilePath)

if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
if err := yaml.Unmarshal(b, &e.taskvars); err != nil {
return err
}
}
return nil
}
Loading