Compare commits

...

132 Commits

Author SHA1 Message Date
Andrey Nering
f2abc13ce2 v3.12.0 2022-03-31 21:44:59 -03:00
Andrey Nering
0f4621fb02 CHANGELOG: Add entry for #691 2022-03-31 21:40:16 -03:00
Andrey Nering
c6ff641f6d Merge branch 'list-task-names' of https://github.com/ardnew/task into ardnew-list-task-names 2022-03-31 21:31:56 -03:00
Andrey Nering
350f74a53d CHANGELOG: Add entry for #656 2022-03-31 21:19:16 -03:00
Andrey Nering
41cd7acc87 Merge pull request #656 from tylermmorton/master
Add support for multi-level includes
2022-03-31 21:12:15 -03:00
Andrey Nering
c6eea26660 go mod tidy 2022-03-21 15:26:41 -03:00
Andrey Nering
61c5718663 Upgrade to Go 1.18 is out. Set 1.17 as the minimal version 2022-03-21 15:23:06 -03:00
ardnew
9897f4b527 refactor with support for --list and --list-all 2022-03-21 12:59:25 -05:00
ardnew
978a6e5ecb quickly print task names only with flags --silent and --list 2022-03-21 12:02:56 -05:00
Andrey Nering
a018997ddc CHANGELOG: Fix typo 2022-03-21 10:31:48 -03:00
Andrey Nering
de09843467 Improvements + CHANGELOG for #677 2022-03-19 18:41:03 -03:00
Andrey Nering
78a57fdb4b Merge branch 'dballweg/vars_in_includedtaskfile' of https://github.com/dballweg/task into dballweg-dballweg/vars_in_includedtaskfile 2022-03-19 17:31:11 -03:00
Andrey Nering
0bc2fd72f0 Merge pull request #689 from go-task/dependabot/go_modules/github.com/stretchr/testify-1.7.1
Bump github.com/stretchr/testify from 1.7.0 to 1.7.1
2022-03-19 11:01:09 -03:00
dependabot[bot]
dda5164efd Bump github.com/stretchr/testify from 1.7.0 to 1.7.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-19 11:07:08 +00:00
Andrey Nering
3df2396b63 Merge pull request #688 from vicmattos/vicmattosissue-683-docs-install-choco
Add chocolatey in installation documentation
2022-03-18 20:46:50 -03:00
Victor Mattos
d3da84e724 docs: add ownership of choco installation method 2022-03-18 17:41:34 -03:00
Victor Mattos
eb61015477 fix: reference chocolatey link anchor
Co-authored-by: Andrey Nering <andrey@nering.com.br>
2022-03-18 17:38:29 -03:00
Victor Mattos
40c644f006 docs: add chocolatey installation method
Resolves #683
2022-03-09 15:39:54 -03:00
Andrey Nering
c9aa0180a8 Merge pull request #679 from philpennock/zsh-completion
completion: zsh: overhaul and sync to current flags
2022-02-27 16:01:11 -03:00
Phil Pennock
a06e46885d completion: zsh: overhaul and sync to current flags
* List all current option flags
* Provide descriptions for every flag
* Pass the `task -l` descriptions as descriptions for the task completions
  + The prior logic had 4 invocations of sed and 1 of awk, and only kept the
    task name
  + Do all filtering in zsh without forking (except for `task` itself)
* When `--taskfile` is used, complete tasks from _that_ file
  + And otherwise, enable completions if only the `.dist` variant files are
    present
* Ensure mutually exclusive options preclude each other
  + the `+ '(groupname)'` clause defines this
