task icon indicating copy to clipboard operation
task copied to clipboard

Any variables experiment

Open pd93 opened this issue 7 months ago • 14 comments

[!WARNING] 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.

[!NOTE] You can view the Any Variables experiment documentation on our website, including instructions on how to enable/disable it.

Context

This experiment attempts to solve the problems originally described by #140.

Currently, all Task variables are strings. You can define a variable as a YAML string, bool, int, float or null value. However, when task interprets it, it will be decoded as a string regardless of the input type.

For example, the following Taskfile will always output "foo" even when BOOL is false because the variable is being interpreted as a string and an if statement evaluates to true when the string is not empty:

version: 3

tasks:
  foo:
    vars:
      BOOL: false
    cmds:
      - '{{ if .BOOL }}echo foo{{ end}}'

Lists are also interpreted as strings and this has led to some annoying workarounds in the codebase. For example, the for feature is great for running a task multiple times with different variables. However, if you want to loop over an arbitrary list of strings, you have to define the list as a delimiter separated string and then split it by specifying the delimiter in the for statement. For example:

version: 3

tasks:
  foo:
    vars:
      LIST: 'foo,bar,baz'
    cmds:
      - for:
          var: LIST
          split: ','
        cmd: echo {{ .ITEM }}

This could be simplified if we supported variables as lists:

version: 3

tasks:
  foo:
    vars:
      LIST: [foo, bar, baz]
    cmds:
      - for:
          var: LIST
        cmd: echo {{ .ITEM }}

Proposal

We propose to change the type of internal Task variables to any (otherwise known as an empty interface{} in Go). This will allow users to define variables as any type they want, and Task will interpret them properly when used in tasks. The following types should be supported:

  • string
  • bool
  • int
  • float
  • array
  • map

Adding support for these types is relatively simple by itself. However, there a few changes that will be needed to make the rest of Task's features work nicely with the new variable types:

  • [ ] for should support iterating over arrays (and maybe maps?) https://github.com/go-task/task/pull/1436
  • [ ] sh needs to be removed and replaced with a new syntax for dynamically defined variables (see backwards compatibility)
  • [ ] ...

Backwards Compatibility

The current implementation of Task variables allows for dynamic variables to be specified by using the sh subkey. For example:

version: 3

task:
  foo:
    vars:
      CALCULATED_VAR:
        sh: 'echo hello'
    cmds:
      - 'echo {{ .CALCULATED_VAR }}'

Running task foo will output the following:

task: [foo] echo hello
hello

Since we are adding support for map variables, this syntax will conflict and can no longer be supported. Instead, we should detect string variables that begin with a $ and interpret them as a command to run. For example:

version: 3

task:
  foo:
    vars:
      CALCULATED_VAR: '$echo hello'
    cmds:
      - 'echo {{ .CALCULATED_VAR }}'

If a user wants a string variable that starts with $, they can escape it with a backslash: \$.

Removing the sh subkey will break any Taskfiles that currently use it and it is possible that the new syntax will also break existing taskfiles that have variables that start with $. For this reason, the functionality in this proposal will stay as an experiment until at least the next major version of Task.

pd93 avatar Nov 30 '23 01:11 pd93