mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +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:
		| @@ -11,7 +11,7 @@ linters: | ||||
|  | ||||
| linters-settings: | ||||
|   goimports: | ||||
|     local-prefixes: github.com/go-task/task | ||||
|     local-prefixes: github.com/go-task | ||||
|   gofmt: | ||||
|     rewrite-rules: | ||||
|       - pattern: 'interface{}' | ||||
|   | ||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| module github.com/go-task/task/v3 | ||||
|  | ||||
| go 1.21 | ||||
| go 1.21.0 | ||||
|  | ||||
| require ( | ||||
| 	github.com/Masterminds/semver/v3 v3.2.1 | ||||
| @@ -8,6 +8,7 @@ require ( | ||||
| 	github.com/dominikbraun/graph v0.23.0 | ||||
| 	github.com/fatih/color v1.16.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/mattn/go-zglob v0.0.4 | ||||
| 	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/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/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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| 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} | ||||
| 			// Replace values | ||||
| 			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 | ||||
| 			// This stops empty interface errors when using the templater to replace values later | ||||
| 			if !evaluateShVars && newVar.Value == nil { | ||||
|   | ||||
| @@ -4,13 +4,13 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/davecgh/go-spew/spew" | ||||
| 	"mvdan.cc/sh/v3/shell" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
|  | ||||
| 	sprig "github.com/go-task/slim-sprig/v3" | ||||
| 	"github.com/go-task/template" | ||||
| ) | ||||
|  | ||||
| var templateFuncs template.FuncMap | ||||
| @@ -82,7 +82,7 @@ func init() { | ||||
| 	taskFuncs["ToSlash"] = taskFuncs["toSlash"] | ||||
| 	taskFuncs["ExeExt"] = taskFuncs["exeExt"] | ||||
|  | ||||
| 	templateFuncs = sprig.TxtFuncMap() | ||||
| 	templateFuncs = template.FuncMap(sprig.TxtFuncMap()) | ||||
| 	for k, v := range taskFuncs { | ||||
| 		templateFuncs[k] = v | ||||
| 	} | ||||
|   | ||||
| @@ -4,10 +4,10 @@ import ( | ||||
| 	"bytes" | ||||
| 	"maps" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/go-task/task/v3/internal/deepcopy" | ||||
| 	"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 | ||||
| @@ -29,6 +29,25 @@ func (r *Cache) Err() error { | ||||
| 	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 { | ||||
| 	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 { | ||||
| 	if v.Ref != "" { | ||||
| 		return ast.Var{Value: ResolveRef(v.Ref, cache)} | ||||
| 	} | ||||
| 	return ast.Var{ | ||||
| 		Value: ReplaceWithExtra(v.Value, 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-sh | ||||
|     - task: ref-dep | ||||
|     - task: ref-resolver | ||||
|     - task: json | ||||
|     - task: yaml | ||||
|  | ||||
| @@ -16,10 +17,10 @@ tasks: | ||||
|       MAP: | ||||
|         map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]} | ||||
|     cmds: | ||||
|       - task: print-var | ||||
|       - task: print-story | ||||
|         vars: | ||||
|           VAR: | ||||
|             ref: MAP | ||||
|             ref: .MAP | ||||
|  | ||||
|   nested-map: | ||||
|     vars: | ||||
| @@ -44,12 +45,12 @@ tasks: | ||||
|       MAP: | ||||
|         map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]} | ||||
|       MAP_REF: | ||||
|         ref: MAP | ||||
|         ref: .MAP | ||||
|     cmds: | ||||
|       - task: print-var | ||||
|       - task: print-story | ||||
|         vars: | ||||
|           VAR: | ||||
|             ref: MAP_REF | ||||
|             ref: .MAP_REF | ||||
|  | ||||
|   ref-sh: | ||||
|     vars: | ||||
| @@ -58,22 +59,34 @@ tasks: | ||||
|       JSON: | ||||
|         json: "{{.JSON_STRING}}" | ||||
|       MAP_REF: | ||||
|         ref: JSON | ||||
|         ref: .JSON | ||||
|     cmds: | ||||
|       - task: print-var | ||||
|       - task: print-story | ||||
|         vars: | ||||
|           VAR: | ||||
|             ref: MAP_REF | ||||
|             ref: .MAP_REF | ||||
|  | ||||
|   ref-dep: | ||||
|     vars: | ||||
|       MAP: | ||||
|         map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]} | ||||
|     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 | ||||
|         vars: | ||||
|           VAR: | ||||
|             ref: MAP | ||||
|             ref: (index .MAP_REF.children 0).name | ||||
|  | ||||
|   json: | ||||
|     vars: | ||||
| @@ -82,10 +95,10 @@ tasks: | ||||
|       JSON: | ||||
|         json: "{{.JSON_STRING}}" | ||||
|     cmds: | ||||
|       - task: print-var | ||||
|       - task: print-story | ||||
|         vars: | ||||
|           VAR: | ||||
|             ref: JSON | ||||
|             ref: .JSON | ||||
|  | ||||
|   yaml: | ||||
|     vars: | ||||
| @@ -94,12 +107,16 @@ tasks: | ||||
|       YAML: | ||||
|         yaml: "{{.YAML_STRING}}" | ||||
|     cmds: | ||||
|       - task: print-var | ||||
|       - task: print-story | ||||
|         vars: | ||||
|           VAR: | ||||
|             ref: YAML | ||||
|             ref: .YAML | ||||
|  | ||||
|   print-var: | ||||
|     cmds: | ||||
|       - echo "{{.VAR}}" | ||||
|  | ||||
|   print-story: | ||||
|     cmds: | ||||
|       - >- | ||||
|         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.Task = templater.Replace(cmd.Task, 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) | ||||
| 		} | ||||
| 	} | ||||
| @@ -214,17 +203,6 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task, | ||||
| 			newDep := dep.DeepCopy() | ||||
| 			newDep.Task = templater.Replace(dep.Task, 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) | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -222,7 +222,7 @@ tasks: | ||||
|       - task: bar | ||||
|         vars: | ||||
|           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: | ||||
|     cmds: | ||||
|       - 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected | ||||
| @@ -242,17 +242,38 @@ tasks: | ||||
|     vars: | ||||
|       FOO: [A, B, C] # <-- FOO is defined as an array | ||||
|       BAR: | ||||
|         ref: FOO # <-- BAR is defined as a reference to FOO | ||||
|         ref: .FOO # <-- BAR is defined as a reference to FOO | ||||
|     deps: | ||||
|       - task: bar | ||||
|         vars: | ||||
|           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: | ||||
|     cmds: | ||||
|       - '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> | ||||
|  | ||||
| ## Looping over maps | ||||
|   | ||||
		Reference in New Issue
	
	Block a user