From d41f4370b70d59dae5d441af8d432584b1b6cd35 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Thu, 21 Dec 2023 10:23:44 +0000 Subject: [PATCH 1/2] feat: support looping over map variables --- docs/docs/experiments/any_variables.md | 26 ++++++++++++++++++++++++-- testdata/vars/any/Taskfile.yml | 26 ++++++++++++++++++++++++++ variables.go | 6 ++---- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/docs/docs/experiments/any_variables.md b/docs/docs/experiments/any_variables.md index 61c95f752b..d216bf4a8e 100644 --- a/docs/docs/experiments/any_variables.md +++ b/docs/docs/experiments/any_variables.md @@ -86,8 +86,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 +102,28 @@ tasks: cmd: echo {{.ITEM}} ``` +This also works for maps. However, 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 {{.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..e4580f636d 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 {{.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 {{.ITEM.SUBKEY}} diff --git a/variables.go b/variables.go index b7071c3d5f..1cbda2e70b 100644 --- a/variables.go +++ b/variables.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/joho/godotenv" + "golang.org/x/exp/maps" "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/execext" @@ -170,10 +171,7 @@ 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."), - } + list = maps.Values(value) default: return nil, errors.TaskfileInvalidError{ URI: origTask.Location.Taskfile, From f6b9fb0c744217d0037d7ef05f99182422ff2f1f Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Thu, 21 Dec 2023 15:37:11 +0000 Subject: [PATCH 2/2] feat: add .KEY variable --- docs/docs/experiments/any_variables.md | 11 ++++++----- testdata/vars/any/Taskfile.yml | 4 ++-- variables.go | 12 +++++++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/docs/experiments/any_variables.md b/docs/docs/experiments/any_variables.md index d216bf4a8e..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 @@ -102,8 +101,10 @@ tasks: cmd: echo {{.ITEM}} ``` -This also works for maps. However, remember that maps are unordered, so the -order in which the items are looped over is random: +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 @@ -121,7 +122,7 @@ tasks: cmds: - for: var: MAP - cmd: echo {{.ITEM.SUBKEY}} + cmd: echo {{.KEY}} {{.ITEM.SUBKEY}} ``` String splitting is still supported and remember that for simple cases, you have diff --git a/testdata/vars/any/Taskfile.yml b/testdata/vars/any/Taskfile.yml index e4580f636d..285996348e 100644 --- a/testdata/vars/any/Taskfile.yml +++ b/testdata/vars/any/Taskfile.yml @@ -89,7 +89,7 @@ tasks: cmds: - for: var: MAP - cmd: echo {{.ITEM}} + cmd: echo {{.KEY}} {{.ITEM}} for-multi-layer-map: vars: @@ -103,4 +103,4 @@ tasks: cmds: - for: var: MAP - cmd: echo {{.ITEM.SUBKEY}} + cmd: echo {{.KEY}} {{.ITEM.SUBKEY}} diff --git a/variables.go b/variables.go index 1cbda2e70b..9e7a4b0d4a 100644 --- a/variables.go +++ b/variables.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/joho/godotenv" - "golang.org/x/exp/maps" "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/execext" @@ -134,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 { @@ -171,7 +171,10 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf case []any: list = value case map[string]any: - list = maps.Values(value) + for k, v := range value { + keys = append(keys, k) + list = append(list, v) + } default: return nil, errors.TaskfileInvalidError{ URI: origTask.Location.Taskfile, @@ -189,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),