* Fix `--dir` to take directories, not files
2022-02-25 01:07:43 -05:00
Dan Ballweg
60fa6e6c0a update 2022-02-24 13:18:35 -06:00
Dan Ballweg
2f18f7927d test include variables 2022-02-24 13:17:20 -06:00
Dan Ballweg
292cf75836 add vars to included taskfiles 2022-02-23 16:53:46 -06:00
tylermmorton
fc95061f4c Add missing newlines 2022-02-21 15:33:54 -05:00
tylermmorton
1f1275255c Fix bug in includes where default taskfiles were not being checked. 2022-02-21 15:31:55 -05:00
Andrey Nering
d8555e5a5d v3.11.0 2022-02-19 19:40:29 -03:00
Andrey Nering
b323531dd5 Improvements and CHANGELOG for #651 2022-02-19 19:31:27 -03:00
Andrey Nering
cfb665310e Merge branch 'group-begin-message' of https://github.com/janslow/task into janslow-group-begin-message 2022-02-19 18:42:34 -03:00
Andrey Nering
51c6ebcd4d Add tests, documentation and changelog for #666 2022-02-19 18:24:43 -03:00
Andrey Nering
e94d1b6b9f Merge branch 'task-498-taskfile-dist' of https://github.com/tylermmorton/task into tylermmorton-task-498-taskfile-dist 2022-02-19 18:01:00 -03:00
Andrey Nering
ca7b32105d Merge pull request #674 from go-task/dependabot/go_modules/mvdan.cc/sh/v3-3.4.3
Bump mvdan.cc/sh/v3 from 3.4.2 to 3.4.3
2022-02-19 17:55:30 -03:00
dependabot[bot]
264db2737b Bump mvdan.cc/sh/v3 from 3.4.2 to 3.4.3
Bumps [mvdan.cc/sh/v3](https://github.com/mvdan/sh) from 3.4.2 to 3.4.3.
- [Release notes](https://github.com/mvdan/sh/releases)
- [Changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdan/sh/compare/v3.4.2...v3.4.3)

---
updated-dependencies:
- dependency-name: mvdan.cc/sh/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 20:51:22 +00:00
Andrey Nering
5f2c9a6e45 Merge pull request #671 from JessThrysoee/bashcompletion
bash-completion refactoring
2022-02-13 19:51:40 -03:00
Jess Thrysoee
19be1f1bf0 bash-completion refactoring
1. 'compgen -c' lists _all_ command names on the system, which is not
   appropriate for this script, furthermore echo does not read from stdin
   so the output is lost.

2. use _get_comp_words_by_ref and __ltrim_colon_completions to handle task
   names with colons.

   "...modifying COMP_WORDBREAKS in your completion script is not safe
   (as it is a global variable and it has the side effect of affecting
   the behavior of other completion scripts"
   Ref.: https://stackoverflow.com/a/12495727/7044304

3. Add options completion

4. Use task --list-all
2022-02-13 23:44:04 +01:00
tylermmorton
7cdf0000d9 Fix error message assertion in task_test 2022-02-03 22:23:01 -05:00
tylermmorton
13606e5e00 Remove note about multi level includes from usage documentation 2022-02-03 22:20:49 -05:00
tylermmorton
35af240faa Add newlines to multi-level test Taskfiles 2022-02-03 22:19:07 -05:00
tylermmorton
0ac56f8973 Add newlines to test Taskfiles 2022-02-03 22:13:43 -05:00
tylermmorton
6e5f8b1fb0 Append task prefix to log messages 2022-02-03 22:12:58 -05:00
tylermmorton
15e831c0b0 Revert "Update docs to account for new feature"
This reverts commit 66748ab5e5.
2022-02-03 22:09:57 -05:00
tylermmorton
248952bc8f Add dist fallbacks to defaultTaskfiles 2022-01-29 11:53:36 -05:00
Andrey Nering
2373743eac Merge pull request #652 from jcwillox/master
Add completions to nfpms packages
2022-01-17 10:18:22 -03:00
Andrey Nering
f119596be6 Docs: Small change to title 2022-01-17 09:38:32 -03:00
Andrey Nering
b7cb41b388 Merge pull request #657 from lechuckroh/intellij-plugin
Update 'community.md' to add IntelliJ plugin link
2022-01-17 09:37:06 -03:00
Lechuck Roh
a65ee26446 Update 'community.md' to add IntelliJ plugin link 2022-01-16 22:24:12 +09:00
tylermmorton
d3e2fbf1e2 Merge branch 'master' of https://github.com/tylermmorton/task 2022-01-15 23:37:40 -05:00
tylermmorton
66748ab5e5 Update docs to account for new feature 2022-01-15 23:37:39 -05:00
tylermmorton
c73a2c8f84 Move circular include logic to a separate function 2022-01-15 23:34:59 -05:00
Tyler Morton
4bbcd99b8b Merge branch 'go-task:master' into master 2022-01-14 22:39:27 -05:00
tylermmorton
02e7ff27c7 Add support for multi-level includes and cyclic include detection 2022-01-14 22:38:37 -05:00
Josh Willox
7ed3cea40b Add completions to nfpms packages 2022-01-14 20:48:28 +11:00
Jay Anslow
74f5cf8f29 Add support for begin/end messages with grouped output
Fixes #647

This allows CI systems that support grouping (such as with [GitHub Actions's `::group::` command](https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#grouping-log-lines) and [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands)) to collapse all of the logs for a single task, to improve readability of logs

## Example

The following Taskfile

```
# Taskfile.yml
version: 3
output:
  group:
    begin: "::group::{{ .TASK }}"
    end: "::endgroup::"
tasks:
  default:
    cmds:
      - "echo 'Hello, World!'"
```

Results in the following output
```bash
$ task
task: [default] echo 'Hello, World!'
::group::default
Hello, World!
::endgroup::
```

See [this GitHub Actions job](https://github.com/janslow/task/runs/4811059609?check_suite_focus=true) for a full example

<img width="771" alt="image" src="https://user-images.githubusercontent.com/1253367/149429832-6cb0c1b5-0758-442e-9375-c4daa65771bc.png">
<img width="394" alt="image" src="https://user-images.githubusercontent.com/1253367/149429851-1d5d2ab5-9095-4795-9b57-f91750720d40.png">
2022-01-14 00:22:14 +00:00
Andrey Nering
086d13ca2f Docs: Remove line reference from file link 2022-01-13 10:39:38 -03:00
Andrey Nering
2780e96179 Merge pull request #648 from sagikazarmark/docs-nix-install
docs: add nix as an installation method
2022-01-13 10:38:49 -03:00
Mark Sagi-Kazar
191678f9d6 docs: add nix as an installation method 2022-01-13 00:15:04 +01:00
Andrey Nering
79f595d8d1 v3.10.0 2022-01-04 18:19:48 -03:00
Andrey Nering
db2865fb17 Add CHANGELOG entry for #579 2022-01-04 18:10:24 -03:00
Andrey Nering
f945fa60d9 Merge branch 'bugfix/issue-481-dynamic-vars-broken' of https://github.com/masaushi/task into masaushi-bugfix/issue-481-dynamic-vars-broken 2022-01-04 17:39:14 -03:00
Andrey Nering
454988f657 Fix typo 🤦 2022-01-04 17:19:38 -03:00
Andrey Nering
7e0346d6eb Add CHANGELOG, documentation and small improvements to #401 2022-01-04 17:16:21 -03:00
Andrey Nering
00a90d1fe6 Merge branch 'f/list-all' of https://github.com/therealkevinard/task into therealkevinard-f/list-all 2022-01-04 17:03:12 -03:00
Andrey Nering
d6c185580a Add CHANGELOG, documentation and small improvements to #626 2022-01-04 16:56:13 -03:00
Jacob McCollum
fd9132c15d remove extra file 2022-01-03 13:22:06 -05:00
Kevin Ard
42702e81b3 refactor: wrap PrintTasksHelp with arg-less signatures
provide exported methods for accessing PrintTasksHelp variants.
2022-01-03 12:12:18 -05:00
Jacob McCollum
09c9d55695 Changes from PR Review:
- Remove ^task syntax from `defer`
- Support task call syntax in defer
2022-01-02 16:38:06 -05:00
Jacob McCollum
69e9effc88 initial pass at deferred commands 2022-01-02 15:55:43 -05:00
Andrey Nering
1c782c599f Remove deprecated "$" and "^" prefixes
`$` was a variable prefix that make it being evaluated as shell. It was
replaced with `sh:`.

`^` is a command prefix that make it run another task. It was replaced
with `task:`.

These were added long ago when we were experimenting with stuff and kept for
some time for backward compatibility reasons, but sometimes causes confusion
and I think the time to remove the code came.

Closes #644
Closes #645
Ref #642

Co-authored-by: Trite <60318513+Trite8Q1@users.noreply.github.com>
2022-01-02 15:26:42 -03:00
Andrey Nering
ed37071fd6 Merge pull request #621 from kerma/feat/support-yaml
Add support for yaml extension
2022-01-02 15:08:50 -03:00
Margus Kerma
d73cf106b1 Merge branch 'master' into feat/support-yaml 2022-01-02 15:30:23 +02:00
Margus Kerma
1d7982e80a fix(#584): Add support to yaml extension
- init creates Taskfile.yaml
- add changelog entry
- add zsh completion support for Taskfile.yaml
2022-01-02 15:23:10 +02:00
Andrey Nering
d5d1984116 Pin released vetsion of mvdan/sh v3.4.2 2021-12-30 16:45:08 -03:00
Andrey Nering
9eda1629bb Merge pull request #636 from nichady/replace-ioutil
replace usages of ioutil with io and os
2021-12-20 23:58:23 -03:00
nichady
9a5d49774e replace usages of ioutil with io and os 2021-12-20 20:00:34 -06:00
Andrey Nering
b2efebce96 Merge pull request #634 from go-task/upgrade-github-actions
Upgrade GitHub actions
2021-12-19 22:14:23 -03:00
Andrey Nering
85232bd704 Upgrade GitHub actions
Closes #633
2021-12-19 22:06:51 -03:00
Andrey Nering
df4e3aea79 v3.9.2 2021-12-02 09:48:30 -03:00
Andrey Nering
290d45fd05 Upgrade mvdan.cc/sh 2021-12-02 09:45:34 -03:00
Andrey Nering
168e8c925c CHANGELOG: Fix typo 2021-11-29 12:55:43 -03:00
Andrey Nering
d9859b18fe v3.9.1 2021-11-28 16:19:30 -03:00
Andrey Nering
784847f35b CHANGELOG: Add entry for #533 and #588 2021-11-28 16:16:12 -03:00
Andrey Nering
97287377d1 CHANGELOG: Add entries for #597 and #598 2021-11-28 16:16:12 -03:00
Andrey Nering
a15b66e003 Merge pull request #588 from jespino/issue-533
Adding task started and task finished in the verbose otput
2021-11-28 16:14:56 -03:00
Andrey Nering
a441b4b90d Merge pull request #598 from sylv-io/fix/false_fail
task: Check context error
2021-11-28 16:10:32 -03:00
Andrey Nering
0dcc1390a6 GitHub: Remove automatic "bug" or "feature" lables in issues 2021-11-28 15:58:41 -03:00
Andrey Nering
01c86636e9 Add CHANGELOG and minor changes to #613 2021-11-28 15:54:49 -03:00
Andrey Nering
846c27d579 Merge pull request #613 from Peter554/fix-quoting-cli-args
Fix quoting of CLI_ARGS
2021-11-28 15:51:03 -03:00
Andrey Nering
db05059b42 CHANGELOG: Add entry for #614 and #612 2021-11-28 15:43:38 -03:00
Andrey Nering
b824328850 Merge pull request #614 from kerma/fix/panic-on-empty-included-cmd
Add nil check for included cmd
2021-11-28 15:41:38 -03:00
Andrey Nering
a8767a2b1a CHANGELOG: Mention mvdan/sh upgrade 2021-11-28 15:38:47 -03:00
Andrey Nering
5e14e7fb70 Merge pull request #615 from go-task/dependabot/go_modules/mvdan.cc/sh/v3-3.4.1
Bump mvdan.cc/sh/v3 from 3.4.0 to 3.4.1
2021-11-27 17:32:22 -03:00
dependabot[bot]
fbaa7be52e Bump mvdan.cc/sh/v3 from 3.4.0 to 3.4.1
Bumps [mvdan.cc/sh/v3](https://github.com/mvdan/sh) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/mvdan/sh/releases)
- [Changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdan/sh/compare/v3.4.0...v3.4.1)

---
updated-dependencies:
- dependency-name: mvdan.cc/sh/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-27 11:03:48 +00:00
Margus Kerma
b6016b244e fix(#612): Add nil check for included cmd 2021-11-26 11:20:05 +02:00
Peter Byfield
e339a64261 Fix quoting of CLI_ARGS
Consider a task:

test:
  cmds:
    - pytest {{.CLI_ARGS}}

Running `task test -- -m "not foo"` should be equivalent
to running `pytest -m "not foo"`. However, with the current
implementation the quoting of CLI_ARGS is lost and the task
executes `python -m not foo`, which results in an error.
2021-11-25 14:13:45 +01:00
Andrey Nering
17e18442ab Merge pull request #600 from OmriSteiner/typofix
Fix typo in usage.md
2021-11-09 12:15:26 -03:00
Omri Steiner
e8aa3a17a6 Fix typo in usage.md 2021-11-09 16:18:42 +02:00
Marcello Sylvester Bauer
bdb97eab86 task: Check context error
Check context error after running dependencies, to prevent false
negative precondition errors due to "context canceled".

Signed-off-by: Marcello Sylvester Bauer <sylv@sylv.io>
2021-11-05 15:06:51 +01:00
Jesús Espino
690000254c Apply suggestions from code review
Co-authored-by: Andrey Nering <andrey@nering.com.br>
2021-10-31 09:37:23 +01:00
Andrey Nering
6a0b778978 Add CHANGELOG for #592 2021-10-30 11:15:56 -03:00
Andrey Nering
549d141053 Merge pull request #592 from caarlos0/brew
fix: goreleaser brew completions
2021-10-30 11:07:13 -03:00
Carlos A Becker
c31ecdb8de fix: goreleaser brew completions 2021-10-27 14:48:00 -03:00
Jesús Espino
8a09d044c7 Adding task started and task finished in the verbose otput 2021-10-16 21:12:26 +02:00
Andrey Nering
a3b5b89930 Fix typo in CHANGELOG 2021-10-02 18:51:52 -03:00
Andrey Nering
ad6f100f6a v3.9.0 2021-10-02 18:47:30 -03:00
Andrey Nering
3cfe21af58 Add shellQuote template function 2021-10-02 18:29:07 -03:00
Andrey Nering
b70a660975 Add CHANGELOG for #580 2021-10-02 18:07:54 -03:00
Andrey Nering
04c1d1389f go mod tidy 2021-10-02 17:52:02 -03:00
Andrey Nering
f12156bf81 Merge pull request #580 from system-transparency/update/sh
upgrade mvdan.cc/sh/v3 v3.3.1 => v3.4.0
2021-10-02 17:50:37 -03:00
Marcello Sylvester Bauer
0177ac660b upgrade mvdan.cc/sh/v3 v3.3.1 => v3.4.0
Signed-off-by: Marcello Sylvester Bauer <sylv@sylv.io>
2021-10-02 12:33:07 +02:00
Andrey Nering
361b9b4ce4 v3.8.0 2021-09-26 22:30:26 -03:00
Andrey Nering
78792bd11c Add CHANGELOG + Small improvement for #563 2021-09-26 21:55:31 -03:00
Andrey Nering
8b38ddfcd9 Merge pull request #563 from system-transparency/feature/interactive
Task: Add interactive parameter
2021-09-26 21:40:50 -03:00
Andrey Nering
78ddf50d2d Documentation: Remove reference to deprecated "expansions" keyword
Closes #575
2021-09-26 21:28:40 -03:00
masaushi
93dcb20e12 fix error in evaluating dynamic variables with newly created directory 2021-09-26 22:30:32 +09:00
Andrey Nering
41a71e1dee Add CHANGELOG for #573 2021-09-25 09:48:49 -03:00
Andrey Nering
a5ed8ad58c Merge pull request #573 from masaushi/bugfix/issue-534-seg-fault-on-empty-command
Fix segmentation fault on nil slice element for issue #534
2021-09-25 09:46:47 -03:00
Andrey Nering
e45ed85b55 Add CHANGELOG + Small nits for #552 2021-09-25 09:40:03 -03:00
Andrey Nering
52474f9103 Merge pull request #552 from justafish/justafish/519/optional-includes
Allow includes to be optional
2021-09-25 09:36:44 -03:00
Andrey Nering
c2587da27d Merge pull request #577 from go-task/dependabot/go_modules/github.com/fatih/color-1.13.0
Bump github.com/fatih/color from 1.12.0 to 1.13.0
2021-09-25 09:32:28 -03:00
Andrey Nering
26036877b2 Merge pull request #576 from go-task/dependabot/go_modules/github.com/joho/godotenv-1.4.0
Bump github.com/joho/godotenv from 1.3.0 to 1.4.0
2021-09-25 09:31:54 -03:00
dependabot[bot]
906cdd9050 Bump github.com/fatih/color from 1.12.0 to 1.13.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-25 11:04:26 +00:00
dependabot[bot]
762662d056 Bump github.com/joho/godotenv from 1.3.0 to 1.4.0
Bumps [github.com/joho/godotenv](https://github.com/joho/godotenv) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/joho/godotenv/releases)
- [Commits](https://github.com/joho/godotenv/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/joho/godotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-25 11:04:15 +00:00
masaushi
1de4b38766 fix segmentation fault on nil slice element 2021-09-15 00:01:33 +09:00
Andrey Nering
6c73ab823b Add CHANGELOG + gofmt for #557 2021-09-06 10:30:45 -03:00
Andrey Nering
5ef1651151 Merge branch 'include-homedir' of https://github.com/amancevice/task into amancevice-include-homedir 2021-09-06 10:24:23 -03:00
Alexander Mancevice
8d695bc8d7 simplify logic to expand included paths 2021-09-05 10:57:49 -04:00
Alexander Mancevice
c892d055ed Use internal execext.Expand to expand ~ in includes 2021-09-05 08:18:47 -04:00
Andrey Nering
b327e54be1 Give up on trying to make Gorelease release Homebrew automatically
It refuses to do it and I don't know why. Gonna publish the manifest manually as before
2021-09-04 22:17:41 -03:00
Marcello Sylvester Bauer
2dc181c75e Task: Add interactive parameter
Add the task parameter "interactive" to force interleaved output in order
to make interactive CLI apps work.

Feature request in #217

TODO:
* Update documentation

Signed-off-by: Marcello Sylvester Bauer <sylv@sylv.io>
2021-09-03 10:31:24 +02:00
Alexander Mancevice
0a6833e9d8 Allow included Taskfiles to use ~/* paths 2021-08-21 07:20:33 -04:00
Sally Young
8f80fc4e2c Issue #519: Allow includes to be optional 2021-08-11 17:28:44 +01:00
Kevin Ard
347c796662 add tests to previous 2020-11-13 16:24:34 -05:00
Kevin Ard
9bed7f7a9b feat (help): allow cli option to list tasks with no desc
added an add'l cli option that lists all tasks, with or without description.
orig. behavior: task -l lists tasks with desc field
new behaviour: task -la or task -a will list all tasks. if task has desc, it will be included.

BREAKING CHANGES: none, that I know of.
NOTES/Concerns:
- This is wip.
- Haven't checked how it interacts with bash completion.
- The new Executor.TaskNames func does not use e.CompiledTask(taskfile.Call{Task: task.Task})
2020-11-13 15:27:03 -05:00
75 changed files with 1619 additions and 278 deletions

View File

@@ -1,7 +1,6 @@
---
name: Bug Report
about: Use the template to report bugs and issues
labels: bug
---
- Task version:

View File

@@ -1,7 +1,6 @@
---
name: Feature Request
about: Use the template to make feature requests
labels: feature
---
Describe in detail what feature do you want to see in Task.

View File

@@ -10,12 +10,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.18.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
@@ -23,4 +23,4 @@ jobs:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
GITHUB_TOKEN: ${{secrets.GH_PAT}}

View File

@@ -5,18 +5,18 @@ jobs:
name: Test
strategy:
matrix:
go-version: [1.16.x, 1.17.x]
go-version: [1.17.x, 1.18.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:
- name: Set up Go ${{matrix.go-version}}
uses: actions/setup-go@v1
uses: actions/setup-go@v2
with:
go-version: ${{matrix.go-version}}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Download Go modules
run: go mod download
@@ -27,4 +27,4 @@ jobs:
run: go build -o ./bin/task -v ./cmd/task
- name: Test
run: ./bin/task test
run: ./bin/task test --output=group --output-group-begin='::group::{{.TASK}}' --output-group-end='::endgroup::'

View File

@@ -25,6 +25,10 @@ gomod:
archives:
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
files:
- README.md
- LICENSE
- completion/**/*
format_overrides:
- goos: windows
format: zip
@@ -50,6 +54,13 @@ nfpms:
- deb
- rpm
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
contents:
- src: completion/bash/task.bash
dst: /etc/bash_completion.d/task
- src: completion/fish/task.fish
dst: /usr/share/fish/completions/task.fish
- src: completion/zsh/_task
dst: /usr/local/share/zsh/site-functions/_task
brews:
- name: go-task
@@ -60,8 +71,10 @@ brews:
tap:
owner: go-task
name: homebrew-tap
token: "{{.Env.GORELEASER_TOKEN}}"
test:
system "#{bin}/task", "--help"
url_template: https://github.com/go-task/task/releases/download/{{.Tag}}/{{.ArtifactName}}
commit_msg_template: Bump {{.Tag}}
install: |-
bin.install "task"
bash_completion.install "completion/bash/task.bash" => "task"
zsh_completion.install "completion/zsh/_task" => "_task"
fish_completion.install "completion/fish/task.fish"

View File

@@ -1,6 +1,100 @@
# Changelog
## 3.7.3 - 2021-09-04
## v3.12.0 - 2022-03-31
- The `--list` and `--list-all` flags can now be combined with the `--silent`
flag to print the task names only, without their description
([#691](https://github.com/go-task/task/pull/691)).
- Added support for multi-level inclusion of Taskfiles. This means that
included Taskfiles can also include other Taskfiles. Before this was limited
to one level
([#390](https://github.com/go-task/task/issues/390), [#623](https://github.com/go-task/task/discussions/623), [#656](https://github.com/go-task/task/pull/656)).
- Add ability to specify vars when including a Taskfile.
[Check out the documentation](https://taskfile.dev/#/usage?id=vars-of-included-taskfiles)
for more information.
([#677](https://github.com/go-task/task/pull/677)).
## v3.11.0 - 2022-02-19
- Task now supports printing begin and end messages when using the `group`
output mode, useful for grouping tasks in CI systems.
[Check out the documentation](http://taskfile.dev/#/usage?id=output-syntax) for more information
([#647](https://github.com/go-task/task/issues/647), [#651](https://github.com/go-task/task/pull/651)).
- Add `Taskfile.dist.yml` and `Taskfile.dist.yaml` to the supported file
name list. [Check out the documentation](https://taskfile.dev/#/usage?id=supported-file-names) for more information
([#498](https://github.com/go-task/task/issues/498), [#666](https://github.com/go-task/task/pull/666)).
## v3.10.0 - 2022-01-04
- A new `--list-all` (alias `-a`) flag is now available. It's similar to the
exiting `--list` (`-l`) but prints all tasks, even those without a
description
([#383](https://github.com/go-task/task/issues/383), [#401](https://github.com/go-task/task/pull/401)).
- It's now possible to schedule cleanup commands to run once a task finishes
with the `defer:` keyword
([Documentation](https://taskfile.dev/#/usage?id=doing-task-cleanup-with-defer), [#475](https://github.com/go-task/task/issues/475), [#626](https://github.com/go-task/task/pull/626)).
- Remove long deprecated and undocumented `$` variable prefix and `^` command
prefix
([#642](https://github.com/go-task/task/issues/642), [#644](https://github.com/go-task/task/issues/644), [#645](https://github.com/go-task/task/pull/645)).
- Add support for `.yaml` extension (as an alternative to `.yml`).
This was requested multiple times throughout the years. Enjoy!
([#183](https://github.com/go-task/task/issues/183), [#184](https://github.com/go-task/task/pull/184), [#369](https://github.com/go-task/task/issues/369), [#584](https://github.com/go-task/task/issues/584), [#621](https://github.com/go-task/task/pull/621)).
- Fixed error when computing a variable when the task directory do not exist
yet
([#481](https://github.com/go-task/task/issues/481), [#579](https://github.com/go-task/task/pull/579)).
## v3.9.2 - 2021-12-02
- Upgrade [mvdan/sh](https://github.com/mvdan/sh) which contains a fix a for
a important regression on Windows
([#619](https://github.com/go-task/task/issues/619), [mvdan/sh#768](https://github.com/mvdan/sh/issues/768), [mvdan/sh#769](https://github.com/mvdan/sh/pull/769)).
## v3.9.1 - 2021-11-28
- Add logging in verbose mode for when a task starts and finishes
([#533](https://github.com/go-task/task/issues/533), [#588](https://github.com/go-task/task/pull/588)).
- Fix an issue with preconditions and context errors
([#597](https://github.com/go-task/task/issues/597), [#598](https://github.com/go-task/task/pull/598)).
- Quote each `{{.CLI_ARGS}}` argument to prevent one with spaces to become many
([#613](https://github.com/go-task/task/pull/613)).
- Fix nil pointer when `cmd:` was left empty
([#612](https://github.com/go-task/task/issues/612), [#614](https://github.com/go-task/task/pull/614)).
- Upgrade [mvdan/sh](https://github.com/mvdan/sh) which contains two
relevant fixes:
- Fix quote of empty strings in `shellQuote`
([#609](https://github.com/go-task/task/issues/609), [mvdan/sh#763](https://github.com/mvdan/sh/issues/763)).
- Fix issue of wrong environment variable being picked when there's another
very similar one
([#586](https://github.com/go-task/task/issues/586), [mvdan/sh#745](https://github.com/mvdan/sh/pull/745)).
- Install shell completions automatically when installing via Homebrew
([#264](https://github.com/go-task/task/issues/264), [#592](https://github.com/go-task/task/pull/592), [go-task/homebrew-tap#2](https://github.com/go-task/homebrew-tap/pull/2)).
## v3.9.0 - 2021-10-02
- A new `shellQuote` function was added to the template system
(`{{shellQuote "a string"}}`) to ensure a string is safe for use in shell
([mvdan/sh#727](https://github.com/mvdan/sh/pull/727), [mvdan/sh#737](https://github.com/mvdan/sh/pull/737), [Documentation](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote))
- In this version [mvdan.cc/sh](https://github.com/mvdan/sh) was upgraded
with some small fixes and features
- The `read -p` flag is now supported
([#314](https://github.com/go-task/task/issues/314), [mvdan/sh#551](https://github.com/mvdan/sh/issues/551), [mvdan/sh#772](https://github.com/mvdan/sh/pull/722))
- The `pwd -P` and `pwd -L` flags are now supported
([#553](https://github.com/go-task/task/issues/553), [mvdan/sh#724](https://github.com/mvdan/sh/issues/724), [mvdan/sh#728](https://github.com/mvdan/sh/pull/728))
- The `$GID` environment variable is now correctly being set
([#561](https://github.com/go-task/task/issues/561), [mvdan/sh#723](https://github.com/mvdan/sh/pull/723))
## v3.8.0 - 2021-09-26
- Add `interactive: true` setting to improve support for interactive CLI apps
([#217](https://github.com/go-task/task/issues/217), [#563](https://github.com/go-task/task/pull/563)).
- Fix some `nil` errors
([#534](https://github.com/go-task/task/issues/534), [#573](https://github.com/go-task/task/pull/573)).
- Add ability to declare an included Taskfile as optional
([#519](https://github.com/go-task/task/issues/519), [#552](https://github.com/go-task/task/pull/552)).
- Add support for including Taskfiles in the home directory by using `~`
([#539](https://github.com/go-task/task/issues/539), [#557](https://github.com/go-task/task/pull/557)).
## v3.7.3 - 2021-09-04
- Add official support to Apple M1 ([#564](https://github.com/go-task/task/pull/564), [#567](https://github.com/go-task/task/pull/567)).
- Our [official Homebrew tap](https://github.com/go-task/homebrew-tap) will

View File

@@ -12,6 +12,7 @@ import (
"syscall"
"github.com/spf13/pflag"
"mvdan.cc/sh/v3/syntax"
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/args"
@@ -59,6 +60,7 @@ func main() {
helpFlag bool
init bool
list bool
listAll bool
status bool
force bool
watch bool
@@ -70,14 +72,15 @@ func main() {
concurrency int
dir string
entrypoint string
output string
output taskfile.Output
color bool
)
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
pflag.BoolVarP(&helpFlag, "help", "h", false, "shows Task usage")
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yaml in the current folder")
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
pflag.BoolVarP(&listAll, "list-all", "a", false, "lists tasks with or without a description")
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
@@ -88,7 +91,9 @@ func main() {
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
pflag.StringVarP(&output.Name, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
pflag.StringVar(&output.Group.Begin, "output-group-begin", "", "message template to print before a task's grouped output")
pflag.StringVar(&output.Group.End, "output-group-end", "", "message template to print after a task's grouped output")
pflag.BoolVarP(&color, "color", "c", true, "colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable")
pflag.IntVarP(&concurrency, "concurrency", "C", 0, "limit number tasks to run concurrently")
pflag.Parse()
@@ -121,8 +126,17 @@ func main() {
if entrypoint != "" {
dir = filepath.Dir(entrypoint)
entrypoint = filepath.Base(entrypoint)
} else {
entrypoint = "Taskfile.yml"
}
if output.Name != "group" {
if output.Group.Begin != "" {
log.Fatal("task: You can't set --output-group-begin without --output=group")
return
}
if output.Group.End != "" {
log.Fatal("task: You can't set --output-group-end without --output=group")
return
}
}
e := task.Executor{
@@ -144,6 +158,12 @@ func main() {
OutputStyle: output,
}
if (list || listAll) && silent {
e.ListTaskNames(listAll)
return
}
if err := e.Setup(); err != nil {
log.Fatal(err)
}
@@ -154,23 +174,32 @@ func main() {
}
if list {
e.PrintTasksHelp()
e.ListTasksWithDesc()
return
}
if listAll {
e.ListAllTasks()
return
}
var (
calls []taskfile.Call
globals *taskfile.Vars
tasksAndVars, cliArgs = getArgs()
calls []taskfile.Call
globals *taskfile.Vars
)
tasksAndVars, cliArgs, err := getArgs()
if err != nil {
log.Fatal(err)
}
if v >= 3.0 {
calls, globals = args.ParseV3(tasksAndVars...)
} else {
calls, globals = args.ParseV2(tasksAndVars...)
}
globals.Set("CLI_ARGS", taskfile.Var{Static: strings.Join(cliArgs, " ")})
globals.Set("CLI_ARGS", taskfile.Var{Static: cliArgs})
e.Taskfile.Vars.Merge(globals)
ctx := context.Background()
@@ -191,20 +220,25 @@ func main() {
}
}
func getArgs() (tasksAndVars, cliArgs []string) {
func getArgs() ([]string, string, error) {
var (
args = pflag.Args()
doubleDashPos = pflag.CommandLine.ArgsLenAtDash()
)
if doubleDashPos != -1 {
tasksAndVars = args[:doubleDashPos]
cliArgs = args[doubleDashPos:]
} else {
tasksAndVars = args
if doubleDashPos == -1 {
return args, "", nil
}
return
var quotedCliArgs []string
for _, arg := range args[doubleDashPos:] {
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
if err != nil {
return nil, "", err
}
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
}
return args[:doubleDashPos], strings.Join(quotedCliArgs, " "), nil
}
func getSignalContext() context.Context {

View File

@@ -1,21 +1,22 @@
# /bin/bash
_task_completion()
{
local scripts;
local curr_arg;
local cur
_get_comp_words_by_ref -n : cur
# Remove colon from word breaks
COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
case "$cur" in
--*)
local options="$(_parse_help task)"
COMPREPLY=( $(compgen -W "$options" -- "$cur") )
;;
*)
local tasks="$(task --list-all | awk 'NR>1 { sub(/:$/,"",$2); print $2 }')"
COMPREPLY=( $(compgen -W "$tasks" -- "$cur") )
;;
esac
scripts=$(task -l | sed '1d' | awk '{ print $2 }' | sed 's/:$//');
curr_arg="${COMP_WORDS[COMP_CWORD]:-"."}"
# Do not accept more than 1 argument
if [ "${#COMP_WORDS[@]}" != "2" ]; then
return
fi
COMPREPLY=($(compgen -c | echo "$scripts" | grep -- $curr_arg));
__ltrim_colon_completions "$cur"
}
complete -F _task_completion task

View File

@@ -1,25 +1,60 @@
#compdef task
# Listing commands from Taskfile.yml
function __list() {
local -a scripts
local context state state_descr line
typeset -A opt_args
if [ -f Taskfile.yml ]; then
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/g'))
_describe 'script' scripts
# Listing commands from Taskfile.yml
function __task_list() {
local -a scripts cmd
local -i enabled=0
local taskfile item task desc
cmd=(task)
taskfile="${(v)opt_args[(i)-t|--taskfile]}"
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
enabled=1
cmd+=(--taskfile "$taskfile")
else
for taskfile in Taskfile{,.dist}.{yaml,yml}; do
if [[ -f "$taskfile" ]]; then
enabled=1
break
fi
done
fi
(( enabled )) || return 0
scripts=()
for item in "${(@)${(f)$("${cmd[@]}" --list)}[2,-1]#\* }"; do
task="${item%%:[[:space:]]*}"
desc="${item##[^[:space:]]##[[:space:]]##}"
scripts+=( "${task//:/\\:}:$desc" )
done
_describe 'Task to run' scripts
}
_arguments \
'(-d --dir)'{-d,--dir}': :_files' \
'(--dry)'--dry \
'(-f --force)'{-f,--force} \
'(-i --init)'{-i,--init} \
'(-l --list)'{-l,--list} \
'(-s --silent)'{-s,--silent} \
'(--status)'--status \
'(-v --verbose)'{-v,--verbose} \
'(--version)'--version \
'(-w --watch)'{-w,--watch} \
'(- *)'{-h,--help} \
'*: :__list' \
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
'(-c --color)'{-c,--color}'[colored output]' \
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
'(-s --silent)'{-s,--silent}'[disable echoing]' \
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
+ '(operation)' \
{-l,--list}'[list describable tasks]' \
{-a,--list-all}'[list all tasks]' \
{-i,--init}'[create new Taskfile.yaml]' \
'(-*)'{-h,--help}'[show help]' \
'(-*)--version[show version and exit]' \
'*: :__task_list'

View File

@@ -29,6 +29,12 @@ There is a convenience wrapper for initializing and running tasks from Sublime T
developed by [@biozz](https://github.com/biozz), the source code is available [here](https://github.com/biozz/sublime-taskfile)
and it is published on Package Control [here](https://packagecontrol.io/packages/Taskfile).
### IntelliJ plugin
There's a JetBrains IntelliJ plugin done by
[@lechuckroh](https://github.com/lechuckroh), which has its code [here](https://github.com/lechuckroh/task-intellij-plugin)
and is published [here](https://plugins.jetbrains.com/plugin/17058-taskfile).
## Installation methods
Some installation methods are maintained by third party:

View File

@@ -25,6 +25,18 @@ right:
sudo snap install task --classic
```
#### **Chocolatey**
If you're on Windows and have [Chocolatey][choco] installed, getting
Task is as simple as running:
```bash
choco install go-task
```
This installation method is community owned.
#### **Scoop**
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
@@ -51,6 +63,19 @@ yay -S taskfile-git
This installation method is community owned, but since it's `-git` version of
the package, it's always latest available version based on the Git repository.
#### **Nix**
If you're on NixOS or have Nix installed
you can install Task from [nixpkgs](https://github.com/NixOS/nixpkgs):
```cmd
nix-env -iA nixpkgs.go-task
```
This installation method is community owned. After a new release of Task, it
may take some time until it's available in [nixpkgs](https://github.com/NixOS/nixpkgs).
<!-- tabs:end -->
## Get The Binary
@@ -137,4 +162,5 @@ env GO111MODULE=on go get -u github.com/go-task/task/v3/cmd/task@latest
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
[releases]: https://github.com/go-task/task/releases
[godownloader]: https://github.com/goreleaser/godownloader
[choco]: https://chocolatey.org/
[scoop]: https://scoop.sh/

View File

@@ -31,6 +31,13 @@ of updating versions there by editing
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
If you think its Task version is outdated, open an issue to let us know.
# Nix
Nix is a community owned installation method. Nix package maintainers usually take care
of updating versions there by editing
[this file](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/development/tools/go-task/default.nix).
If you think its Task version is outdated, open an issue to let us know.
[goreleaser]: https://goreleaser.com/#continuous_integration
[homebrewtap]: https://github.com/go-task/homebrew-tap
[gotaskrb]: https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb

View File

@@ -28,7 +28,7 @@ officially supported. On Linux, only `Taskfile.yml` will work, though.
- `version:`
- `includes:`
- Configuration ones, like `output:`, `expansions:` or `silent:`
- Configuration ones, like `output:` or `silent:`
- `vars:`
- `env:`
- `tasks:`
@@ -60,7 +60,6 @@ version: '3'
includes:
docker: ./docker/Taskfile.yml
output: prefixed
expansions: 3
vars:
FOO: bar
env:
@@ -76,7 +75,6 @@ includes:
docker: ./docker/Taskfile.yml
output: prefixed
expansions: 3
vars:
FOO: bar

View File

@@ -33,6 +33,20 @@ executable called must be available by the OS or in PATH.
If you omit a task name, "default" will be assumed.
## Supported file names
Task will look for the following file names, in order of priority:
- Taskfile.yml
- Taskfile.yaml
- Taskfile.dist.yml
- Taskfile.dist.yaml
The intention of having the `.dist` variants is to allow projects to have one
commited version (`.dist`) while still allowing individual users to override
the Taskfile by adding an additional `Taskfile.yml` (which would be on
`.gitignore`).
## Environment variables
### Task
@@ -150,9 +164,49 @@ includes:
> The included Taskfiles must be using the same schema version the main
> Taskfile uses.
> Also, for now included Taskfiles can't include other Taskfiles.
> This was a deliberate decision to keep use and implementation simple.
> If you disagree, open an GitHub issue and explain your use case. =)
### Optional includes
Includes marked as optional will allow Task to continue execution as normal if
the included file is missing.
```yaml
version: '3'
includes:
tests:
taskfile: ./tests/Taskfile.yml
optional: true
tasks:
greet:
cmds:
- echo "This command can still be successfully executed if ./tests/Taskfile.yml does not exist"
```
### Vars of included Taskfiles
You can also specify variables when including a Taskfile. This may be useful
for having reusable Taskfile that can be tweaked or even included more than once:
```yaml
version: '3'
includes:
backend:
taskfile: ./taskfiles/Docker.yml
vars:
DOCKER_IMAGE: backend_image
frontend:
taskfile: ./taskfiles/Docker.yml
vars:
DOCKER_IMAGE: frontend_image
```
> NOTE: Vars declared in the included Taskfile have preference over the
included ones! If you want a variable in an included Taskfile to be overridable
use the [default function](https://go-task.github.io/slim-sprig/defaults.html):
`MY_VAR: '{{.MY_VAR | default "my-default-value"}}'`.
## Task directory
@@ -502,6 +556,8 @@ They are listed below in order of importance (e.g. most important first):
- Variables declared in the task definition
- Variables given while calling a task from another
(See [Calling another task](#calling-another-task) above)
- Variables of the [included Taskfile](#including-other-taskfiles) (when the task is included)
- Variables of the [inclusion of the Taskfile](#vars-of-included-taskfiles) (when the task is included)
- Global variables (those declared in the `vars:` option in the Taskfile)
- Environment variables
@@ -570,7 +626,7 @@ This works for all types of variables.
## Forwarding CLI arguments to commands
If `--` is given in the CLI, all following paramaters are added to a
If `--` is given in the CLI, all following parameters are added to a
special `.CLI_ARGS` variable. This is useful to forward arguments to another
command.
@@ -589,6 +645,45 @@ tasks:
- yarn {{.CLI_ARGS}}
```
## Doing task cleanup with `defer`
With the `defer` keyword, it's possible to schedule cleanup to be run once
the task finishes. The difference with just putting it as the last command is
that this command will run even when the task fails.
In the example below `rm -rf tmpdir/` will run even if the third command fails:
```yaml
version: '3'
tasks:
default:
cmds:
- mkdir -p tmpdir/
- defer: rm -rf tmpdir/
- echo 'Do work on tmpdir/'
```
If you want to move the cleanup command into another task, that's possible as
well:
```yaml
version: '3'
tasks:
default:
cmds:
- mkdir -p tmpdir/
- defer: { task: cleanup }
- echo 'Do work on tmpdir/'
cleanup: rm -rf tmpdir/
```
> NOTE: Due to the nature of how the
[Go's own `defer` work](https://go.dev/tour/flowcontrol/13), the deferred
commands are executed in the reverse order if you schedule multiple of them.
## Go's template engine
Task parse commands as [Go's template engine][gotemplate] before executing
@@ -620,6 +715,9 @@ Task also adds the following functions:
converts a string from `/` path format to `\`.
- `exeExt`: Returns the right executable extension for the current OS
(`".exe"` for Windows, `""` for others).
- `shellQuote`: Quotes a string to make it safe for use in shell scripts.
Task uses [this Go function](https://pkg.go.dev/mvdan.cc/sh/v3@v3.4.0/syntax#Quote)
for this. The Bash dialect is assumed.
Example:
@@ -681,6 +779,8 @@ would print the following output:
* test: Run all the go tests.
```
If you want to see all tasks, there's a `--list-all` (alias `-a`) flag as well.
## Display summary of task
Running `task --summary task-name` will show a summary of a task.
@@ -896,6 +996,34 @@ tasks:
finishes, so you won't have live feedback for commands that take a long time
to run.
When using the `group` output, you can optionally provide a templated message
to print at the start and end of the group. This can be useful for instructing
CI systems to group all of the output for a given task, such as with
[GitHub Actions' `::group::` command](https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#grouping-log-lines)
or [Azure Pipelines](https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?expand=1&view=azure-devops&tabs=bash#formatting-commands).
```yaml
version: '3'
output:
group:
begin: '::begin::{{.TASK}}'
end: '::endgroup::'
tasks:
default:
cmds:
- echo 'Hello, World!'
silent: true
```
```bash
$ task default
::begin::default
Hello, World!
::endgroup::
```
The `prefix` output will prefix every line printed by a command with
`[task-name] ` as the prefix, but you can customize the prefix for a command
with the `prefix:` attribute:
@@ -931,6 +1059,28 @@ $ task default
> The `output` option can also be specified by the `--output` or `-o` flags.
## Interactive CLI application
When running interactive CLI applications inside Task they can sometimes behave
weirdly, specially when the [output mode](#output-syntax) is set to something
other than `interleaved` (the default), or when interactive apps are ran in
parallel with other tasks.
The `interactive: true` tells Task this is an interactive application, and Task
will try to optimize for it:
```yaml
version: '3'
tasks:
cmds:
- vim my-file.txt
interactive: true
```
If you still have problem running an interactive app through Task, please open
an issue about it.
## Short task syntax
Starting on Task v3, you can now write tasks with a shorter syntax if they

19
go.mod
View File

@@ -1,17 +1,26 @@
module github.com/go-task/task/v3
require (
github.com/fatih/color v1.12.0
github.com/fatih/color v1.13.0
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
github.com/joho/godotenv v1.3.0
github.com/joho/godotenv v1.4.0
github.com/mattn/go-zglob v0.0.3
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/radovskyb/watcher v1.0.7
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
mvdan.cc/sh/v3 v3.3.1
mvdan.cc/sh/v3 v3.4.3
)
go 1.16
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 // indirect
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf // indirect
)
go 1.17

48
go.sum
View File

@@ -1,30 +1,35 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.13 h1:rTPnd/xocYRjutMfqide2zle1u96upp1gm6eUHKi7us=
github.com/creack/pty v1.1.13/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
github.com/creack/pty v1.1.15/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/frankban/quicktest v1.13.1 h1:xVm/f9seEhZFL9+n5kv5XLrGwy6elc4V9v/XFY2vmd8=
github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
@@ -34,22 +39,27 @@ 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.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 h1:QOQNt6vCjMpXE7JSK5VvAzJC1byuN3FgTNSBwf+CJgI=
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI=
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
@@ -58,5 +68,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
mvdan.cc/sh/v3 v3.3.1 h1:aA0i7NZOc1oV5jfAH20FCz+QsmI/TX7FiAquC5Rdo5o=
mvdan.cc/sh/v3 v3.3.1/go.mod h1:DpbFT2B4fXpKiq69fEoMe+71JrmUn5aUekYy9fNKnQw=
mvdan.cc/sh/v3 v3.4.3 h1:zbuKH7YH9cqU6PGajhFFXZY7dhPXcDr55iN/cUAqpuw=
mvdan.cc/sh/v3 v3.4.3/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY=

72
help.go
View File

@@ -2,18 +2,43 @@ package task
import (
"fmt"
"io"
"log"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile"
)
// PrintTasksHelp prints help os tasks that have a description
func (e *Executor) PrintTasksHelp() {
tasks := e.tasksWithDesc()
// ListTasksWithDesc reports tasks that have a description spec.
func (e *Executor) ListTasksWithDesc() {
e.printTasks(false)
return
}
// ListAllTasks reports all tasks, with or without a description spec.
func (e *Executor) ListAllTasks() {
e.printTasks(true)
return
}
func (e *Executor) printTasks(listAll bool) {
var tasks []*taskfile.Task
if listAll {
tasks = e.allTaskNames()
} else {
tasks = e.tasksWithDesc()
}
if len(tasks) == 0 {
e.Logger.Outf(logger.Yellow, "task: No tasks with description available")
if listAll {
e.Logger.Outf(logger.Yellow, "task: No tasks available")
} else {
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
}
return
}
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
@@ -26,6 +51,15 @@ func (e *Executor) PrintTasksHelp() {
w.Flush()
}
func (e *Executor) allTaskNames() (tasks []*taskfile.Task) {
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
for _, task := range e.Taskfile.Tasks {
tasks = append(tasks, task)
}
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
return
}
func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
for _, task := range e.Taskfile.Tasks {
@@ -40,3 +74,33 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
return
}
// PrintTaskNames 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
}
}
// use stdout if no output defined
var w io.Writer = os.Stdout
if e.Stdout != nil {
w = e.Stdout
}
// create a string slice from all map values (*taskfile.Task)
s := make([]string, 0, len(e.Taskfile.Tasks))
for _, t := range e.Taskfile.Tasks {
if allTasks || t.Desc != "" {
s = append(s, strings.TrimRight(t.Task, ":"))
}
}
// sort and print all task names
sort.Strings(s)
for _, t := range s {
fmt.Fprintln(w, t)
}
}

View File

@@ -3,7 +3,6 @@ package task
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
@@ -24,15 +23,15 @@ tasks:
// InitTaskfile Taskfile creates a new Taskfile
func InitTaskfile(w io.Writer, dir string) error {
f := filepath.Join(dir, "Taskfile.yml")
f := filepath.Join(dir, "Taskfile.yaml")
if _, err := os.Stat(f); err == nil {
return ErrTaskfileAlreadyExists
}
if err := ioutil.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
if err := os.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
return err
}
fmt.Fprintf(w, "Taskfile.yml created in the current directory\n")
fmt.Fprintf(w, "Taskfile.yaml created in the current directory\n")
return nil
}

View File

@@ -74,12 +74,35 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluat
}
rangeFunc := getRangeFunc(c.Dir)
var taskRangeFunc func(k string, v taskfile.Var) error
if t != nil {
// NOTE(@andreynering): We're manually joining these paths here because
// this is the raw task, not the compiled one.
tr := templater.Templater{Vars: result, RemoveNoValue: true}
dir := tr.Replace(t.Dir)
if err := tr.Err(); err != nil {
return nil, err
}
if !filepath.IsAbs(dir) {
dir = filepath.Join(c.Dir, dir)
}
taskRangeFunc = getRangeFunc(dir)
}
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
return nil, err
}
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
return nil, err
}
if t != nil {
if err := t.IncludedTaskfileVars.Range(taskRangeFunc); err != nil {
return nil, err
}
if err := t.IncludeVars.Range(rangeFunc); err != nil {
return nil, err
}
}
if t == nil || call == nil {
return result, nil
@@ -88,19 +111,6 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluat
if err := call.Vars.Range(rangeFunc); err != nil {
return nil, err
}
// NOTE(@andreynering): We're manually joining these paths here because
// this is the raw task, not the compiled one.
tr := templater.Templater{Vars: result, RemoveNoValue: true}
dir := tr.Replace(t.Dir)
if err := tr.Err(); err != nil {
return nil, err
}
if !filepath.IsAbs(dir) {
dir = filepath.Join(c.Dir, dir)
}
taskRangeFunc := getRangeFunc(dir)
if err := t.Vars.Range(taskRangeFunc); err != nil {
return nil, err
}

View File

@@ -47,10 +47,10 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
r, err := interp.New(
interp.Params("-e"),
interp.Dir(opts.Dir),
interp.Env(expand.ListEnviron(environ...)),
interp.OpenHandler(openHandler),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
dirOption(opts.Dir),
)
if err != nil {
return err
@@ -87,3 +87,24 @@ func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (
}
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
}
func dirOption(path string) interp.RunnerOption {
return func(r *interp.Runner) error {
err := interp.Dir(path)(r)
if err == nil {
return nil
}
// If the specified directory doesn't exist, it will be created later.
// Therefore, even if `interp.Dir` method returns an error, the
// directory path should be set only when the directory cannot be found.
if absPath, _ := filepath.Abs(path); absPath != "" {
if _, err := os.Stat(absPath); os.IsNotExist(err) {
r.Dir = absPath
return nil
}
}
return err
}
}

View File

@@ -5,15 +5,25 @@ import (
"io"
)
type Group struct{}
type Group struct{
Begin, End string
}
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
return &groupWriter{writer: w}
func (g Group) WrapWriter(w io.Writer, _ string, tmpl Templater) io.Writer {
gw := &groupWriter{writer: w}
if g.Begin != "" {
gw.begin = tmpl.Replace(g.Begin) + "\n"
}
if g.End != "" {
gw.end = tmpl.Replace(g.End) + "\n"
}
return gw
}
type groupWriter struct {
writer io.Writer
buff bytes.Buffer
begin, end string
}
func (gw *groupWriter) Write(p []byte) (int, error) {
@@ -21,6 +31,14 @@ func (gw *groupWriter) Write(p []byte) (int, error) {
}
func (gw *groupWriter) Close() error {
if gw.buff.Len() == 0 {
// don't print begin/end messages if there's no buffered entries
return nil
}
if _, err := io.WriteString(gw.writer, gw.begin); err != nil {
return err
}
gw.buff.WriteString(gw.end)
_, err := io.Copy(gw.writer, &gw.buff)
return err
}

View File

@@ -6,6 +6,6 @@ import (
type Interleaved struct{}
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
func (Interleaved) WrapWriter(w io.Writer, _ string, _ Templater) io.Writer {
return w
}

View File

@@ -1,9 +1,49 @@
package output
import (
"fmt"
"io"
"github.com/go-task/task/v3/taskfile"
)
type Output interface {
WrapWriter(w io.Writer, prefix string) io.Writer
// Templater executes a template engine.
// It is provided by the templater.Templater package.
type Templater interface {
// Replace replaces the provided template string with a rendered string.
Replace(tmpl string) string
}
type Output interface {
WrapWriter(w io.Writer, prefix string, tmpl Templater) io.Writer
}
// Build the Output for the requested taskfile.Output.
func BuildFor(o *taskfile.Output) (Output, error) {
switch o.Name {
case "interleaved", "":
if err := checkOutputGroupUnset(o); err != nil {
return nil, err
}
return Interleaved{}, nil
case "group":
return Group{
Begin: o.Group.Begin,
End: o.Group.End,
}, nil
case "prefixed":
if err := checkOutputGroupUnset(o); err != nil {
return nil, err
}
return Prefixed{}, nil
default:
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
}
}
func checkOutputGroupUnset(o *taskfile.Output) error {
if o.Group.IsSet() {
return fmt.Errorf("task: output style %q does not support the group begin/end parameter", o.Name)
}
return nil
}

View File

@@ -6,6 +6,8 @@ import (
"io"
"testing"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3/internal/output"
@@ -14,7 +16,7 @@ import (
func TestInterleaved(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Interleaved{}
var w = o.WrapWriter(&b, "")
var w = o.WrapWriter(&b, "", nil)
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, "foo\nbar\n", b.String())
@@ -25,7 +27,7 @@ func TestInterleaved(t *testing.T) {
func TestGroup(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Group{}
var w = o.WrapWriter(&b, "").(io.WriteCloser)
var w = o.WrapWriter(&b, "", nil).(io.WriteCloser)
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, "", b.String())
@@ -35,10 +37,43 @@ func TestGroup(t *testing.T) {
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
}
func TestGroupWithBeginEnd(t *testing.T) {
tmpl := templater.Templater{
Vars: &taskfile.Vars{
Keys: []string{"VAR1"},
Mapping: map[string]taskfile.Var{
"VAR1": {Static: "example-value"},
},
},
}
var o output.Output = output.Group{
Begin: "::group::{{ .VAR1 }}",
End: "::endgroup::",
}
t.Run("simple", func(t *testing.T) {
var b bytes.Buffer
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, "", b.String())
fmt.Fprintln(w, "baz")
assert.Equal(t, "", b.String())
assert.NoError(t, w.Close())
assert.Equal(t, "::group::example-value\nfoo\nbar\nbaz\n::endgroup::\n", b.String())
})
t.Run("no output", func(t *testing.T) {
var b bytes.Buffer
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
assert.NoError(t, w.Close())
assert.Equal(t, "", b.String())
})
}
func TestPrefixed(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Prefixed{}
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
var w = o.WrapWriter(&b, "prefix", nil).(io.WriteCloser)
t.Run("simple use cases", func(t *testing.T) {
b.Reset()

View File

@@ -9,7 +9,7 @@ import (
type Prefixed struct{}
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
func (Prefixed) WrapWriter(w io.Writer, prefix string, _ Templater) io.Writer {
return &prefixWriter{writer: w, prefix: prefix}
}

View File

@@ -4,7 +4,6 @@ import (
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
@@ -30,7 +29,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
checksumFile := c.checksumFilePath()
data, _ := ioutil.ReadFile(checksumFile)
data, _ := os.ReadFile(checksumFile)
oldMd5 := strings.TrimSpace(string(data))
sources, err := globs(c.TaskDir, c.Sources)
@@ -45,7 +44,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
if !c.Dry {
_ = os.MkdirAll(filepath.Join(c.BaseDir, ".task", "checksum"), 0755)
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
return false, err
}
}

View File

@@ -6,7 +6,8 @@ import (
"strings"
"text/template"
"github.com/go-task/slim-sprig"
sprig "github.com/go-task/slim-sprig"
"mvdan.cc/sh/v3/syntax"
)
var (
@@ -37,6 +38,9 @@ func init() {
}
return ""
},
"shellQuote": func(str string) (string, error) {
return syntax.Quote(str, syntax.LangBash)
},
// IsSH is deprecated.
"IsSH": func() bool { return true },
}

89
task.go
View File

@@ -16,6 +16,7 @@ import (
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/summary"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/read"
@@ -51,7 +52,7 @@ type Executor struct {
Logger *logger.Logger
Compiler compiler.Compiler
Output output.Output
OutputStyle string
OutputStyle taskfile.Output
taskvars *taskfile.Vars
@@ -68,7 +69,7 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
for _, c := range calls {
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
// FIXME: move to the main package
e.PrintTasksHelp()
e.ListTasksWithDesc()
return &taskNotFoundError{taskName: c.Task}
}
}
@@ -103,14 +104,21 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
return g.Wait()
}
// readTaskfile selects and parses the entrypoint.
func (e *Executor) readTaskfile() error {
var err error
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
Dir: e.Dir,
Entrypoint: e.Entrypoint,
Parent: nil,
Optional: false,
})
return err
}
// Setup setups Executor's internal state
func (e *Executor) Setup() error {
if e.Entrypoint == "" {
e.Entrypoint = "Taskfile.yml"
}
var err error
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
err := e.readTaskfile()
if err != nil {
return err
}
@@ -152,11 +160,11 @@ func (e *Executor) Setup() error {
v = 2.6
}
if v == 3.0 {
v = 3.7
v = 3.8
}
if v > 3.7 {
return fmt.Errorf(`task: Taskfile versions greater than v3.7 not implemented in the version of Task`)
if v > 3.8 {
return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`)
}
// Color available only on v3
@@ -198,7 +206,7 @@ func (e *Executor) Setup() error {
}
}
if v < 2.1 && e.Taskfile.Output != "" {
if v < 2.1 && !e.Taskfile.Output.IsSet() {
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
}
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
@@ -207,19 +215,16 @@ func (e *Executor) Setup() error {
if v >= 3.0 && e.Taskfile.Expansions > 2 {
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
}
if e.OutputStyle != "" {
e.Taskfile.Output = e.OutputStyle
if v < 3.8 && e.Taskfile.Output.Group.IsSet() {
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
}
switch e.Taskfile.Output {
case "", "interleaved":
e.Output = output.Interleaved{}
case "group":
e.Output = output.Group{}
case "prefixed":
e.Output = output.Prefixed{}
default:
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
if !e.OutputStyle.IsSet() {
e.OutputStyle = e.Taskfile.Output
}
e.Output, err = output.BuildFor(&e.OutputStyle)
if err != nil {
return err
}
if e.Taskfile.Method == "" {
@@ -310,11 +315,16 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
defer release()
return e.startExecution(ctx, t, func(ctx context.Context) error {
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" started`, call.Task)
if err := e.runDeps(ctx, t); err != nil {
return err
}
if !e.Force {
if err := ctx.Err(); err != nil {
return err
}
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
if err != nil {
return err
@@ -338,6 +348,11 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
}
for i := range t.Cmds {
if t.Cmds[i].Defer {
defer e.runDeferred(t, call, i)
continue
}
if err := e.runCommand(ctx, t, call, i); err != nil {
if err2 := e.statusOnError(t); err2 != nil {
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v", err2)
@@ -351,6 +366,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
return &taskRunError{t.Task, err}
}
}
e.Logger.VerboseErrf(logger.Magenta, `task: "%s" finished`, call.Task)
return nil
})
}
@@ -393,6 +409,15 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
return g.Wait()
}
func (e *Executor) runDeferred(t *taskfile.Task, call taskfile.Call, i int) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := e.runCommand(ctx, t, call, i); err != nil {
e.Logger.VerboseErrf(logger.Yellow, `task: ignored error in deferred cmd: %s`, err.Error())
}
}
func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
cmd := t.Cmds[i]
@@ -415,8 +440,18 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
return nil
}
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
outputWrapper := e.Output
if t.Interactive {
outputWrapper = output.Interleaved{}
}
vars, err := e.Compiler.FastGetVariables(t, call)
outputTemplater := &templater.Templater{Vars: vars, RemoveNoValue: true}
if err != nil {
return fmt.Errorf("task: failed to get variables: %w", err)
}
stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix, outputTemplater)
stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix, outputTemplater)
defer func() {
if _, ok := stdOut.(*os.File); !ok {
if closer, ok := stdOut.(io.Closer); ok {
@@ -430,7 +465,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
}
}()
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: cmd.Cmd,
Dir: t.Dir,
Env: getEnviron(t),

View File

@@ -4,7 +4,7 @@ import (
"bytes"
"context"
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"runtime"
@@ -24,10 +24,11 @@ func init() {
// fileContentTest provides a basic reusable test-case for running a Taskfile
// and inspect generated files.
type fileContentTest struct {
Dir string
Target string
TrimSpace bool
Files map[string]string
Dir string
Entrypoint string
Target string
TrimSpace bool
Files map[string]string
}
func (fct fileContentTest) name(file string) string {
@@ -40,16 +41,17 @@ func (fct fileContentTest) Run(t *testing.T) {
}
e := &task.Executor{
Dir: fct.Dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Dir: fct.Dir,
Entrypoint: fct.Entrypoint,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup(), "e.Setup()")
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
b, err := ioutil.ReadFile(filepath.Join(fct.Dir, name))
b, err := os.ReadFile(filepath.Join(fct.Dir, name))
assert.NoError(t, err, "Error reading file")
s := string(b)
if fct.TrimSpace {
@@ -63,8 +65,8 @@ func (fct fileContentTest) Run(t *testing.T) {
func TestEmptyTask(t *testing.T) {
e := &task.Executor{
Dir: "testdata/empty_task",
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup(), "e.Setup()")
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
@@ -168,8 +170,8 @@ func TestVarsInvalidTmpl(t *testing.T) {
e := &task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup(), "e.Setup()")
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
@@ -183,8 +185,8 @@ func TestConcurrency(t *testing.T) {
e := &task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
Concurrency: 1,
}
assert.NoError(t, e.Setup(), "e.Setup()")
@@ -236,8 +238,8 @@ func TestDeps(t *testing.T) {
e := &task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
@@ -516,10 +518,58 @@ func TestLabelInList(t *testing.T) {
Stderr: &buff,
}
assert.NoError(t, e.Setup())
e.PrintTasksHelp()
e.ListTasksWithDesc()
assert.Contains(t, buff.String(), "foobar")
}
// task -al case 1: listAll list all tasks
func TestListAllShowsNoDesc(t *testing.T) {
const dir = "testdata/list_mixed_desc"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
var title string
e.ListAllTasks()
for _, title = range []string{
"foo",
"voo",
"doo",
} {
assert.Contains(t, buff.String(), title)
}
}
// task -al case 2: !listAll list some tasks (only those with desc)
func TestListCanListDescOnly(t *testing.T) {
const dir = "testdata/list_mixed_desc"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
e.ListTasksWithDesc()
var title string
assert.Contains(t, buff.String(), "foo")
for _, title = range []string{
"voo",
"doo",
} {
assert.NotContains(t, buff.String(), title)
}
}
func TestStatusVariables(t *testing.T) {
const dir = "testdata/status_vars"
@@ -550,20 +600,21 @@ func TestStatusVariables(t *testing.T) {
func TestInit(t *testing.T) {
const dir = "testdata/init"
var file = filepath.Join(dir, "Taskfile.yml")
var file = filepath.Join(dir, "Taskfile.yaml")
_ = os.Remove(file)
if _, err := os.Stat(file); err == nil {
t.Errorf("Taskfile.yml should not exist")
t.Errorf("Taskfile.yaml should not exist")
}
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
if err := task.InitTaskfile(io.Discard, dir); err != nil {
t.Error(err)
}
if _, err := os.Stat(file); err != nil {
t.Errorf("Taskfile.yml should exist")
t.Errorf("Taskfile.yaml should exist")
}
_ = os.Remove(file)
}
func TestCyclicDep(t *testing.T) {
@@ -571,8 +622,8 @@ func TestCyclicDep(t *testing.T) {
e := task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup())
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
@@ -590,8 +641,8 @@ func TestTaskVersion(t *testing.T) {
t.Run(test.Dir, func(t *testing.T) {
e := task.Executor{
Dir: test.Dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup())
assert.Equal(t, test.Version, e.Taskfile.Version)
@@ -605,8 +656,8 @@ func TestTaskIgnoreErrors(t *testing.T) {
e := task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
assert.NoError(t, e.Setup())
@@ -668,8 +719,8 @@ func TestDryChecksum(t *testing.T) {
e := task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
Dry: true,
}
assert.NoError(t, e.Setup())
@@ -702,6 +753,35 @@ func TestIncludes(t *testing.T) {
tt.Run(t)
}
func TestIncludesMultiLevel(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes_multi_level",
Target: "default",
TrimSpace: true,
Files: map[string]string{
"called_one.txt": "one",
"called_two.txt": "two",
"called_three.txt": "three",
},
}
tt.Run(t)
}
func TestIncludeCycle(t *testing.T) {
const dir = "testdata/includes_cycle"
expectedError := "task: include cycle detected between testdata/includes_cycle/Taskfile.yml <--> testdata/includes_cycle/one/two/Taskfile.yml"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
assert.EqualError(t, e.Setup(), expectedError)
}
func TestIncorrectVersionIncludes(t *testing.T) {
const dir = "testdata/incorrect_includes"
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
@@ -755,6 +835,78 @@ func TestIncludesCallingRoot(t *testing.T) {
tt.Run(t)
}
func TestIncludesOptional(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes_optional",
Target: "default",
TrimSpace: true,
Files: map[string]string{
"called_dep.txt": "called_dep",
}}
tt.Run(t)
}
func TestIncludesOptionalImplicitFalse(t *testing.T) {
e := task.Executor{
Dir: "testdata/includes_optional_implicit_false",
Stdout: io.Discard,
Stderr: io.Discard,
}
err := e.Setup()
assert.Error(t, err)
assert.Equal(t, "stat testdata/includes_optional_implicit_false/TaskfileOptional.yml: no such file or directory", err.Error())
}
func TestIncludesOptionalExplicitFalse(t *testing.T) {
e := task.Executor{
Dir: "testdata/includes_optional_explicit_false",
Stdout: io.Discard,
Stderr: io.Discard,
}
err := e.Setup()
assert.Error(t, err)
assert.Equal(t, "stat testdata/includes_optional_explicit_false/TaskfileOptional.yml: no such file or directory", err.Error())
}
func TestIncludesFromCustomTaskfile(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes_yaml",
Entrypoint: "Custom.ext",
Target: "default",
TrimSpace: true,
Files: map[string]string{
"main.txt": "main",
"included_with_yaml_extension.txt": "included_with_yaml_extension",
"included_with_custom_file.txt": "included_with_custom_file",
},
}
tt.Run(t)
}
func TestSupportedFileNames(t *testing.T) {
fileNames := []string{
"Taskfile.yml",
"Taskfile.yaml",
"Taskfile.dist.yml",
"Taskfile.dist.yaml",
}
for _, fileName := range fileNames {
t.Run(fileName, func(t *testing.T) {
tt := fileContentTest{
Dir: fmt.Sprintf("testdata/file_names/%s", fileName),
Target: "default",
TrimSpace: true,
Files: map[string]string{
"output.txt": "hello",
},
}
tt.Run(t)
})
}
}
func TestSummary(t *testing.T) {
const dir = "testdata/summary"
@@ -769,7 +921,7 @@ func TestSummary(t *testing.T) {
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
data, err := os.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
assert.NoError(t, err)
expectedOutput := string(data)
@@ -842,6 +994,33 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
_ = os.RemoveAll(toBeCreated)
}
func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
const expected = "created"
const dir = "testdata/dir/dynamic_var_on_created_dir/"
const toBeCreated = dir + expected
const target = "default"
var out bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: &out,
Stderr: &out,
}
// Ensure that the directory to be created doesn't actually exist.
_ = os.RemoveAll(toBeCreated)
if _, err := os.Stat(toBeCreated); err == nil {
t.Errorf("Directory should not exist: %v", err)
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory")
// Clean-up after ourselves only if no error.
_ = os.RemoveAll(toBeCreated)
}
func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dir/dynamic_var",
@@ -860,8 +1039,8 @@ func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
e := task.Executor{
Dir: "testdata/version/v1",
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
err := e.Setup()
assert.Error(t, err)
@@ -990,3 +1169,110 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
}
tt.Run(t)
}
func TestDeferredCmds(t *testing.T) {
const dir = "testdata/deferred"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
expectedOutputOrder := strings.TrimSpace(`
task: [task-2] echo 'cmd ran'
cmd ran
task: [task-2] exit 1
task: [task-2] echo 'failing' && exit 2
failing
task: [task-2] echo 'echo ran'
echo ran
task: [task-1] echo 'task-1 ran successfully'
task-1 ran successfully
`)
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-2"}))
fmt.Println(buff.String())
assert.Contains(t, buff.String(), expectedOutputOrder)
}
func TestIgnoreNilElements(t *testing.T) {
tests := []struct {
name string
dir string
}{
{"nil cmd", "testdata/ignore_nil_elements/cmds"},
{"nil dep", "testdata/ignore_nil_elements/deps"},
{"nil include", "testdata/ignore_nil_elements/includes"},
{"nil precondition", "testdata/ignore_nil_elements/preconditions"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: test.dir,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\n", buff.String())
})
}
}
func TestOutputGroup(t *testing.T) {
const dir = "testdata/output_group"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
expectedOutputOrder := strings.TrimSpace(`
task: [hello] echo 'Hello!'
::group::hello
Hello!
::endgroup::
task: [bye] echo 'Bye!'
::group::bye
Bye!
::endgroup::
`)
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "bye"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}
func TestIncludedVars(t *testing.T) {
const dir = "testdata/include_with_vars"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
expectedOutputOrder := strings.TrimSpace(`
task: [included1:task1] echo "VAR_1 is included1-var1"
VAR_1 is included1-var1
task: [included1:task1] echo "VAR_2 is included-default-var2"
VAR_2 is included-default-var2
task: [included2:task1] echo "VAR_1 is included2-var1"
VAR_1 is included2-var1
task: [included2:task1] echo "VAR_2 is included-default-var2"
VAR_2 is included-default-var2
task: [included3:task1] echo "VAR_1 is included-default-var1"
VAR_1 is included-default-var1
task: [included3:task1] echo "VAR_2 is included-default-var2"
VAR_2 is included-default-var2
`)
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task1"}))
t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
}

View File

@@ -1,9 +1,5 @@
package taskfile
import (
"strings"
)
// Cmd is a task command
type Cmd struct {
Cmd string
@@ -11,6 +7,7 @@ type Cmd struct {
Task string
Vars *Vars
IgnoreError bool
Defer bool
}
// Dep is a task dependency
@@ -23,11 +20,7 @@ type Dep struct {
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd string
if err := unmarshal(&cmd); err == nil {
if strings.HasPrefix(cmd, "^") {
c.Task = strings.TrimPrefix(cmd, "^")
} else {
c.Cmd = cmd
}
c.Cmd = cmd
return nil
}
var cmdStruct struct {
@@ -41,6 +34,23 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
c.IgnoreError = cmdStruct.IgnoreError
return nil
}
var deferredCmd struct {
Defer string
}
if err := unmarshal(&deferredCmd); err == nil && deferredCmd.Defer != "" {
c.Defer = true
c.Cmd = deferredCmd.Defer
return nil
}
var deferredCall struct {
Defer Call
}
if err := unmarshal(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
c.Defer = true
c.Task = deferredCall.Defer.Task
c.Vars = deferredCall.Defer.Vars
return nil
}
var taskCall struct {
Task string
Vars *Vars

View File

@@ -10,7 +10,9 @@ import (
type IncludedTaskfile struct {
Taskfile string
Dir string
Optional bool
AdvancedImport bool
Vars *Vars
}
// IncludedTaskfiles represents information about included tasksfiles
@@ -92,12 +94,16 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
var includedTaskfile struct {
Taskfile string
Dir string
Optional bool
Vars *Vars
}
if err := unmarshal(&includedTaskfile); err != nil {
return err
}
it.Dir = includedTaskfile.Dir
it.Taskfile = includedTaskfile.Taskfile
it.Dir = includedTaskfile.Dir
it.Optional = includedTaskfile.Optional
it.AdvancedImport = true
it.Vars = includedTaskfile.Vars
return nil
}

View File

@@ -17,7 +17,7 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
if t2.Expansions != 0 && t2.Expansions != 2 {
t1.Expansions = t2.Expansions
}
if t2.Output != "" {
if t2.Output.IsSet() {
t1.Output = t2.Output
}
@@ -49,7 +49,7 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
}
for _, cmd := range v.Cmds {
if cmd.Task != "" {
if cmd != nil && cmd.Task != "" {
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
}
}

55
taskfile/output.go Normal file
View File

@@ -0,0 +1,55 @@
package taskfile
import (
"fmt"
)
// Output of the Task output
type Output struct {
// Name of the Output.
Name string `yaml:"-"`
// Group specific style
Group OutputGroup
}
// IsSet returns true if and only if a custom output style is set.
func (s *Output) IsSet() bool {
return s.Name != ""
}
// UnmarshalYAML implements yaml.Unmarshaler
// It accepts a scalar node representing the Output.Name or a mapping node representing the OutputGroup.
func (s *Output) UnmarshalYAML(unmarshal func(interface{}) error) error {
var name string
if err := unmarshal(&name); err == nil {
s.Name = name
return nil
}
var tmp struct {
Group *OutputGroup
}
if err := unmarshal(&tmp); err != nil {
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
}
if tmp.Group == nil {
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
}
*s = Output{
Name: "group",
Group: *tmp.Group,
}
return nil
}
// OutputGroup is the style options specific to the Group style.
type OutputGroup struct {
Begin, End string
}
// IsSet returns true if and only if a custom output style is set.
func (g *OutputGroup) IsSet() bool {
if g == nil {
return false
}
return g.Begin != "" || g.End != ""
}

View File

@@ -9,23 +9,47 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
var (
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
defaultTaskfiles = []string{
"Taskfile.yml",
"Taskfile.yaml",
"Taskfile.dist.yml",
"Taskfile.dist.yaml",
}
)
type ReaderNode struct {
Dir string
Entrypoint string
Optional bool
Parent *ReaderNode
}
// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, entrypoint)
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf(`task: No Taskfile found on "%s". Use "task --init" to create a new one`, path)
// Uses current dir when dir is left empty. Uses Taskfile.yml
// or Taskfile.yaml when entrypoint is left empty
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
if readerNode.Dir == "" {
d, err := os.Getwd()
if err != nil {
return nil, err
}
readerNode.Dir = d
}
path, err := exists(filepath.Join(readerNode.Dir, readerNode.Entrypoint))
if err != nil {
return nil, err
}
readerNode.Entrypoint = filepath.Base(path)
t, err := readTaskfile(path)
if err != nil {
return nil, err
@@ -42,33 +66,48 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
includedTask = taskfile.IncludedTaskfile{
Taskfile: tr.Replace(includedTask.Taskfile),
Dir: tr.Replace(includedTask.Dir),
Optional: includedTask.Optional,
AdvancedImport: includedTask.AdvancedImport,
Vars: includedTask.Vars,
}
if err := tr.Err(); err != nil {
return err
}
}
if filepath.IsAbs(includedTask.Taskfile) {
path = includedTask.Taskfile
} else {
path = filepath.Join(dir, includedTask.Taskfile)
path, err := execext.Expand(includedTask.Taskfile)
if err != nil {
return err
}
if !filepath.IsAbs(path) {
path = filepath.Join(readerNode.Dir, path)
}
path, err = exists(path)
if err != nil {
if includedTask.Optional {
return nil
}
return err
}
info, err := os.Stat(path)
if err != nil {
includeReaderNode := &ReaderNode{
Dir: filepath.Dir(path),
Entrypoint: filepath.Base(path),
Parent: readerNode,
Optional: includedTask.Optional,
}
if err := checkCircularIncludes(includeReaderNode); err != nil {
return err
}
if info.IsDir() {
path = filepath.Join(path, "Taskfile.yml")
}
includedTaskfile, err := readTaskfile(path)
includedTaskfile, err := Taskfile(includeReaderNode)
if err != nil {
if includedTask.Optional {
return nil
}
return err
}
if includedTaskfile.Includes.Len() > 0 {
return ErrIncludedTaskfilesCantHaveIncludes
}
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
return ErrIncludedTaskfilesCantHaveDotenvs
@@ -77,12 +116,12 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
if includedTask.AdvancedImport {
for k, v := range includedTaskfile.Vars.Mapping {
o := v
o.Dir = filepath.Join(dir, includedTask.Dir)
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
includedTaskfile.Vars.Mapping[k] = o
}
for k, v := range includedTaskfile.Env.Mapping {
o := v
o.Dir = filepath.Join(dir, includedTask.Dir)
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
includedTaskfile.Env.Mapping[k] = o
}
@@ -90,6 +129,8 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
if !filepath.IsAbs(task.Dir) {
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
}
task.IncludeVars = includedTask.Vars
task.IncludedTaskfileVars = includedTaskfile.Vars
}
}
@@ -103,7 +144,7 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
}
if v < 3.0 {
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
path = filepath.Join(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
@@ -134,3 +175,44 @@ func readTaskfile(file string) (*taskfile.Taskfile, error) {
var t taskfile.Taskfile
return &t, yaml.NewDecoder(f).Decode(&t)
}
func exists(path string) (string, error) {
fi, err := os.Stat(path)
if err != nil {
return "", err
}
if fi.Mode().IsRegular() {
return path, nil
}
for _, n := range defaultTaskfiles {
fpath := filepath.Join(path, n)
if _, err := os.Stat(fpath); err == nil {
return fpath, nil
}
}
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
}
func checkCircularIncludes(node *ReaderNode) error {
if node == nil {
return errors.New("task: failed to check for include cycle: node was nil")
}
if node.Parent == nil {
return errors.New("task: failed to check for include cycle: node.Parent was nil")
}
var curNode = node
var basePath = filepath.Join(node.Dir, node.Entrypoint)
for curNode.Parent != nil {
curNode = curNode.Parent
curPath := filepath.Join(curNode.Dir, curNode.Entrypoint)
if curPath == basePath {
return fmt.Errorf("task: include cycle detected between %s <--> %s",
curPath,
filepath.Join(node.Parent.Dir, node.Parent.Entrypoint),
)
}
}
return nil
}

View File

@@ -5,24 +5,27 @@ type Tasks map[string]*Task
// Task represents a task
type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Summary string
Sources []string
Generates []string
Status []string
Preconditions []*Precondition
Dir string
Vars *Vars
Env *Vars
Silent bool
Method string
Prefix string
IgnoreError bool
Run string
Task string
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Summary string
Sources []string
Generates []string
Status []string
Preconditions []*Precondition
Dir string
Vars *Vars
Env *Vars
Silent bool
Interactive bool
Method string
Prefix string
IgnoreError bool
Run string
IncludeVars *Vars
IncludedTaskfileVars *Vars
}
func (t *Task) Name() string {
@@ -59,6 +62,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
Vars *Vars
Env *Vars
Silent bool
Interactive bool
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
@@ -80,6 +84,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
t.Vars = task.Vars
t.Env = task.Env
t.Silent = task.Silent
t.Interactive = task.Interactive
t.Method = task.Method
t.Prefix = task.Prefix
t.IgnoreError = task.IgnoreError

View File

@@ -9,7 +9,7 @@ import (
type Taskfile struct {
Version string
Expansions int
Output string
Output Output
Method string
Includes *IncludedTaskfiles
Vars *Vars
@@ -25,7 +25,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
var taskfile struct {
Version string
Expansions int
Output string
Output Output
Method string
Includes *IncludedTaskfiles
Vars *Vars

View File

@@ -19,6 +19,8 @@ vars:
PARAM1: VALUE1
PARAM2: VALUE2
`
yamlDeferredCall = `defer: { task: some_task, vars: { PARAM1: "var" } }`
yamlDeferredCmd = `defer: echo 'test'`
)
tests := []struct {
content string
@@ -41,6 +43,21 @@ vars:
},
}},
},
{
yamlDeferredCmd,
&taskfile.Cmd{},
&taskfile.Cmd{Cmd: "echo 'test'", Defer: true},
},
{
yamlDeferredCall,
&taskfile.Cmd{},
&taskfile.Cmd{Task: "some_task", Vars: &taskfile.Vars{
Keys: []string{"PARAM1"},
Mapping: map[string]taskfile.Var{
"PARAM1": taskfile.Var{Static: "var"},
},
}, Defer: true},
},
{
yamlDep,
&taskfile.Dep{},

View File

@@ -2,7 +2,6 @@ package taskfile
import (
"errors"
"strings"
"gopkg.in/yaml.v3"
)
@@ -108,11 +107,7 @@ type Var struct {
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
if strings.HasPrefix(str, "$") {
v.Sh = strings.TrimPrefix(str, "$")
} else {
v.Static = str
}
v.Static = str
return nil
}

12
testdata/deferred/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: "3"
tasks:
task-1:
- echo 'task-1 ran {{.PARAM}}'
task-2:
- defer: { task: "task-1", vars: { PARAM: "successfully" } }
- defer: echo 'echo ran'
- defer: echo 'failing' && exit 2
- echo 'cmd ran'
- exit 1

View File

@@ -0,0 +1,10 @@
version: '3'
tasks:
default:
dir: created
cmds:
- echo {{.TASK_DIR}}
vars:
TASK_DIR:
sh: echo $(pwd)

1
testdata/file_names/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
default: echo "hello" > output.txt

View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
default: echo "hello" > output.txt

View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
default: echo "hello" > output.txt

View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
default: echo "hello" > output.txt

View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
default:
cmds:
- echo "string-slice-1"
-

View File

@@ -0,0 +1,11 @@
version: '3'
tasks:
default:
deps:
-
- task: dep
dep:
cmds:
- echo "string-slice-1"

View File

@@ -0,0 +1,9 @@
version: '3'
includes:
inc: inc.yml
tasks:
default:
cmds:
- task: inc:default

View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
default:
cmds:
-
- echo "string-slice-1"

View File

@@ -0,0 +1,9 @@
version: '3'
tasks:
default:
preconditions:
-
- sh: "[ 1 = 1 ]"
cmds:
- echo "string-slice-1"

20
testdata/include_with_vars/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
version: "3"
includes:
included1:
taskfile: include/Taskfile.include.yml
vars:
VAR_1: included1-var1
included2:
taskfile: include/Taskfile.include.yml
vars:
VAR_1: included2-var1
included3:
taskfile: include/Taskfile.include.yml
tasks:
task1:
cmds:
- task: included1:task1
- task: included2:task1
- task: included3:task1

View File

@@ -0,0 +1,11 @@
version: "3"
vars:
VAR_1: '{{.VAR_1 | default "included-default-var1"}}'
VAR_2: '{{.VAR_2 | default "included-default-var2"}}'
tasks:
task1:
cmds:
- echo "VAR_1 is {{.VAR_1}}"
- echo "VAR_2 is {{.VAR_2}}"

12
testdata/includes_cycle/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: '3'
includes:
'one': ./one/Taskfile.yml
tasks:
default:
cmds:
- echo "called_dep" > called_dep.txt
level1:
cmds:
- echo "hello level 1"

View File

@@ -0,0 +1,9 @@
version: '3'
includes:
'two': ./two/Taskfile.yml
tasks:
level2:
cmds:
- echo "hello level 2"

View File

@@ -0,0 +1,9 @@
version: '3'
includes:
bad: "../../Taskfile.yml"
tasks:
level3:
cmds:
- echo "hello level 3"

View File

@@ -0,0 +1,11 @@
version: '3'
includes:
'one': ./one/
tasks:
default:
cmds:
- task: one:default
- task: one:two:default
- task: one:two:three:default

View File

@@ -0,0 +1 @@
one

View File

@@ -0,0 +1 @@
three

View File

@@ -0,0 +1 @@
two

View File

@@ -0,0 +1,7 @@
version: '3'
includes:
'two': ./two/
tasks:
default: echo one > called_one.txt

View File

@@ -0,0 +1,7 @@
version: '3'
includes:
'three': ./three/Taskfile.yml
tasks:
default: echo two > called_two.txt

View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
default: echo three > called_three.txt

1
testdata/includes_optional/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

11
testdata/includes_optional/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: '3'
includes:
included:
taskfile: TaskfileOptional.yml
optional: true
tasks:
default:
cmds:
- echo "called_dep" > called_dep.txt

View File

@@ -0,0 +1,11 @@
version: '3'
includes:
included:
taskfile: TaskfileOptional.yml
optional: false
tasks:
default:
cmds:
- echo "Hello, world!"

View File

@@ -0,0 +1,9 @@
version: '3'
includes:
included: TaskfileOptional.yml
tasks:
default:
cmds:
- echo "Hello, world!"

1
testdata/includes_yaml/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

16
testdata/includes_yaml/Custom.ext vendored Normal file
View File

@@ -0,0 +1,16 @@
version: '3'
includes:
included: ./included
custom: ./included/custom.yaml
tasks:
default:
cmds:
- task: gen
- task: included:gen
- task: custom:gen
gen:
cmds:
- echo main > main.txt

View File

@@ -0,0 +1,6 @@
version: '3'
tasks:
gen:
cmds:
- echo included_with_yaml_extension > included_with_yaml_extension.txt

View File

@@ -0,0 +1,6 @@
version: '3'
tasks:
gen:
cmds:
- echo included_with_custom_file > included_with_custom_file.txt

12
testdata/list_mixed_desc/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: '3'
tasks:
foo:
label: "foobar"
desc: "foo has desc and label"
voo:
label: "voo has no desc"
doo:
label: "doo has desc, no label"

16
testdata/output_group/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
version: '3'
output:
group:
begin: '::group::{{ .TASK }}'
end: '::endgroup::'
tasks:
hello:
cmds:
- echo 'Hello!'
bye:
deps:
- hello
cmds:
- echo 'Bye!'

View File

@@ -35,7 +35,8 @@ tasks:
- echo '{{.TASK}}' > task_name.txt
vars:
FOO: foo
BAR: $echo bar
BAR:
sh: echo bar
BAZ:
sh: echo baz
TMPL_FOO: "{{.FOO}}"

View File

@@ -1,5 +1,6 @@
FOO2: foo2
BAR2: $echo bar2
BAR2:
sh: echo bar2
BAZ2:
sh: echo baz2
TMPL2_FOO: "{{.FOO}}"

View File

@@ -46,20 +46,23 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
r := templater.Templater{Vars: vars, RemoveNoValue: v >= 3.0}
new := taskfile.Task{
Task: origTask.Task,
Label: r.Replace(origTask.Label),
Desc: r.Replace(origTask.Desc),
Summary: r.Replace(origTask.Summary),
Sources: r.ReplaceSlice(origTask.Sources),
Generates: r.ReplaceSlice(origTask.Generates),
Dir: r.Replace(origTask.Dir),
Vars: nil,
Env: nil,
Silent: origTask.Silent,
Method: r.Replace(origTask.Method),
Prefix: r.Replace(origTask.Prefix),
IgnoreError: origTask.IgnoreError,
Run: r.Replace(origTask.Run),
Task: origTask.Task,
Label: r.Replace(origTask.Label),
Desc: r.Replace(origTask.Desc),
Summary: r.Replace(origTask.Summary),
Sources: r.ReplaceSlice(origTask.Sources),
Generates: r.ReplaceSlice(origTask.Generates),
Dir: r.Replace(origTask.Dir),
Vars: nil,
Env: nil,
Silent: origTask.Silent,
Interactive: origTask.Interactive,
Method: r.Replace(origTask.Method),
Prefix: r.Replace(origTask.Prefix),
IgnoreError: origTask.IgnoreError,
Run: r.Replace(origTask.Run),
IncludeVars: origTask.IncludeVars,
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
}
new.Dir, err = execext.Expand(new.Dir)
if err != nil {
@@ -90,34 +93,44 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
}
if len(origTask.Cmds) > 0 {
new.Cmds = make([]*taskfile.Cmd, len(origTask.Cmds))
for i, cmd := range origTask.Cmds {
new.Cmds[i] = &taskfile.Cmd{
new.Cmds = make([]*taskfile.Cmd, 0, len(origTask.Cmds))
for _, cmd := range origTask.Cmds {
if cmd == nil {
continue
}
new.Cmds = append(new.Cmds, &taskfile.Cmd{
Task: r.Replace(cmd.Task),
Silent: cmd.Silent,
Cmd: r.Replace(cmd.Cmd),
Vars: r.ReplaceVars(cmd.Vars),
IgnoreError: cmd.IgnoreError,
}
Defer: cmd.Defer,
})
}
}
if len(origTask.Deps) > 0 {
new.Deps = make([]*taskfile.Dep, len(origTask.Deps))
for i, dep := range origTask.Deps {
new.Deps[i] = &taskfile.Dep{
new.Deps = make([]*taskfile.Dep, 0, len(origTask.Deps))
for _, dep := range origTask.Deps {
if dep == nil {
continue
}
new.Deps = append(new.Deps, &taskfile.Dep{
Task: r.Replace(dep.Task),
Vars: r.ReplaceVars(dep.Vars),
}
})
}
}
if len(origTask.Preconditions) > 0 {
new.Preconditions = make([]*taskfile.Precondition, len(origTask.Preconditions))
for i, precond := range origTask.Preconditions {
new.Preconditions[i] = &taskfile.Precondition{
new.Preconditions = make([]*taskfile.Precondition, 0, len(origTask.Preconditions))
for _, precond := range origTask.Preconditions {
if precond == nil {
continue
}
new.Preconditions = append(new.Preconditions, &taskfile.Precondition{
Sh: r.Replace(precond.Sh),
Msg: r.Replace(precond.Msg),
}
})
}
}