--- slug: /experiments/map-variables/ --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Map Variables (#1585) :::caution All experimental features are subject to breaking changes and/or removal _at any time_. We strongly recommend that you do not use these features in a production environment. They are intended for testing and feedback only. ::: Currently, Task supports all variable types except for maps. This experiment adds two different proposals for map variables. Click on the tabs below to switch between them. :::warning This experiment proposal breaks the following functionality: - Dynamically defined variables (using the `sh` keyword) ::: :::info To enable this experiment, set the environment variable: `TASK_X_MAP_VARIABLES=1`. Check out [our guide to enabling experiments ][enabling-experiments] for more information. ::: This proposal removes support for the `sh` keyword in favour of a new syntax for dynamically defined variables, This allows you to define a map directly as you would for any other type: ```yaml version: 3 tasks: foo: vars: FOO: {a: 1, b: 2, c: 3} # <-- Directly defined map on the `FOO` key cmds: - 'echo {{.FOO.a}}' ``` ## Migration Taskfiles with dynamically defined variables via the `sh` subkey will no longer work with this experiment enabled. In order to keep using dynamically defined variables, you will need to migrate your Taskfile to use the new syntax. Previously, you might have defined a dynamic variable like this: ```yaml version: 3 tasks: foo: vars: CALCULATED_VAR: sh: 'echo hello' cmds: - 'echo {{.CALCULATED_VAR}}' ``` With this experiment enabled, you will need to remove the `sh` subkey and define your command as a string that begins with a `$`. This will instruct Task to interpret the string as a command instead of a literal value and the variable will be populated with the output of the command. For example: ```yaml version: 3 tasks: foo: vars: CALCULATED_VAR: '$echo hello' cmds: - 'echo {{.CALCULATED_VAR}}' ``` If your current Taskfile contains a string variable that begins with a `$`, you will now need to escape the `$` with a backslash (`\`) to stop Task from executing it as a command. :::info To enable this experiment, set the environment variable: `TASK_X_MAP_VARIABLES=2`. Check out [our guide to enabling experiments ][enabling-experiments] for more information. ::: This proposal maintains backwards-compatibility and the `sh` subkey and adds another new `map` subkey for defining map variables: ```yaml version: 3 tasks: foo: vars: FOO: map: {a: 1, b: 2, c: 3} # <-- Defined using the `map' subkey instead of directly on 'FOO' BAR: true # <-- Other types of variables are still defined directly on the key BAZ: sh: 'echo Hello Task' # <-- The `sh` subkey is still supported cmds: - 'echo {{.FOO.a}}' ``` ## Parsing JSON and YAML In addition to the new `map` keyword, this proposal also adds support for the `json` and `yaml` keywords for parsing JSON and YAML strings into real objects/arrays. This is similar to the `fromJSON` template function, but means that you only have to parse the JSON/YAML once when you declare the variable, instead of every time you want to access a value. ```yaml version: 3 tasks: foo: vars: FOO: '{"a": 1, "b": 2, "c": 3}' # <-- JSON string cmds: - 'echo {{(fromJSON .FOO).a}}' # <-- Parse JSON string every time you want to access a value - 'echo {{(fromJSON .FOO).b}}' ``` ```yaml version: 3 tasks: foo: vars: FOO: json: '{"a": 1, "b": 2, "c": 3}' # <-- JSON string parsed once cmds: - 'echo {{.FOO.a}}' # <-- Access values directly - 'echo {{.FOO.b}}' ``` ## Variables by reference Lastly, this proposal adds support for defining and passing variables by reference. This is really important now that variables can be types other than a string. Previously, to send a variable from one task to another, you would have to use the templating system. Unfortunately, the templater _always_ outputs a string and operations on the passed variable may not have behaved as expected. With this proposal, you can now pass variables by reference using the `ref` subkey: ```yaml version: 3 tasks: foo: vars: FOO: [A, B, C] # <-- FOO is defined as an array cmds: - task: bar vars: FOO: '{{.FOO}}' # <-- FOO gets converted to a string when passed to bar bar: cmds: - 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A' ``` ```yaml version: 3 tasks: foo: vars: FOO: [A, B, C] # <-- FOO is defined as an array cmds: - task: bar vars: FOO: 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 ``` This means that the type of the variable is maintained when it is passed to another Task. This also works the same way when calling `deps` and when defining a variable and can be used in any combination: ```yaml version: 3 tasks: foo: vars: FOO: [A, B, C] # <-- FOO is defined as an array BAR: 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 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' ``` ## Looping over maps This experiment also adds support for looping over maps using the `for` keyword, just like arrays. In addition to the `{{.ITEM}}` variable being populated when looping over a map, we also make an additional `{{.KEY}}` variable available that holds the string value of the map key. ```yaml version: 3 tasks: foo: vars: MAP: {a: 1, b: 2, c: 3} cmds: - for: var: MAP cmd: 'echo "{{.KEY}}: {{.ITEM}}"' ``` ```yaml version: 3 tasks: foo: vars: map: MAP: {a: 1, b: 2, c: 3} cmds: - for: var: MAP cmd: 'echo "{{.KEY}}: {{.ITEM}}"' ``` :::note Remember that maps are unordered, so the order in which the items are looped over is random. ::: {/* prettier-ignore-start */} [enabling-experiments]: ./experiments.mdx#enabling-experiments {/* prettier-ignore-end */}