mirror of
https://github.com/go-task/task.git
synced 2025-06-17 00:17:51 +02:00
feat: parse templates in collection-type variables (#1526)
* refactor: replacer * feat: move traverser to deepcopy package * feat: nested map variable templating * refactor: ReplaceVar function * feat: test cases * fix: TraverseStringsFunc copy value instead of pointer
This commit is contained in:
@ -59,20 +59,9 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
|||||||
|
|
||||||
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
||||||
return func(k string, v ast.Var) error {
|
return func(k string, v ast.Var) error {
|
||||||
tr := templater.Templater{Vars: result}
|
cache := &templater.Cache{Vars: result}
|
||||||
// Replace values
|
// Replace values
|
||||||
newVar := ast.Var{}
|
newVar := templater.ReplaceVar(v, cache)
|
||||||
switch value := v.Value.(type) {
|
|
||||||
case string:
|
|
||||||
newVar.Value = tr.Replace(value)
|
|
||||||
default:
|
|
||||||
newVar.Value = value
|
|
||||||
}
|
|
||||||
newVar.Sh = tr.Replace(v.Sh)
|
|
||||||
newVar.Ref = v.Ref
|
|
||||||
newVar.Json = tr.Replace(v.Json)
|
|
||||||
newVar.Yaml = tr.Replace(v.Yaml)
|
|
||||||
newVar.Dir = v.Dir
|
|
||||||
// If the variable is a reference, we can resolve it
|
// If the variable is a reference, we can resolve it
|
||||||
if newVar.Ref != "" {
|
if newVar.Ref != "" {
|
||||||
newVar.Value = result.Get(newVar.Ref).Value
|
newVar.Value = result.Get(newVar.Ref).Value
|
||||||
@ -89,7 +78,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
||||||
if err := tr.Err(); err != nil {
|
if err := cache.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Evaluate JSON
|
// Evaluate JSON
|
||||||
@ -124,9 +113,9 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
|||||||
if t != nil {
|
if t != nil {
|
||||||
// NOTE(@andreynering): We're manually joining these paths here because
|
// NOTE(@andreynering): We're manually joining these paths here because
|
||||||
// this is the raw task, not the compiled one.
|
// this is the raw task, not the compiled one.
|
||||||
tr := templater.Templater{Vars: result}
|
cache := &templater.Cache{Vars: result}
|
||||||
dir := tr.Replace(t.Dir)
|
dir := templater.Replace(t.Dir, cache)
|
||||||
if err := tr.Err(); err != nil {
|
if err := cache.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dir = filepathext.SmartJoin(c.Dir, dir)
|
dir = filepathext.SmartJoin(c.Dir, dir)
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package deepcopy
|
package deepcopy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
type Copier[T any] interface {
|
type Copier[T any] interface {
|
||||||
DeepCopy() T
|
DeepCopy() T
|
||||||
}
|
}
|
||||||
@ -33,3 +37,105 @@ func Map[K comparable, V any](orig map[K]V) map[K]V {
|
|||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TraverseStringsFunc runs the given function on every string in the given
|
||||||
|
// value by traversing it recursively. If the given value is a string, the
|
||||||
|
// function will run on a copy of the string and return it. If the value is a
|
||||||
|
// struct, map or a slice, the function will recursively call itself for each
|
||||||
|
// field or element of the struct, map or slice until all strings inside the
|
||||||
|
// struct or slice are replaced.
|
||||||
|
func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, error) {
|
||||||
|
original := reflect.ValueOf(v)
|
||||||
|
if original.Kind() == reflect.Invalid || !original.IsValid() {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
copy := reflect.New(original.Type()).Elem()
|
||||||
|
|
||||||
|
var traverseFunc func(copy, v reflect.Value) error
|
||||||
|
traverseFunc = func(copy, v reflect.Value) error {
|
||||||
|
switch v.Kind() {
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Unwrap the pointer
|
||||||
|
originalValue := v.Elem()
|
||||||
|
// If the pointer is nil, do nothing
|
||||||
|
if !originalValue.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Create an empty copy from the original value's type
|
||||||
|
copy.Set(reflect.New(originalValue.Type()))
|
||||||
|
// Unwrap the newly created pointer and call traverseFunc recursively
|
||||||
|
if err := traverseFunc(copy.Elem(), originalValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// Unwrap the interface
|
||||||
|
originalValue := v.Elem()
|
||||||
|
if !originalValue.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Create an empty copy from the original value's type
|
||||||
|
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||||
|
// Unwrap the newly created pointer and call traverseFunc recursively
|
||||||
|
if err := traverseFunc(copyValue, originalValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy.Set(copyValue)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
// Loop over each field and call traverseFunc recursively
|
||||||
|
for i := 0; i < v.NumField(); i += 1 {
|
||||||
|
if err := traverseFunc(copy.Field(i), v.Field(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
// Create an empty copy from the original value's type
|
||||||
|
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
|
||||||
|
// Loop over each element and call traverseFunc recursively
|
||||||
|
for i := 0; i < v.Len(); i += 1 {
|
||||||
|
if err := traverseFunc(copy.Index(i), v.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// Create an empty copy from the original value's type
|
||||||
|
copy.Set(reflect.MakeMap(v.Type()))
|
||||||
|
// Loop over each key
|
||||||
|
for _, key := range v.MapKeys() {
|
||||||
|
// Create a copy of each map index
|
||||||
|
originalValue := v.MapIndex(key)
|
||||||
|
if originalValue.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||||
|
// Call traverseFunc recursively
|
||||||
|
if err := traverseFunc(copyValue, originalValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy.SetMapIndex(key, copyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
rv, err := fn(v.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy.Set(reflect.ValueOf(rv))
|
||||||
|
|
||||||
|
default:
|
||||||
|
copy.Set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := traverseFunc(copy, original); err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy.Interface().(T), nil
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ package output
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
@ -10,13 +12,13 @@ type Group struct {
|
|||||||
ErrorOnly bool
|
ErrorOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, tmpl Templater) (io.Writer, io.Writer, CloseFunc) {
|
func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, cache *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||||
gw := &groupWriter{writer: stdOut}
|
gw := &groupWriter{writer: stdOut}
|
||||||
if g.Begin != "" {
|
if g.Begin != "" {
|
||||||
gw.begin = tmpl.Replace(g.Begin) + "\n"
|
gw.begin = templater.Replace(g.Begin, cache) + "\n"
|
||||||
}
|
}
|
||||||
if g.End != "" {
|
if g.End != "" {
|
||||||
gw.end = tmpl.Replace(g.End) + "\n"
|
gw.end = templater.Replace(g.End, cache) + "\n"
|
||||||
}
|
}
|
||||||
return gw, gw, func(err error) error {
|
return gw, gw, func(err error) error {
|
||||||
if g.ErrorOnly && err == nil {
|
if g.ErrorOnly && err == nil {
|
||||||
|
@ -2,10 +2,12 @@ package output
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Interleaved struct{}
|
type Interleaved struct{}
|
||||||
|
|
||||||
func (Interleaved) WrapWriter(stdOut, stdErr io.Writer, _ string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
func (Interleaved) WrapWriter(stdOut, stdErr io.Writer, _ string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||||
return stdOut, stdErr, func(error) error { return nil }
|
return stdOut, stdErr, func(error) error { return nil }
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Templater executes a template engine.
|
|
||||||
// It is provided by the templater.Templater package.
|
|
||||||
type Templater interface {
|
|
||||||
// Replace replaces the provided template string with a rendered string.
|
|
||||||
Replace(tmpl string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Output interface {
|
type Output interface {
|
||||||
WrapWriter(stdOut, stdErr io.Writer, prefix string, tmpl Templater) (io.Writer, io.Writer, CloseFunc)
|
WrapWriter(stdOut, stdErr io.Writer, prefix string, cache *templater.Cache) (io.Writer, io.Writer, CloseFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CloseFunc func(err error) error
|
type CloseFunc func(err error) error
|
||||||
|
@ -46,7 +46,7 @@ func TestGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupWithBeginEnd(t *testing.T) {
|
func TestGroupWithBeginEnd(t *testing.T) {
|
||||||
tmpl := templater.Templater{
|
tmpl := templater.Cache{
|
||||||
Vars: &ast.Vars{
|
Vars: &ast.Vars{
|
||||||
OrderedMap: omap.FromMap(map[string]ast.Var{
|
OrderedMap: omap.FromMap(map[string]ast.Var{
|
||||||
"VAR1": {Value: "example-value"},
|
"VAR1": {Value: "example-value"},
|
||||||
|
@ -5,11 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Prefixed struct{}
|
type Prefixed struct{}
|
||||||
|
|
||||||
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||||
pw := &prefixWriter{writer: stdOut, prefix: prefix}
|
pw := &prefixWriter{writer: stdOut, prefix: prefix}
|
||||||
return pw, pw, func(error) error { return pw.close() }
|
return pw, pw, func(error) error { return pw.close() }
|
||||||
}
|
}
|
||||||
|
@ -6,122 +6,116 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/deepcopy"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Templater is a help struct that allow us to call "replaceX" funcs multiple
|
// Cache is a help struct that allow us to call "replaceX" funcs multiple
|
||||||
// times, without having to check for error each time. The first error that
|
// times, without having to check for error each time. The first error that
|
||||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||||
// return the zero value.
|
// return the zero value.
|
||||||
type Templater struct {
|
type Cache struct {
|
||||||
Vars *ast.Vars
|
Vars *ast.Vars
|
||||||
|
|
||||||
cacheMap map[string]any
|
cacheMap map[string]any
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ResetCache() {
|
func (r *Cache) ResetCache() {
|
||||||
r.cacheMap = r.Vars.ToCacheMap()
|
r.cacheMap = r.Vars.ToCacheMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) Replace(str string) string {
|
func (r *Cache) Err() error {
|
||||||
return r.replace(str, nil)
|
return r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ReplaceWithExtra(str string, extra map[string]any) string {
|
func Replace[T any](v T, cache *Cache) T {
|
||||||
return r.replace(str, extra)
|
return ReplaceWithExtra(v, cache, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) replace(str string, extra map[string]any) string {
|
func ReplaceWithExtra[T any](v T, cache *Cache, extra map[string]any) T {
|
||||||
if r.err != nil || str == "" {
|
// If there is already an error, do nothing
|
||||||
return ""
|
if cache.err != nil {
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
|
// Initialize the cache map if it's not already initialized
|
||||||
|
if cache.cacheMap == nil {
|
||||||
|
cache.cacheMap = cache.Vars.ToCacheMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a copy of the cache map to avoid editing the original
|
||||||
|
// If there is extra data, merge it with the cache map
|
||||||
|
data := maps.Clone(cache.cacheMap)
|
||||||
|
if extra != nil {
|
||||||
|
maps.Copy(data, extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the value and parse any template variables
|
||||||
|
copy, err := deepcopy.TraverseStringsFunc(v, func(v string) (string, error) {
|
||||||
|
tpl, err := template.New("").Funcs(templateFuncs).Parse(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.err = err
|
return v, err
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.cacheMap == nil {
|
|
||||||
r.cacheMap = r.Vars.ToCacheMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if extra == nil {
|
if err := tpl.Execute(&b, data); err != nil {
|
||||||
err = templ.Execute(&b, r.cacheMap)
|
return v, err
|
||||||
} else {
|
|
||||||
// Copy the map to avoid modifying the cached map
|
|
||||||
m := maps.Clone(r.cacheMap)
|
|
||||||
maps.Copy(m, extra)
|
|
||||||
err = templ.Execute(&b, m)
|
|
||||||
}
|
}
|
||||||
|
return strings.ReplaceAll(b.String(), "<no value>", ""), nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.err = err
|
cache.err = err
|
||||||
return ""
|
return v
|
||||||
}
|
}
|
||||||
return strings.ReplaceAll(b.String(), "<no value>", "")
|
|
||||||
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ReplaceSlice(strs []string) []string {
|
func ReplaceGlobs(globs []*ast.Glob, cache *Cache) []*ast.Glob {
|
||||||
if r.err != nil || len(strs) == 0 {
|
if cache.err != nil || len(globs) == 0 {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
new := make([]string, len(strs))
|
|
||||||
for i, str := range strs {
|
|
||||||
new[i] = r.Replace(str)
|
|
||||||
}
|
|
||||||
return new
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Templater) ReplaceGlobs(globs []*ast.Glob) []*ast.Glob {
|
|
||||||
if r.err != nil || len(globs) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
new := make([]*ast.Glob, len(globs))
|
new := make([]*ast.Glob, len(globs))
|
||||||
for i, g := range globs {
|
for i, g := range globs {
|
||||||
new[i] = &ast.Glob{
|
new[i] = &ast.Glob{
|
||||||
Glob: r.Replace(g.Glob),
|
Glob: Replace(g.Glob, cache),
|
||||||
Negate: g.Negate,
|
Negate: g.Negate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ReplaceVars(vars *ast.Vars) *ast.Vars {
|
func ReplaceVar(v ast.Var, cache *Cache) ast.Var {
|
||||||
return r.replaceVars(vars, nil)
|
return ReplaceVarWithExtra(v, cache, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ReplaceVarsWithExtra(vars *ast.Vars, extra map[string]any) *ast.Vars {
|
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var {
|
||||||
return r.replaceVars(vars, extra)
|
return ast.Var{
|
||||||
|
Value: ReplaceWithExtra(v.Value, cache, extra),
|
||||||
|
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
||||||
|
Live: v.Live,
|
||||||
|
Ref: v.Ref,
|
||||||
|
Dir: v.Dir,
|
||||||
|
Json: ReplaceWithExtra(v.Json, cache, extra),
|
||||||
|
Yaml: ReplaceWithExtra(v.Yaml, cache, extra),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) replaceVars(vars *ast.Vars, extra map[string]any) *ast.Vars {
|
func ReplaceVars(vars *ast.Vars, cache *Cache) *ast.Vars {
|
||||||
if r.err != nil || vars.Len() == 0 {
|
return ReplaceVarsWithExtra(vars, cache, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplaceVarsWithExtra(vars *ast.Vars, cache *Cache, extra map[string]any) *ast.Vars {
|
||||||
|
if cache.err != nil || vars.Len() == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var newVars ast.Vars
|
var newVars ast.Vars
|
||||||
_ = vars.Range(func(k string, v ast.Var) error {
|
_ = vars.Range(func(k string, v ast.Var) error {
|
||||||
var newVar ast.Var
|
newVars.Set(k, ReplaceVarWithExtra(v, cache, extra))
|
||||||
switch value := v.Value.(type) {
|
|
||||||
case string:
|
|
||||||
newVar.Value = r.ReplaceWithExtra(value, extra)
|
|
||||||
}
|
|
||||||
newVar.Live = v.Live
|
|
||||||
newVar.Sh = r.ReplaceWithExtra(v.Sh, extra)
|
|
||||||
newVar.Ref = v.Ref
|
|
||||||
newVar.Json = r.ReplaceWithExtra(v.Json, extra)
|
|
||||||
newVar.Yaml = r.ReplaceWithExtra(v.Yaml, extra)
|
|
||||||
newVars.Set(k, newVar)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return &newVars
|
return &newVars
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) Err() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
|
2
task.go
2
task.go
@ -348,7 +348,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
|
|||||||
outputWrapper = output.Interleaved{}
|
outputWrapper = output.Interleaved{}
|
||||||
}
|
}
|
||||||
vars, err := e.Compiler.FastGetVariables(t, call)
|
vars, err := e.Compiler.FastGetVariables(t, call)
|
||||||
outputTemplater := &templater.Templater{Vars: vars}
|
outputTemplater := &templater.Cache{Vars: vars}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("task: failed to get variables: %w", err)
|
return fmt.Errorf("task: failed to get variables: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,10 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
env := &ast.Vars{}
|
env := &ast.Vars{}
|
||||||
|
cache := &templater.Cache{Vars: vars}
|
||||||
tr := templater.Templater{Vars: vars}
|
|
||||||
|
|
||||||
for _, dotEnvPath := range tf.Dotenv {
|
for _, dotEnvPath := range tf.Dotenv {
|
||||||
dotEnvPath = tr.Replace(dotEnvPath)
|
dotEnvPath = templater.Replace(dotEnvPath, cache)
|
||||||
if dotEnvPath == "" {
|
if dotEnvPath == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,11 @@ func Read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
err = tf.Includes.Range(func(namespace string, include ast.Include) error {
|
||||||
tr := templater.Templater{Vars: tf.Vars}
|
cache := &templater.Cache{Vars: tf.Vars}
|
||||||
include = ast.Include{
|
include = ast.Include{
|
||||||
Namespace: include.Namespace,
|
Namespace: include.Namespace,
|
||||||
Taskfile: tr.Replace(include.Taskfile),
|
Taskfile: templater.Replace(include.Taskfile, cache),
|
||||||
Dir: tr.Replace(include.Dir),
|
Dir: templater.Replace(include.Dir, cache),
|
||||||
Optional: include.Optional,
|
Optional: include.Optional,
|
||||||
Internal: include.Internal,
|
Internal: include.Internal,
|
||||||
Aliases: include.Aliases,
|
Aliases: include.Aliases,
|
||||||
@ -72,7 +72,7 @@ func Read(
|
|||||||
Vars: include.Vars,
|
Vars: include.Vars,
|
||||||
BaseDir: include.BaseDir,
|
BaseDir: include.BaseDir,
|
||||||
}
|
}
|
||||||
if err := tr.Err(); err != nil {
|
if err := cache.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
testdata/vars/any2/Taskfile.yml
vendored
20
testdata/vars/any2/Taskfile.yml
vendored
@ -3,6 +3,8 @@ version: '3'
|
|||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
- task: map
|
- task: map
|
||||||
|
- task: nested-map
|
||||||
|
- task: slice
|
||||||
- task: ref
|
- task: ref
|
||||||
- task: ref-sh
|
- task: ref-sh
|
||||||
- task: ref-dep
|
- task: ref-dep
|
||||||
@ -19,6 +21,24 @@ tasks:
|
|||||||
VAR:
|
VAR:
|
||||||
ref: MAP
|
ref: MAP
|
||||||
|
|
||||||
|
nested-map:
|
||||||
|
vars:
|
||||||
|
FOO: "foo"
|
||||||
|
nested:
|
||||||
|
map:
|
||||||
|
variables:
|
||||||
|
work: "{{.FOO}}"
|
||||||
|
cmds:
|
||||||
|
- echo {{.nested.variables.work}}
|
||||||
|
|
||||||
|
slice:
|
||||||
|
vars:
|
||||||
|
FOO: "foo"
|
||||||
|
BAR: "bar"
|
||||||
|
slice_variables_work: ["{{.FOO}}","{{.BAR}}"]
|
||||||
|
cmds:
|
||||||
|
- echo {{index .slice_variables_work 0}} {{index .slice_variables_work 1}}
|
||||||
|
|
||||||
ref:
|
ref:
|
||||||
vars:
|
vars:
|
||||||
MAP:
|
MAP:
|
||||||
|
58
variables.go
58
variables.go
@ -42,30 +42,30 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := templater.Templater{Vars: vars}
|
cache := &templater.Cache{Vars: vars}
|
||||||
|
|
||||||
new := ast.Task{
|
new := ast.Task{
|
||||||
Task: origTask.Task,
|
Task: origTask.Task,
|
||||||
Label: r.Replace(origTask.Label),
|
Label: templater.Replace(origTask.Label, cache),
|
||||||
Desc: r.Replace(origTask.Desc),
|
Desc: templater.Replace(origTask.Desc, cache),
|
||||||
Prompt: r.Replace(origTask.Prompt),
|
Prompt: templater.Replace(origTask.Prompt, cache),
|
||||||
Summary: r.Replace(origTask.Summary),
|
Summary: templater.Replace(origTask.Summary, cache),
|
||||||
Aliases: origTask.Aliases,
|
Aliases: origTask.Aliases,
|
||||||
Sources: r.ReplaceGlobs(origTask.Sources),
|
Sources: templater.ReplaceGlobs(origTask.Sources, cache),
|
||||||
Generates: r.ReplaceGlobs(origTask.Generates),
|
Generates: templater.ReplaceGlobs(origTask.Generates, cache),
|
||||||
Dir: r.Replace(origTask.Dir),
|
Dir: templater.Replace(origTask.Dir, cache),
|
||||||
Set: origTask.Set,
|
Set: origTask.Set,
|
||||||
Shopt: origTask.Shopt,
|
Shopt: origTask.Shopt,
|
||||||
Vars: nil,
|
Vars: nil,
|
||||||
Env: nil,
|
Env: nil,
|
||||||
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
Dotenv: templater.Replace(origTask.Dotenv, cache),
|
||||||
Silent: origTask.Silent,
|
Silent: origTask.Silent,
|
||||||
Interactive: origTask.Interactive,
|
Interactive: origTask.Interactive,
|
||||||
Internal: origTask.Internal,
|
Internal: origTask.Internal,
|
||||||
Method: r.Replace(origTask.Method),
|
Method: templater.Replace(origTask.Method, cache),
|
||||||
Prefix: r.Replace(origTask.Prefix),
|
Prefix: templater.Replace(origTask.Prefix, cache),
|
||||||
IgnoreError: origTask.IgnoreError,
|
IgnoreError: origTask.IgnoreError,
|
||||||
Run: r.Replace(origTask.Run),
|
Run: templater.Replace(origTask.Run, cache),
|
||||||
IncludeVars: origTask.IncludeVars,
|
IncludeVars: origTask.IncludeVars,
|
||||||
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
||||||
Platforms: origTask.Platforms,
|
Platforms: origTask.Platforms,
|
||||||
@ -104,9 +104,9 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
}
|
}
|
||||||
|
|
||||||
new.Env = &ast.Vars{}
|
new.Env = &ast.Vars{}
|
||||||
new.Env.Merge(r.ReplaceVars(e.Taskfile.Env))
|
new.Env.Merge(templater.ReplaceVars(e.Taskfile.Env, cache))
|
||||||
new.Env.Merge(r.ReplaceVars(dotenvEnvs))
|
new.Env.Merge(templater.ReplaceVars(dotenvEnvs, cache))
|
||||||
new.Env.Merge(r.ReplaceVars(origTask.Env))
|
new.Env.Merge(templater.ReplaceVars(origTask.Env, cache))
|
||||||
if evaluateShVars {
|
if evaluateShVars {
|
||||||
err = new.Env.Range(func(k string, v ast.Var) error {
|
err = new.Env.Range(func(k string, v ast.Var) error {
|
||||||
// If the variable is not dynamic, we can set it and return
|
// If the variable is not dynamic, we can set it and return
|
||||||
@ -200,17 +200,17 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
extra["KEY"] = keys[i]
|
extra["KEY"] = keys[i]
|
||||||
}
|
}
|
||||||
newCmd := cmd.DeepCopy()
|
newCmd := cmd.DeepCopy()
|
||||||
newCmd.Cmd = r.ReplaceWithExtra(cmd.Cmd, extra)
|
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||||
newCmd.Task = r.ReplaceWithExtra(cmd.Task, extra)
|
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
||||||
newCmd.Vars = r.ReplaceVarsWithExtra(cmd.Vars, extra)
|
newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
||||||
new.Cmds = append(new.Cmds, newCmd)
|
new.Cmds = append(new.Cmds, newCmd)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newCmd := cmd.DeepCopy()
|
newCmd := cmd.DeepCopy()
|
||||||
newCmd.Cmd = r.Replace(cmd.Cmd)
|
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
||||||
newCmd.Task = r.Replace(cmd.Task)
|
newCmd.Task = templater.Replace(cmd.Task, cache)
|
||||||
newCmd.Vars = r.ReplaceVars(cmd.Vars)
|
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
||||||
// Loop over the command's variables and resolve any references to other variables
|
// Loop over the command's variables and resolve any references to other variables
|
||||||
err := cmd.Vars.Range(func(k string, v ast.Var) error {
|
err := cmd.Vars.Range(func(k string, v ast.Var) error {
|
||||||
if v.Ref != "" {
|
if v.Ref != "" {
|
||||||
@ -232,8 +232,8 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newDep := dep.DeepCopy()
|
newDep := dep.DeepCopy()
|
||||||
newDep.Task = r.Replace(dep.Task)
|
newDep.Task = templater.Replace(dep.Task, cache)
|
||||||
newDep.Vars = r.ReplaceVars(dep.Vars)
|
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
|
||||||
// Loop over the dep's variables and resolve any references to other variables
|
// Loop over the dep's variables and resolve any references to other variables
|
||||||
err := dep.Vars.Range(func(k string, v ast.Var) error {
|
err := dep.Vars.Range(func(k string, v ast.Var) error {
|
||||||
if v.Ref != "" {
|
if v.Ref != "" {
|
||||||
@ -256,8 +256,8 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newPrecondition := precondition.DeepCopy()
|
newPrecondition := precondition.DeepCopy()
|
||||||
newPrecondition.Sh = r.Replace(precondition.Sh)
|
newPrecondition.Sh = templater.Replace(precondition.Sh, cache)
|
||||||
newPrecondition.Msg = r.Replace(precondition.Msg)
|
newPrecondition.Msg = templater.Replace(precondition.Msg, cache)
|
||||||
new.Preconditions = append(new.Preconditions, newPrecondition)
|
new.Preconditions = append(new.Preconditions, newPrecondition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,14 +276,14 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
|
|
||||||
// Adding new variables, requires us to refresh the templaters
|
// Adding new variables, requires us to refresh the templaters
|
||||||
// cache of the the values manually
|
// cache of the the values manually
|
||||||
r.ResetCache()
|
cache.ResetCache()
|
||||||
|
|
||||||
new.Status = r.ReplaceSlice(origTask.Status)
|
new.Status = templater.Replace(origTask.Status, cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only care about templater errors if we are evaluating shell variables
|
// We only care about templater errors if we are evaluating shell variables
|
||||||
if evaluateShVars && r.Err() != nil {
|
if evaluateShVars && cache.Err() != nil {
|
||||||
return &new, r.Err()
|
return &new, cache.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &new, nil
|
return &new, nil
|
||||||
|
Reference in New Issue
Block a user