Skip to content
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
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,55 @@ task assets build
If Bash is available (Linux and Windows while on Git Bash), the commands will
run in Bash, otherwise will fallback to `cmd` (on Windows).

### Variables

```yml
build:
deps: [setvar]
cmds:
- echo "{{prefix}} {{THEVAR}}"
variables:
prefix: "Path:"

setvar:
cmds:
- echo "{{PATH}}"
set: THEVAR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setvar instead of set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set is the property with the variable name. I searched for a valid name of the most simplistic task to set a variable.

```

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 `Variables` as source of variables.

They are evaluated in the following order:

Task local variables are overwritten by variables found in `Variables`. Variables found in `Variables` are overwritten with variables from the environment. The output of the last command is stored in the environment. So you can do something like this:

```yml
build:
deps: [setvar]
cmds:
- echo "{{prefix}} '{{THEVAR}}'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think prefix should be PREFIX. Uppercase should be a convention for variables, just like environment variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a choice of the user instead of the tool?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, user can call it the name they want. But I changed to uppercase as a documentation convention.

variables:
prefix: "Result: "

setvar:
cmds:
- echo -n "a"
- echo -n "{{THEVAR}}b"
- echo -n "{{THEVAR}}c"
set: THEVAR
```

The result of a run of build would be:

```
a
ab
abc
Result: 'abc'
```

### Running task in another dir

By default, tasks will be executed in the directory where the Taskfile is
Expand Down
41 changes: 33 additions & 8 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package task

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
Expand Down Expand Up @@ -45,6 +46,8 @@ type Task struct {
Sources []string
Generates []string
Dir string
Variables map[string]string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to Vars for brevity.

Set string
}

// Run runs Task
Expand Down Expand Up @@ -81,8 +84,13 @@ func RunTask(name string) error {
return &taskNotFoundError{name}
}

vars, err := t.handleVariables()
if err != nil {
return &taskRunError{name, err}
}

for _, d := range t.Deps {
if err := RunTask(d); err != nil {
if err := RunTask(ReplaceVariables(d, vars)); err != nil {
return err
}
}
Expand All @@ -93,9 +101,23 @@ func RunTask(name string) error {
}

for _, c := range t.Cmds {
if err := runCommand(c, t.Dir); err != nil {
// read in a each time, as a command could change a variable or it has been changed by a dependency
vars, err = t.handleVariables()
if err != nil {
return &taskRunError{name, err}
}
var (
output string
err error
)
if output, err = runCommand(ReplaceVariables(c, vars), ReplaceVariables(t.Dir, vars)); err != nil {
return &taskRunError{name, err}
}
if t.Set != "" {
os.Setenv(t.Set, output)
} else {
fmt.Println(output)
}
}
return nil
}
Expand All @@ -118,8 +140,12 @@ func isTaskUpToDate(t *Task) bool {
return generatesMinTime.After(sourcesMaxTime)
}

func runCommand(c, path string) error {
var cmd *exec.Cmd
func runCommand(c, path string) (string, error) {
var (
cmd *exec.Cmd
b []byte
err error
)
if ShExists {
cmd = exec.Command(ShPath, "-c", c)
} else {
Expand All @@ -128,12 +154,11 @@ func runCommand(c, path string) error {
if path != "" {
cmd.Dir = path
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
if b, err = cmd.Output(); err != nil {
return "", err
}
return nil
return string(b), nil
}

func readTaskfile() (tasks map[string]*Task, err error) {
Expand Down
82 changes: 82 additions & 0 deletions variable_handling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package task

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/BurntSushi/toml"
"gopkg.in/yaml.v2"
)

var (
// VariableFilePath file containing additional variables
VariableFilePath = "Taskvars"
)

func (t Task) handleVariables() (map[string]string, error) {
localVariables := make(map[string]string)
for key, value := range t.Variables {
localVariables[key] = value
}
if fileVariables, err := readVariablefile(); err == nil {
for key, value := range fileVariables {
localVariables[key] = value
}
} else {
return nil, err
}
for key, value := range getEnvironmentVariables() {
localVariables[key] = value
}
return localVariables, nil
}

// ReplaceVariables writes variables into initial string
func ReplaceVariables(initial string, variables map[string]string) string {
replaced := initial
for name, val := range variables {
replaced = strings.Replace(replaced, fmt.Sprintf("{{%s}}", name), val, -1)
}
return replaced
}

// GetEnvironmentVariables returns environment variables as map
func getEnvironmentVariables() map[string]string {
getenvironment := func(data []string, getkeyval func(item string) (key, val string)) map[string]string {
items := make(map[string]string)
for _, item := range data {
key, val := getkeyval(item)
items[key] = val
}
return items
}
return getenvironment(os.Environ(), func(item string) (key, val string) {
splits := strings.Split(item, "=")
key = splits[0]
val = splits[1]
return
})
}

func readVariablefile() (map[string]string, error) {
var variables map[string]string
if b, err := ioutil.ReadFile(VariableFilePath + ".yml"); err == nil {
if err := yaml.Unmarshal(b, &variables); err != nil {
return nil, err
}
}
if b, err := ioutil.ReadFile(VariableFilePath + ".json"); err == nil {
if err := json.Unmarshal(b, &variables); err != nil {
return nil, err
}
}
if b, err := ioutil.ReadFile(VariableFilePath + ".toml"); err == nil {
if err := toml.Unmarshal(b, &variables); err != nil {
return nil, err
}
}
return variables, nil
}