From 1ff618cc1778eb1cd4b043ca9253f39b4e332641 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Tue, 9 Apr 2024 12:14:14 +0100 Subject: [PATCH] feat: enable any variables without maps (#1547) * feat: enable any variable experiment (without maps) * chore: rename any_variables experiment to map_variables * docs: create map variables experiment docs and update usage * blog: any variables * fix: links * fix: warn about broken links instead of failing --- internal/experiments/experiments.go | 6 +- taskfile/ast/var.go | 27 +-- ...=> 2023-09-02-introducing-experiments.mdx} | 6 +- website/blog/2024-04-09-variables.mdx | 182 ++++++++++++++++++ website/docs/experiments/gentle_force.mdx | 2 +- .../{any_variables.mdx => map_variables.mdx} | 160 ++++++--------- website/docs/experiments/remote_taskfiles.mdx | 2 +- website/docs/experiments/template.mdx | 2 +- website/docs/installation.mdx | 4 +- website/docs/taskfile_versions.mdx | 8 +- website/docs/usage.mdx | 51 ++++- website/docusaurus.config.ts | 4 +- 12 files changed, 322 insertions(+), 132 deletions(-) rename website/blog/{2023-09-02-introducing-experiments.md => 2023-09-02-introducing-experiments.mdx} (98%) create mode 100644 website/blog/2024-04-09-variables.mdx rename website/docs/experiments/{any_variables.mdx => map_variables.mdx} (68%) diff --git a/internal/experiments/experiments.go b/internal/experiments/experiments.go index c185052e..bcd37236 100644 --- a/internal/experiments/experiments.go +++ b/internal/experiments/experiments.go @@ -27,14 +27,14 @@ type Experiment struct { var ( GentleForce Experiment RemoteTaskfiles Experiment - AnyVariables Experiment + MapVariables Experiment ) func init() { readDotEnv() GentleForce = New("GENTLE_FORCE") RemoteTaskfiles = New("REMOTE_TASKFILES") - AnyVariables = New("ANY_VARIABLES", "1", "2") + MapVariables = New("MAP_VARIABLES", "1", "2") } func New(xName string, enabledValues ...string) Experiment { @@ -101,6 +101,6 @@ func List(l *logger.Logger) error { w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, ' ', 0) printExperiment(w, l, GentleForce) printExperiment(w, l, RemoteTaskfiles) - printExperiment(w, l, AnyVariables) + printExperiment(w, l, MapVariables) return w.Flush() } diff --git a/taskfile/ast/var.go b/taskfile/ast/var.go index 5ce8fd6d..7ec0a44b 100644 --- a/taskfile/ast/var.go +++ b/taskfile/ast/var.go @@ -83,10 +83,10 @@ type Var struct { } func (v *Var) UnmarshalYAML(node *yaml.Node) error { - if experiments.AnyVariables.Enabled { + if experiments.MapVariables.Enabled { // This implementation is not backwards-compatible and replaces the 'sh' key with map variables - if experiments.AnyVariables.Value == "1" { + if experiments.MapVariables.Value == "1" { var value any if err := node.Decode(&value); err != nil { return err @@ -103,7 +103,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error { } // This implementation IS backwards-compatible and keeps the 'sh' key and allows map variables to be added under the `map` key - if experiments.AnyVariables.Value == "2" { + if experiments.MapVariables.Value == "2" { switch node.Kind { case yaml.MappingNode: key := node.Content[0].Value @@ -141,15 +141,10 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { - case yaml.ScalarNode: - var str string - if err := node.Decode(&str); err != nil { - return err - } - v.Value = str - return nil - case yaml.MappingNode: + if len(node.Content) > 2 || node.Content[0].Value != "sh" { + return fmt.Errorf(`task: line %d: maps cannot be assigned to variables`, node.Line) + } var sh struct { Sh string } @@ -158,7 +153,13 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error { } v.Sh = sh.Sh return nil - } - return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variable", node.Line, node.ShortTag()) + default: + var value any + if err := node.Decode(&value); err != nil { + return err + } + v.Value = value + return nil + } } diff --git a/website/blog/2023-09-02-introducing-experiments.md b/website/blog/2023-09-02-introducing-experiments.mdx similarity index 98% rename from website/blog/2023-09-02-introducing-experiments.md rename to website/blog/2023-09-02-introducing-experiments.mdx index b7bccdf5..bcd7279e 100644 --- a/website/blog/2023-09-02-introducing-experiments.md +++ b/website/blog/2023-09-02-introducing-experiments.mdx @@ -16,7 +16,7 @@ communicate these kinds of thoughts to the community. So, with that in mind, this is the first (hopefully of many) blog posts talking about Task and what we're up to. - +{/* truncate */} ## :calendar: So, what have we been up to? @@ -122,7 +122,7 @@ I plan to write more of these blog posts in the future on a variety of Task-related topics, so make sure to check in occasionally and see what we're up to! - +{/* prettier-ignore-start */} [vscode-task]: https://github.com/go-task/vscode-task [crowdin]: https://crowdin.com [contributors]: https://github.com/go-task/task/graphs/contributors @@ -139,4 +139,4 @@ to! [experiments-project]: https://github.com/orgs/go-task/projects/1 [gentle-force-experiment]: https://github.com/go-task/task/issues/1200 [remote-taskfiles-experiment]: https://github.com/go-task/task/issues/1317 - +{/* prettier-ignore-end */} diff --git a/website/blog/2024-04-09-variables.mdx b/website/blog/2024-04-09-variables.mdx new file mode 100644 index 00000000..b2a829e7 --- /dev/null +++ b/website/blog/2024-04-09-variables.mdx @@ -0,0 +1,182 @@ +--- +title: Any Variables +description: Task variables are no longer limited to strings! +slug: any-variables +authors: [pd93] +tags: [experiments, variables] +image: https://i.imgur.com/mErPwqL.png +hide_table_of_contents: false +draft: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Task has always had variables, but even though you were able to define them +using different YAML types, they would always be converted to strings by Task. +This limited users to string manipulation and encouraged messy workarounds for +simple problems. Starting from [v3.36.0][v3.36.0], this is no longer the case! +Task now supports most variable types, including **booleans**, **integers**, +**floats** and **arrays**! + +{/* truncate */} + +## What's the big deal? + +These changes allow you to use variables in a much more natural way and opens up +a wide variety of sprig functions that were previously useless. Take a look at +some of the examples below for some inspiration. + +### Evaluating booleans + +No more comparing strings to "true" or "false". Now you can use actual boolean +values in your templates: + + + + + +```yaml +version: 3 + +tasks: + foo: + vars: + BOOL: true # <-- Parsed as a string even though its a YAML boolean + cmds: + - '{{if eq .BOOL "true"}}echo foo{{end}}' +``` + + + + +```yaml +version: 3 + +tasks: + foo: + vars: + BOOL: true # <-- Parsed as a boolean + cmds: + - '{{if .BOOL}}echo foo{{end}}' # <-- No need to compare to "true" +``` + + + +### Arithmetic + +You can now perform basic arithmetic operations on integer and float variables: + +```yaml +version: 3 + +tasks: + foo: + vars: + INT: 10 + FLOAT: 3.14159 + cmds: + - 'echo {{add .INT .FLOAT}}' +``` + +You can use any of the following arithmetic functions: `add`, `sub`, `mul`, +`div`, `mod`, `max`, `min`, `floor`, `ceil`, `round` and `randInt`. Check out +the [slim-sprig math documentation][slim-sprig-math] for more information. + +### Arrays + +You can now range over arrays inside templates and use list-based functions: + +```yaml +version: 3 + +tasks: + foo: + vars: + ARRAY: [1, 2, 3] + cmds: + - 'echo {{range .ARRAY}}{{.}}{{end}}' +``` + +You can use any of the following list-based functions: `first`, `rest`, `last`, +`initial`, `append`, `prepend`, `concat`, `reverse`, `uniq`, `without`, `has`, +`compact`, `slice` and `chunk`. Check out the [slim-sprg lists +documentation][slim-sprig-list] for more information. + +### Looping over variables using `for` + +Previously, you would have to use a delimiter separated string to loop over an +arbitrary list of items in a variable and split them by using the `split` subkey +to specify the delimiter. However, we have now added support for looping over +"collection-type" variables using the `for` keyword, so now you are able to loop +over list variables directly: + + + + + +```yaml +version: 3 + +tasks: + foo: + vars: + LIST: 'foo,bar,baz' + cmds: + - for: + var: LIST + split: ',' + cmd: echo {{.ITEM}} +``` + + + + +```yaml +version: 3 + +tasks: + foo: + vars: + LIST: ['foo', 'bar', 'baz'] + cmds: + - for: + var: LIST + cmd: echo {{.ITEM}} +``` + + + +## What about maps? + +Maps were originally included in the Any Variables experiment. However, they +weren't quite ready yet. Instead of making you wait for everything to be ready +at once, we have released support for all other variable types and we will +continue working on map support in the new "[Map Variables][map-variables]" +experiment. + +:::note + +If you were previously using maps with the Any Variables experiment and wish to +continue using them, you will need to enable the new [Map Variables +experiment][map-variables] instead. + +::: + +We're looking for feedback on a couple of different proposals, so please give +them a go and let us know what you think. :pray: + +{/* prettier-ignore-start */} +[v3.36.0]: https://github.com/go-task/task/releases/tag/v3.36.0 +[slim-sprig-math]: https://go-task.github.io/slim-sprig/math.html +[slim-sprig-list]: https://go-task.github.io/slim-sprig/lists.html +[map-variables]: /experiments/map-variables +{/* prettier-ignore-end */} diff --git a/website/docs/experiments/gentle_force.mdx b/website/docs/experiments/gentle_force.mdx index ab3d52d3..5521303b 100644 --- a/website/docs/experiments/gentle_force.mdx +++ b/website/docs/experiments/gentle_force.mdx @@ -45,5 +45,5 @@ if you want to adopt the new behavior, you can continue to use the `--force` flag as you do now! {/* prettier-ignore-start */} -[enabling-experiments]: /experiments/#enabling-experiments +[enabling-experiments]: ./experiments.mdx#enabling-experiments {/* prettier-ignore-end */} diff --git a/website/docs/experiments/any_variables.mdx b/website/docs/experiments/map_variables.mdx similarity index 68% rename from website/docs/experiments/any_variables.mdx rename to website/docs/experiments/map_variables.mdx index be768d10..f607519b 100644 --- a/website/docs/experiments/any_variables.mdx +++ b/website/docs/experiments/map_variables.mdx @@ -1,11 +1,11 @@ --- -slug: /experiments/any-variables/ +slug: /experiments/map-variables/ --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Any Variables (#1415) +# Map Variables (#1585) :::caution @@ -15,19 +15,9 @@ environment. They are intended for testing and feedback only. ::: -Currently, Task only supports string variables. This experiment allows you to -specify and use the following variable types: - -- `string` -- `bool` -- `int` -- `float` -- `array` -- `map` - -This allows you to have a lot more flexibility in how you use variables in -Task's templating engine. There are two active proposals for this experiment. -Click on the tabs below to switch between them. +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. - :::info To enable this experiment, set the environment variable: -`TASK_X_ANY_VARIABLES=2`. Check out [our guide to enabling experiments +`TASK_X_MAP_VARIABLES=2`. Check out [our guide to enabling experiments ][enabling-experiments] for more information. ::: -## Maps - This proposal maintains backwards-compatibility and the `sh` subkey and adds another new `map` subkey for defining map variables: @@ -150,7 +135,13 @@ 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. -Before: + + + ```yaml version: 3 @@ -164,7 +155,8 @@ tasks: - 'echo {{(fromJSON .FOO).b}}' ``` -After: + + ```yaml version: 3 @@ -179,12 +171,26 @@ tasks: - '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 to pass it: +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 @@ -202,10 +208,8 @@ tasks: - 'echo {{index .FOO 0}}' # <-- FOO is a string so the task outputs '91' which is the ASCII code for '[' instead of the expected 'A' ``` -Unfortunately, this results in the value always being passed as a string as this -is the output type of the templater and operations on the passed variable may -not behave as expected. With this proposal, you can now pass variables by -reference using the `ref` subkey: + + ```yaml version: 3 @@ -224,6 +228,8 @@ tasks: - '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: @@ -249,14 +255,20 @@ tasks: ---- +## Looping over maps -## Common to both proposals +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. -Both proposals add support for all other variable types by directly defining -them in the Taskfile. For example: + -### Evaluating booleans + ```yaml version: 3 @@ -264,64 +276,15 @@ version: 3 tasks: foo: vars: - BOOL: false - cmds: - - '{{if .BOOL}}echo foo{{end}}' -``` - -### Arithmetic - -```yaml -version: 3 - -tasks: - foo: - vars: - INT: 10 - FLOAT: 3.14159 - cmds: - - 'echo {{add .INT .FLOAT}}' -``` - -### Ranging - -```yaml -version: 3 - -tasks: - foo: - vars: - ARRAY: [1, 2, 3] - cmds: - - 'echo {{range .ARRAY}}{{.}}{{end}}' -``` - -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. - -## Looping over variables - -Previously, you would have to use a delimiter separated string to loop over an -arbitrary list of items in a variable and split them by using the `split` subkey -to specify the delimiter: - -```yaml -version: 3 - -tasks: - foo: - vars: - LIST: 'foo,bar,baz' + MAP: {a: 1, b: 2, c: 3} cmds: - for: - var: LIST - split: ',' - cmd: echo {{.ITEM}} + var: MAP + cmd: 'echo "{{.KEY}}: {{.ITEM}}"' ``` -Both of these proposals add support for looping over "collection-type" variables -using the `for` keyword, so now you are able to loop over a map/array variable -directly: + + ```yaml version: 3 @@ -329,18 +292,23 @@ version: 3 tasks: foo: vars: - LIST: [foo, bar, baz] + map: + MAP: {a: 1, b: 2, c: 3} cmds: - for: - var: LIST - cmd: echo {{.ITEM}} + var: MAP + cmd: 'echo "{{.KEY}}: {{.ITEM}}"' ``` -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 +:::note + +Remember that maps are unordered, so the order in which the items are looped over is random. +::: + + + {/* prettier-ignore-start */} -[enabling-experiments]: /experiments/#enabling-experiments -[slim-sprig]: https://go-task.github.io/slim-sprig/ +[enabling-experiments]: ./experiments.mdx#enabling-experiments {/* prettier-ignore-end */} diff --git a/website/docs/experiments/remote_taskfiles.mdx b/website/docs/experiments/remote_taskfiles.mdx index 742b451c..9c83ab1c 100644 --- a/website/docs/experiments/remote_taskfiles.mdx +++ b/website/docs/experiments/remote_taskfiles.mdx @@ -99,6 +99,6 @@ the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will set the timeout to 5 seconds. {/* prettier-ignore-start */} -[enabling-experiments]: /experiments/#enabling-experiments +[enabling-experiments]: ./experiments.mdx#enabling-experiments [man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack {/* prettier-ignore-end */} diff --git a/website/docs/experiments/template.mdx b/website/docs/experiments/template.mdx index fca9a527..f1bb9887 100644 --- a/website/docs/experiments/template.mdx +++ b/website/docs/experiments/template.mdx @@ -38,5 +38,5 @@ information. \{Short explanation of how users should migrate to the new behavior\} {/* prettier-ignore-start */} -[enabling-experiments]: /experiments/#enabling-experiments +[enabling-experiments]: ./experiments.mdx#enabling-experiments {/* prettier-ignore-end */} diff --git a/website/docs/installation.mdx b/website/docs/installation.mdx index ac5e063d..94dd42ca 100644 --- a/website/docs/installation.mdx +++ b/website/docs/installation.mdx @@ -31,7 +31,7 @@ brew install go-task ### pkgx -If you're on macOS or Linux and have [pkgx](https://pkgx.sh/) installed, getting Task is as +If you're on macOS or Linux and have [pkgx][pkgx] installed, getting Task is as simple as running: ```shell @@ -299,5 +299,5 @@ Invoke-Expression -Command path/to/task.ps1 [godownloader]: https://github.com/goreleaser/godownloader [choco]: https://chocolatey.org/ [scoop]: https://scoop.sh/ -[tea]: https://tea.xyz/ +[pkgx]: https://pkgx.sh/ {/* prettier-ignore-end */} diff --git a/website/docs/taskfile_versions.mdx b/website/docs/taskfile_versions.mdx index 0b080726..b85a6f7a 100644 --- a/website/docs/taskfile_versions.mdx +++ b/website/docs/taskfile_versions.mdx @@ -256,8 +256,8 @@ The variable priority order was also different: 4. `Taskvars.yml` variables {/* prettier-ignore-start */} -[deprecate-version-2-schema]: /deprecations/version-2-schema/ -[output]: /usage#output-syntax -[ignore_errors]: /usage#ignore-errors -[includes]: /usage#including-other-taskfiles +[deprecate-version-2-schema]: ./deprecations/version_2_schema.mdx +[output]: ./usage.mdx#output-syntax +[ignore_errors]: ./usage.mdx#ignore-errors +[includes]: ./usage.mdx#including-other-taskfiles {/* prettier-ignore-end */} diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx index cea5a950..aab75174 100644 --- a/website/docs/usage.mdx +++ b/website/docs/usage.mdx @@ -947,8 +947,26 @@ tasks: ## Variables -When doing interpolation of variables, Task will look for the below. They are -listed below in order of importance (i.e. most important first): +Task allows you to set variables using the `vars` keyword. The following +variable types are supported: + +- `string` +- `bool` +- `int` +- `float` +- `array` + +:::note + +Maps are not supported by default, but there is an +[experiment][map-variables] that can be enabled to add support. If +you're interested in this functionality, we would appreciate your feedback. +:pray: + +::: + +Variables can be set in many places in a Taskfile. When executing templates, +Task will look for variables in the order listed below (most important first): - Variables declared in the task definition - Variables given while calling a task from another (See @@ -1093,8 +1111,8 @@ tasks: ### Looping over variables To loop over the contents of a variable, you simply need to specify the variable -you want to loop over. By default, variables will be split on any whitespace -characters. +you want to loop over. By default, string variables will be split on any +whitespace characters. ```yaml version: '3' @@ -1108,8 +1126,8 @@ tasks: cmd: cat {{.ITEM}} ``` -If you need to split on a different character, you can do this by specifying the -`split` property: +If you need to split a string on a different character, you can do this by +specifying the `split` property: ```yaml version: '3' @@ -1123,6 +1141,26 @@ tasks: cmd: cat {{.ITEM}} ``` +You can also loop over arrays directly (and maps if you have the +[maps experiment](/experiments/map-variables) enabled): + +```yaml +version: 3 + +tasks: + foo: + vars: + LIST: [foo, bar, baz] + cmds: + - for: + var: LIST + cmd: echo {{.ITEM}} +``` + +When looping over a map we also make an additional `{{.KEY}}` variable available +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. + All of this also works with dynamic variables! ```yaml @@ -1956,4 +1994,5 @@ if called by another task, either directly or as a dependency. {/* prettier-ignore-start */} [gotemplate]: https://golang.org/pkg/text/template/ +[map-variables]: ./experiments/map_variables.mdx {/* prettier-ignore-end */} diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 67d0cfb7..ea0d011d 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -21,8 +21,8 @@ const config: Config = { tagline: 'A task runner / simpler Make alternative written in Go ', url: 'https://taskfile.dev', baseUrl: '/', - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'throw', + onBrokenLinks: 'warn', + onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.ico', organizationName: 'go-task',