1
0
mirror of https://github.com/go-task/task.git synced 2025-11-25 22:32:55 +02:00

feat: looping over dependencies (#1541)

* feat: support for loops in deps

* chore: tests

* docs: looping over deps
This commit is contained in:
Pete Davison
2024-03-10 17:21:50 +00:00
committed by GitHub
parent 29e91a4137
commit f06f48e225
11 changed files with 384 additions and 78 deletions

View File

@@ -133,56 +133,9 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
continue
}
if cmd.For != nil {
var keys []string
var list []any
// Get the list from the explicit for list
if cmd.For.List != nil && len(cmd.For.List) > 0 {
list = cmd.For.List
}
// Get the list from the task sources
if cmd.For.From == "sources" {
glist, err := fingerprint.Globs(new.Dir, new.Sources)
if err != nil {
return nil, err
}
// Make the paths relative to the task dir
for i, v := range glist {
if glist[i], err = filepath.Rel(new.Dir, v); err != nil {
return nil, err
}
}
list = asAnySlice(glist)
}
// Get the list from a variable and split it up
if cmd.For.Var != "" {
if vars != nil {
v := vars.Get(cmd.For.Var)
// If the variable is dynamic, then it hasn't been resolved yet
// and we can't use it as a list. This happens when fast compiling a task
// for use in --list or --list-all etc.
if v.Value != nil && v.Sh == "" {
switch value := v.Value.(type) {
case string:
if cmd.For.Split != "" {
list = asAnySlice(strings.Split(value, cmd.For.Split))
} else {
list = asAnySlice(strings.Fields(value))
}
case []any:
list = value
case map[string]any:
for k, v := range value {
keys = append(keys, k)
list = append(list, v)
}
default:
return nil, errors.TaskfileInvalidError{
URI: origTask.Location.Taskfile,
Err: errors.New("var must be a delimiter-separated string or a list"),
}
}
}
}
list, keys, err := itemsFromFor(cmd.For, new.Dir, new.Sources, vars, origTask.Location)
if err != nil {
return nil, err
}
// Name the iterator variable
var as string
@@ -231,6 +184,33 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
if dep == nil {
continue
}
if dep.For != nil {
list, keys, err := itemsFromFor(dep.For, new.Dir, new.Sources, vars, origTask.Location)
if err != nil {
return nil, err
}
// Name the iterator variable
var as string
if dep.For.As != "" {
as = dep.For.As
} else {
as = "ITEM"
}
// Create a new command for each item in the list
for i, loopValue := range list {
extra := map[string]any{
as: loopValue,
}
if len(keys) > 0 {
extra["KEY"] = keys[i]
}
newDep := dep.DeepCopy()
newDep.Task = r.ReplaceWithExtra(dep.Task, extra)
newDep.Vars = r.ReplaceVarsWithExtra(dep.Vars, extra)
new.Deps = append(new.Deps, newDep)
}
continue
}
newDep := dep.DeepCopy()
newDep.Task = templater.Replace(dep.Task, cache)
newDep.Vars = templater.ReplaceVars(dep.Vars, cache)
@@ -296,3 +276,64 @@ func asAnySlice[T any](slice []T) []any {
}
return ret
}
func itemsFromFor(
f *ast.For,
dir string,
sources []*ast.Glob,
vars *ast.Vars,
location *ast.Location,
) ([]any, []string, error) {
var keys []string // The list of keys to loop over (only if looping over a map)
var values []any // The list of values to loop over
// Get the list from the explicit for list
if f.List != nil && len(f.List) > 0 {
values = f.List
}
// Get the list from the task sources
if f.From == "sources" {
glist, err := fingerprint.Globs(dir, sources)
if err != nil {
return nil, nil, err
}
// Make the paths relative to the task dir
for i, v := range glist {
if glist[i], err = filepath.Rel(dir, v); err != nil {
return nil, nil, err
}
}
values = asAnySlice(glist)
}
// Get the list from a variable and split it up
if f.Var != "" {
if vars != nil {
v := vars.Get(f.Var)
// If the variable is dynamic, then it hasn't been resolved yet
// and we can't use it as a list. This happens when fast compiling a task
// for use in --list or --list-all etc.
if v.Value != nil && v.Sh == "" {
switch value := v.Value.(type) {
case string:
if f.Split != "" {
values = asAnySlice(strings.Split(value, f.Split))
} else {
values = asAnySlice(strings.Fields(value))
}
case []any:
values = value
case map[string]any:
for k, v := range value {
keys = append(keys, k)
values = append(values, v)
}
default:
return nil, nil, errors.TaskfileInvalidError{
URI: location.Taskfile,
Err: errors.New("loop var must be a delimiter-separated string, list or a map"),
}
}
}
}
}
return values, keys, nil
}