From b592648d5547fd7cee85ce4b5d9f4af5c3671242 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Thu, 21 Dec 2023 09:43:56 -0600 Subject: [PATCH] feat: support looping over map variables (#1436) * feat: support looping over map variables * feat: add .KEY variable --- docs/docs/experiments/any_variables.md | 31 ++++++++++++++++++++++---- testdata/vars/any/Taskfile.yml | 26 +++++++++++++++++++++ variables.go | 12 ++++++---- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/docs/docs/experiments/any_variables.md b/docs/docs/experiments/any_variables.md index 61c95f752b..eb9c18193f 100644 --- a/docs/docs/experiments/any_variables.md +++ b/docs/docs/experiments/any_variables.md @@ -63,8 +63,7 @@ tasks: ``` There are many more templating functions which can be used with the new types of -variables. For a full list, see the -[slim-sprig][slim-sprig] documentation. +variables. For a full list, see the [slim-sprig][slim-sprig] documentation. ## Looping over variables @@ -86,8 +85,8 @@ tasks: cmd: echo {{.ITEM}} ``` -Because this experiment adds support for array variables, the `for` keyword has -been updated to support looping over arrays directly: +Because this experiment adds support for "collection-type" variables, the `for` +keyword has been updated to support looping over arrays directly: ```yaml version: 3 @@ -102,6 +101,30 @@ tasks: cmd: echo {{.ITEM}} ``` +This also works for maps. When looping over a map we also make an additional +`{{.KEY}}` variable availabe that holds the string value of the map key. +Remember that maps are unordered, so the order in which the items are looped +over is random: + +```yaml +version: 3 + +tasks: + foo: + vars: + MAP: + KEY_1: + SUBKEY: sub_value_1 + KEY_2: + SUBKEY: sub_value_2 + KEY_3: + SUBKEY: sub_value_3 + cmds: + - for: + var: MAP + cmd: echo {{.KEY}} {{.ITEM.SUBKEY}} +``` + String splitting is still supported and remember that for simple cases, you have always been able to loop over an array without using variables at all: diff --git a/testdata/vars/any/Taskfile.yml b/testdata/vars/any/Taskfile.yml index e1167ec1ee..285996348e 100644 --- a/testdata/vars/any/Taskfile.yml +++ b/testdata/vars/any/Taskfile.yml @@ -9,6 +9,8 @@ tasks: - task: string-array - task: for-string - task: for-int + - task: for-map + - task: for-multi-layer-map dynamic: vars: @@ -78,3 +80,27 @@ tasks: var: LIST cmd: echo {{add .ITEM 100}} + for-map: + vars: + MAP: + KEY_1: value_1 + KEY_2: value_2 + KEY_3: value_3 + cmds: + - for: + var: MAP + cmd: echo {{.KEY}} {{.ITEM}} + + for-multi-layer-map: + vars: + MAP: + KEY_1: + SUBKEY: sub_value_1 + KEY_2: + SUBKEY: sub_value_2 + KEY_3: + SUBKEY: sub_value_3 + cmds: + - for: + var: MAP + cmd: echo {{.KEY}} {{.ITEM.SUBKEY}} diff --git a/variables.go b/variables.go index b7071c3d5f..9e7a4b0d4a 100644 --- a/variables.go +++ b/variables.go @@ -133,6 +133,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf 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 { @@ -170,9 +171,9 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf case []any: list = value case map[string]any: - return &taskfile.Task{}, errors.TaskfileInvalidError{ - URI: origTask.Location.Taskfile, - Err: errors.New("sh is not supported with the 'Any Variables' experiment enabled.\nSee https://taskfile.dev/experiments/any-variables for more information."), + for k, v := range value { + keys = append(keys, k) + list = append(list, v) } default: return nil, errors.TaskfileInvalidError{ @@ -191,10 +192,13 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf as = "ITEM" } // Create a new command for each item in the list - for _, loopValue := range list { + for i, loopValue := range list { extra := map[string]any{ as: loopValue, } + if len(keys) > 0 { + extra["KEY"] = keys[i] + } new.Cmds = append(new.Cmds, &taskfile.Cmd{ Cmd: r.ReplaceWithExtra(cmd.Cmd, extra), Task: r.ReplaceWithExtra(cmd.Task, extra),