diff --git a/README.md b/README.md index 8df9dc61..d18d5d2e 100644 --- a/README.md +++ b/README.md @@ -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 +``` + +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}}'" + 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 diff --git a/task.go b/task.go index 535b2e7d..09a15fbd 100644 --- a/task.go +++ b/task.go @@ -2,6 +2,7 @@ package task import ( "encoding/json" + "fmt" "io/ioutil" "log" "os" @@ -45,6 +46,8 @@ type Task struct { Sources []string Generates []string Dir string + Variables map[string]string + Set string } // Run runs Task @@ -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 } } @@ -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 } @@ -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 { @@ -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) { diff --git a/variable_handling.go b/variable_handling.go new file mode 100644 index 00000000..651dd9ed --- /dev/null +++ b/variable_handling.go @@ -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 +}