mirror of
https://github.com/go-task/task.git
synced 2025-02-01 13:17:56 +02:00
feat: ability to resolve refs using templating syntax (#1612)
* feat: resolve references using templating syntax * refactor: moved when references are resolved to one place * fix: linter * docs: update map variables doc
This commit is contained in:
parent
d87e5de56f
commit
630e58767b
@ -11,7 +11,7 @@ linters:
|
|||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: github.com/go-task/task
|
local-prefixes: github.com/go-task
|
||||||
gofmt:
|
gofmt:
|
||||||
rewrite-rules:
|
rewrite-rules:
|
||||||
- pattern: 'interface{}'
|
- pattern: 'interface{}'
|
||||||
|
3
go.mod
3
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/go-task/task/v3
|
module github.com/go-task/task/v3
|
||||||
|
|
||||||
go 1.21
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.2.1
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
@ -8,6 +8,7 @@ require (
|
|||||||
github.com/dominikbraun/graph v0.23.0
|
github.com/dominikbraun/graph v0.23.0
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||||
|
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-zglob v0.0.4
|
github.com/mattn/go-zglob v0.0.4
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
|
2
go.sum
2
go.sum
@ -12,6 +12,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90 h1:JBbiZ2CXIZ9Upe3O2yI5+3ksWoa7hNVNi4BINs8TIrs=
|
||||||
|
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
@ -62,10 +62,6 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
|||||||
cache := &templater.Cache{Vars: result}
|
cache := &templater.Cache{Vars: result}
|
||||||
// Replace values
|
// Replace values
|
||||||
newVar := templater.ReplaceVar(v, cache)
|
newVar := templater.ReplaceVar(v, cache)
|
||||||
// If the variable is a reference, we can resolve it
|
|
||||||
if newVar.Ref != "" {
|
|
||||||
newVar.Value = result.Get(newVar.Ref).Value
|
|
||||||
}
|
|
||||||
// If the variable should not be evaluated, but is nil, set it to an empty string
|
// If the variable should not be evaluated, but is nil, set it to an empty string
|
||||||
// This stops empty interface errors when using the templater to replace values later
|
// This stops empty interface errors when using the templater to replace values later
|
||||||
if !evaluateShVars && newVar.Value == nil {
|
if !evaluateShVars && newVar.Value == nil {
|
||||||
|
@ -4,13 +4,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"mvdan.cc/sh/v3/shell"
|
"mvdan.cc/sh/v3/shell"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
|
||||||
sprig "github.com/go-task/slim-sprig/v3"
|
sprig "github.com/go-task/slim-sprig/v3"
|
||||||
|
"github.com/go-task/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templateFuncs template.FuncMap
|
var templateFuncs template.FuncMap
|
||||||
@ -82,7 +82,7 @@ func init() {
|
|||||||
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
|
||||||
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
|
||||||
|
|
||||||
templateFuncs = sprig.TxtFuncMap()
|
templateFuncs = template.FuncMap(sprig.TxtFuncMap())
|
||||||
for k, v := range taskFuncs {
|
for k, v := range taskFuncs {
|
||||||
templateFuncs[k] = v
|
templateFuncs[k] = v
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/deepcopy"
|
"github.com/go-task/task/v3/internal/deepcopy"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
|
"github.com/go-task/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache 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
|
||||||
@ -29,6 +29,25 @@ func (r *Cache) Err() error {
|
|||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResolveRef(ref string, cache *Cache) any {
|
||||||
|
// If there is already an error, do nothing
|
||||||
|
if cache.err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the cache map if it's not already initialized
|
||||||
|
if cache.cacheMap == nil {
|
||||||
|
cache.cacheMap = cache.Vars.ToCacheMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := template.ResolveRef(ref, cache.cacheMap)
|
||||||
|
if err != nil {
|
||||||
|
cache.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
func Replace[T any](v T, cache *Cache) T {
|
func Replace[T any](v T, cache *Cache) T {
|
||||||
return ReplaceWithExtra(v, cache, nil)
|
return ReplaceWithExtra(v, cache, nil)
|
||||||
}
|
}
|
||||||
@ -91,6 +110,9 @@ func ReplaceVar(v ast.Var, cache *Cache) ast.Var {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var {
|
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var {
|
||||||
|
if v.Ref != "" {
|
||||||
|
return ast.Var{Value: ResolveRef(v.Ref, cache)}
|
||||||
|
}
|
||||||
return ast.Var{
|
return ast.Var{
|
||||||
Value: ReplaceWithExtra(v.Value, cache, extra),
|
Value: ReplaceWithExtra(v.Value, cache, extra),
|
||||||
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
||||||
|
43
testdata/vars/any2/Taskfile.yml
vendored
43
testdata/vars/any2/Taskfile.yml
vendored
@ -8,6 +8,7 @@ tasks:
|
|||||||
- task: ref
|
- task: ref
|
||||||
- task: ref-sh
|
- task: ref-sh
|
||||||
- task: ref-dep
|
- task: ref-dep
|
||||||
|
- task: ref-resolver
|
||||||
- task: json
|
- task: json
|
||||||
- task: yaml
|
- task: yaml
|
||||||
|
|
||||||
@ -16,10 +17,10 @@ tasks:
|
|||||||
MAP:
|
MAP:
|
||||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP
|
ref: .MAP
|
||||||
|
|
||||||
nested-map:
|
nested-map:
|
||||||
vars:
|
vars:
|
||||||
@ -44,12 +45,12 @@ tasks:
|
|||||||
MAP:
|
MAP:
|
||||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
MAP_REF:
|
MAP_REF:
|
||||||
ref: MAP
|
ref: .MAP
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP_REF
|
ref: .MAP_REF
|
||||||
|
|
||||||
ref-sh:
|
ref-sh:
|
||||||
vars:
|
vars:
|
||||||
@ -58,22 +59,34 @@ tasks:
|
|||||||
JSON:
|
JSON:
|
||||||
json: "{{.JSON_STRING}}"
|
json: "{{.JSON_STRING}}"
|
||||||
MAP_REF:
|
MAP_REF:
|
||||||
ref: JSON
|
ref: .JSON
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP_REF
|
ref: .MAP_REF
|
||||||
|
|
||||||
ref-dep:
|
ref-dep:
|
||||||
vars:
|
vars:
|
||||||
MAP:
|
MAP:
|
||||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
deps:
|
deps:
|
||||||
|
- task: print-story
|
||||||
|
vars:
|
||||||
|
VAR:
|
||||||
|
ref: .MAP
|
||||||
|
|
||||||
|
ref-resolver:
|
||||||
|
vars:
|
||||||
|
MAP:
|
||||||
|
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||||
|
MAP_REF:
|
||||||
|
ref: .MAP
|
||||||
|
cmds:
|
||||||
- task: print-var
|
- task: print-var
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: MAP
|
ref: (index .MAP_REF.children 0).name
|
||||||
|
|
||||||
json:
|
json:
|
||||||
vars:
|
vars:
|
||||||
@ -82,10 +95,10 @@ tasks:
|
|||||||
JSON:
|
JSON:
|
||||||
json: "{{.JSON_STRING}}"
|
json: "{{.JSON_STRING}}"
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: JSON
|
ref: .JSON
|
||||||
|
|
||||||
yaml:
|
yaml:
|
||||||
vars:
|
vars:
|
||||||
@ -94,12 +107,16 @@ tasks:
|
|||||||
YAML:
|
YAML:
|
||||||
yaml: "{{.YAML_STRING}}"
|
yaml: "{{.YAML_STRING}}"
|
||||||
cmds:
|
cmds:
|
||||||
- task: print-var
|
- task: print-story
|
||||||
vars:
|
vars:
|
||||||
VAR:
|
VAR:
|
||||||
ref: YAML
|
ref: .YAML
|
||||||
|
|
||||||
print-var:
|
print-var:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.VAR}}"
|
||||||
|
|
||||||
|
print-story:
|
||||||
cmds:
|
cmds:
|
||||||
- >-
|
- >-
|
||||||
echo "{{.VAR.name}} has {{len .VAR.children}} children called
|
echo "{{.VAR.name}} has {{len .VAR.children}} children called
|
||||||
|
22
variables.go
22
variables.go
@ -164,17 +164,6 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
||||||
newCmd.Task = templater.Replace(cmd.Task, cache)
|
newCmd.Task = templater.Replace(cmd.Task, cache)
|
||||||
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
||||||
// Loop over the command's variables and resolve any references to other variables
|
|
||||||
err := cmd.Vars.Range(func(k string, v ast.Var) error {
|
|
||||||
if v.Ref != "" {
|
|
||||||
refVal := vars.Get(v.Ref)
|
|
||||||
newCmd.Vars.Set(k, refVal)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
new.Cmds = append(new.Cmds, newCmd)
|
new.Cmds = append(new.Cmds, newCmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,17 +203,6 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
newDep := dep.DeepCopy()
|
newDep := dep.DeepCopy()
|
||||||
newDep.Task = templater.Replace(dep.Task, cache)
|
newDep.Task = templater.Replace(dep.Task, cache)
|
||||||
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
|
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
|
||||||
// Loop over the dep's variables and resolve any references to other variables
|
|
||||||
err := dep.Vars.Range(func(k string, v ast.Var) error {
|
|
||||||
if v.Ref != "" {
|
|
||||||
refVal := vars.Get(v.Ref)
|
|
||||||
newDep.Vars.Set(k, refVal)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
new.Deps = append(new.Deps, newDep)
|
new.Deps = append(new.Deps, newDep)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ tasks:
|
|||||||
- task: bar
|
- task: bar
|
||||||
vars:
|
vars:
|
||||||
FOO:
|
FOO:
|
||||||
ref: FOO # <-- FOO gets passed by reference to bar and maintains its type
|
ref: .FOO # <-- FOO gets passed by reference to bar and maintains its type
|
||||||
bar:
|
bar:
|
||||||
cmds:
|
cmds:
|
||||||
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
|
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
|
||||||
@ -242,17 +242,38 @@ tasks:
|
|||||||
vars:
|
vars:
|
||||||
FOO: [A, B, C] # <-- FOO is defined as an array
|
FOO: [A, B, C] # <-- FOO is defined as an array
|
||||||
BAR:
|
BAR:
|
||||||
ref: FOO # <-- BAR is defined as a reference to FOO
|
ref: .FOO # <-- BAR is defined as a reference to FOO
|
||||||
deps:
|
deps:
|
||||||
- task: bar
|
- task: bar
|
||||||
vars:
|
vars:
|
||||||
BAR:
|
BAR:
|
||||||
ref: BAR # <-- BAR gets passed by reference to bar and maintains its type
|
ref: .BAR # <-- BAR gets passed by reference to bar and maintains its type
|
||||||
bar:
|
bar:
|
||||||
cmds:
|
cmds:
|
||||||
- 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A'
|
- 'echo {{index .BAR 0}}' # <-- BAR still refers to FOO so the task outputs 'A'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
All references use the same templating syntax as regular templates, so in
|
||||||
|
addition to simply calling `.FOO`, you can also pass subkeys (`.FOO.BAR`) or
|
||||||
|
indexes (`index .FOO 0`) and use functions (`len .FOO`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 3
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
vars:
|
||||||
|
FOO: [A, B, C] # <-- FOO is defined as an array
|
||||||
|
cmds:
|
||||||
|
- task: bar
|
||||||
|
vars:
|
||||||
|
FOO:
|
||||||
|
ref: index .FOO 0 # <-- The element at index 0 is passed by reference to bar
|
||||||
|
bar:
|
||||||
|
cmds:
|
||||||
|
- 'echo {{.MYVAR}}' # <-- FOO is just the letter 'A'
|
||||||
|
```
|
||||||
|
|
||||||
</TabItem></Tabs>
|
</TabItem></Tabs>
|
||||||
|
|
||||||
## Looping over maps
|
## Looping over maps
|
||||||
|
Loading…
x
Reference in New Issue
Block a user