Compare commits

..

29 Commits

Author SHA1 Message Date
Andrey Nering
5395921acc v3.35.0 2024-02-28 19:55:28 -03:00
Andrey Nering
8a73411803 chore: add changelog for #1520 2024-02-28 19:46:30 -03:00
Pete Davison
6c21568447 fix: list tasks (#1520) 2024-02-28 19:44:34 -03:00
David Fregoli
330722335d docs: improve wording (#1518) 2024-02-27 14:25:25 +00:00
dependabot[bot]
99397dfe98 chore(deps): bump mvdan.cc/sh/v3 from 3.7.0 to 3.8.0 (#1515) 2024-02-26 10:09:38 -03:00
Pete Davison
1157b213de chore: add changelogs for #1483 and #1489 2024-02-22 21:05:08 +00:00
Pete Davison
fa40e8a762 feat: more permissive file modes 2024-02-22 14:59:54 -06:00
Pete Davison
98e0cea469 docs: added reading from stdin section to usage 2024-02-22 14:59:54 -06:00
Pete Davison
508ff717c9 feat: add missing syntax highlighters 2024-02-22 14:59:54 -06:00
Pete Davison
c7ba42b81a fix: resolve directory correctly when using --dir 2024-02-22 14:59:54 -06:00
Pete Davison
bb9d582255 feat: stdin node 2024-02-22 14:59:54 -06:00
Pete Davison
38a06dad8e feat: error when multiple wildcard matches are found 2024-02-22 14:58:24 -06:00
Pete Davison
beb9f42215 docs: updated usage to include section on wildcard arguments 2024-02-22 14:58:24 -06:00
Pete Davison
df251de33e feat: tests for wildcard matching 2024-02-22 14:58:24 -06:00
Pete Davison
9a3d2bc3aa feat: wildcard matching of task names 2024-02-22 14:58:24 -06:00
Pete Davison
1ef5cf71d0 feat: pass ast.Call by reference 2024-02-22 14:58:24 -06:00
Andrey Nering
65fdb618aa chore: add changelog for #1491 2024-02-21 21:25:27 -03:00
teatimeguest
3b44da323b docs(api): remove duplicated entry for requires (#1491) 2024-02-21 21:24:12 -03:00
Andrey Nering
2c20407e1b chore: add changelog for #1495 2024-02-21 21:19:09 -03:00
iwittkau
27455fc4c8 docs: improve style guide docs (#1495) 2024-02-21 21:18:04 -03:00
Andrey Nering
971c3e3a01 chore: add changelog for #1510 2024-02-21 21:15:14 -03:00
kirkrodrigues
67b94798b7 docs: specify that variable declaration order is respected (#1510) 2024-02-21 21:13:40 -03:00
Andrey Nering
c465234aa9 chore: add changelog for #1512 2024-02-21 21:08:32 -03:00
Pete Davison
07a0b8938f refactor: bubble errors from ListTaskNames 2024-02-21 21:06:34 -03:00
Pete Davison
ba81181eb7 fix: setup logger if nil when listing task names 2024-02-21 21:06:34 -03:00
Pete Davison
e2b0789b0c chore: added changelog for #1500 2024-02-20 00:04:01 +00:00
Pete Davison
2c6969d572 chore: update go minimum version to 1.21 2024-02-17 23:29:49 -03:00
Pete Davison
8d0754af4d feat: go 1.21 2024-02-17 23:29:49 -03:00
dependabot[bot]
81148c312e chore(deps): bump golang.org/x/term from 0.16.0 to 0.17.0 (#1502) 2024-02-17 23:26:26 -03:00
47 changed files with 648 additions and 280 deletions

View File

@@ -10,11 +10,15 @@ on:
jobs:
lint:
name: Lint
strategy:
matrix:
go-version: [1.21.x, 1.22.x]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.21.x
go-version: ${{matrix.go-version}}
- uses: actions/checkout@v3

View File

@@ -3,7 +3,7 @@ name: goreleaser
on:
push:
tags:
- '*'
- 'v*'
jobs:
goreleaser:

View File

@@ -13,7 +13,7 @@ jobs:
name: Test
strategy:
matrix:
go-version: [1.20.x, 1.21.x]
go-version: [1.21.x, 1.22.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:

View File

@@ -1,5 +1,22 @@
# Changelog
## v3.35.0 - 2024-02-28
- Added support for
[wildcards in task names](https://taskfile.dev/usage/#wildcard-arguments)
(#836, #1489 by @pd93).
- Added the ability to
[run Taskfiles via stdin](https://taskfile.dev/usage/#reading-a-taskfile-from-stdin)
(#655, #1483 by @pd93).
- Bumped minimum Go version to 1.21 (#1500 by @pd93).
- Fixed bug related to the `--list` flag (#1509, #1512 by @pd93, #1514, #1520 by
@pd93).
- Add mention on the documentation to the fact that the variable declaration
order is respected (#1510 by @kirkrodrigues).
- Improved style guide docs (#1495 by @iwittkau).
- Removed duplicated entry for `requires` on the API docs (#1491 by
@teatimeguest).
## v3.34.1 - 2024-01-27
- Fixed prompt regression on

View File

@@ -7,13 +7,13 @@ import (
)
// Parse parses command line argument: tasks and global variables
func Parse(args ...string) ([]ast.Call, *ast.Vars) {
calls := []ast.Call{}
func Parse(args ...string) ([]*ast.Call, *ast.Vars) {
calls := []*ast.Call{}
globals := &ast.Vars{}
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, ast.Call{Task: arg})
calls = append(calls, &ast.Call{Task: arg})
continue
}

View File

@@ -14,12 +14,12 @@ import (
func TestArgs(t *testing.T) {
tests := []struct {
Args []string
ExpectedCalls []ast.Call
ExpectedCalls []*ast.Call
ExpectedGlobals *ast.Vars
}{
{
Args: []string{"task-a", "task-b", "task-c"},
ExpectedCalls: []ast.Call{
ExpectedCalls: []*ast.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
@@ -27,7 +27,7 @@ func TestArgs(t *testing.T) {
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
ExpectedCalls: []ast.Call{
ExpectedCalls: []*ast.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
@@ -45,7 +45,7 @@ func TestArgs(t *testing.T) {
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []ast.Call{
ExpectedCalls: []*ast.Call{
{Task: "task-a"},
},
ExpectedGlobals: &ast.Vars{
@@ -59,7 +59,7 @@ func TestArgs(t *testing.T) {
},
{
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []ast.Call{
ExpectedCalls: []*ast.Call{
{Task: "task-a"},
{Task: "task-b"},
},
@@ -74,15 +74,15 @@ func TestArgs(t *testing.T) {
},
{
Args: nil,
ExpectedCalls: []ast.Call{},
ExpectedCalls: []*ast.Call{},
},
{
Args: []string{},
ExpectedCalls: []ast.Call{},
ExpectedCalls: []*ast.Call{},
},
{
Args: []string{"FOO=bar", "BAR=baz"},
ExpectedCalls: []ast.Call{},
ExpectedCalls: []*ast.Call{},
ExpectedGlobals: &ast.Vars{
OrderedMap: omap.FromMapWithOrder(
map[string]ast.Var{

View File

@@ -264,11 +264,6 @@ func run() error {
return err
}
if (listOptions.ShouldListTasks()) && flags.silent {
e.ListTaskNames(flags.listAll)
return nil
}
if err := e.Setup(); err != nil {
return err
}
@@ -279,6 +274,10 @@ func run() error {
return nil
}
if (listOptions.ShouldListTasks()) && flags.silent {
return e.ListTaskNames(flags.listAll)
}
if listOptions.ShouldListTasks() {
foundTasks, err := e.ListTasks(listOptions)
if err != nil {
@@ -291,7 +290,7 @@ func run() error {
}
var (
calls []ast.Call
calls []*ast.Call
globals *ast.Vars
)
@@ -304,7 +303,7 @@ func run() error {
// If there are no calls, run the default task instead
if len(calls) == 0 {
calls = append(calls, ast.Call{Task: "default"})
calls = append(calls, &ast.Call{Task: "default"})
}
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})

View File

@@ -11,7 +11,7 @@ toc_max_heading_level: 5
Task command line tool has the following syntax:
```bash
```shell
task [--flags] [tasks...] [-- CLI_ARGS...]
```
@@ -214,6 +214,19 @@ vars:
:::
:::info
In a variables map, variables defined later may reference variables defined
earlier (declaration order is respected):
```yaml
vars:
FIRST_VAR: "hello"
SECOND_VAR: "{{.FIRST_VAR}} world"
```
:::
### Task
| Attribute | Type | Default | Description |
@@ -228,7 +241,6 @@ vars:
| `sources` | `[]string` | | A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
| `generates` | `[]string` | | A list of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs. |
| `status` | `[]string` | | A list of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`. |
| `requires` | `[]string` | | A list of variables which should be set if this task is to run, if any of these variables are unset the task will error and not run. |
| `preconditions` | [`[]Precondition`](#precondition) | | A list of commands to check if this task should run. If a condition is not met, the task will error. |
| `requires` | [`Requires`](#requires) | | A list of required variables which should be set if this task is to run, if any variables listed are unset the task will error and not run. |
| `dir` | `string` | | The directory in which this task should run. Defaults to the current working directory. |

View File

@@ -5,6 +5,23 @@ sidebar_position: 14
# Changelog
## v3.35.0 - 2024-02-28
- Added support for
[wildcards in task names](https://taskfile.dev/usage/#wildcard-arguments)
(#836, #1489 by @pd93).
- Added the ability to
[run Taskfiles via stdin](https://taskfile.dev/usage/#reading-a-taskfile-from-stdin)
(#655, #1483 by @pd93).
- Bumped minimum Go version to 1.21 (#1500 by @pd93).
- Fixed bug related to the `--list` flag (#1509, #1512 by @pd93, #1514, #1520 by
@pd93).
- Add mention on the documentation to the fact that the variable declaration
order is respected (#1510 by @kirkrodrigues).
- Improved style guide docs (#1495 by @iwittkau).
- Removed duplicated entry for `requires` on the API docs (#1491 by
@teatimeguest).
## v3.34.1 - 2024-01-27
- Fixed prompt regression on

View File

@@ -69,7 +69,7 @@ tasks:
- ./foo-printer.bash
```
```bash
```shell
#!/bin/bash
a=foo
echo $a

View File

@@ -14,7 +14,7 @@ Task offers many installation methods. Check out the available methods below.
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
Task is as simple as running:
```bash
```shell
brew install go-task/tap/go-task
```
@@ -25,24 +25,25 @@ Recently, Task was also made available
[on the official Homebrew repository](https://formulae.brew.sh/formula/go-task),
so you also have that option if you prefer:
```bash
```shell
brew install go-task
```
### Tea
If you're on macOS or Linux and have [tea][tea] installed, getting
Task is as simple as running:
If you're on macOS or Linux and have [tea][tea] installed, getting Task is as
simple as running:
```bash
```shell
tea task
```
or, if you have teas magic enabled:
```bash
```shell
task
```
This installation method is community owned. After a new release of Task, they
are automatically released by tea in a minimum of time.
@@ -51,7 +52,7 @@ are automatically released by tea in a minimum of time.
Task is available in [Snapcraft][snapcraft], but keep in mind that your Linux
distribution should allow classic confinement for Snaps to Task work right:
```bash
```shell
sudo snap install task --classic
```
@@ -60,7 +61,7 @@ sudo snap install task --classic
If you're on Windows and have [Chocolatey][choco] installed, getting Task is as
simple as running:
```bash
```shell
choco install go-task
```
@@ -71,7 +72,7 @@ This installation method is community owned.
If you're on Windows and have [Scoop][scoop] installed, getting Task is as
simple as running:
```cmd
```shell
scoop install task
```
@@ -84,7 +85,7 @@ If you're on Arch Linux you can install Task from
[AUR](https://aur.archlinux.org/packages/go-task-bin) using your favorite
package manager such as `yay`, `pacaur` or `yaourt`:
```cmd
```shell
yay -S go-task-bin
```
@@ -93,7 +94,7 @@ Alternatively, there's
the source code instead of downloading the binary from the
[releases page](https://github.com/go-task/task/releases):
```cmd
```shell
yay -S go-task
```
@@ -105,7 +106,7 @@ If you're on Fedora Linux you can install Task from the official
[Fedora](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)
repository using `dnf`:
```cmd
```shell
sudo dnf install go-task
```
@@ -118,7 +119,7 @@ take some time until it's available in
If you're on NixOS or have Nix installed you can install Task from
[nixpkgs](https://github.com/NixOS/nixpkgs):
```cmd
```shell
nix-env -iA nixpkgs.go-task
```
@@ -131,7 +132,7 @@ take some time until it's available in
You can also use Node and npm to install Task by installing
[this package](https://www.npmjs.com/package/@go-task/cli).
```bash
```shell
npm install -g @go-task/cli
```
@@ -141,7 +142,7 @@ If you are using Windows and installed the
[winget](https://github.com/microsoft/winget-cli) package management tool, you
can install Task from [winget-pkgs](https://github.com/microsoft/winget-pkgs).
```bash
```shell
winget install Task.Task
```
@@ -165,7 +166,7 @@ easy generation of this script.
By default, it installs on the `./bin` directory relative to the working
directory:
```bash
```shell
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
```
@@ -173,7 +174,7 @@ It is possible to override the installation directory with the `-b` parameter.
On Linux, common choices are `~/.local/bin` and `~/bin` to install for the
current user or `/usr/local/bin` to install for all users:
```bash
```shell
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
```
@@ -209,13 +210,13 @@ setup. You can find the minimum required version of Go in the
You can then install the latest release globally by running:
```bash
```shell
go install github.com/go-task/task/v3/cmd/task@latest
```
Or you can install into another directory:
```bash
```shell
env GOBIN=/bin go install github.com/go-task/task/v3/cmd/task@latest
```
@@ -239,7 +240,7 @@ First, ensure that you installed bash-completion using your package manager.
Make the completion file executable:
```
```shell
chmod +x path/to/task.bash
```
@@ -278,7 +279,7 @@ mv path/to/task.fish ~/.config/fish/completions/task.fish
Open your profile script with:
```
```powershell
mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue
notepad $profile
```

View File

@@ -3,27 +3,28 @@ slug: /styleguide/
sidebar_position: 10
---
# Styleguide
# Style guide
This is the official Task styleguide for `Taskfile.yml` files. This guide
contains some basic instructions to keep your Taskfile clean and familiar to
other users.
This is the official style guide for `Taskfile.yml` files. It provides basic
instructions for keeping your Taskfiles clean and familiar to other users.
This contains general guidelines, but they don't necessarily need to be strictly
followed. Feel free to disagree and proceed differently at some point if you
need or want to. Also, feel free to open issues or pull requests with
improvements to this guide.
This guide contains general guidelines, but they do not necessarily need to be
followed strictly. Feel free to disagree and do things differently if you need
or want to. Any improvements to this guide are welcome! Please open an issue or
create a pull request to contribute.
## Use the correct order of keywords
## Use the suggested ordering of the main sections
- `version:`
- `includes:`
- Configuration ones, like `output:`, `silent:`, `method:` and `run:`
- `vars:`
- `env:`, `dotenv:`
- `tasks:`
```yaml
version:
includes:
# optional configurations (output, silent, method, run, etc.)
vars:
env: # followed or replaced by dotenv
tasks:
```
## Use 2 spaces for indentation
## Use two spaces for indentation
This is the most common convention for YAML files, and Task follows it.
@@ -42,7 +43,7 @@ tasks:
- echo 'foo'
```
## Separate with spaces the mains sections
## Separate the main sections with empty lines
```yaml
# bad
@@ -76,7 +77,7 @@ tasks:
# ...
```
## Add spaces between tasks
## Separate tasks with empty lines
```yaml
# bad
@@ -111,7 +112,7 @@ tasks:
- echo 'baz'
```
## Use upper-case variable names
## Use only uppercase letters for variable names
```yaml
# bad
@@ -138,7 +139,7 @@ tasks:
- go build -o {{.BINARY_NAME}} .
```
## Don't wrap vars in spaces when templating
## Avoid using whitespace when templating variables
```yaml
# bad
@@ -159,9 +160,10 @@ tasks:
- echo '{{.MESSAGE}}'
```
This convention is also used by most people for any Go templating.
This convention is also commonly used in templates for the Go programming
language.
## Separate task name words with a dash
## Use kebab case for task names
```yaml
# bad
@@ -182,7 +184,7 @@ tasks:
- echo 'Do something'
```
## Use colon for task namespacing
## Use a colon to separate the task namespace and name
```yaml
# good
@@ -200,7 +202,7 @@ tasks:
This is also done automatically when using included Taskfiles.
## Prefer external scripts over complex multi-line commands
## Prefer using external scripts instead of multi-line commands
```yaml
# bad

View File

@@ -27,7 +27,7 @@ tasks:
Running the tasks is as simple as running:
```bash
```shell
task assets build
```
@@ -118,6 +118,18 @@ tasks:
:::
### Reading a Taskfile from stdin
Taskfile also supports reading from stdin. This is useful if you are generating
Taskfiles dynamically and don't want write them to disk. This works just like
any other program that supports stdin. For example:
```shell
task < <(cat ./Taskfile.yml)
# OR
cat ./Taskfile.yml | task
```
## Environment variables
### Task
@@ -162,11 +174,11 @@ variables, as you can see in the [Variables](#variables) section.
You can also ask Task to include `.env` like files by using the `dotenv:`
setting:
```bash title=".env"
```shell title=".env"
KEYNAME=VALUE
```
```bash title="testing/.env"
```shell title="testing/.env"
ENDPOINT=testing.com
```
@@ -699,7 +711,7 @@ path like `tmp/task` that will be interpreted as relative to the project
directory, or an absolute or home path like `/tmp/.task` or `~/.task`
(subdirectories will be created for each project).
```bash
```shell
export TASK_TEMP_DIR='~/.task'
```
@@ -950,7 +962,7 @@ listed below in order of importance (i.e. most important first):
Example of sending parameters with environment variables:
```bash
```shell
$ TASK_VARIABLE=a-value task do-something
```
@@ -964,7 +976,7 @@ Since some shells do not support the above syntax to set environment variables
(Windows) tasks also accept a similar style when not at the beginning of the
command.
```bash
```shell
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
```
@@ -1192,7 +1204,7 @@ If `--` is given in the CLI, all following parameters are added to a special
The below example will run `yarn install`.
```bash
```shell
$ task yarn -- install
```
@@ -1205,6 +1217,53 @@ tasks:
- yarn {{.CLI_ARGS}}
```
## Wildcard arguments
Another way to parse arguments into a task is to use a wildcard in your task's
name. Wildcards are denoted by an asterisk (`*`) and can be used multiple times
in a task's name to pass in multiple arguments.
Matching arguments will be captured and stored in the `.MATCH` variable and can
then be used in your task's commands like any other variable. This variable is
an array of strings and so will need to be indexed to access the individual
arguments. We suggest creating a named variable for each argument to make it
clear what they contain:
```yaml
version: '3'
tasks:
echo-*:
vars:
TEXT: '{{index .MATCH 0}}'
cmds:
- echo {{.TEXT}}
run-*-*:
vars:
ARG_1: '{{index .MATCH 0}}'
ARG_2: '{{index .MATCH 1}}'
cmds:
- echo {{.ARG_1}} {{.ARG_2}}
```
```shell
# This call matches the "echo-*" task and the string "hello" is captured by the
# wildcard and stored in the .MATCH variable. We then index the .MATCH array and
# store the result in the .TEXT variable which is then echoed out in the cmds.
$ task echo-hello
hello
# You can use whitespace in your arguments as long as you quote the task name
$ task "echo-hello world"
hello world
# And you can pass multiple arguments
$ task run-foo-bar
foo bar
```
If multiple matching tasks are found, an error occurs. If you are using included
Taskfiles, tasks in parent files will be considered first.
## Doing task cleanup with `defer`
With the `defer` keyword, it's possible to schedule cleanup to be run once the
@@ -1353,7 +1412,7 @@ tasks:
would print the following output:
```bash
```shell
* build: Build the go binary.
* test: Run all the go tests.
```
@@ -1486,7 +1545,7 @@ tasks:
- echo 'dangerous command'
```
```bash
```shell
task dangerous
task: "This is a dangerous command... Do you want to continue?" [y/N]
```
@@ -1495,7 +1554,7 @@ Warning prompts are called before executing a task. If a prompt is denied Task
will exit with [exit code](/api#exit-codes) 205. If approved, Task will continue
as normal.
```bash
```shell
task example
not dangerous command
task: "This is a dangerous command. Do you want to continue?" [y/N]
@@ -1532,14 +1591,14 @@ tasks:
Normally this will be printed:
```sh
```shell
echo "Print something"
Print something
```
With silent mode on, the below will be printed instead:
```sh
```shell
Print something
```
@@ -1686,7 +1745,7 @@ tasks:
silent: true
```
```bash
```shell
$ task default
::group::default
Hello, World!
@@ -1711,7 +1770,7 @@ tasks:
errors: echo 'output-of-errors' && exit 1
```
```bash
```shell
$ task passes
$ task errors
output-of-errors
@@ -1744,7 +1803,7 @@ tasks:
silent: true
```
```bash
```shell
$ task default
[print-foo] foo
[print-bar] bar
@@ -1829,8 +1888,8 @@ task again. This requires the `sources` attribute to be given, so task knows
which files to watch.
The default watch interval is 5 seconds, but it's possible to change it by
either setting `interval: '500ms'` in the root of the Taskfile passing it as an
argument like `--interval=500ms`.
either setting `interval: '500ms'` in the root of the Taskfile or by passing it
as an argument like `--interval=500ms`.
Also, it's possible to set `watch: true` in a given task and it'll automatically
run in watch mode:

View File

@@ -254,7 +254,12 @@ const config: Config = {
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme
darkTheme: darkCodeTheme,
additionalLanguages: [
"bash", // aka. shell
"json",
"powershell"
]
},
// NOTE(@andreynering): Don't worry, these keys are meant to be public =)
algolia: {

View File

@@ -65,15 +65,15 @@ func (err *TaskInternalError) Code() int {
return CodeTaskInternal
}
// TaskNameConflictError is returned when multiple tasks with the same name or
// TaskNameConflictError is returned when multiple tasks with a matching name or
// alias are found.
type TaskNameConflictError struct {
AliasName string
Call string
TaskNames []string
}
func (err *TaskNameConflictError) Error() string {
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.TaskNames, ", "), err.AliasName)
return fmt.Sprintf(`task: Found multiple tasks (%s) that match %q`, strings.Join(err.TaskNames, ", "), err.Call)
}
func (err *TaskNameConflictError) Code() int {

9
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/go-task/task/v3
go 1.20
go 1.21
require (
github.com/Masterminds/semver/v3 v3.2.1
@@ -15,11 +15,10 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
github.com/zeebo/xxh3 v1.0.2
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e
golang.org/x/sync v0.6.0
golang.org/x/term v0.16.0
golang.org/x/term v0.17.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.7.0
mvdan.cc/sh/v3 v3.8.0
)
require (
@@ -28,6 +27,6 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/sys v0.17.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

29
go.sum
View File

@@ -1,21 +1,26 @@
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -29,7 +34,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -43,23 +49,22 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8=
mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY=

11
help.go
View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"
"text/tabwriter"
@@ -121,14 +120,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
// ListTaskNames prints only the task names in a Taskfile.
// Only tasks with a non-empty description are printed if allTasks is false.
// Otherwise, all task names are printed.
func (e *Executor) ListTaskNames(allTasks bool) {
// if called from cmd/task.go, e.Taskfile has not yet been parsed
if e.Taskfile == nil {
if err := e.readTaskfile(); err != nil {
log.Fatal(err)
return
}
}
func (e *Executor) ListTaskNames(allTasks bool) error {
// use stdout if no output defined
var w io.Writer = os.Stdout
if e.Stdout != nil {
@@ -157,6 +149,7 @@ func (e *Executor) ListTaskNames(allTasks bool) {
for _, t := range taskNames {
fmt.Fprintln(w, t)
}
return nil
}
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {

View File

@@ -36,12 +36,12 @@ func (c *Compiler) GetTaskfileVariables() (*ast.Vars, error) {
return c.getVariables(nil, nil, true)
}
func (c *Compiler) GetVariables(t *ast.Task, call ast.Call) (*ast.Vars, error) {
return c.getVariables(t, &call, true)
func (c *Compiler) GetVariables(t *ast.Task, call *ast.Call) (*ast.Vars, error) {
return c.getVariables(t, call, true)
}
func (c *Compiler) FastGetVariables(t *ast.Task, call ast.Call) (*ast.Vars, error) {
return c.getVariables(t, &call, false)
func (c *Compiler) FastGetVariables(t *ast.Task, call *ast.Call) (*ast.Vars, error) {
return c.getVariables(t, call, false)
}
func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool) (*ast.Vars, error) {

26
internal/exp/maps.go Normal file
View File

@@ -0,0 +1,26 @@
// This package is intended as a place to copy functions from the
// golang.org/x/exp package. Copying these functions allows us to rely on our
// own code instead of an external package that may change unpredictably in the
// future.
//
// It also prevents problems with transitive dependencies whereby a
// package that imports Task (and therefore our version of golang.org/x/exp)
// cannot import a different version of golang.org/x/exp.
//
// Finally, it serves as a place to track functions that may be able to be
// removed in the future if they are added to the standard library. This is also
// why this package is under the internal directory since these functions are
// not intended to be used outside of Task.
package exp
import "cmp"
// Keys is a copy of https://pkg.go.dev/golang.org/x/exp@v0.0.0-20240103183307-be819d1f06fc/maps#Keys.
// This is not yet included in the standard library. See https://github.com/golang/go/issues/61538.
func Keys[K cmp.Ordered, V any](m map[K]V) []K {
var keys []K
for key := range m {
keys = append(keys, key)
}
return keys
}

View File

@@ -5,12 +5,12 @@ import (
"io"
"os"
"path"
"slices"
"strings"
"text/tabwriter"
"github.com/joho/godotenv"
"github.com/spf13/pflag"
"golang.org/x/exp/slices"
"github.com/go-task/task/v3/internal/logger"
)

View File

@@ -4,11 +4,11 @@ import (
"bufio"
"io"
"os"
"slices"
"strconv"
"strings"
"github.com/fatih/color"
"golang.org/x/exp/slices"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/term"

View File

@@ -1,26 +1,26 @@
package omap
import (
"cmp"
"fmt"
"slices"
"golang.org/x/exp/constraints"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/deepcopy"
"github.com/go-task/task/v3/internal/exp"
)
// An OrderedMap is a wrapper around a regular map that maintains an ordered
// list of the map's keys. This allows you to run deterministic and ordered
// operations on the map such as printing/serializing/iterating.
type OrderedMap[K constraints.Ordered, V any] struct {
type OrderedMap[K cmp.Ordered, V any] struct {
s []K
m map[K]V
}
// New will create a new OrderedMap of the given type and return it.
func New[K constraints.Ordered, V any]() OrderedMap[K, V] {
func New[K cmp.Ordered, V any]() OrderedMap[K, V] {
return OrderedMap[K, V]{
s: make([]K, 0),
m: make(map[K]V),
@@ -29,14 +29,14 @@ func New[K constraints.Ordered, V any]() OrderedMap[K, V] {
// FromMap will create a new OrderedMap from the given map. Since Golang maps
// are unordered, the order of the created OrderedMap will be random.
func FromMap[K constraints.Ordered, V any](m map[K]V) OrderedMap[K, V] {
func FromMap[K cmp.Ordered, V any](m map[K]V) OrderedMap[K, V] {
om := New[K, V]()
om.m = m
om.s = maps.Keys(m)
om.s = exp.Keys(m)
return om
}
func FromMapWithOrder[K constraints.Ordered, V any](m map[K]V, order []K) OrderedMap[K, V] {
func FromMapWithOrder[K cmp.Ordered, V any](m map[K]V, order []K) OrderedMap[K, V] {
om := New[K, V]()
if len(m) != len(order) {
panic("length of map and order must be equal")

View File

@@ -43,13 +43,7 @@ func TestSortFunc(t *testing.T) {
om.Set(1, "one")
om.Set(2, "two")
om.SortFunc(func(a, b int) int {
if a < b {
return 1
}
if a > b {
return -1
}
return 0
return b - a
})
assert.Equal(t, []int{3, 2, 1}, om.s)
}

View File

@@ -1,11 +1,11 @@
package slicesext
import (
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
"cmp"
"slices"
)
func UniqueJoin[T constraints.Ordered](ss ...[]T) []T {
func UniqueJoin[T cmp.Ordered](ss ...[]T) []T {
var length int
for _, s := range ss {
length += len(s)

View File

@@ -7,7 +7,7 @@ import (
"github.com/go-task/task/v3/taskfile/ast"
)
func PrintTasks(l *logger.Logger, t *ast.Taskfile, c []ast.Call) {
func PrintTasks(l *logger.Logger, t *ast.Taskfile, c []*ast.Call) {
for i, call := range c {
PrintSpaceBetweenSummaries(l, i)
PrintTask(l, t.Tasks.Get(call.Task))

View File

@@ -163,7 +163,7 @@ func TestPrintAllWithSpaces(t *testing.T) {
summary.PrintTasks(&l,
&ast.Taskfile{Tasks: tasks},
[]ast.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
[]*ast.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
assert.True(t, strings.HasPrefix(buffer.String(), "task: t1"))
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t2")

View File

@@ -2,11 +2,10 @@ package templater
import (
"bytes"
"maps"
"strings"
"text/template"
"golang.org/x/exp/maps"
"github.com/go-task/task/v3/taskfile/ast"
)

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.34.1",
"version": "3.35.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.34.1",
"version": "3.35.0",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",

View File

@@ -7,7 +7,7 @@ import (
"github.com/go-task/task/v3/taskfile/ast"
)
func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call ast.Call) error {
func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call *ast.Call) error {
if t.Requires == nil || len(t.Requires.Vars) == 0 {
return nil
}

View File

@@ -67,22 +67,19 @@ func (e *Executor) setCurrentDir() error {
return err
}
e.Dir = wd
} else {
var err error
e.Dir, err = filepath.Abs(e.Dir)
if err != nil {
return err
}
}
// Search for a taskfile
root, err := taskfile.ExistsWalk(e.Dir)
if err != nil {
return err
}
e.Dir = filepath.Dir(root)
e.Entrypoint = filepath.Base(root)
return nil
}
func (e *Executor) readTaskfile() error {
uri := filepath.Join(e.Dir, e.Entrypoint)
node, err := taskfile.NewNode(uri, e.Insecure)
node, err := taskfile.NewRootNode(e.Dir, e.Entrypoint, e.Insecure)
if err != nil {
return err
}

View File

@@ -9,7 +9,7 @@ import (
)
// Status returns an error if any the of given tasks is not up-to-date
func (e *Executor) Status(ctx context.Context, calls ...ast.Call) error {
func (e *Executor) Status(ctx context.Context, calls ...*ast.Call) error {
for _, call := range calls {
// Compile the task

44
task.go
View File

@@ -6,6 +6,7 @@ import (
"io"
"os"
"runtime"
"slices"
"sync"
"sync/atomic"
"time"
@@ -24,7 +25,6 @@ import (
"github.com/go-task/task/v3/taskfile/ast"
"github.com/sajari/fuzzy"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
)
@@ -80,7 +80,7 @@ type Executor struct {
}
// Run runs Task
func (e *Executor) Run(ctx context.Context, calls ...ast.Call) error {
func (e *Executor) Run(ctx context.Context, calls ...*ast.Call) error {
// check if given tasks exist
for _, call := range calls {
task, err := e.GetTask(call)
@@ -142,7 +142,7 @@ func (e *Executor) Run(ctx context.Context, calls ...ast.Call) error {
return nil
}
func (e *Executor) splitRegularAndWatchCalls(calls ...ast.Call) (regularCalls []ast.Call, watchCalls []ast.Call, err error) {
func (e *Executor) splitRegularAndWatchCalls(calls ...*ast.Call) (regularCalls []*ast.Call, watchCalls []*ast.Call, err error) {
for _, c := range calls {
t, err := e.GetTask(c)
if err != nil {
@@ -159,7 +159,7 @@ func (e *Executor) splitRegularAndWatchCalls(calls ...ast.Call) (regularCalls []
}
// RunTask runs a task by its name
func (e *Executor) RunTask(ctx context.Context, call ast.Call) error {
func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
t, err := e.FastCompiledTask(call)
if err != nil {
return err
@@ -296,7 +296,7 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
for _, d := range t.Deps {
d := d
g.Go(func() error {
err := e.RunTask(ctx, ast.Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
err := e.RunTask(ctx, &ast.Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
if err != nil {
return err
}
@@ -307,7 +307,7 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
return g.Wait()
}
func (e *Executor) runDeferred(t *ast.Task, call ast.Call, i int) {
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -316,7 +316,7 @@ func (e *Executor) runDeferred(t *ast.Task, call ast.Call, i int) {
}
}
func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call ast.Call, i int) error {
func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call, i int) error {
cmd := t.Cmds[i]
switch {
@@ -324,7 +324,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call ast.Call, i
reacquire := e.releaseConcurrencyLimit()
defer reacquire()
err := e.RunTask(ctx, ast.Call{Task: cmd.Task, Vars: cmd.Vars, Silent: cmd.Silent, Indirect: true})
err := e.RunTask(ctx, &ast.Call{Task: cmd.Task, Vars: cmd.Vars, Silent: cmd.Silent, Indirect: true})
if err != nil {
return err
}
@@ -413,14 +413,30 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
// GetTask will return the task with the name matching the given call from the taskfile.
// If no task is found, it will search for tasks with a matching alias.
// If multiple tasks contain the same alias or no matches are found an error is returned.
func (e *Executor) GetTask(call ast.Call) (*ast.Task, error) {
func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) {
// Search for a matching task
matchingTask := e.Taskfile.Tasks.Get(call.Task)
if matchingTask != nil {
return matchingTask, nil
matchingTasks := e.Taskfile.Tasks.FindMatchingTasks(call)
switch len(matchingTasks) {
case 0: // Carry on
case 1:
if call.Vars == nil {
call.Vars = &ast.Vars{}
}
call.Vars.Set("MATCH", ast.Var{Value: matchingTasks[0].Wildcards})
return matchingTasks[0].Task, nil
default:
taskNames := make([]string, len(matchingTasks))
for i, matchingTask := range matchingTasks {
taskNames[i] = matchingTask.Task.Task
}
return nil, &errors.TaskNameConflictError{
Call: call.Task,
TaskNames: taskNames,
}
}
// If didn't find one, search for a task with a matching alias
var matchingTask *ast.Task
var aliasedTasks []string
for _, task := range e.Taskfile.Tasks.Values() {
if slices.Contains(task.Aliases, call.Task) {
@@ -431,7 +447,7 @@ func (e *Executor) GetTask(call ast.Call) (*ast.Task, error) {
// If we found multiple tasks
if len(aliasedTasks) > 1 {
return nil, &errors.TaskNameConflictError{
AliasName: call.Task,
Call: call.Task,
TaskNames: aliasedTasks,
}
}
@@ -476,7 +492,7 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
idx := i
task := tasks[idx]
g.Go(func() error {
compiledTask, err := e.FastCompiledTask(ast.Call{Task: task.Task})
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: task.Task})
if err != nil {
return err
}

View File

@@ -53,7 +53,7 @@ func (fct fileContentTest) Run(t *testing.T) {
Stderr: io.Discard,
}
require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(context.Background(), ast.Call{Task: fct.Target}), "e.Run(target)")
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
@@ -76,7 +76,7 @@ func TestEmptyTask(t *testing.T) {
Stderr: io.Discard,
}
require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
}
func TestEnv(t *testing.T) {
@@ -141,7 +141,7 @@ func TestSpecialVars(t *testing.T) {
Silent: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: test.target}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.target}))
assert.Equal(t, test.expected+"\n", buff.String())
})
}
@@ -160,7 +160,7 @@ func TestConcurrency(t *testing.T) {
Concurrency: 1,
}
require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(context.Background(), ast.Call{Task: target}), "e.Run(target)")
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: target}), "e.Run(target)")
}
func TestParams(t *testing.T) {
@@ -212,7 +212,7 @@ func TestDeps(t *testing.T) {
Stderr: io.Discard,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
for _, f := range files {
f = filepathext.SmartJoin(dir, f)
@@ -249,15 +249,15 @@ func TestStatus(t *testing.T) {
}
require.NoError(t, e.Setup())
// gen-foo creates foo.txt, and will always fail it's status check.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-foo"}))
// gen-foo creates bar.txt, and will pass its status-check the 3. time it
// is run. It creates bar.txt, but also lists it as its source. So, the checksum
// for the file won't match before after the second run as we the file
// only exists after the first run.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
// gen-silent-baz is marked as being silent, and should only produce output
// if e.Verbose is set to true.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-silent-baz"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
for _, f := range files {
if _, err := os.Stat(filepathext.SmartJoin(dir, f)); err != nil {
@@ -266,10 +266,10 @@ func TestStatus(t *testing.T) {
}
// Run gen-bar a second time to produce a checksum file that matches bar.txt
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
// Run gen-bar a third time, to make sure we've triggered the status check.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
// We're silent, so no output should have been produced.
assert.Empty(t, buff.String())
@@ -278,7 +278,7 @@ func TestStatus(t *testing.T) {
// for the next test.
err := os.Remove(filepathext.SmartJoin(dir, "bar.txt"))
require.NoError(t, err)
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
buff.Reset()
// Global silence switched of, so we should see output unless the task itself
@@ -286,34 +286,34 @@ func TestStatus(t *testing.T) {
e.Silent = false
// all: not up-to-date
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-foo"}))
assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
buff.Reset()
// status: not up-to-date
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-foo"}))
assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
buff.Reset()
// sources: not up-to-date
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
assert.Equal(t, "task: [gen-bar] touch bar.txt", strings.TrimSpace(buff.String()))
buff.Reset()
// all: up-to-date
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
assert.Equal(t, `task: Task "gen-bar" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
// sources: not up-to-date, no output produced.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-silent-baz"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
assert.Empty(t, buff.String())
// up-to-date, no output produced
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-silent-baz"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
assert.Empty(t, buff.String())
e.Verbose = true
// up-to-date, output produced due to Verbose mode.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-silent-baz"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
assert.Equal(t, `task: Task "gen-silent-baz" is up to date`, strings.TrimSpace(buff.String()))
buff.Reset()
}
@@ -330,13 +330,13 @@ func TestPrecondition(t *testing.T) {
// A precondition that has been met
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
if buff.String() != "" {
t.Errorf("Got Output when none was expected: %s", buff.String())
}
// A precondition that was not met
require.Error(t, e.Run(context.Background(), ast.Call{Task: "impossible"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "impossible"}))
if buff.String() != "task: 1 != 0 obviously!\n" {
t.Errorf("Wrong output message: %s", buff.String())
@@ -344,7 +344,7 @@ func TestPrecondition(t *testing.T) {
buff.Reset()
// Calling a task with a precondition in a dependency fails the task
require.Error(t, e.Run(context.Background(), ast.Call{Task: "depends_on_impossible"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "depends_on_impossible"}))
if buff.String() != "task: 1 != 0 obviously!\n" {
t.Errorf("Wrong output message: %s", buff.String())
@@ -352,7 +352,7 @@ func TestPrecondition(t *testing.T) {
buff.Reset()
// Calling a task with a precondition in a cmd fails the task
require.Error(t, e.Run(context.Background(), ast.Call{Task: "executes_failing_task_as_cmd"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "executes_failing_task_as_cmd"}))
if buff.String() != "task: 1 != 0 obviously!\n" {
t.Errorf("Wrong output message: %s", buff.String())
}
@@ -393,7 +393,7 @@ func TestGenerates(t *testing.T) {
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
// Run task for the first time.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: theTask}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: theTask}))
if _, err := os.Stat(srcFile); err != nil {
t.Errorf("File should exist: %v", err)
@@ -408,7 +408,7 @@ func TestGenerates(t *testing.T) {
buff.Reset()
// Re-run task to ensure it's now found to be up-to-date.
require.NoError(t, e.Run(context.Background(), ast.Call{Task: theTask}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: theTask}))
if buff.String() != upToDate {
t.Errorf("Wrong output message: %s", buff.String())
}
@@ -446,7 +446,7 @@ func TestStatusChecksum(t *testing.T) {
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: test.task}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.task}))
for _, f := range test.files {
_, err := os.Stat(filepathext.SmartJoin(dir, f))
require.NoError(t, err)
@@ -459,7 +459,7 @@ func TestStatusChecksum(t *testing.T) {
time := s.ModTime()
buff.Reset()
require.NoError(t, e.Run(context.Background(), ast.Call{Task: test.task}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.task}))
assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
s, err = os.Stat(filepathext.SmartJoin(tempdir, "checksum/"+test.task))
@@ -482,7 +482,7 @@ func TestAlias(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "f"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "f"}))
assert.Equal(t, string(data), buff.String())
}
@@ -496,7 +496,7 @@ func TestDuplicateAlias(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.Error(t, e.Run(context.Background(), ast.Call{Task: "x"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "x"}))
assert.Equal(t, "", buff.String())
}
@@ -514,7 +514,7 @@ func TestAliasSummary(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "f"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "f"}))
assert.Equal(t, string(data), buff.String())
}
@@ -528,7 +528,7 @@ func TestLabelUpToDate(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
@@ -543,7 +543,7 @@ func TestLabelSummary(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
@@ -554,7 +554,7 @@ func TestLabelInStatus(t *testing.T) {
Dir: dir,
}
require.NoError(t, e.Setup())
err := e.Status(context.Background(), ast.Call{Task: "foo"})
err := e.Status(context.Background(), &ast.Call{Task: "foo"})
assert.ErrorContains(t, err, "foobar")
}
@@ -568,7 +568,7 @@ func TestLabelWithVariableExpansion(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobaz")
}
@@ -582,7 +582,7 @@ func TestLabelInSummary(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "foo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
@@ -618,7 +618,7 @@ func TestPromptInSummary(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "foo"})
err := e.Run(context.Background(), &ast.Call{Task: "foo"})
if test.wantError {
require.Error(t, err)
@@ -646,7 +646,7 @@ func TestPromptWithIndirectTask(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "bar"})
err := e.Run(context.Background(), &ast.Call{Task: "bar"})
assert.Contains(t, outBuff.String(), "show-prompt")
require.NoError(t, err)
}
@@ -679,7 +679,7 @@ func TestPromptAssumeYes(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "foo"})
err := e.Run(context.Background(), &ast.Call{Task: "foo"})
if !test.assumeYes {
require.Error(t, err)
@@ -791,7 +791,7 @@ func TestStatusVariables(t *testing.T) {
Verbose: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "build"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
@@ -832,7 +832,7 @@ func TestCyclicDep(t *testing.T) {
Stderr: io.Discard,
}
require.NoError(t, e.Setup())
assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), ast.Call{Task: "task-1"}))
assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), &ast.Call{Task: "task-1"}))
}
func TestTaskVersion(t *testing.T) {
@@ -875,10 +875,10 @@ func TestTaskIgnoreErrors(t *testing.T) {
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "task-should-pass"}))
require.Error(t, e.Run(context.Background(), ast.Call{Task: "task-should-fail"}))
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "cmd-should-pass"}))
require.Error(t, e.Run(context.Background(), ast.Call{Task: "cmd-should-fail"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task-should-pass"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "task-should-fail"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "cmd-should-pass"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "cmd-should-fail"}))
}
func TestExpand(t *testing.T) {
@@ -896,7 +896,7 @@ func TestExpand(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "pwd"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "pwd"}))
assert.Equal(t, home, strings.TrimSpace(buff.String()))
}
@@ -915,7 +915,7 @@ func TestDry(t *testing.T) {
Dry: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "build"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
@@ -939,13 +939,13 @@ func TestDryChecksum(t *testing.T) {
Dry: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
_, err := os.Stat(checksumFile)
require.Error(t, err, "checksum file should not exist")
e.Dry = false
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
_, err = os.Stat(checksumFile)
require.NoError(t, err, "checksum file should exist")
}
@@ -1127,11 +1127,11 @@ func TestIncludesRelativePath(t *testing.T) {
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "common:pwd"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "common:pwd"}))
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
buff.Reset()
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "included:common:pwd"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "included:common:pwd"}))
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
}
@@ -1159,7 +1159,7 @@ func TestIncludesInternal(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: test.task})
err := e.Run(context.Background(), &ast.Call{Task: test.task})
if test.expectedErr {
require.Error(t, err)
} else {
@@ -1193,7 +1193,7 @@ func TestIncludesInterpolation(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: test.task})
err := e.Run(context.Background(), &ast.Call{Task: test.task})
if test.expectedErr {
require.Error(t, err)
} else {
@@ -1228,7 +1228,7 @@ func TestInternalTask(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: test.task})
err := e.Run(context.Background(), &ast.Call{Task: test.task})
if test.expectedErr {
require.Error(t, err)
} else {
@@ -1297,7 +1297,7 @@ func TestSummary(t *testing.T) {
Silent: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "task-with-summary"}, ast.Call{Task: "other-task-with-summary"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task-with-summary"}, &ast.Call{Task: "other-task-with-summary"}))
data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
require.NoError(t, err)
@@ -1321,7 +1321,7 @@ func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "whereami"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "whereami"}))
// got should be the "dir" part of "testdata/dir"
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
@@ -1339,7 +1339,7 @@ func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "whereami"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "whereami"}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1363,7 +1363,7 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
t.Errorf("Directory should not exist: %v", err)
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: target}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1390,7 +1390,7 @@ func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
t.Errorf("Directory should not exist: %v", err)
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: target}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1448,7 +1448,7 @@ func TestShortTaskNotation(t *testing.T) {
Silent: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
}
@@ -1592,7 +1592,7 @@ func TestExitImmediately(t *testing.T) {
}
require.NoError(t, e.Setup())
require.Error(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
}
@@ -1628,7 +1628,7 @@ echo ran
task: [task-1] echo 'task-1 ran successfully'
task-1 ran successfully
`)
require.Error(t, e.Run(context.Background(), ast.Call{Task: "task-2"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "task-2"}))
assert.Contains(t, buff.String(), expectedOutputOrder)
}
@@ -1653,7 +1653,7 @@ func TestIgnoreNilElements(t *testing.T) {
Silent: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\n", buff.String())
})
}
@@ -1679,7 +1679,7 @@ task: [bye] echo 'Bye!'
Bye!
::endgroup::
`)
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "bye"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "bye"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
@@ -1694,7 +1694,7 @@ func TestOutputGroupErrorOnlySwallowsOutputOnSuccess(t *testing.T) {
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "passing"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "passing"}))
t.Log(buff.String())
assert.Empty(t, buff.String())
}
@@ -1709,7 +1709,7 @@ func TestOutputGroupErrorOnlyShowsOutputOnFailure(t *testing.T) {
}
require.NoError(t, e.Setup())
require.Error(t, e.Run(context.Background(), ast.Call{Task: "failing"}))
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "failing"}))
t.Log(buff.String())
assert.Contains(t, "failing-output", strings.TrimSpace(buff.String()))
assert.NotContains(t, "passing", strings.TrimSpace(buff.String()))
@@ -1739,7 +1739,7 @@ VAR_1 is included-default-var1
task: [included3:task1] echo "VAR_2 is included-default-var2"
VAR_2 is included-default-var2
`)
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "task1"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task1"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
@@ -1762,7 +1762,7 @@ Hello foo
task: [bar:lib:greet] echo 'Hello bar'
Hello bar
`)
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
@@ -1795,7 +1795,7 @@ func TestErrorCode(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: test.task})
err := e.Run(context.Background(), &ast.Call{Task: test.task})
require.Error(t, err)
taskRunErr, ok := err.(*errors.TaskRunError)
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
@@ -1847,7 +1847,7 @@ func TestEvaluateSymlinksInPaths(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: test.task})
err := e.Run(context.Background(), &ast.Call{Task: test.task})
require.NoError(t, err)
assert.Equal(t, test.expected, strings.TrimSpace(buff.String()))
buff.Reset()
@@ -1886,7 +1886,7 @@ func TestTaskfileWalk(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
assert.Equal(t, test.expected, buff.String())
})
}
@@ -1902,7 +1902,7 @@ func TestUserWorkingDirectory(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "default"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
}
@@ -1922,7 +1922,7 @@ func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
require.NoError(t, err)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "included:echo"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "included:echo"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
}
@@ -1934,7 +1934,7 @@ func TestPlatforms(t *testing.T) {
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "build-" + runtime.GOOS}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build-" + runtime.GOOS}))
assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String())
}
@@ -1947,7 +1947,7 @@ func TestPOSIXShellOptsGlobalLevel(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "pipefail"})
err := e.Run(context.Background(), &ast.Call{Task: "pipefail"})
require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String())
}
@@ -1961,7 +1961,7 @@ func TestPOSIXShellOptsTaskLevel(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "pipefail"})
err := e.Run(context.Background(), &ast.Call{Task: "pipefail"})
require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String())
}
@@ -1975,7 +1975,7 @@ func TestPOSIXShellOptsCommandLevel(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "pipefail"})
err := e.Run(context.Background(), &ast.Call{Task: "pipefail"})
require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String())
}
@@ -1989,7 +1989,7 @@ func TestBashShellOptsGlobalLevel(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "globstar"})
err := e.Run(context.Background(), &ast.Call{Task: "globstar"})
require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String())
}
@@ -2003,7 +2003,7 @@ func TestBashShellOptsTaskLevel(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "globstar"})
err := e.Run(context.Background(), &ast.Call{Task: "globstar"})
require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String())
}
@@ -2017,7 +2017,7 @@ func TestBashShellOptsCommandLevel(t *testing.T) {
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), ast.Call{Task: "globstar"})
err := e.Run(context.Background(), &ast.Call{Task: "globstar"})
require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String())
}
@@ -2035,7 +2035,7 @@ func TestSplitArgs(t *testing.T) {
vars := &ast.Vars{}
vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"})
err := e.Run(context.Background(), ast.Call{Task: "default", Vars: vars})
err := e.Run(context.Background(), &ast.Call{Task: "default", Vars: vars})
require.NoError(t, err)
assert.Equal(t, "3\n", buff.String())
}
@@ -2063,20 +2063,20 @@ func TestSilence(t *testing.T) {
require.NoError(t, e.Setup())
// First verify that the silent flag is in place.
task, err := e.GetTask(ast.Call{Task: "task-test-silent-calls-chatty-silenced"})
task, err := e.GetTask(&ast.Call{Task: "task-test-silent-calls-chatty-silenced"})
require.NoError(t, err, "Unable to look up task task-test-silent-calls-chatty-silenced")
require.True(t, task.Cmds[0].Silent, "The task task-test-silent-calls-chatty-silenced should have a silent call to chatty")
// Then test the two basic cases where the task is silent or not.
// A silenced task.
err = e.Run(context.Background(), ast.Call{Task: "silent"})
err = e.Run(context.Background(), &ast.Call{Task: "silent"})
require.NoError(t, err)
require.Empty(t, buff.String(), "siWhile running lent: Expected not see output, because the task is silent")
buff.Reset()
// A chatty (not silent) task.
err = e.Run(context.Background(), ast.Call{Task: "chatty"})
err = e.Run(context.Background(), &ast.Call{Task: "chatty"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "chWhile running atty: Expected to see output, because the task is not silent")
@@ -2084,42 +2084,42 @@ func TestSilence(t *testing.T) {
// Then test invoking the two task from other tasks.
// A silenced task that calls a chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-silent-calls-chatty-non-silenced: Expected to see output. The task is silenced, but the called task is not. Silence does not propagate to called tasks.")
buff.Reset()
// A silent task that does a silent call to a chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-silent-calls-chatty-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-silent-calls-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-silent-calls-chatty-silenced: Expected not to see output. The task calls chatty task, but the call is silenced.")
buff.Reset()
// A chatty task that does a call to a chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-non-silenced: Expected to see output. Both caller and callee are chatty and not silenced.")
buff.Reset()
// A chatty task that does a silenced call to a chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-chatty-calls-chatty-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-chatty-calls-chatty-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-silenced: Expected to see output. Call to a chatty task is silenced, but the parent task is not.")
buff.Reset()
// A chatty task with no cmd's of its own that does a silenced call to a chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-no-cmds-calls-chatty-silenced: Expected not to see output. While the task itself is not silenced, it does not have any cmds and only does an invocation of a silenced task.")
buff.Reset()
// A chatty task that does a silenced invocation of a task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-chatty-calls-silenced-cmd"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-chatty-calls-silenced-cmd"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-chatty-calls-silenced-cmd: Expected not to see output. While the task itself is not silenced, its call to the chatty task is silent.")
@@ -2127,21 +2127,21 @@ func TestSilence(t *testing.T) {
// Then test calls via dependencies.
// A silent task that depends on a chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-non-silenced: Expected to see output. The task is silent and depends on a chatty task. Dependencies does not inherit silence.")
buff.Reset()
// A silent task that depends on a silenced chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-silenced: Expected not to see output. The task is silent and has a silenced dependency on a chatty task.")
buff.Reset()
// A chatty task that, depends on a silenced chatty task.
err = e.Run(context.Background(), ast.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
err = e.Run(context.Background(), &ast.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-is-chatty-depends-on-chatty-silenced: Expected not to see output. The task is chatty but does not have commands and has a silenced dependency on a chatty task.")
@@ -2189,7 +2189,7 @@ func TestForce(t *testing.T) {
ForceAll: tt.forceAll,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: "task-with-dep"}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task-with-dep"}))
})
}
}
@@ -2244,7 +2244,67 @@ func TestFor(t *testing.T) {
Force: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), ast.Call{Task: test.name}))
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.name}))
assert.Equal(t, test.expectedOutput, buff.String())
})
}
}
func TestWildcard(t *testing.T) {
tests := []struct {
name string
call string
expectedOutput string
wantErr bool
}{
{
name: "basic wildcard",
call: "wildcard-foo",
expectedOutput: "Hello foo\n",
},
{
name: "double wildcard",
call: "foo-wildcard-bar",
expectedOutput: "Hello foo bar\n",
},
{
name: "store wildcard",
call: "start-foo",
expectedOutput: "Starting foo\n",
},
{
name: "matches exactly",
call: "matches-exactly-*",
expectedOutput: "I don't consume matches: []\n",
},
{
name: "no matches",
call: "no-match",
wantErr: true,
},
{
name: "multiple matches",
call: "wildcard-foo-bar",
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.call, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: "testdata/wildcards",
Stdout: &buff,
Stderr: &buff,
Silent: true,
Force: true,
}
require.NoError(t, e.Setup())
if test.wantErr {
require.Error(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
return
}
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
assert.Equal(t, test.expectedOutput, buff.String())
})
}

View File

@@ -5,11 +5,11 @@ import (
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
omap "github.com/go-task/task/v3/internal/omap"
"gopkg.in/yaml.v3"
)
// Include represents information about included taskfiles

View File

@@ -2,6 +2,8 @@ package ast
import (
"fmt"
"regexp"
"strings"
"gopkg.in/yaml.v3"
@@ -51,6 +53,30 @@ func (t *Task) Name() string {
return t.Task
}
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values.
func (t *Task) WildcardMatch(name string) (bool, []string) {
// Convert the name into a regex string
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(t.Task, "*", "(.*)"))
regex := regexp.MustCompile(regexStr)
wildcards := regex.FindStringSubmatch(name)
wildcardCount := strings.Count(t.Task, "*")
// If there are no wildcards, return false
if len(wildcards) == 0 {
return false, nil
}
// Remove the first match, which is the full string
wildcards = wildcards[1:]
// If there are more/less wildcards than matches, return false
if len(wildcards) != wildcardCount {
return false, wildcards
}
return true, wildcards
}
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {

View File

@@ -14,6 +14,36 @@ type Tasks struct {
omap.OrderedMap[string, *Task]
}
type MatchingTask struct {
Task *Task
Wildcards []string
}
func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
if call == nil {
return nil
}
var task *Task
var matchingTasks []*MatchingTask
// If there is a direct match, return it
if task = t.OrderedMap.Get(call.Task); task != nil {
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
return matchingTasks
}
// Attempt a wildcard match
// For now, we can just nil check the task before each loop
_ = t.Range(func(key string, value *Task) error {
if match, wildcards := value.WildcardMatch(call.Task); match {
matchingTasks = append(matchingTasks, &MatchingTask{
Task: value,
Wildcards: wildcards,
})
}
return nil
})
return matchingTasks
}
func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
_ = t2.Range(func(k string, v *Task) error {
// We do a deep copy of the task struct here to ensure that no data can

View File

@@ -2,6 +2,8 @@ package taskfile
import (
"context"
"os"
"path/filepath"
"strings"
"github.com/go-task/task/v3/errors"
@@ -14,6 +16,30 @@ type Node interface {
Location() string
Optional() bool
Remote() bool
BaseDir() string
}
func NewRootNode(
dir string,
entrypoint string,
insecure bool,
) (Node, error) {
// Check if there is something to read on STDIN
stat, _ := os.Stdin.Stat()
if (stat.Mode()&os.ModeCharDevice) == 0 && stat.Size() > 0 {
return NewStdinNode(dir)
}
// If no entrypoint is specified, search for a taskfile
if entrypoint == "" {
root, err := ExistsWalk(dir)
if err != nil {
return nil, err
}
return NewNode(root, insecure)
}
// Use the specified entrypoint
uri := filepath.Join(dir, entrypoint)
return NewNode(uri, insecure)
}
func NewNode(

View File

@@ -52,3 +52,7 @@ func (node *FileNode) Read(ctx context.Context) ([]byte, error) {
defer f.Close()
return io.ReadAll(f)
}
func (node *FileNode) BaseDir() string {
return node.Dir
}

View File

@@ -65,3 +65,7 @@ func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
return b, nil
}
func (node *HTTPNode) BaseDir() string {
return ""
}

46
taskfile/node_stdin.go Normal file
View File

@@ -0,0 +1,46 @@
package taskfile
import (
"bufio"
"context"
"fmt"
"os"
)
// A StdinNode is a node that reads a taskfile from the standard input stream.
type StdinNode struct {
*BaseNode
Dir string
}
func NewStdinNode(dir string) (*StdinNode, error) {
base := NewBaseNode()
return &StdinNode{
BaseNode: base,
Dir: dir,
}, nil
}
func (node *StdinNode) Location() string {
return "__stdin__"
}
func (node *StdinNode) Remote() bool {
return false
}
func (node *StdinNode) Read(ctx context.Context) ([]byte, error) {
var stdin []byte
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
stdin = fmt.Appendln(stdin, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return stdin, nil
}
func (node *StdinNode) BaseDir() string {
return node.Dir
}

View File

@@ -48,12 +48,11 @@ func Read(
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
}
// Annotate any included Taskfile reference with a base directory for resolving relative paths
if node, isFileNode := node.(*FileNode); isFileNode {
if dir := node.BaseDir(); dir != "" {
_ = tf.Includes.Range(func(namespace string, include ast.Include) error {
// Set the base directory for resolving relative paths, but only if not already set
if include.BaseDir == "" {
include.BaseDir = node.Dir
include.BaseDir = dir
tf.Includes.Set(namespace, include)
}
return nil

View File

@@ -35,7 +35,10 @@ func Exists(path string) (string, error) {
if err != nil {
return "", err
}
if fi.Mode().IsRegular() {
if fi.Mode().IsRegular() ||
fi.Mode()&os.ModeDevice != 0 ||
fi.Mode()&os.ModeSymlink != 0 ||
fi.Mode()&os.ModeNamedPipe != 0 {
return filepath.Abs(path)
}

25
testdata/wildcards/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
version: 3
tasks:
wildcard-*:
cmds:
- echo "Hello {{index .MATCH 0}}"
wildcard-*-*:
cmds:
- echo "Hello {{index .MATCH 0}}"
'*-wildcard-*':
cmds:
- echo "Hello {{index .MATCH 0}} {{index .MATCH 1}}"
# Matches is empty when you call the task name exactly (i.e. `task matches-exactly-*`)
matches-exactly-*:
cmds:
- "echo \"I don't consume matches: {{.MATCH}}\""
start-*:
vars:
SERVICE: "{{index .MATCH 0}}"
cmds:
- echo "Starting {{.SERVICE}}"

View File

@@ -17,16 +17,16 @@ import (
// CompiledTask returns a copy of a task, but replacing variables in almost all
// properties using the Go template package.
func (e *Executor) CompiledTask(call ast.Call) (*ast.Task, error) {
func (e *Executor) CompiledTask(call *ast.Call) (*ast.Task, error) {
return e.compiledTask(call, true)
}
// FastCompiledTask is like CompiledTask, but it skippes dynamic variables.
func (e *Executor) FastCompiledTask(call ast.Call) (*ast.Task, error) {
func (e *Executor) FastCompiledTask(call *ast.Call) (*ast.Task, error) {
return e.compiledTask(call, false)
}
func (e *Executor) compiledTask(call ast.Call, evaluateShVars bool) (*ast.Task, error) {
func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task, error) {
origTask, err := e.GetTask(call)
if err != nil {
return nil, err

View File

@@ -21,7 +21,7 @@ import (
const defaultWatchInterval = 5 * time.Second
// watchTasks start watching the given tasks
func (e *Executor) watchTasks(calls ...ast.Call) error {
func (e *Executor) watchTasks(calls ...*ast.Call) error {
tasks := make([]string, len(calls))
for i, c := range calls {
tasks[i] = c.Task
@@ -119,24 +119,24 @@ func closeOnInterrupt(w *watcher.Watcher) {
}()
}
func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...ast.Call) error {
func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...*ast.Call) error {
watchedFiles := w.WatchedFiles()
var registerTaskFiles func(ast.Call) error
registerTaskFiles = func(c ast.Call) error {
var registerTaskFiles func(*ast.Call) error
registerTaskFiles = func(c *ast.Call) error {
task, err := e.CompiledTask(c)
if err != nil {
return err
}
for _, d := range task.Deps {
if err := registerTaskFiles(ast.Call{Task: d.Task, Vars: d.Vars}); err != nil {
if err := registerTaskFiles(&ast.Call{Task: d.Task, Vars: d.Vars}); err != nil {
return err
}
}
for _, c := range task.Cmds {
if c.Task != "" {
if err := registerTaskFiles(ast.Call{Task: c.Task, Vars: c.Vars}); err != nil {
if err := registerTaskFiles(&ast.Call{Task: c.Task, Vars: c.Vars}); err != nil {
return err
}
}