mirror of
https://github.com/go-task/task.git
synced 2026-05-18 21:26:37 +02:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4c95d6b0b | ||
|
|
c4766e2611 | ||
|
|
796097e3ab | ||
|
|
c7d9efebf9 | ||
|
|
8f4306d321 | ||
|
|
435f086cb7 | ||
|
|
01c9158120 | ||
|
|
e235d77d64 | ||
|
|
dbe8131b75 | ||
|
|
0a9d76515e | ||
|
|
0ce1af9ee0 | ||
|
|
c4452d2698 | ||
|
|
491888f6c0 | ||
|
|
e4158dc5e4 | ||
|
|
0307ca8ac6 | ||
|
|
156a273351 | ||
|
|
d6d51a2f8b | ||
|
|
a98b41d657 | ||
|
|
87ec78fbaa | ||
|
|
957bff4b89 | ||
|
|
321f7b59d8 | ||
|
|
41a9316523 | ||
|
|
1072ff5950 | ||
|
|
983f6fff5d | ||
|
|
b3627fcb18 | ||
|
|
99d7338c29 | ||
|
|
9cf930454d | ||
|
|
4b4962e8c6 | ||
|
|
f2afa77114 | ||
|
|
3aa647c89b | ||
|
|
45ab4dc718 | ||
|
|
d1850e8fd2 | ||
|
|
f1d516cf2a | ||
|
|
d55282b53c | ||
|
|
ef9f7af0c5 | ||
|
|
8823887bb4 | ||
|
|
593980e45a | ||
|
|
081dc16312 | ||
|
|
35599af04b | ||
|
|
a74b35379e | ||
|
|
7d16c9f68d | ||
|
|
890759cc5f | ||
|
|
e710e2cc5d | ||
|
|
d787faece4 | ||
|
|
9702109ea9 | ||
|
|
e547829505 | ||
|
|
a664a26062 | ||
|
|
fa105a8a93 | ||
|
|
3a0c7a8c36 | ||
|
|
13f4b376e8 | ||
|
|
5a08409a27 | ||
|
|
9bbdac3c2e | ||
|
|
a990ffe53d | ||
|
|
3a4b347d50 | ||
|
|
b80e1e4a43 | ||
|
|
fd71dfda6a | ||
|
|
fdbcbd395d | ||
|
|
dba964b559 | ||
|
|
8e0816a09d | ||
|
|
405b79f86c | ||
|
|
620e6955e5 | ||
|
|
a4997dd54d | ||
|
|
3efa9ac8c3 | ||
|
|
fdd52d74e9 | ||
|
|
ac81dea3ec | ||
|
|
549c37ef87 | ||
|
|
6a369ee31c | ||
|
|
2e573d37ae | ||
|
|
394afe2633 | ||
|
|
99ed3001f0 | ||
|
|
9e4cab2af9 | ||
|
|
33b6927b79 | ||
|
|
852a176e1f | ||
|
|
7511249514 | ||
|
|
3429cdd8af | ||
|
|
a1cd8eafd8 | ||
|
|
fbfb4ba9c4 | ||
|
|
ba9ba63792 | ||
|
|
460b89ce51 | ||
|
|
a4ec6e5257 | ||
|
|
44aa2ee3b3 | ||
|
|
80b417c4ab | ||
|
|
6d90c781c9 | ||
|
|
c51f04eca8 | ||
|
|
dda2004753 | ||
|
|
297f9eccea | ||
|
|
d2f2cba6d8 | ||
|
|
172d71435a | ||
|
|
bb1aec8a7e | ||
|
|
476d9f5e70 | ||
|
|
99014ad38d | ||
|
|
403456d3dc | ||
|
|
6335878317 | ||
|
|
6bff658af0 | ||
|
|
b111e7bd12 | ||
|
|
3e5ee2332a | ||
|
|
66f6998c86 | ||
|
|
f2a8f8ad8f | ||
|
|
540f6ecfdb | ||
|
|
8ec89f1bbd | ||
|
|
d33906b6e4 | ||
|
|
bb79fa1dc3 | ||
|
|
376a6182eb | ||
|
|
81de61d8db | ||
|
|
d2061ec898 | ||
|
|
077efbd2e7 | ||
|
|
8ce1782380 | ||
|
|
c2f20465ab | ||
|
|
fb0e43989d | ||
|
|
754248395c | ||
|
|
6e975ca155 | ||
|
|
79a2bc404e | ||
|
|
42a26e1741 | ||
|
|
695711e124 | ||
|
|
0d5811e502 | ||
|
|
b9d070f76b | ||
|
|
122c3f083e | ||
|
|
d8dc091267 | ||
|
|
1c44d8049a |
@@ -8,6 +8,6 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
|
||||
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash}]
|
||||
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: andreynering
|
||||
github: [andreynering, pd93]
|
||||
open_collective: task
|
||||
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url'
|
||||
custom: https://taskfile.dev/donate/
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,13 +1,15 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Use the template to report bugs and issues
|
||||
about: Use this to report bugs and issues
|
||||
---
|
||||
|
||||
> Thanks for your bug report!
|
||||
>
|
||||
> Before submitting this issue, please make sure the same problem was
|
||||
> not already reported by someone else.
|
||||
>
|
||||
> Please describe the bug you're facing. Consider pasting example
|
||||
> Taskfiles showing how to reproduce the problem.
|
||||
|
||||
- Task version:
|
||||
- Operating System:
|
||||
|
||||
### Example Taskfile showing the issue
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Help forum on Discord
|
||||
url: https://discord.com/channels/974121106208354339/1025054680289660989
|
||||
about: 'The Discord #help channel is the best way to get help from the community.'
|
||||
- name: Questions, Ideas and General Discussions
|
||||
url: https://github.com/go-task/task/discussions
|
||||
about: Ask questions and discuss general ideas with the community
|
||||
about: Ask questions and discuss general ideas with the community.
|
||||
|
||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Use the template to make feature requests
|
||||
about: Use this to make feature requests
|
||||
---
|
||||
|
||||
Describe in detail what feature do you want to see in Task.
|
||||
Give examples if possible.
|
||||
|
||||
Please, search if this wasn't proposed before, and if this is more like an idea
|
||||
than a strong feature request, consider opening a
|
||||
[discussion](https://github.com/go-task/task/discussions) instead.
|
||||
> Describe in detail what feature do you want to see in Task.
|
||||
> Give examples if possible.
|
||||
>
|
||||
> Please, search if this wasn't proposed before, and if this is more like an idea
|
||||
> than a strong feature request, consider opening a
|
||||
> [discussion](https://github.com/go-task/task/discussions) instead.
|
||||
|
||||
5
.github/pull_request_template.md
vendored
Normal file
5
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
> Thanks for your pull request, we really appreciate contributions!
|
||||
>
|
||||
> Please understand that it may take some time to be reviewed.
|
||||
>
|
||||
> Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
43
.github/workflows/issue-awaiting-response.yml
vendored
Normal file
43
.github/workflows/issue-awaiting-response.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: issue awaiting response
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
issue-awaiting-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
const issue = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
})
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
}
|
||||
)
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
}
|
||||
)
|
||||
if (labels.find(label => label.name === 'awaiting response')) {
|
||||
if (comments[comments.length-1].user?.login === issue.data.user?.login) {
|
||||
github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'awaiting response'
|
||||
})
|
||||
}
|
||||
}
|
||||
29
.github/workflows/issue-closed.yml
vendored
Normal file
29
.github/workflows/issue-closed.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: issue closed
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
issue-closed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
}
|
||||
)
|
||||
if (labels.find(label => label.name === 'needs triage')) {
|
||||
github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'needs triage'
|
||||
})
|
||||
}
|
||||
29
.github/workflows/issue-needs-triage.yml
vendored
Normal file
29
.github/workflows/issue-needs-triage.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: issue needs triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
issue-needs-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
}
|
||||
)
|
||||
if (labels.length === 0) {
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['needs triage']
|
||||
})
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,6 +22,7 @@ dist/
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/
|
||||
.fleet/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
|
||||
12
.golangci.yml
Normal file
12
.golangci.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# NOTE(@andreynering): The linters listed here are additions on top of
|
||||
# those enabled by default:
|
||||
#
|
||||
# https://golangci-lint.run/usage/linters/#enabled-by-default
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- goimports
|
||||
|
||||
linters-settings:
|
||||
goimports:
|
||||
local-prefixes: github.com/go-task/task
|
||||
56
CHANGELOG.md
56
CHANGELOG.md
@@ -1,5 +1,61 @@
|
||||
# Changelog
|
||||
|
||||
## v3.19.1 - 2022-12-31
|
||||
|
||||
- Small bug fix: closing `Taskfile.yml` once we're done reading it
|
||||
([#963](https://github.com/go-task/task/issues/963), [#964](https://github.com/go-task/task/pull/964) by @HeCorr).
|
||||
- Fixes a bug in v2 that caused a panic when using a `Taskfile_{{OS}}.yml` file
|
||||
([#961](https://github.com/go-task/task/issues/961), [#971](https://github.com/go-task/task/pull/971) by @pd93).
|
||||
- Fixed a bug where watch intervals set in the Taskfile were not being respected ([#969](https://github.com/go-task/task/pull/969), [#970](https://github.com/go-task/task/pull/970) by @pd93)
|
||||
- Add `--json` flag (alias `-j`) with the intent to improve support for code
|
||||
editors and add room to other possible integrations. This is basic for now,
|
||||
but we plan to add more info in the near future
|
||||
([#936](https://github.com/go-task/task/pull/936) by @davidalpert, [#764](https://github.com/go-task/task/issues/764)).
|
||||
|
||||
## v3.19.0 - 2022-12-05
|
||||
|
||||
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
|
||||
([go-task/go-npm#2](https://github.com/go-task/go-npm/issues/2), [go-task/go-npm#3](https://github.com/go-task/go-npm/pull/3)).
|
||||
- It's now possible to run Taskfiles from subdirectories! A new `USER_WORKING_DIR` special
|
||||
variable was added to add even more flexibility for monorepos
|
||||
([#289](https://github.com/go-task/task/issues/289), [#920](https://github.com/go-task/task/pull/920)).
|
||||
- Add task-level `dotenv` support
|
||||
([#389](https://github.com/go-task/task/issues/389), [#904](https://github.com/go-task/task/pull/904)).
|
||||
- It's now possible to use global level variables on `includes`
|
||||
([#942](https://github.com/go-task/task/issues/942), [#943](https://github.com/go-task/task/pull/943)).
|
||||
- The website got a brand new [translation to Chinese](https://task-zh.readthedocs.io/zh_CN/latest/)
|
||||
by [@DeronW](https://github.com/DeronW). Thanks!
|
||||
|
||||
## v3.18.0 - 2022-11-12
|
||||
|
||||
- Show aliases on `task --list --silent` (`task --ls`). This means that aliases
|
||||
will be completed by the completion scripts
|
||||
([#919](https://github.com/go-task/task/pull/919)).
|
||||
- Tasks in the root Taskfile will now be displayed first in `--list`/`--list-all`
|
||||
output ([#806](https://github.com/go-task/task/pull/806), [#890](https://github.com/go-task/task/pull/890)).
|
||||
- It's now possible to call a `default` task in an included Taskfile by using
|
||||
just the namespace. For example: `docs:default` is now automatically
|
||||
aliased to `docs`
|
||||
([#661](https://github.com/go-task/task/issues/661), [#815](https://github.com/go-task/task/pull/815)).
|
||||
|
||||
## v3.17.0 - 2022-10-14
|
||||
|
||||
- Add a "Did you mean ...?" suggestion when a task does not exits another one
|
||||
with a similar name is found
|
||||
([#867](https://github.com/go-task/task/issues/867), [#880](https://github.com/go-task/task/pull/880)).
|
||||
- Now YAML parse errors will print which Taskfile failed to parse
|
||||
([#885](https://github.com/go-task/task/issues/885), [#887](https://github.com/go-task/task/pull/887)).
|
||||
- Add ability to set `aliases` for tasks and namespaces ([#268](https://github.com/go-task/task/pull/268), [#340](https://github.com/go-task/task/pull/340), [#879](https://github.com/go-task/task/pull/879)).
|
||||
- Improvements to Fish shell completion
|
||||
([#897](https://github.com/go-task/task/pull/897)).
|
||||
- Added ability to set a different watch interval by setting
|
||||
`interval: '500ms'` or using the `--interval=500ms` flag
|
||||
([#813](https://github.com/go-task/task/issues/813), [#865](https://github.com/go-task/task/pull/865)).
|
||||
- Add colored output to `--list`, `--list-all` and `--summary` flags ([#845](https://github.com/go-task/task/pull/845), [#874](https://github.com/go-task/task/pull/874)).
|
||||
- Fix unexpected behavior where `label:` was being shown instead of the task
|
||||
name on `--list`
|
||||
([#603](https://github.com/go-task/task/issues/603), [#877](https://github.com/go-task/task/pull/877)).
|
||||
|
||||
## v3.16.0 - 2022-09-29
|
||||
|
||||
- Add `npm` as new installation method: `npm i -g @go-task/cli`
|
||||
|
||||
12
README.md
12
README.md
@@ -13,3 +13,15 @@
|
||||
<a href="https://taskfile.dev/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Gold Sponsors
|
||||
|
||||
<div align="center">
|
||||
|
||||
| [Appwrite][appwrite] |
|
||||
| - |
|
||||
| [][appwrite] |
|
||||
|
||||
</div>
|
||||
|
||||
[appwrite]: https://appwrite.io/?utm_source=task_github&utm_medium=social&utm_campaign=task_oss_fund
|
||||
|
||||
13
Taskfile.yml
13
Taskfile.yml
@@ -2,6 +2,7 @@ version: '3'
|
||||
|
||||
includes:
|
||||
docs:
|
||||
aliases: [d]
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
@@ -23,6 +24,7 @@ tasks:
|
||||
|
||||
install:
|
||||
desc: Installs Task
|
||||
aliases: [i]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
cmds:
|
||||
@@ -42,8 +44,10 @@ tasks:
|
||||
|
||||
lint:
|
||||
desc: Runs golangci-lint
|
||||
aliases: [l]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
@@ -65,15 +69,16 @@ tasks:
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
aliases: [t]
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}}
|
||||
|
||||
test:signals:
|
||||
desc: Runs test suite with signals tests included
|
||||
test:all:
|
||||
desc: Runs test suite with signals and watch tests included
|
||||
deps: [install, sleepit:build]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}} -tags signals
|
||||
- go test {{catLines .GO_PACKAGES}} -tags 'signals watch'
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
@@ -88,7 +93,7 @@ tasks:
|
||||
- rm {{.FILE}}
|
||||
- 'echo "---" >> {{.FILE}}'
|
||||
- 'echo "slug: /changelog/" >> {{.FILE}}'
|
||||
- 'echo "sidebar_position: 6" >> {{.FILE}}'
|
||||
- 'echo "sidebar_position: 7" >> {{.FILE}}'
|
||||
- 'echo "---" >> {{.FILE}}'
|
||||
- 'echo "" >> {{.FILE}}'
|
||||
- 'cat CHANGELOG.md >> {{.FILE}}'
|
||||
|
||||
@@ -130,11 +130,11 @@ func supervisor(
|
||||
// The goroutine will prepend its prints with the prefix `name`.
|
||||
// The goroutine will simulate some work and will terminate when one of the following
|
||||
// conditions happens:
|
||||
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
||||
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
||||
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
||||
// it should send a message; if instead it wants an asynchronous cancel, it should
|
||||
// close the channel.
|
||||
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
||||
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
||||
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
||||
// it should send a message; if instead it wants an asynchronous cancel, it should
|
||||
// close the channel.
|
||||
func worker(
|
||||
canceled <-chan struct{},
|
||||
howlong time.Duration,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
@@ -59,6 +60,7 @@ func main() {
|
||||
init bool
|
||||
list bool
|
||||
listAll bool
|
||||
listJson bool
|
||||
status bool
|
||||
force bool
|
||||
watch bool
|
||||
@@ -73,6 +75,7 @@ func main() {
|
||||
entrypoint string
|
||||
output taskfile.Output
|
||||
color bool
|
||||
interval time.Duration
|
||||
)
|
||||
|
||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||
@@ -80,6 +83,7 @@ func main() {
|
||||
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.BoolVarP(&listJson, "json", "j", false, "formats task list as json")
|
||||
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")
|
||||
@@ -96,6 +100,7 @@ func main() {
|
||||
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.DurationVarP(&interval, "interval", "I", 0, "interval to watch for changes")
|
||||
pflag.Parse()
|
||||
|
||||
if versionFlag {
|
||||
@@ -151,6 +156,7 @@ func main() {
|
||||
Parallel: parallel,
|
||||
Color: color,
|
||||
Concurrency: concurrency,
|
||||
Interval: interval,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
@@ -159,7 +165,12 @@ func main() {
|
||||
OutputStyle: output,
|
||||
}
|
||||
|
||||
if (list || listAll) && silent {
|
||||
var listOptions = task.NewListOptions(list, listAll, listJson)
|
||||
if err := listOptions.Validate(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if (listOptions.ShouldListTasks()) && silent {
|
||||
e.ListTaskNames(listAll)
|
||||
return
|
||||
}
|
||||
@@ -173,13 +184,10 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if list {
|
||||
e.ListTasksWithDesc()
|
||||
return
|
||||
}
|
||||
|
||||
if listAll {
|
||||
e.ListAllTasks()
|
||||
if listOptions.ShouldListTasks() {
|
||||
if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
set GO_TASK_PROGNAME task
|
||||
|
||||
function __task_get_tasks --description "Prints all available tasks with their description"
|
||||
set -l output ($GO_TASK_PROGNAME --list-all | sed '1d; s/\* \(.*\):\s*\(.*\)/\1\t\2/' | string split0)
|
||||
# Read the list of tasks (and potential errors)
|
||||
$GO_TASK_PROGNAME --list-all 2>&1 | read -lz rawOutput
|
||||
|
||||
# Return on non-zero exit code (for cases when there is no Taskfile found or etc.)
|
||||
if test $status -ne 0
|
||||
return
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed '1d; s/\* \(.*\):\s*\(.*\)/\1\t\2/' | string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
echo $output
|
||||
end
|
||||
end
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was
|
||||
specified.' -xa "(__task_get_tasks)"
|
||||
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
$scriptBlock = {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$curReg = "task{.exe}? (.*?)$"
|
||||
$startsWith = $wordToComplete | Select-String $curReg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||
$reg = "\* ($startsWith.+?):"
|
||||
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters )
|
||||
$reg = "\* ($commandName.+?):"
|
||||
$listOutput = $(task --list-all)
|
||||
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value + " " }
|
||||
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||
}
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName task -ScriptBlock $scriptBlock
|
||||
Register-ArgumentCompleter -CommandName task -ScriptBlock $scriptBlock
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
setup:
|
||||
yarn:install:
|
||||
desc: Setup Docusaurus locally
|
||||
cmds:
|
||||
- yarn install
|
||||
sources:
|
||||
- package.json
|
||||
- yarn.lock
|
||||
|
||||
start:
|
||||
default:
|
||||
desc: Start website
|
||||
deps: [yarn:install]
|
||||
aliases: [s, start]
|
||||
vars:
|
||||
HOST: '{{default "localhost" .HOST}}'
|
||||
PORT: '{{default "3001" .PORT}}'
|
||||
@@ -16,6 +21,7 @@ tasks:
|
||||
|
||||
build:
|
||||
desc: Build website
|
||||
deps: [yarn:install]
|
||||
cmds:
|
||||
- npx docusaurus build
|
||||
|
||||
@@ -25,6 +31,12 @@ tasks:
|
||||
- rm -rf ./build
|
||||
|
||||
deploy:
|
||||
desc: Build and deploy Docusaurus. Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
desc: Build and deploy Docusaurus
|
||||
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
cmds:
|
||||
- npx docusaurus deploy
|
||||
|
||||
upgrade:
|
||||
desc: Upgrade Docusaurus
|
||||
cmds:
|
||||
- yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest
|
||||
|
||||
11
docs/constants.js
Normal file
11
docs/constants.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const GITHUB_URL = 'https://github.com/go-task/task';
|
||||
const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
||||
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
||||
const CHINESE_URL = 'https://task-zh.readthedocs.io/zh_CN/latest/';
|
||||
|
||||
module.exports = {
|
||||
GITHUB_URL,
|
||||
TWITTER_URL,
|
||||
DISCORD_URL,
|
||||
CHINESE_URL
|
||||
};
|
||||
@@ -30,11 +30,12 @@ variable
|
||||
| `-f` | `--force` | `bool` | `false` | Forces execution even when the task is up-to-date. |
|
||||
| `-h` | `--help` | `bool` | `false` | Shows Task usage. |
|
||||
| `-i` | `--init` | `bool` | `false` | Creates a new Taskfile.yaml in the current folder. |
|
||||
| `-I` | `--interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
|
||||
| `-l` | `--list` | `bool` | `false` | Lists tasks with description of current Taskfile. |
|
||||
| `-a` | `--list-all` | `bool` | `false` | Lists tasks with or without a description. |
|
||||
| `-o` | `--output` | `string` | Default set in the Taskfile or `intervealed` | Sets output style: [`interleaved`/`group`/`prefixed`]. |
|
||||
| | `--output-group-begin` | `string` | | Message template to print before a task's grouped output. |
|
||||
| | `--output-group-end ` | `string` | | Message template to print after a task's grouped output. |
|
||||
| | `--output-group-end` | `string` | | Message template to print after a task's grouped output. |
|
||||
| `-p` | `--parallel` | `bool` | `false` | Executes tasks provided on command line in parallel. |
|
||||
| `-s` | `--silent` | `bool` | `false` | Disables echoing. |
|
||||
| | `--status` | `bool` | `false` | Exits with non-zero exit code if any of the given tasks is not up-to-date. |
|
||||
@@ -54,6 +55,7 @@ There are some special variables that is available on the templating system:
|
||||
| `TASK` | The name of the current task. |
|
||||
| `ROOT_DIR` | The absolute path of the root Taskfile. |
|
||||
| `TASKFILE_DIR` | The absolute path of the included Taskfile. |
|
||||
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from. |
|
||||
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
|
||||
| `TIMESTAMP` | The date object of the greatest timestamp of the files listes in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
|
||||
|
||||
@@ -79,16 +81,16 @@ Some environment variables can be overriden to adjust Task behavior.
|
||||
| Attribute | Type | Default | Description |
|
||||
| - | - | - | - |
|
||||
| `version` | `string` | | Version of the Taskfile. The current version is `3`. |
|
||||
| `includes` | [`map[string]Include`](#include) | | Additional Taskfiles to be included. |
|
||||
| `output` | `string` | `interleaved` | Output mode. Available options: `interleaved`, `group` and `prefixed`. |
|
||||
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overriden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
|
||||
| `silent` | `bool` | `false` | Default "silent" options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis. |
|
||||
| `run` | `string` | `always` | Default "run" option for this Taskfile. Available options: `always`, `once` and `when_changed`. |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | Global variables. |
|
||||
| `env` | [`map[string]Variable`](#variable) | | Global environment. |
|
||||
| `includes` | [`map[string]Include`](#include) | | Additional Taskfiles to be included. |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | A set of global variables. |
|
||||
| `env` | [`map[string]Variable`](#variable) | | A set of global environment variables. |
|
||||
| `tasks` | [`map[string]Task`](#task) | | A set of task definitions. |
|
||||
| `silent` | `bool` | `false` | Default 'silent' options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis. |
|
||||
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
||||
| `tasks` | [`map[string]Task`](#task) | | The task definitions. |
|
||||
|
||||
| `run` | `string` | `always` | Default 'run' option for this Taskfile. Available options: `always`, `once` and `when_changed`. |
|
||||
| `interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
|
||||
|
||||
### Include
|
||||
|
||||
@@ -97,7 +99,9 @@ Some environment variables can be overriden to adjust Task behavior.
|
||||
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
|
||||
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
|
||||
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
|
||||
| `internal` | `bool` | `false` | If `true`, tasks will be omitted from both `--list` and `--list-all`. |
|
||||
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
|
||||
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
|
||||
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
|
||||
|
||||
:::info
|
||||
|
||||
@@ -114,23 +118,27 @@ includes:
|
||||
|
||||
| Attribute | Type | Default | Description |
|
||||
| - | - | - | - |
|
||||
| `desc` | `string` | | A short description of the task. This is listed when calling `task --list`. |
|
||||
| `summary` | `string` | | A longer description of the task. This is listed when calling `task --summary [task]`. |
|
||||
| `sources` | `[]string` | | List of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
|
||||
| `dir` | `string` | | The current directory which this task should run. |
|
||||
| `method` | `string` | `checksum` | Method used by this task. Default to the one declared globally or `checksum`. Available options: `checksum`, `timestamp` and `none` |
|
||||
| `silent` | `bool` | `false` | Skips some output for this task. Note that STDOUT and STDERR of the commands will still be redirected. |
|
||||
| `internal` | `bool` | `false` | If `true`, omit this task from both `--list` and `--list-all`. |
|
||||
| `cmds` | [`[]Command`](#command) | | A list of shell commands to be executed. |
|
||||
| `deps` | [`[]Dependency`](#dependency) | | A list of dependencies of this task. Tasks defined here will run in parallel before this task. |
|
||||
| `label` | `string` | | Overrides the name of the task in the output when a task is run. Supports variables. |
|
||||
| `desc` | `string` | | A short description of the task. This is displayed when calling `task --list`. |
|
||||
| `summary` | `string` | | A longer description of the task. This is displayed when calling `task --summary [task]`. |
|
||||
| `aliases` | `[]string` | | A list of alternative names by which the task can be called. |
|
||||
| `sources` | `[]string` | | A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
|
||||
| `generates` | `[]string` | | A list of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs. |
|
||||
| `status` | `[]string` | | A list of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`. |
|
||||
| `preconditions` | [`[]Precondition`](#precondition) | | A list of commands to check if this task should run. If a condition is not met, the task will error. |
|
||||
| `dir` | `string` | | The directory in which this task should run. Defaults to the current working directory. |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | A set of variables that can be used in the task. |
|
||||
| `env` | [`map[string]Variable`](#variable) | | A set of environment variables that will be made available to shell commands. |
|
||||
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
||||
| `silent` | `bool` | `false` | Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. When combined with the `--list` flag, task descriptions will be hidden. |
|
||||
| `interactive` | `bool` | `false` | Tells task that the command is interactive. |
|
||||
| `internal` | `bool` | `false` | Stops a task from being callable on the command line. It will also be omitted from the output when used with `--list`. |
|
||||
| `method` | `string` | `checksum` | Defines which method is used to check the task is up-to-date. `timestamp` will compare the timestamp of the sources and generates files. `checksum` will check the checksum (You probably want to ignore the .task folder in your .gitignore file). `none` skips any validation and always run the task. |
|
||||
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
||||
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
||||
| `prefix` | `string` | | Allows to override the prefix print before the STDOUT. Only relevant when using the `prefixed` output mode. |
|
||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the commands. |
|
||||
| `generates` | `[]string` | | List of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs. |
|
||||
| `status` | `[]string` | | List of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`. |
|
||||
| `preconditions` | [`[]Precondition`](#precondition) | | List of commands to check if this task should run. The task errors otherwise. |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | Task variables. |
|
||||
| `env` | [`map[string]Variable`](#variable) | | Task environment. |
|
||||
| `deps` | [`[]Dependency`](#dependency) | | List of dependencies of this task. |
|
||||
| `cmds` | [`[]Command`](#command) | | List of commands to be executed. |
|
||||
|
||||
:::info
|
||||
|
||||
@@ -176,11 +184,11 @@ tasks:
|
||||
| Attribute | Type | Default | Description |
|
||||
| - | - | - | - |
|
||||
| `cmd` | `string` | | The shell command to be executed. |
|
||||
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
||||
| `silent` | `bool` | `false` | Skips some output for this command. Note that STDOUT and STDERR of the commands will still be redirected. |
|
||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
||||
| `task` | `string` | | Set this to trigger execution of another task instead of running a command. This cannot be set together with `cmd`. |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
||||
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
||||
|
||||
:::info
|
||||
|
||||
|
||||
@@ -1,10 +1,66 @@
|
||||
---
|
||||
slug: /changelog/
|
||||
sidebar_position: 6
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Changelog
|
||||
|
||||
## v3.19.1 - 2022-12-31
|
||||
|
||||
- Small bug fix: closing `Taskfile.yml` once we're done reading it
|
||||
([#963](https://github.com/go-task/task/issues/963), [#964](https://github.com/go-task/task/pull/964) by @HeCorr).
|
||||
- Fixes a bug in v2 that caused a panic when using a `Taskfile_{{OS}}.yml` file
|
||||
([#961](https://github.com/go-task/task/issues/961), [#971](https://github.com/go-task/task/pull/971) by @pd93).
|
||||
- Fixed a bug where watch intervals set in the Taskfile were not being respected ([#969](https://github.com/go-task/task/pull/969), [#970](https://github.com/go-task/task/pull/970) by @pd93)
|
||||
- Add `--json` flag (alias `-j`) with the intent to improve support for code
|
||||
editors and add room to other possible integrations. This is basic for now,
|
||||
but we plan to add more info in the near future
|
||||
([#936](https://github.com/go-task/task/pull/936) by @davidalpert, [#764](https://github.com/go-task/task/issues/764)).
|
||||
|
||||
## v3.19.0 - 2022-12-05
|
||||
|
||||
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
|
||||
([go-task/go-npm#2](https://github.com/go-task/go-npm/issues/2), [go-task/go-npm#3](https://github.com/go-task/go-npm/pull/3)).
|
||||
- It's now possible to run Taskfiles from subdirectories! A new `USER_WORKING_DIR` special
|
||||
variable was added to add even more flexibility for monorepos
|
||||
([#289](https://github.com/go-task/task/issues/289), [#920](https://github.com/go-task/task/pull/920)).
|
||||
- Add task-level `dotenv` support
|
||||
([#389](https://github.com/go-task/task/issues/389), [#904](https://github.com/go-task/task/pull/904)).
|
||||
- It's now possible to use global level variables on `includes`
|
||||
([#942](https://github.com/go-task/task/issues/942), [#943](https://github.com/go-task/task/pull/943)).
|
||||
- The website got a brand new [translation to Chinese](https://task-zh.readthedocs.io/zh_CN/latest/)
|
||||
by [@DeronW](https://github.com/DeronW). Thanks!
|
||||
|
||||
## v3.18.0 - 2022-11-12
|
||||
|
||||
- Show aliases on `task --list --silent` (`task --ls`). This means that aliases
|
||||
will be completed by the completion scripts
|
||||
([#919](https://github.com/go-task/task/pull/919)).
|
||||
- Tasks in the root Taskfile will now be displayed first in `--list`/`--list-all`
|
||||
output ([#806](https://github.com/go-task/task/pull/806), [#890](https://github.com/go-task/task/pull/890)).
|
||||
- It's now possible to call a `default` task in an included Taskfile by using
|
||||
just the namespace. For example: `docs:default` is now automatically
|
||||
aliased to `docs`
|
||||
([#661](https://github.com/go-task/task/issues/661), [#815](https://github.com/go-task/task/pull/815)).
|
||||
|
||||
## v3.17.0 - 2022-10-14
|
||||
|
||||
- Add a "Did you mean ...?" suggestion when a task does not exits another one
|
||||
with a similar name is found
|
||||
([#867](https://github.com/go-task/task/issues/867), [#880](https://github.com/go-task/task/pull/880)).
|
||||
- Now YAML parse errors will print which Taskfile failed to parse
|
||||
([#885](https://github.com/go-task/task/issues/885), [#887](https://github.com/go-task/task/pull/887)).
|
||||
- Add ability to set `aliases` for tasks and namespaces ([#268](https://github.com/go-task/task/pull/268), [#340](https://github.com/go-task/task/pull/340), [#879](https://github.com/go-task/task/pull/879)).
|
||||
- Improvements to Fish shell completion
|
||||
([#897](https://github.com/go-task/task/pull/897)).
|
||||
- Added ability to set a different watch interval by setting
|
||||
`interval: '500ms'` or using the `--interval=500ms` flag
|
||||
([#813](https://github.com/go-task/task/issues/813), [#865](https://github.com/go-task/task/pull/865)).
|
||||
- Add colored output to `--list`, `--list-all` and `--summary` flags ([#845](https://github.com/go-task/task/pull/845), [#874](https://github.com/go-task/task/pull/874)).
|
||||
- Fix unexpected behavior where `label:` was being shown instead of the task
|
||||
name on `--list`
|
||||
([#603](https://github.com/go-task/task/issues/603), [#877](https://github.com/go-task/task/pull/877)).
|
||||
|
||||
## v3.16.0 - 2022-09-29
|
||||
|
||||
- Add `npm` as new installation method: `npm i -g @go-task/cli`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /community/
|
||||
sidebar_position: 6
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Community
|
||||
@@ -9,17 +9,22 @@ Some of the work to improve the Task ecosystem is done by the community, be
|
||||
it installation methods or integrations with code editor. I (the author) am
|
||||
thankful for everyone that helps me to improve the overall experience.
|
||||
|
||||
## Translations
|
||||
|
||||
[@DeronW](https://github.com/DeronW) maintains the
|
||||
[Chinese translation](https://task-zh.readthedocs.io/zh_CN/latest/) of the
|
||||
website [on this repository](https://github.com/DeronW/task).
|
||||
|
||||
## Editor Integrations
|
||||
|
||||
### JSON Schema
|
||||
|
||||
[@KROSF](https://github.com/KROSF) worked on a JSON Schema [into this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895),
|
||||
which later was made officially available by [@Crandel](https://github.com/Crandel)
|
||||
at [https://json.schemastore.org/taskfile.json](https://json.schemastore.org/taskfile.json).
|
||||
Further improvements are possible by opening pull requests changing
|
||||
[this file](https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json).
|
||||
Some code editors, like Visual Studio Code, make use of Schema Store
|
||||
automatically.
|
||||
Initial work on the schema was made by [@KROSF](https://github.com/KROSF)
|
||||
on [this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895).
|
||||
The schema is currently available at
|
||||
https://taskfile.dev/schema.json and linked at https://json.schemastore.org/taskfile.json
|
||||
so it is be used automatically many code editors, like VSCode.
|
||||
Contributions can be done by editing [this file](https://github.com/go-task/task/blob/master/docs/static/schema.json).
|
||||
|
||||
### Visual Studio Code extension
|
||||
|
||||
|
||||
124
docs/docs/contributing.md
Normal file
124
docs/docs/contributing.md
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
slug: /contributing/
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions to Task are very welcome, but we ask that you read this document
|
||||
before submitting a PR.
|
||||
|
||||
## Before you start
|
||||
|
||||
- **Check existing work** - Is there an existing PR? Are there issues discussing
|
||||
the feature/change you want to make? Please make sure you consider/address these
|
||||
discussions in your work.
|
||||
- **Backwards compatibility** - Will your change break existing Taskfiles? It is
|
||||
much more likely that your change will merged if it backwards compatible. Is
|
||||
there an approach you can take that maintains this compatibility? If not,
|
||||
consider opening an issue first so that API changes can be discussed before you
|
||||
invest you time into a PR.
|
||||
|
||||
## 1. Setup
|
||||
|
||||
- **Go** - Task is written in [Go]. We always support the latest two major Go
|
||||
versions, so make sure your version is recent enough.
|
||||
- **Node.js** - [Node.js] is used to host Task's documentation server and is
|
||||
required if you want to run this server locally.
|
||||
- **Yarn** - [Yarn] is the Node.js package manager used by Task.
|
||||
|
||||
## 2. Making changes
|
||||
|
||||
- **Code style** - Try to maintain the existing code style where possible and
|
||||
ensure that code is formatted by `gofmt`. We use `golangci-lint` in our CI to
|
||||
enforce a consistent style and best-practise. There's a `lint` command in
|
||||
the Taskfile to run this locally.
|
||||
- **Documentation** - Ensure that you add/update any relevant documentation. See
|
||||
the [updating documentation](#updating-documentation) section below.
|
||||
- **Tests** - Ensure that you add/update any relevant tests and that all tests
|
||||
are passing before submitting the PR. See the [writing tests](#writing-tests)
|
||||
section below.
|
||||
|
||||
### Running your changes
|
||||
|
||||
To run Task with working changes, you can use `go run ./cmd/task`. To run a
|
||||
development build of task against a test Taskfile in `testdata`, you can use `go
|
||||
run ./cmd/task --dir ./testdata/<my_test_dir> <task_name>`.
|
||||
|
||||
### Updating documentation
|
||||
|
||||
Task uses [Docusaurus] to host a documentation server. This can be setup and run
|
||||
locally by using `task docs:setup` and `task docs:start` respectively (requires
|
||||
`nodejs` & `yarn`). All content is written in Markdown and is located in the
|
||||
`docs/docs` directory. All Markdown documents should have an 80 character line
|
||||
wrap limit.
|
||||
|
||||
When making a change, consider whether a change to the [Usage Guide](./usage.md)
|
||||
is necessary. This document contains descriptions and examples of how to use
|
||||
Task features. If you're adding a new feature, try to find an appropriate place
|
||||
to add a new section. If you're updating an existing feature, ensure that the
|
||||
documentation and any examples are up-to-date. Ensure that any examples follow
|
||||
the [Taskfile Styleguide](./styleguide.md).
|
||||
|
||||
If you added a new field, command or flag, ensure that you add it to the [API
|
||||
Reference](./api_reference.md). New fields also need to be added to the
|
||||
[JSON Schema](../static/schema.json). The descriptions for fields in the API
|
||||
reference and the schema should match.
|
||||
|
||||
### Writing tests
|
||||
|
||||
Most of Task's test are held in the `task_test.go` file in the project root and
|
||||
this is where you'll most likely want to add new ones too. Most of these tests
|
||||
also have a subdirectory in the `testdata` directory where any Taskfiles/data
|
||||
required to run the tests are stored.
|
||||
|
||||
When making a changes, consider whether new tests are required. These tests
|
||||
should ensure that the functionality you are adding will continue to work in the
|
||||
future. Existing tests may also need updating if you have changed Task's
|
||||
behaviour.
|
||||
|
||||
## 3. Committing your code
|
||||
|
||||
Try to write meaningful commit messages and avoid having too many commits on
|
||||
the PR. Most PRs should likely have a single commit (although for bigger PRs it
|
||||
may be reasonable to split it in a few). Git squash and rebase is your friend!
|
||||
|
||||
## 4. Submitting a PR
|
||||
|
||||
- **Describe your changes** - Ensure that you provide a comprehensive
|
||||
description of your changes.
|
||||
- **Issue/PR links** - Link any previous work such as related issues or PRs.
|
||||
Please describe how your changes differ to/extend this work.
|
||||
- **Examples** - Add any examples that you think are useful to demonstrate the
|
||||
effect of your changes.
|
||||
- **Draft PRs** - If your changes are incomplete, but you would like to discuss
|
||||
them, open the PR as a draft and add a comment to start a discussion. Using
|
||||
comments rather than the PR description allows the description to be updated
|
||||
later while preserving any discussions.
|
||||
|
||||
## FAQ
|
||||
|
||||
> I want to contribute, where do I start?
|
||||
|
||||
Take a look at the list of [open issues]. We have a [good first issue] label for
|
||||
simpler issues that are ideal for first time contributions.
|
||||
|
||||
All kinds of contributions are welcome, whether its a typo fix or a shiny new
|
||||
feature. You can also contribute by upvoting/commenting on issues, helping to
|
||||
answer questions or contributing to other [community projects](./community.md).
|
||||
|
||||
> I'm stuck, where can I get help?
|
||||
|
||||
If you have questions, feel free to ask them in the `#help` channel on our
|
||||
[Discord server].
|
||||
|
||||
---
|
||||
|
||||
[Go]: https://go.dev
|
||||
[install version 1.18+]: https://go.dev/doc/install
|
||||
[Node.js]: https://nodejs.org/en/
|
||||
[Yarn]: https://yarnpkg.com/
|
||||
[Docusaurus]: https://docusaurus.io
|
||||
[open issues]: https://github.com/go-task/task/issues
|
||||
[good first issue]: https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
|
||||
[Discord server]: https://discord.gg/6TY36E39UK
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /donate/
|
||||
sidebar_position: 9
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# Donate
|
||||
@@ -11,10 +11,23 @@ channels listed below.
|
||||
This is just a way of saying "thank you", it won't give you any benefits like
|
||||
higher priority on issues or something similar.
|
||||
|
||||
Companies who donate at least $100/month will be featured as a "Gold Sponsor"
|
||||
in the website homepage and on the GitHub repository README. Make contact with
|
||||
[@andreynering] with the logo you want to be shown.
|
||||
Suspect businesses (gambling, casinos, etc) won't be allowed, though.
|
||||
|
||||
## GitHub Sponsors
|
||||
|
||||
The preferred way to donate to the maintainers is via GitHub Sponsors.
|
||||
Just use the following links to do your donation:
|
||||
|
||||
- [@andreynering](https://github.com/sponsors/andreynering)
|
||||
- [@pd93](https://github.com/sponsors/pd93)
|
||||
|
||||
## Open Collective
|
||||
|
||||
Task is on [Open Collective](https://opencollective.com/task) and you have
|
||||
these options to donate:
|
||||
If you prefer [Open Collective](https://opencollective.com/task) you can donate
|
||||
by using these links:
|
||||
|
||||
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
|
||||
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
|
||||
@@ -22,15 +35,15 @@ these options to donate:
|
||||
- [$50 per month](https://opencollective.com/task/contribute/sponsor-28775/checkout)
|
||||
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
|
||||
|
||||
## GitHub Sponsors
|
||||
|
||||
- [@andreynering](https://github.com/sponsors/andreynering)
|
||||
|
||||
## PayPal
|
||||
|
||||
You can donate to [@andreynering] via PayPal as well:
|
||||
|
||||
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A¤cy_code=USD&source=url)
|
||||
|
||||
## PIX (Brazil only)
|
||||
|
||||
If you're Brazilian, you can donate any value by
|
||||
And if you're Brazilian, you can also donate to [@andreynering] via PIX by
|
||||
[using this QR Code](/img/pix.png).
|
||||
|
||||
[@andreynering]: https://github.com/andreynering
|
||||
|
||||
54
docs/docs/faq.md
Normal file
54
docs/docs/faq.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
slug: /faq/
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# FAQ
|
||||
|
||||
This page contains a list of frequently asked questions about Task.
|
||||
|
||||
- [Why won't my task update my shell environment?](#why-wont-my-task-update-my-shell-environment)
|
||||
- ['x' builtin command doesn't work on Windows](#x-builtin-command-doesnt-work-on-windows)
|
||||
|
||||
## Why won't my task update my shell environment?
|
||||
|
||||
This is a limitation of how shells work. Task runs as a subprocess of your
|
||||
current shell, so it can't change the environment of the shell that started it.
|
||||
This limitation is shared by other task runners and build tools too.
|
||||
|
||||
A common way to work around this is to create a task that will generate output
|
||||
that can be parsed by your shell. For example, to set an environment variable on
|
||||
your shell you can write a task like this:
|
||||
|
||||
```yaml
|
||||
my-shell-env:
|
||||
cmds:
|
||||
- echo "export FOO=foo"
|
||||
- echo "export BAR=bar"
|
||||
```
|
||||
|
||||
Now run `eval $(task my-shell-env)` and the variables `$FOO` and `$BAR` will be
|
||||
available in your shell.
|
||||
|
||||
## 'x' builtin command doesn't work on Windows
|
||||
|
||||
The default shell on Windows (`cmd` and `powershell`) do not have commands like
|
||||
`rm` and `cp` available as builtins. This means that these commands won't work.
|
||||
If you want to make your Taskfile fully cross-platform, you'll need to work
|
||||
around this limitation using one of the following methods:
|
||||
|
||||
- Use the `{{OS}}` function to run an OS-specific script.
|
||||
- Use something like `{{if eq OS "windows"}}powershell {{end}}<my_cmd>` to
|
||||
detect windows and run the command in Powershell directly.
|
||||
- Use a shell on Windows that supports these commands as builtins, such as [Git
|
||||
Bash] or [WSL].
|
||||
|
||||
We want to make improvements to this part of Task and the issues below track
|
||||
this work. Constructive comments and contributions are very welcome!
|
||||
|
||||
- [#197](https://github.com/go-task/task/issues/197)
|
||||
- [mvdan/sh#93](https://github.com/mvdan/sh/issues/93)
|
||||
- [mvdan/sh#97](https://github.com/mvdan/sh/issues/97)
|
||||
|
||||
[Git Bash]: https://gitforwindows.org/
|
||||
[WSL]: https://learn.microsoft.com/en-us/windows/wsl/install
|
||||
@@ -18,6 +18,15 @@ Task is as simple as running:
|
||||
brew install go-task/tap/go-task
|
||||
```
|
||||
|
||||
The above Formula is [maintained by ourselves](https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb).
|
||||
|
||||
Recently, Task was also made available [on the official Homebrew repository](https://formulae.brew.sh/formula/go-task),
|
||||
so you also have that option if you prefer:
|
||||
|
||||
```bash
|
||||
brew install go-task
|
||||
```
|
||||
|
||||
### Snap
|
||||
|
||||
Task is available in [Snapcraft][snapcraft], but keep in mind that your
|
||||
@@ -39,7 +48,6 @@ choco install go-task
|
||||
|
||||
This installation method is community owned.
|
||||
|
||||
|
||||
### Scoop
|
||||
|
||||
If you're on Windows and have [Scoop][scoop] installed, getting
|
||||
@@ -190,6 +198,68 @@ released binary.
|
||||
|
||||
:::
|
||||
|
||||
## Setup completions
|
||||
|
||||
Download the autocompletion file corresponding to your shell.
|
||||
|
||||
[All completions are available on the Task repository](https://github.com/go-task/task/tree/master/completion).
|
||||
|
||||
### Bash
|
||||
|
||||
First, ensure that you installed bash-completion using your package manager.
|
||||
|
||||
Make the completion file executable:
|
||||
|
||||
```
|
||||
chmod +x path/to/task.bash
|
||||
```
|
||||
|
||||
After, add this to your `~/.bash_profile`:
|
||||
|
||||
```shell
|
||||
source path/to/task.bash
|
||||
```
|
||||
|
||||
### ZSH
|
||||
|
||||
Put the `_task` file somewhere in your `$FPATH`:
|
||||
|
||||
```shell
|
||||
mv path/to/_task /usr/local/share/zsh/site-functions/_task
|
||||
```
|
||||
|
||||
Ensure that the following is present in your `~/.zshrc`:
|
||||
|
||||
```shell
|
||||
autoload -U compinit
|
||||
compinit -i
|
||||
```
|
||||
|
||||
ZSH version 5.7 or later is recommended.
|
||||
|
||||
### Fish
|
||||
|
||||
Move the `task.fish` completion script:
|
||||
|
||||
```shell
|
||||
mv path/to/task.fish ~/.config/fish/completions/task.fish
|
||||
```
|
||||
|
||||
### PowerShell
|
||||
|
||||
Open your profile script with:
|
||||
|
||||
```
|
||||
mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue
|
||||
notepad $profile
|
||||
```
|
||||
|
||||
Add the line and save the file:
|
||||
|
||||
```shell
|
||||
Invoke-Expression -Command path/to/task.ps1
|
||||
```
|
||||
|
||||
[go]: https://golang.org/
|
||||
[snapcraft]: https://snapcraft.io/task
|
||||
[homebrew]: https://brew.sh/
|
||||
|
||||
@@ -48,6 +48,16 @@ guide to check the full schema documentation and Task features.
|
||||
if a given set of files haven't changed since last run (based either on its
|
||||
timestamp or content).
|
||||
|
||||
## Gold Sponsors
|
||||
|
||||
<div class="gold-sponsors">
|
||||
|
||||
| [Appwrite][appwrite] |
|
||||
| - |
|
||||
| [][appwrite] |
|
||||
|
||||
</div>
|
||||
|
||||
[make]: https://www.gnu.org/software/make/
|
||||
[go]: https://go.dev/
|
||||
[yaml]: http://yaml.org/
|
||||
@@ -55,3 +65,4 @@ guide to check the full schema documentation and Task features.
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[scoop]: https://scoop.sh/
|
||||
[sh]: https://github.com/mvdan/sh
|
||||
[appwrite]: https://appwrite.io/?utm_source=taskfile.dev&utm_medium=website&utm_campaign=task_oss_fund
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /releasing/
|
||||
sidebar_position: 7
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Releasing
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /styleguide/
|
||||
sidebar_position: 5
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Styleguide
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /taskfile-versions/
|
||||
sidebar_position: 8
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
# Taskfile Versions
|
||||
|
||||
@@ -52,6 +52,35 @@ committed version (`.dist`) while still allowing individual users to override
|
||||
the Taskfile by adding an additional `Taskfile.yml` (which would be on
|
||||
`.gitignore`).
|
||||
|
||||
### Running a Taskfile from a subdirectory
|
||||
|
||||
If a Taskfile cannot be found in the current working directory, it will walk up
|
||||
the file tree until it finds one (similar to how `git` works). When running Task
|
||||
from a subdirectory like this, it will behave as if you ran it from the
|
||||
directory containing the Taskfile.
|
||||
|
||||
You can use this functionality along with the special `{{.USER_WORKING_DIR}}`
|
||||
variable to create some very useful reusable tasks. For example, if you have a
|
||||
monorepo with directories for each microservice, you can `cd` into a
|
||||
microservice directory and run a task command to bring it up without having to
|
||||
create multiple tasks or Taskfiles with identical content. For example:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
up:
|
||||
dir: '{{.USER_WORKING_DIR}}'
|
||||
preconditions:
|
||||
- test -f docker-compose.yml
|
||||
cmds:
|
||||
- docker-compose up -d
|
||||
```
|
||||
|
||||
In this example, we can run `cd <service>` and `task up` and as long as the
|
||||
`<service>` directory contains a `docker-compose.yml`, the Docker composition will be
|
||||
brought up.
|
||||
|
||||
## Environment variables
|
||||
|
||||
### Task
|
||||
@@ -118,6 +147,45 @@ tasks:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
Dotenv files can also be specified at the task level:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
ENV: testing
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
|
||||
cmds:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
Environment variables specified explicitly at the task-level will override
|
||||
variables defined in dotfiles:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
ENV: testing
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
|
||||
env:
|
||||
KEYNAME: DIFFERENT_VALUE
|
||||
cmds:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
Please note that you are not currently able to use the `dotenv` key inside included Taskfiles.
|
||||
|
||||
:::
|
||||
|
||||
## Including other Taskfiles
|
||||
|
||||
If you want to share tasks between different projects (Taskfiles), you can use
|
||||
@@ -230,6 +298,21 @@ includes:
|
||||
DOCKER_IMAGE: frontend_image
|
||||
```
|
||||
|
||||
### Namespace aliases
|
||||
|
||||
When including a Taskfile, you can give the namespace a list of `aliases`.
|
||||
This works in the same way as [task aliases](#task-aliases) and can be used
|
||||
together to create shorter and easier-to-type commands.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
generate:
|
||||
taskfile: ./taskfiles/Generate.yml
|
||||
aliases: [gen]
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
Vars declared in the included Taskfile have preference over the
|
||||
@@ -465,6 +548,11 @@ tasks:
|
||||
method: timestamp
|
||||
```
|
||||
|
||||
In situations where you need more flexibility the `status` keyword can be used.
|
||||
You can even combine the two. See the documentation for
|
||||
[status](#using-programmatic-checks-to-indicate-a-task-is-up-to-date) for an
|
||||
example.
|
||||
|
||||
:::info
|
||||
|
||||
By default, task stores checksums on a local `.task` directory in the project's
|
||||
@@ -555,6 +643,30 @@ up-to-date.
|
||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
||||
the tasks are not up-to-date.
|
||||
|
||||
`status` can be combined with the [fingerprinting](#by-fingerprinting-locally-generated-files-and-their-sources)
|
||||
to have a task run if either the the source/generated artifacts changes, or the
|
||||
programmatic check fails:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:prod:
|
||||
desc: Build for production usage.
|
||||
cmds:
|
||||
- composer install
|
||||
# Run this task if source files changes.
|
||||
sources:
|
||||
- composer.json
|
||||
- composer.lock
|
||||
generates:
|
||||
- ./vendor/composer/installed.json
|
||||
- ./vendor/autoload.php
|
||||
# But also run the task if the last build was not a production build.
|
||||
status:
|
||||
- grep -q '"dev": false' ./vendor/composer/installed.json
|
||||
```
|
||||
|
||||
### Using programmatic checks to cancel the execution of a task and its dependencies
|
||||
|
||||
In addition to `status` checks, `preconditions` checks are
|
||||
@@ -936,6 +1048,30 @@ If the task does not have a summary or a description, a warning is printed.
|
||||
|
||||
Please note: *showing the summary will not execute the command*.
|
||||
|
||||
## Task aliases
|
||||
|
||||
Aliases are alternative names for tasks. They can be used to make it easier and
|
||||
quicker to run tasks with long or hard-to-type names. You can use them on the
|
||||
command line, when [calling sub-tasks](#calling-another-task) in your Taskfile
|
||||
and when [including tasks](#including-other-taskfiles) with aliases from another
|
||||
Taskfile. They can also be used together with [namespace
|
||||
aliases](#namespace-aliases).
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
generate:
|
||||
aliases: [gen]
|
||||
cmds:
|
||||
- task: gen-mocks
|
||||
|
||||
generate-mocks:
|
||||
aliases: [gen-mocks]
|
||||
cmds:
|
||||
- echo "generating..."
|
||||
```
|
||||
|
||||
## Overriding task name
|
||||
|
||||
Sometimes you may want to override the task name printed on the summary, up-to-date
|
||||
@@ -1218,5 +1354,9 @@ With the flags `--watch` or `-w` task will watch for file changes
|
||||
and run the task again. This requires the `sources` attribute to be given,
|
||||
so task knows which files to watch.
|
||||
|
||||
The default watch interval is 5 seconds, but it's possible to change it by
|
||||
either setting `interval: '500ms'` in the root of the Taskfile passing it
|
||||
as an argument like `--interval=500ms`.
|
||||
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const {
|
||||
GITHUB_URL,
|
||||
TWITTER_URL,
|
||||
DISCORD_URL,
|
||||
CHINESE_URL
|
||||
} = require('./constants');
|
||||
const lightCodeTheme = require('./src/themes/prismLight');
|
||||
const darkCodeTheme = require('./src/themes/prismDark');
|
||||
|
||||
const GITHUB_URL = 'https://github.com/go-task/task';
|
||||
const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
||||
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'Task',
|
||||
@@ -153,6 +155,15 @@ const config = {
|
||||
href: 'https://opencollective.com/task'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Translations',
|
||||
items: [
|
||||
{
|
||||
label: 'Chinese | 中国人',
|
||||
href: CHINESE_URL
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.0.0-beta.20",
|
||||
"@docusaurus/preset-classic": "2.0.0-beta.20",
|
||||
"@docusaurus/core": "^2.2.0",
|
||||
"@docusaurus/preset-classic": "^2.2.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.1.1",
|
||||
"prism-react-renderer": "^1.3.1",
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
// @ts-check
|
||||
|
||||
const { CHINESE_URL } = require('./constants');
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
tutorialSidebar: [
|
||||
{ type: 'autogenerated', dirName: '.' },
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: '.'
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
label: 'Chinese | 中国人',
|
||||
href: CHINESE_URL
|
||||
},
|
||||
{
|
||||
type: 'html',
|
||||
value: '<div id="sidebar-ads"></div>'
|
||||
|
||||
@@ -27,3 +27,12 @@
|
||||
margin-top: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.gold-sponsors {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gold-sponsors table img {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
7
docs/static/img/appwrite.svg
vendored
Normal file
7
docs/static/img/appwrite.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.1 KiB |
BIN
docs/static/img/og-image.png
vendored
BIN
docs/static/img/og-image.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 19 KiB |
16
docs/static/install.sh
vendored
16
docs/static/install.sh
vendored
@@ -64,15 +64,21 @@ get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/armv5) BINARIES="task" ;;
|
||||
darwin/armv6) BINARIES="task" ;;
|
||||
darwin/armv7) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/armv5) BINARIES="task" ;;
|
||||
linux/armv6) BINARIES="task" ;;
|
||||
linux/armv7) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/armv5) BINARIES="task" ;;
|
||||
windows/armv6) BINARIES="task" ;;
|
||||
windows/armv7) BINARIES="task" ;;
|
||||
*)
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
@@ -184,9 +190,9 @@ uname_arch() {
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
armv5*) arch="arm" ;;
|
||||
armv6*) arch="arm" ;;
|
||||
armv7*) arch="arm" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
@@ -214,9 +220,7 @@ uname_arch_check() {
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
arm) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
|
||||
392
docs/static/schema.json
vendored
Normal file
392
docs/static/schema.json
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"title": "Taskfile YAML Schema",
|
||||
"description": "Schema for Taskfile files.",
|
||||
"definitions": {
|
||||
"3": {
|
||||
"env": {
|
||||
"$ref": "#/definitions/3/vars"
|
||||
},
|
||||
"tasks": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/task_call"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/task"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"task": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"cmds": {
|
||||
"description": "A list of commands to be executed.",
|
||||
"$ref": "#/definitions/3/cmds"
|
||||
},
|
||||
"deps": {
|
||||
"description": "A list of dependencies of this task. Tasks defined here will run in parallel before this task.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/task_call"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"description": "Overrides the name of the task in the output when a task is run. Supports variables.",
|
||||
"type": "string"
|
||||
},
|
||||
"desc": {
|
||||
"description": "A short description of the task. This is displayed when calling `task --list`.",
|
||||
"type": "string"
|
||||
},
|
||||
"summary": {
|
||||
"description": "A longer description of the task. This is displayed when calling `task --summary [task]`.",
|
||||
"type": "string"
|
||||
},
|
||||
"aliases": {
|
||||
"description": "A list of alternative names by which the task can be called.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"description": "A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"generates": {
|
||||
"description": "A list of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"description": "A list of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"preconditions": {
|
||||
"description": "A list of commands to check if this task should run. If a condition is not met, the task will error.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/3/precondition"
|
||||
}
|
||||
},
|
||||
"dir": {
|
||||
"description": "The directory in which this task should run. Defaults to the current working directory.",
|
||||
"type": "string"
|
||||
},
|
||||
"vars": {
|
||||
"description": "A set of variables that can be used in the task.",
|
||||
"$ref": "#/definitions/3/vars"
|
||||
},
|
||||
"env": {
|
||||
"description": "A set of environment variables that will be made available to shell commands.",
|
||||
"$ref": "#/definitions/3/env"
|
||||
},
|
||||
"dotenv": {
|
||||
"description": "A list of `.env` file paths to be parsed.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. When combined with the `--list` flag, task descriptions will be hidden.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"interactive": {
|
||||
"description": "Tells task that the command is interactive.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"internal": {
|
||||
"description": "Stops a task from being callable on the command line. It will also be omitted from the output when used with `--list`.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"method": {
|
||||
"description": "Defines which method is used to check the task is up-to-date. `timestamp` will compare the timestamp of the sources and generates files. `checksum` will check the checksum (You probably want to ignore the .task folder in your .gitignore file). `none` skips any validation and always run the task.",
|
||||
"type": "string",
|
||||
"enum": ["none", "checksum", "timestamp"],
|
||||
"default": "none"
|
||||
},
|
||||
"prefix": {
|
||||
"description": "Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`.",
|
||||
"type": "string"
|
||||
},
|
||||
"ignore_error": {
|
||||
"description": "Continue execution if errors happen while executing commands.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"run": {
|
||||
"description": "Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`.",
|
||||
"$ref": "#/definitions/3/run"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cmds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/3/cmd"
|
||||
}
|
||||
},
|
||||
"cmd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/cmd_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/task_call"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vars": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": ["boolean", "integer", "null", "number", "string"]
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/dynamic_var"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dynamic_var": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sh": {
|
||||
"type": "string",
|
||||
"description": "The value will be treated as a command and the output assigned"
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"task_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"description": "Name of the task to run",
|
||||
"type": "string"
|
||||
},
|
||||
"vars": {
|
||||
"description": "Values passed to the task called",
|
||||
"$ref": "#/definitions/3/vars"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cmd_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cmd": {
|
||||
"description": "Command to run",
|
||||
"type": "string"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Silent mode disables echoing of command before Task runs it",
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignore_error": {
|
||||
"description": "Prevent command from aborting the execution of task even after receiving a status code of 1",
|
||||
"type": "boolean"
|
||||
},
|
||||
"defer": {
|
||||
"description": "",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["cmd"]
|
||||
},
|
||||
"precondition": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/3/precondition_obj"
|
||||
}
|
||||
]
|
||||
},
|
||||
"precondition_obj": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sh": {
|
||||
"description": "Command to run. If that command returns 1, the condition will fail",
|
||||
"type": "string"
|
||||
},
|
||||
"msg": {
|
||||
"description": "Failure message to display when the condition fails",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"type": "string",
|
||||
"enum": ["always", "once", "when_changed"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "Specify the Taskfile format that this file conforms to.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number",
|
||||
"enum": [3]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["3"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"description": "Defines how the STDOUT and STDERR are printed when running tasks in parallel. The interleaved output prints lines in real time (default). The group output will print the entire output of a command once, after it finishes, so you won't have live feedback for commands that take a long time to run. 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.",
|
||||
"type": "string",
|
||||
"enum": ["interleaved", "group", "prefixed"],
|
||||
"default": "interleaved"
|
||||
},
|
||||
"method": {
|
||||
"description": "Defines which method is used to check the task is up-to-date. (default: checksum)",
|
||||
"type": "string",
|
||||
"enum": ["none", "checksum", "timestamp"],
|
||||
"default": "checksum"
|
||||
},
|
||||
"includes": {
|
||||
"description": "Imports tasks from the specified taskfiles. The tasks described in the given Taskfiles will be available with the informed namespace.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskfile": {
|
||||
"description": "The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile.",
|
||||
"type": "string"
|
||||
},
|
||||
"dir": {
|
||||
"description": "The working directory of the included tasks when run.",
|
||||
"type": "string"
|
||||
},
|
||||
"optional": {
|
||||
"description": "If `true`, no errors will be thrown if the specified file does not exist.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"internal": {
|
||||
"description": "Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"aliases": {
|
||||
"description": "Alternative names for the namespace of the included Taskfile.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"vars": {
|
||||
"description": "A set of variables to apply to the included Taskfile.",
|
||||
"$ref": "#/definitions/3/vars"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"vars": {
|
||||
"description": "A set of global variables.",
|
||||
"$ref": "#/definitions/3/vars"
|
||||
},
|
||||
"env": {
|
||||
"description": "A set of global environment variables.",
|
||||
"$ref": "#/definitions/3/env"
|
||||
},
|
||||
"tasks": {
|
||||
"description": "A set of task definitions.",
|
||||
"$ref": "#/definitions/3/tasks"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Default 'silent' options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"dotenv": {
|
||||
"type": "array",
|
||||
"description": "A list of `.env` file paths to be parsed.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"description": "Default 'run' option for this Taskfile. Available options: `always`, `once` and `when_changed`.",
|
||||
"$ref": "#/definitions/3/run"
|
||||
},
|
||||
"interval": {
|
||||
"description": "Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid Go duration: https://pkg.go.dev/time#ParseDuration.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+(?:m|s|ms)$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["version"],
|
||||
"anyOf": [
|
||||
{
|
||||
"required": ["includes"]
|
||||
},
|
||||
{
|
||||
"required": ["tasks"]
|
||||
},
|
||||
{
|
||||
"required": ["includes", "tasks"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2487
docs/yarn.lock
2487
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
23
errors.go
23
errors.go
@@ -3,6 +3,7 @@ package task
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
@@ -13,11 +14,29 @@ var (
|
||||
)
|
||||
|
||||
type taskNotFoundError struct {
|
||||
taskName string
|
||||
taskName string
|
||||
didYouMean string
|
||||
}
|
||||
|
||||
func (err *taskNotFoundError) Error() string {
|
||||
return fmt.Sprintf(`task: Task %q not found`, err.taskName)
|
||||
if err.didYouMean != "" {
|
||||
return fmt.Sprintf(
|
||||
`task: Task %q does not exist. Did you mean %q?`,
|
||||
err.taskName,
|
||||
err.didYouMean,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`task: Task %q does not exist`, err.taskName)
|
||||
}
|
||||
|
||||
type multipleTasksWithAliasError struct {
|
||||
aliasName string
|
||||
taskNames []string
|
||||
}
|
||||
|
||||
func (err *multipleTasksWithAliasError) Error() string {
|
||||
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.taskNames, ", "), err.aliasName)
|
||||
}
|
||||
|
||||
type taskInternalError struct {
|
||||
|
||||
14
go.mod
14
go.mod
@@ -4,14 +4,16 @@ require (
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/mattn/go-zglob v0.0.3
|
||||
github.com/mattn/go-zglob v0.0.4
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/radovskyb/watcher v1.0.7
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||
golang.org/x/sync v0.1.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899
|
||||
mvdan.cc/sh/v3 v3.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -19,8 +21,8 @@ require (
|
||||
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-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/term v0.3.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
|
||||
|
||||
39
go.sum
39
go.sum
@@ -1,49 +1,54 @@
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
|
||||
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
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=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
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=
|
||||
@@ -51,5 +56,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899 h1:nwm4t5PtLlFd/H342GP50CtYf7vyMCOZkPx3g9shO0c=
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
||||
mvdan.cc/sh/v3 v3.6.0 h1:gtva4EXJ0dFNvl5bHjcUEvws+KRcDslT8VKheTYkbGU=
|
||||
mvdan.cc/sh/v3 v3.6.0/go.mod h1:U4mhtBLZ32iWhif5/lD+ygy1zrgaQhUu+XFy7C8+TTA=
|
||||
|
||||
148
help.go
148
help.go
@@ -1,6 +1,8 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -9,73 +11,103 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-task/task/v3/internal/editors"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
// ListTasksWithDesc reports tasks that have a description spec.
|
||||
func (e *Executor) ListTasksWithDesc() {
|
||||
e.printTasks(false)
|
||||
// ListOptions collects list-related options
|
||||
type ListOptions struct {
|
||||
ListOnlyTasksWithDescriptions bool
|
||||
ListAllTasks bool
|
||||
FormatTaskListAsJSON bool
|
||||
}
|
||||
|
||||
// ListAllTasks reports all tasks, with or without a description spec.
|
||||
func (e *Executor) ListAllTasks() {
|
||||
e.printTasks(true)
|
||||
// NewListOptions creates a new ListOptions instance
|
||||
func NewListOptions(list, listAll, listAsJson bool) ListOptions {
|
||||
return ListOptions{
|
||||
ListOnlyTasksWithDescriptions: list,
|
||||
ListAllTasks: listAll,
|
||||
FormatTaskListAsJSON: listAsJson,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) printTasks(listAll bool) {
|
||||
var tasks []*taskfile.Task
|
||||
if listAll {
|
||||
tasks = e.allTaskNames()
|
||||
} else {
|
||||
tasks = e.tasksWithDesc()
|
||||
// ShouldListTasks returns true if one of the options to list tasks has been set to true
|
||||
func (o ListOptions) ShouldListTasks() bool {
|
||||
return o.ListOnlyTasksWithDescriptions || o.ListAllTasks
|
||||
}
|
||||
|
||||
// Validate validates that the collection of list-related options are in a valid configuration
|
||||
func (o ListOptions) Validate() error {
|
||||
if o.ListOnlyTasksWithDescriptions && o.ListAllTasks {
|
||||
return fmt.Errorf("task: cannot use --list and --list-all at the same time")
|
||||
}
|
||||
if o.FormatTaskListAsJSON && !o.ShouldListTasks() {
|
||||
return fmt.Errorf("task: --json only applies to --list or --list-all")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filters returns the slice of FilterFunc which filters a list
|
||||
// of taskfile.Task according to the given ListOptions
|
||||
func (o ListOptions) Filters() []FilterFunc {
|
||||
filters := []FilterFunc{FilterOutInternal()}
|
||||
|
||||
if o.ListOnlyTasksWithDescriptions {
|
||||
filters = append(filters, FilterOutNoDesc())
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
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 filters
|
||||
}
|
||||
|
||||
// ListTasks prints a list of tasks.
|
||||
// Tasks that match the given filters will be excluded from the list.
|
||||
// The function returns a boolean indicating whether tasks were found
|
||||
// and an error if one was encountered while preparing the output.
|
||||
func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
tasks := e.GetTaskList(o.Filters()...)
|
||||
if o.FormatTaskListAsJSON {
|
||||
output, err := e.ToEditorOutput(tasks)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return
|
||||
|
||||
encoder := json.NewEncoder(e.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
if err := encoder.Encode(output); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(tasks) > 0, nil
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
if o.ListOnlyTasksWithDescriptions {
|
||||
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
|
||||
} else if o.ListAllTasks {
|
||||
e.Logger.Outf(logger.Yellow, "task: No tasks available")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 6, ' ', 0)
|
||||
for _, task := range tasks {
|
||||
fmt.Fprintf(w, "* %s: \t%s\n", task.Name(), task.Desc)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (e *Executor) allTaskNames() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if !task.Internal {
|
||||
tasks = append(tasks, task)
|
||||
e.Logger.FOutf(w, logger.Yellow, "* ")
|
||||
e.Logger.FOutf(w, logger.Green, task.Task)
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc)
|
||||
if len(task.Aliases) > 0 {
|
||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||
}
|
||||
_, _ = fmt.Fprint(w, "\n")
|
||||
}
|
||||
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
|
||||
return
|
||||
if err := w.Flush(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if !task.Internal && task.Desc != "" {
|
||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
||||
if err == nil {
|
||||
task = compiledTask
|
||||
}
|
||||
tasks = append(tasks, 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.
|
||||
// ListTaskNames prints only the task names in a Taskfile.
|
||||
// Only tasks with a non-empty description are printed if allTasks is false.
|
||||
// Otherwise, all task names are printed.
|
||||
func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
@@ -96,6 +128,9 @@ func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
for _, t := range e.Taskfile.Tasks {
|
||||
if (allTasks || t.Desc != "") && !t.Internal {
|
||||
s = append(s, strings.TrimRight(t.Task, ":"))
|
||||
for _, alias := range t.Aliases {
|
||||
s = append(s, strings.TrimRight(alias, ":"))
|
||||
}
|
||||
}
|
||||
}
|
||||
// sort and print all task names
|
||||
@@ -104,3 +139,22 @@ func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
fmt.Fprintln(w, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) ToEditorOutput(tasks []*taskfile.Task) (*editors.Output, error) {
|
||||
o := &editors.Output{
|
||||
Tasks: make([]editors.Task, len(tasks)),
|
||||
}
|
||||
for i, t := range tasks {
|
||||
upToDate, err := e.isTaskUpToDate(context.Background(), t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Tasks[i] = editors.Task{
|
||||
Name: t.Name(),
|
||||
Desc: t.Desc,
|
||||
Summary: t.Summary,
|
||||
UpToDate: upToDate,
|
||||
}
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
@@ -64,15 +64,21 @@ get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/armv5) BINARIES="task" ;;
|
||||
darwin/armv6) BINARIES="task" ;;
|
||||
darwin/armv7) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/armv5) BINARIES="task" ;;
|
||||
linux/armv6) BINARIES="task" ;;
|
||||
linux/armv7) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/armv5) BINARIES="task" ;;
|
||||
windows/armv6) BINARIES="task" ;;
|
||||
windows/armv7) BINARIES="task" ;;
|
||||
*)
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
@@ -184,9 +190,9 @@ uname_arch() {
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
armv5*) arch="arm" ;;
|
||||
armv6*) arch="arm" ;;
|
||||
armv7*) arch="arm" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
@@ -214,9 +220,7 @@ uname_arch_check() {
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
arm) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
var _ compiler.Compiler = &CompilerV3{}
|
||||
|
||||
type CompilerV3 struct {
|
||||
Dir string
|
||||
Dir string
|
||||
UserWorkingDir string
|
||||
|
||||
TaskfileEnv *taskfile.Vars
|
||||
TaskfileVars *taskfile.Vars
|
||||
@@ -179,9 +180,10 @@ func (c *CompilerV3) getSpecialVars(t *taskfile.Task) (map[string]string, error)
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"TASK": t.Task,
|
||||
"ROOT_DIR": c.Dir,
|
||||
"TASKFILE_DIR": taskfileDir,
|
||||
"TASK": t.Task,
|
||||
"ROOT_DIR": c.Dir,
|
||||
"TASKFILE_DIR": taskfileDir,
|
||||
"USER_WORKING_DIR": c.UserWorkingDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
14
internal/editors/output.go
Normal file
14
internal/editors/output.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package editors
|
||||
|
||||
// Output wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||
type Output struct {
|
||||
Tasks []Task `json:"tasks"`
|
||||
}
|
||||
|
||||
// Task describes a single task
|
||||
type Task struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
Summary string `json:"summary"`
|
||||
UpToDate bool `json:"up_to_date"`
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package filepathext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@@ -12,3 +13,19 @@ func SmartJoin(a, b string) string {
|
||||
}
|
||||
return filepath.Join(a, b)
|
||||
}
|
||||
|
||||
// TryAbsToRel tries to convert an absolute path to relative based on the
|
||||
// process working directory. If it can't, it returns the absolute path.
|
||||
func TryAbsToRel(abs string) string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return abs
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(wd, abs)
|
||||
if err != nil {
|
||||
return abs
|
||||
}
|
||||
|
||||
return rel
|
||||
}
|
||||
|
||||
@@ -53,6 +53,11 @@ type Logger struct {
|
||||
|
||||
// Outf prints stuff to STDOUT.
|
||||
func (l *Logger) Outf(color Color, s string, args ...interface{}) {
|
||||
l.FOutf(l.Stdout, color, s+"\n", args...)
|
||||
}
|
||||
|
||||
// FOutf prints stuff to the given writer.
|
||||
func (l *Logger) FOutf(w io.Writer, color Color, s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
@@ -60,7 +65,7 @@ func (l *Logger) Outf(color Color, s string, args ...interface{}) {
|
||||
color = Default
|
||||
}
|
||||
print := color()
|
||||
print(l.Stdout, s+"\n", args...)
|
||||
print(w, s, args...)
|
||||
}
|
||||
|
||||
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
|
||||
|
||||
@@ -6,11 +6,11 @@ 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"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestInterleaved(t *testing.T) {
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
// This code is released under the MIT License
|
||||
// Copyright (c) 2020 Marco Molteni and the timeit contributors.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
const usage = `sleepit: sleep for the specified duration, optionally handling signals
|
||||
When the line "sleepit: ready" is printed, it means that it is safe to send signals to it
|
||||
|
||||
Usage: sleepit <command> [<args>]
|
||||
|
||||
Commands
|
||||
|
||||
default Use default action: on reception of SIGINT terminate abruptly
|
||||
handle Handle signals: on reception of SIGINT perform cleanup before exiting
|
||||
version Show the sleepit version`
|
||||
|
||||
var (
|
||||
// Filled by the linker.
|
||||
fullVersion = "unknown" // example: v0.0.9-8-g941583d027-dirty
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(run(os.Args[1:]))
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, usage)
|
||||
return 2
|
||||
}
|
||||
|
||||
defaultCmd := flag.NewFlagSet("default", flag.ExitOnError)
|
||||
defaultSleep := defaultCmd.Duration("sleep", 5*time.Second, "Sleep duration")
|
||||
|
||||
handleCmd := flag.NewFlagSet("handle", flag.ExitOnError)
|
||||
handleSleep := handleCmd.Duration("sleep", 5*time.Second, "Sleep duration")
|
||||
handleCleanup := handleCmd.Duration("cleanup", 5*time.Second, "Cleanup duration")
|
||||
handleTermAfter := handleCmd.Int("term-after", 0,
|
||||
"Terminate immediately after `N` signals.\n"+
|
||||
"Default is to terminate only when the cleanup phase has completed.")
|
||||
|
||||
versionCmd := flag.NewFlagSet("version", flag.ExitOnError)
|
||||
|
||||
switch args[0] {
|
||||
|
||||
case "default":
|
||||
_ = defaultCmd.Parse(args[1:])
|
||||
if len(defaultCmd.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "default: unexpected arguments: %v\n", defaultCmd.Args())
|
||||
return 2
|
||||
}
|
||||
return supervisor(*defaultSleep, 0, 0, nil)
|
||||
|
||||
case "handle":
|
||||
_ = handleCmd.Parse(args[1:])
|
||||
if *handleTermAfter == 1 {
|
||||
fmt.Fprintf(os.Stderr, "handle: term-after cannot be 1\n")
|
||||
return 2
|
||||
}
|
||||
if len(handleCmd.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "handle: unexpected arguments: %v\n", handleCmd.Args())
|
||||
return 2
|
||||
}
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt) // Ctrl-C -> SIGINT
|
||||
return supervisor(*handleSleep, *handleCleanup, *handleTermAfter, sigCh)
|
||||
|
||||
case "version":
|
||||
_ = versionCmd.Parse(args[1:])
|
||||
if len(versionCmd.Args()) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "version: unexpected arguments: %v\n", versionCmd.Args())
|
||||
return 2
|
||||
}
|
||||
fmt.Printf("sleepit version %s\n", fullVersion)
|
||||
return 0
|
||||
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, usage)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func supervisor(
|
||||
sleep time.Duration,
|
||||
cleanup time.Duration,
|
||||
termAfter int,
|
||||
sigCh <-chan os.Signal,
|
||||
) int {
|
||||
fmt.Printf("sleepit: ready\n")
|
||||
fmt.Printf("sleepit: PID=%d sleep=%v cleanup=%v\n",
|
||||
os.Getpid(), sleep, cleanup)
|
||||
|
||||
cancelWork := make(chan struct{})
|
||||
workerDone := worker(cancelWork, sleep, "work")
|
||||
|
||||
cancelCleaner := make(chan struct{})
|
||||
var cleanerDone <-chan struct{}
|
||||
|
||||
sigCount := 0
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigCh:
|
||||
sigCount++
|
||||
fmt.Printf("sleepit: got signal=%s count=%d\n", sig, sigCount)
|
||||
if sigCount == 1 {
|
||||
// since `cancelWork` is unbuffered, sending will be synchronous:
|
||||
// we are ensured that the worker has terminated before starting cleanup.
|
||||
// This is important in some real-life situations.
|
||||
cancelWork <- struct{}{}
|
||||
cleanerDone = worker(cancelCleaner, cleanup, "cleanup")
|
||||
}
|
||||
if sigCount == termAfter {
|
||||
cancelCleaner <- struct{}{}
|
||||
return 4
|
||||
}
|
||||
case <-workerDone:
|
||||
return 0
|
||||
case <-cleanerDone:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start a worker goroutine and return immediately a `workerDone` channel.
|
||||
// The goroutine will prepend its prints with the prefix `name`.
|
||||
// The goroutine will simulate some work and will terminate when one of the following
|
||||
// conditions happens:
|
||||
// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel.
|
||||
// 2. When something happens on channel `canceled`. Note that this simulates real-life,
|
||||
// so cancellation is not instantaneous: if the caller wants a synchronous cancel,
|
||||
// it should send a message; if instead it wants an asynchronous cancel, it should
|
||||
// close the channel.
|
||||
func worker(
|
||||
canceled <-chan struct{},
|
||||
howlong time.Duration,
|
||||
name string,
|
||||
) <-chan struct{} {
|
||||
workerDone := make(chan struct{})
|
||||
deadline := time.Now().Add(howlong)
|
||||
go func() {
|
||||
fmt.Printf("sleepit: %s started\n", name)
|
||||
for {
|
||||
select {
|
||||
case <-canceled:
|
||||
fmt.Printf("sleepit: %s canceled\n", name)
|
||||
return
|
||||
default:
|
||||
if doSomeWork(deadline) {
|
||||
fmt.Printf("sleepit: %s done\n", name) // <== NOTE THIS LINE
|
||||
workerDone <- struct{}{}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return workerDone
|
||||
}
|
||||
|
||||
// Do some work and then return, so that the caller can decide wether to continue or not.
|
||||
// Return true when all work is done.
|
||||
func doSomeWork(deadline time.Time) bool {
|
||||
if time.Now().After(deadline) {
|
||||
return true
|
||||
}
|
||||
timeout := 100 * time.Millisecond
|
||||
time.Sleep(timeout)
|
||||
return false
|
||||
}
|
||||
@@ -28,6 +28,7 @@ func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskAliases(l, t)
|
||||
printTaskCommands(l, t)
|
||||
}
|
||||
|
||||
@@ -56,10 +57,23 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||
}
|
||||
|
||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "task: %s", t.Name())
|
||||
l.FOutf(l.Stdout, logger.Default, "task: ")
|
||||
l.FOutf(l.Stdout, logger.Green, "%s\n", t.Name())
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func printTaskAliases(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Aliases) == 0 {
|
||||
return
|
||||
}
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "aliases:")
|
||||
for _, alias := range t.Aliases {
|
||||
l.FOutf(l.Stdout, logger.Default, " - ")
|
||||
l.Outf(logger.Cyan, alias)
|
||||
}
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
return t.Desc != ""
|
||||
}
|
||||
@@ -94,10 +108,11 @@ func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "commands:")
|
||||
for _, c := range t.Cmds {
|
||||
isCommand := c.Cmd != ""
|
||||
l.FOutf(l.Stdout, logger.Default, " - ")
|
||||
if isCommand {
|
||||
l.Outf(logger.Default, " - %s", c.Cmd)
|
||||
l.FOutf(l.Stdout, logger.Yellow, "%s\n", c.Cmd)
|
||||
} else {
|
||||
l.Outf(logger.Default, " - Task: %s", c.Task)
|
||||
l.FOutf(l.Stdout, logger.Green, "Task: %s\n", c.Task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
internal/sysinfo/uid.go
Normal file
22
internal/sysinfo/uid.go
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build !windows
|
||||
|
||||
package sysinfo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Owner(path string) (int, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var uid int
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
uid = int(stat.Uid)
|
||||
} else {
|
||||
uid = os.Getuid()
|
||||
}
|
||||
return uid, nil
|
||||
}
|
||||
9
internal/sysinfo/uid_win.go
Normal file
9
internal/sysinfo/uid_win.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build windows
|
||||
|
||||
package sysinfo
|
||||
|
||||
// NOTE: This always returns -1 since there is currently no easy way to get
|
||||
// file owner information on Windows.
|
||||
func Owner(path string) (int, error) {
|
||||
return -1, nil
|
||||
}
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.16.0",
|
||||
"version": "3.19.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.16.0",
|
||||
"version": "3.19.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": "^0.1.15"
|
||||
"@go-task/go-npm": "^0.1.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@go-task/go-npm": {
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz",
|
||||
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw==",
|
||||
"version": "0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.17.tgz",
|
||||
"integrity": "sha512-j+xydQWrAxsqLYjweok1fWzDmBAA1g/gmFbPyG8kRI/d/+rzXGGLlro8zdS6mJ3Is+8BrIy2ZBmQkoONhowh7A==",
|
||||
"bin": {
|
||||
"go-npm": "bin/index.js"
|
||||
}
|
||||
@@ -24,9 +24,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": {
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz",
|
||||
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw=="
|
||||
"version": "0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.17.tgz",
|
||||
"integrity": "sha512-j+xydQWrAxsqLYjweok1fWzDmBAA1g/gmFbPyG8kRI/d/+rzXGGLlro8zdS6mJ3Is+8BrIy2ZBmQkoONhowh7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.16.0",
|
||||
"version": "3.19.1",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
@@ -29,6 +29,6 @@
|
||||
},
|
||||
"homepage": "https://taskfile.dev",
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": "^0.1.15"
|
||||
"@go-task/go-npm": "^0.1.17"
|
||||
}
|
||||
}
|
||||
|
||||
40
setup.go
40
setup.go
@@ -17,6 +17,8 @@ import (
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/read"
|
||||
|
||||
"github.com/sajari/fuzzy"
|
||||
)
|
||||
|
||||
func (e *Executor) Setup() error {
|
||||
@@ -28,6 +30,8 @@ func (e *Executor) Setup() error {
|
||||
return err
|
||||
}
|
||||
|
||||
e.setupFuzzyModel()
|
||||
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -76,7 +80,7 @@ func (e *Executor) setCurrentDir() error {
|
||||
|
||||
func (e *Executor) readTaskfile() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
|
||||
e.Taskfile, e.Dir, err = read.Taskfile(&read.ReaderNode{
|
||||
Dir: e.Dir,
|
||||
Entrypoint: e.Entrypoint,
|
||||
Parent: nil,
|
||||
@@ -85,6 +89,27 @@ func (e *Executor) readTaskfile() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Executor) setupFuzzyModel() {
|
||||
if e.Taskfile != nil {
|
||||
return
|
||||
}
|
||||
|
||||
model := fuzzy.NewModel()
|
||||
model.SetThreshold(1) // because we want to build grammar based on every task name
|
||||
|
||||
var words []string
|
||||
for taskName := range e.Taskfile.Tasks {
|
||||
words = append(words, taskName)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
words = append(words, task.Aliases...)
|
||||
}
|
||||
}
|
||||
|
||||
model.Train(words)
|
||||
e.fuzzyModel = model
|
||||
}
|
||||
|
||||
func (e *Executor) setupTempDir() error {
|
||||
if e.TempDir != "" {
|
||||
return nil
|
||||
@@ -154,11 +179,16 @@ func (e *Executor) setupCompiler(v float64) error {
|
||||
Logger: e.Logger,
|
||||
}
|
||||
} else {
|
||||
userWorkingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Compiler = &compilerv3.CompilerV3{
|
||||
Dir: e.Dir,
|
||||
TaskfileEnv: e.Taskfile.Env,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Logger: e.Logger,
|
||||
Dir: e.Dir,
|
||||
UserWorkingDir: userWorkingDir,
|
||||
TaskfileEnv: e.Taskfile.Env,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
149
task.go
149
task.go
@@ -5,8 +5,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
@@ -16,6 +19,8 @@ import (
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
|
||||
"github.com/sajari/fuzzy"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -41,6 +46,7 @@ type Executor struct {
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
@@ -51,7 +57,8 @@ type Executor struct {
|
||||
Output output.Output
|
||||
OutputStyle taskfile.Output
|
||||
|
||||
taskvars *taskfile.Vars
|
||||
taskvars *taskfile.Vars
|
||||
fuzzyModel *fuzzy.Model
|
||||
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
@@ -63,16 +70,24 @@ type Executor struct {
|
||||
// Run runs Task
|
||||
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
// check if given tasks exist
|
||||
for _, c := range calls {
|
||||
t, ok := e.Taskfile.Tasks[c.Task]
|
||||
if !ok {
|
||||
// FIXME: move to the main package
|
||||
e.ListTasksWithDesc()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
for _, call := range calls {
|
||||
task, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
if _, ok := err.(*taskNotFoundError); ok {
|
||||
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if t.Internal {
|
||||
e.ListTasksWithDesc()
|
||||
return &taskInternalError{taskName: c.Task}
|
||||
|
||||
if task.Internal {
|
||||
if _, ok := err.(*taskNotFoundError); ok {
|
||||
if _, err := e.ListTasks(ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return &taskInternalError{taskName: call.Task}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +127,8 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: call.Task}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[t.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: t.Task}
|
||||
}
|
||||
|
||||
release := e.acquireConcurrencyLimit()
|
||||
@@ -330,3 +345,113 @@ func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute
|
||||
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
// GetTask will return the task with the name matching the given call from the taskfile.
|
||||
// If no task is found, it will search for tasks with a matching alias.
|
||||
// If multiple tasks contain the same alias or no matches are found an error is returned.
|
||||
func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
||||
// Search for a matching task
|
||||
matchingTask, ok := e.Taskfile.Tasks[call.Task]
|
||||
if ok {
|
||||
return matchingTask, nil
|
||||
}
|
||||
|
||||
// If didn't find one, search for a task with a matching alias
|
||||
var aliasedTasks []string
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTask = task
|
||||
}
|
||||
}
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &multipleTasksWithAliasError{
|
||||
aliasName: call.Task,
|
||||
taskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
// If we found no tasks
|
||||
if len(aliasedTasks) == 0 {
|
||||
didYouMean := ""
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
}
|
||||
return nil, &taskNotFoundError{
|
||||
taskName: call.Task,
|
||||
didYouMean: didYouMean,
|
||||
}
|
||||
}
|
||||
|
||||
return matchingTask, nil
|
||||
}
|
||||
|
||||
type FilterFunc func(tasks []*taskfile.Task) []*taskfile.Task
|
||||
|
||||
func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
|
||||
tasks := make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
|
||||
// Fetch and compile the list of tasks
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
||||
if err == nil {
|
||||
task = compiledTask
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
|
||||
// Filter the tasks
|
||||
for _, filter := range filters {
|
||||
tasks = filter(tasks)
|
||||
}
|
||||
|
||||
// Sort the tasks.
|
||||
// Tasks that are not namespaced should be listed before tasks that are.
|
||||
// We detect this by searching for a ':' in the task name.
|
||||
sort.Slice(tasks, func(i, j int) bool {
|
||||
iContainsColon := strings.Contains(tasks[i].Task, ":")
|
||||
jContainsColon := strings.Contains(tasks[j].Task, ":")
|
||||
if iContainsColon == jContainsColon {
|
||||
return tasks[i].Task < tasks[j].Task
|
||||
}
|
||||
if !iContainsColon && jContainsColon {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
// Filter is a generic task filtering function. It will remove each task in the
|
||||
// slice where the result of the given function is true.
|
||||
func Filter(f func(task *taskfile.Task) bool) FilterFunc {
|
||||
return func(tasks []*taskfile.Task) []*taskfile.Task {
|
||||
shift := 0
|
||||
for _, task := range tasks {
|
||||
if !f(task) {
|
||||
tasks[shift] = task
|
||||
shift++
|
||||
}
|
||||
}
|
||||
// This loop stops any memory leaks
|
||||
for j := shift; j < len(tasks); j++ {
|
||||
tasks[j] = nil
|
||||
}
|
||||
return slices.Clip(tasks[:shift])
|
||||
}
|
||||
}
|
||||
|
||||
// FilterOutNoDesc removes all tasks that do not contain a description.
|
||||
func FilterOutNoDesc() FilterFunc {
|
||||
return Filter(func(task *taskfile.Task) bool {
|
||||
return task.Desc == ""
|
||||
})
|
||||
}
|
||||
|
||||
// FilterOutInternal removes all tasks that are marked as internal.
|
||||
func FilterOutInternal() FilterFunc {
|
||||
return Filter(func(task *taskfile.Task) bool {
|
||||
return task.Internal
|
||||
})
|
||||
}
|
||||
|
||||
254
task_test.go
254
task_test.go
@@ -476,6 +476,55 @@ func TestStatusChecksum(t *testing.T) {
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
}
|
||||
|
||||
func TestAlias(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
func TestDuplicateAlias(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "x"}))
|
||||
assert.Equal(t, "", buff.String())
|
||||
}
|
||||
|
||||
func TestAliasSummary(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-summary.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Summary: true,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
func TestLabelUpToDate(t *testing.T) {
|
||||
const dir = "testdata/label_uptodate"
|
||||
|
||||
@@ -546,7 +595,7 @@ func TestLabelInSummary(t *testing.T) {
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
}
|
||||
|
||||
func TestLabelInList(t *testing.T) {
|
||||
func TestNoLabelInList(t *testing.T) {
|
||||
const dir = "testdata/label_list"
|
||||
|
||||
var buff bytes.Buffer
|
||||
@@ -556,8 +605,10 @@ func TestLabelInList(t *testing.T) {
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
e.ListTasksWithDesc()
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Contains(t, buff.String(), "foo")
|
||||
}
|
||||
|
||||
// task -al case 1: listAll list all tasks
|
||||
@@ -574,7 +625,9 @@ func TestListAllShowsNoDesc(t *testing.T) {
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
var title string
|
||||
e.ListAllTasks()
|
||||
if _, err := e.ListTasks(task.ListOptions{ListAllTasks: true}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
for _, title = range []string{
|
||||
"foo",
|
||||
"voo",
|
||||
@@ -596,7 +649,9 @@ func TestListCanListDescOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
e.ListTasksWithDesc()
|
||||
if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var title string
|
||||
assert.Contains(t, buff.String(), "foo")
|
||||
@@ -838,6 +893,22 @@ func TestIncorrectVersionIncludes(t *testing.T) {
|
||||
assert.EqualError(t, e.Setup(), expectedError)
|
||||
}
|
||||
|
||||
func TestIncludesIncorrect(t *testing.T) {
|
||||
const dir = "testdata/includes_incorrect"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "task: Failed to parse testdata/includes_incorrect/incomplete.yml:")
|
||||
}
|
||||
|
||||
func TestIncludesEmptyMain(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_empty",
|
||||
@@ -968,12 +1039,7 @@ func TestIncludesInternal(t *testing.T) {
|
||||
}{
|
||||
{"included internal task via task", "task-1", false, "Hello, World!\n"},
|
||||
{"included internal task via dep", "task-2", false, "Hello, World!\n"},
|
||||
{
|
||||
"included internal direct",
|
||||
"included:task-3",
|
||||
true,
|
||||
"task: No tasks with description available. Try --list-all to list all tasks\n",
|
||||
},
|
||||
{"included internal direct", "included:task-3", true, "task: No tasks with description available. Try --list-all to list all tasks\n"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -989,7 +1055,41 @@ func TestIncludesInternal(t *testing.T) {
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err, test.expectedErr)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, test.expectedOutput, buff.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncludesInterpolation(t *testing.T) {
|
||||
const dir = "testdata/includes_interpolation"
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
expectedErr bool
|
||||
expectedOutput string
|
||||
}{
|
||||
{"include", "include", false, "includes_interpolation\n"},
|
||||
{"include with dir", "include-with-dir", false, "included\n"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -1008,12 +1108,7 @@ func TestInternalTask(t *testing.T) {
|
||||
}{
|
||||
{"internal task via task", "task-1", false, "Hello, World!\n"},
|
||||
{"internal task via dep", "task-2", false, "Hello, World!\n"},
|
||||
{
|
||||
"internal direct",
|
||||
"task-3",
|
||||
true,
|
||||
"task: No tasks with description available. Try --list-all to list all tasks\n",
|
||||
},
|
||||
{"internal direct", "task-3", true, ""},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -1029,7 +1124,7 @@ func TestInternalTask(t *testing.T) {
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err, test.expectedErr)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -1038,6 +1133,30 @@ func TestInternalTask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncludesShadowedDefault(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_shadowed_default",
|
||||
Target: "included",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"file.txt": "shadowed",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesUnshadowedDefault(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_unshadowed_default",
|
||||
Target: "included",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"file.txt": "included",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestSupportedFileNames(t *testing.T) {
|
||||
fileNames := []string{
|
||||
"Taskfile.yml",
|
||||
@@ -1296,6 +1415,54 @@ func TestDotenvHasEnvVarInPath(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenv(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "dotenv",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"dotenv.txt": "foo",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenvFail(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "no-dotenv",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"no-dotenv.txt": "global",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenvOverriddenByEnv(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "dotenv-overridden-by-env",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"dotenv-overridden-by-env.txt": "overridden",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenvWithVarName(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "dotenv-with-var-name",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"dotenv-with-var-name.txt": "foo",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestExitImmediately(t *testing.T) {
|
||||
const dir = "testdata/exit_immediately"
|
||||
|
||||
@@ -1480,3 +1647,52 @@ func TestEvaluateSymlinksInPaths(t *testing.T) {
|
||||
err = os.RemoveAll(dir + "/.task")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestTaskfileWalk(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "walk from root directory",
|
||||
dir: "testdata/taskfile_walk",
|
||||
expected: "foo\n",
|
||||
}, {
|
||||
name: "walk from sub directory",
|
||||
dir: "testdata/taskfile_walk/foo",
|
||||
expected: "foo\n",
|
||||
}, {
|
||||
name: "walk from sub sub directory",
|
||||
dir: "testdata/taskfile_walk/foo/bar",
|
||||
expected: "foo\n",
|
||||
},
|
||||
}
|
||||
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,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
assert.Equal(t, test.expected, buff.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserWorkingDirectory(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: "testdata/user_working_dir",
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
|
||||
}
|
||||
|
||||
143
taskfile/cmd.go
143
taskfile/cmd.go
@@ -1,5 +1,11 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Cmd is a task command
|
||||
type Cmd struct {
|
||||
Cmd string
|
||||
@@ -16,68 +22,93 @@ type Dep struct {
|
||||
Vars *Vars
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd string
|
||||
if err := unmarshal(&cmd); err == nil {
|
||||
func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Cmd = cmd
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
|
||||
// A command with additional options
|
||||
var cmdStruct struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
}
|
||||
if err := node.Decode(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
}
|
||||
|
||||
// A deferred command
|
||||
var deferredCmd struct {
|
||||
Defer string
|
||||
}
|
||||
if err := node.Decode(&deferredCmd); err == nil && deferredCmd.Defer != "" {
|
||||
c.Defer = true
|
||||
c.Cmd = deferredCmd.Defer
|
||||
return nil
|
||||
}
|
||||
|
||||
// A deferred task call
|
||||
var deferredCall struct {
|
||||
Defer Call
|
||||
}
|
||||
if err := node.Decode(&deferredCall); err == nil && deferredCall.Defer.Task != "" {
|
||||
c.Defer = true
|
||||
c.Task = deferredCall.Defer.Task
|
||||
c.Vars = deferredCall.Defer.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
// A task call
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := node.Decode(&taskCall); err == nil && taskCall.Task != "" {
|
||||
c.Task = taskCall.Task
|
||||
c.Vars = taskCall.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
|
||||
}
|
||||
var cmdStruct struct {
|
||||
Cmd string
|
||||
Silent bool
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
}
|
||||
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.Silent = cmdStruct.Silent
|
||||
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
|
||||
}
|
||||
if err := unmarshal(&taskCall); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Task = taskCall.Task
|
||||
c.Vars = taskCall.Vars
|
||||
return nil
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var task string
|
||||
if err := unmarshal(&task); err == nil {
|
||||
func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var task string
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Task = task
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := node.Decode(&taskCall); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Task = taskCall.Task
|
||||
d.Vars = taskCall.Vars
|
||||
return nil
|
||||
}
|
||||
var taskCall struct {
|
||||
Task string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&taskCall); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Task = taskCall.Task
|
||||
d.Vars = taskCall.Vars
|
||||
return nil
|
||||
|
||||
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
|
||||
}
|
||||
|
||||
23
taskfile/copy.go
Normal file
23
taskfile/copy.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package taskfile
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
func deepCopySlice[T any](orig []T) []T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make([]T, len(orig))
|
||||
copy(c, orig)
|
||||
return c
|
||||
}
|
||||
|
||||
func deepCopyMap[K constraints.Ordered, V any](orig map[K]V) map[K]V {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make(map[K]V, len(orig))
|
||||
for k, v := range orig {
|
||||
c[k] = v
|
||||
}
|
||||
return c
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ type IncludedTaskfile struct {
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
BaseDir string // The directory from which the including taskfile was loaded; used to resolve relative paths
|
||||
@@ -30,24 +31,26 @@ type IncludedTaskfiles struct {
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
|
||||
if node.Kind != yaml.MappingNode {
|
||||
return errors.New("task: includes is not a map")
|
||||
}
|
||||
switch node.Kind {
|
||||
|
||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
case yaml.MappingNode:
|
||||
// NOTE(@andreynering): on this style of custom unmarshalling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
var v IncludedTaskfile
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
var v IncludedTaskfile
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
tfs.Set(keyNode.Value, v)
|
||||
}
|
||||
tfs.Set(keyNode.Value, v)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// Len returns the length of the map
|
||||
@@ -71,7 +74,7 @@ func (tfs *IncludedTaskfiles) Set(key string, includedTaskfile IncludedTaskfile)
|
||||
if tfs.Mapping == nil {
|
||||
tfs.Mapping = make(map[string]IncludedTaskfile, 1)
|
||||
}
|
||||
if !stringSliceContains(tfs.Keys, key) {
|
||||
if !slices.Contains(tfs.Keys, key) {
|
||||
tfs.Keys = append(tfs.Keys, key)
|
||||
}
|
||||
tfs.Mapping[key] = includedTaskfile
|
||||
@@ -90,31 +93,57 @@ func (tfs *IncludedTaskfiles) Range(yield func(key string, includedTaskfile Incl
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var str string
|
||||
if err := unmarshal(&str); err == nil {
|
||||
func (it *IncludedTaskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var str string
|
||||
if err := node.Decode(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
it.Taskfile = str
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var includedTaskfile struct {
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := node.Decode(&includedTaskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
it.Taskfile = includedTaskfile.Taskfile
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.Internal = includedTaskfile.Internal
|
||||
it.Aliases = includedTaskfile.Aliases
|
||||
it.AdvancedImport = true
|
||||
it.Vars = includedTaskfile.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
var includedTaskfile struct {
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Vars *Vars
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
||||
// data by value from the source struct.
|
||||
func (it *IncludedTaskfile) DeepCopy() *IncludedTaskfile {
|
||||
if it == nil {
|
||||
return nil
|
||||
}
|
||||
if err := unmarshal(&includedTaskfile); err != nil {
|
||||
return err
|
||||
return &IncludedTaskfile{
|
||||
Taskfile: it.Taskfile,
|
||||
Dir: it.Dir,
|
||||
Optional: it.Optional,
|
||||
Internal: it.Internal,
|
||||
AdvancedImport: it.AdvancedImport,
|
||||
Vars: it.Vars.DeepCopy(),
|
||||
BaseDir: it.BaseDir,
|
||||
}
|
||||
it.Taskfile = includedTaskfile.Taskfile
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.Internal = includedTaskfile.Internal
|
||||
it.AdvancedImport = true
|
||||
it.Vars = includedTaskfile.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
// FullTaskfilePath returns the fully qualified path to the included taskfile
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
const NamespaceSeparator = ":"
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
func Merge(t1, t2 *Taskfile, internal bool, namespaces ...string) error {
|
||||
func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...string) error {
|
||||
if t1.Version != t2.Version {
|
||||
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||
}
|
||||
@@ -39,22 +39,38 @@ func Merge(t1, t2 *Taskfile, internal bool, namespaces ...string) error {
|
||||
t1.Tasks = make(Tasks)
|
||||
}
|
||||
for k, v := range t2.Tasks {
|
||||
// FIXME(@andreynering): Refactor this block, otherwise we can
|
||||
// have serious side-effects in the future, since we're editing
|
||||
// the original references instead of deep copying them.
|
||||
// We do a deep copy of the task struct here to ensure that no data can
|
||||
// be changed elsewhere once the taskfile is merged.
|
||||
task := v.DeepCopy()
|
||||
|
||||
v.Internal = v.Internal || internal
|
||||
// Set the task to internal if EITHER the included task or the included
|
||||
// taskfile are marked as internal
|
||||
task.Internal = task.Internal || (includedTaskfile != nil && includedTaskfile.Internal)
|
||||
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
|
||||
|
||||
for _, dep := range v.Deps {
|
||||
// Add namespaces to dependencies, commands and aliases
|
||||
for _, dep := range task.Deps {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
||||
}
|
||||
for _, cmd := range v.Cmds {
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
||||
}
|
||||
}
|
||||
for i, alias := range task.Aliases {
|
||||
task.Aliases[i] = taskNameWithNamespace(alias, namespaces...)
|
||||
}
|
||||
// Add namespace aliases
|
||||
if includedTaskfile != nil {
|
||||
for _, namespaceAlias := range includedTaskfile.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
|
||||
for _, alias := range v.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the task to the merged taskfile
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = task
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,6 +2,8 @@ package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Output of the Task output
|
||||
@@ -17,28 +19,35 @@ 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 {
|
||||
func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var name string
|
||||
if err := node.Decode(&name); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Name = name
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var tmp struct {
|
||||
Group *OutputGroup
|
||||
}
|
||||
if err := node.Decode(&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
|
||||
}
|
||||
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
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// OutputGroup is the style options specific to the Group style.
|
||||
|
||||
@@ -3,6 +3,8 @@ package taskfile
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,29 +19,33 @@ type Precondition struct {
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd string
|
||||
func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
if err := unmarshal(&cmd); err == nil {
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Sh = cmd
|
||||
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var sh struct {
|
||||
Sh string
|
||||
Msg string
|
||||
}
|
||||
if err := node.Decode(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Sh = sh.Sh
|
||||
p.Msg = sh.Msg
|
||||
if p.Msg == "" {
|
||||
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var sh struct {
|
||||
Sh string
|
||||
Msg string
|
||||
}
|
||||
|
||||
if err := unmarshal(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Sh = sh.Sh
|
||||
p.Msg = sh.Msg
|
||||
if p.Msg == "" {
|
||||
p.Msg = fmt.Sprintf("%s failed", sh.Sh)
|
||||
}
|
||||
|
||||
return nil
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/sysinfo"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
@@ -36,29 +37,30 @@ type ReaderNode struct {
|
||||
// Taskfile reads a Taskfile for a given directory
|
||||
// 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) {
|
||||
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, string, error) {
|
||||
if readerNode.Dir == "" {
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
readerNode.Dir = d
|
||||
}
|
||||
|
||||
path, err := exists(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
|
||||
path, err := existsWalk(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
readerNode.Dir = filepath.Dir(path)
|
||||
readerNode.Entrypoint = filepath.Base(path)
|
||||
|
||||
t, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
v, err := t.ParsedVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Annotate any included Taskfile reference with a base directory for resolving relative paths
|
||||
@@ -73,12 +75,13 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
|
||||
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
||||
if v >= 3.0 {
|
||||
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
|
||||
tr := templater.Templater{Vars: t.Vars, RemoveNoValue: true}
|
||||
includedTask = taskfile.IncludedTaskfile{
|
||||
Taskfile: tr.Replace(includedTask.Taskfile),
|
||||
Dir: tr.Replace(includedTask.Dir),
|
||||
Optional: includedTask.Optional,
|
||||
Internal: includedTask.Internal,
|
||||
Aliases: includedTask.Aliases,
|
||||
AdvancedImport: includedTask.AdvancedImport,
|
||||
Vars: includedTask.Vars,
|
||||
BaseDir: includedTask.BaseDir,
|
||||
@@ -112,7 +115,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
includedTaskfile, err := Taskfile(includeReaderNode)
|
||||
includedTaskfile, _, err := Taskfile(includeReaderNode)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
return nil
|
||||
@@ -149,13 +152,20 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = taskfile.Merge(t, includedTaskfile, includedTask.Internal, namespace); err != nil {
|
||||
if err = taskfile.Merge(t, includedTaskfile, &includedTask, namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if includedTaskfile.Tasks["default"] != nil && t.Tasks[namespace] == nil {
|
||||
defaultTaskName := fmt.Sprintf("%s:default", namespace)
|
||||
t.Tasks[defaultTaskName].Aliases = append(t.Tasks[defaultTaskName].Aliases, namespace)
|
||||
t.Tasks[defaultTaskName].Aliases = append(t.Tasks[defaultTaskName].Aliases, includedTask.Aliases...)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
@@ -163,10 +173,10 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
if _, err = os.Stat(path); err == nil {
|
||||
osTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
if err = taskfile.Merge(t, osTaskfile, false); err != nil {
|
||||
return nil, err
|
||||
if err = taskfile.Merge(t, osTaskfile, nil); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +189,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
task.Task = name
|
||||
}
|
||||
|
||||
return t, nil
|
||||
return t, readerNode.Dir, nil
|
||||
}
|
||||
|
||||
func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
||||
@@ -187,8 +197,13 @@ func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var t taskfile.Taskfile
|
||||
return &t, yaml.NewDecoder(f).Decode(&t)
|
||||
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
|
||||
return nil, fmt.Errorf("task: Failed to parse %s:\n%w", filepathext.TryAbsToRel(file), err)
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func exists(path string) (string, error) {
|
||||
@@ -210,6 +225,36 @@ func exists(path string) (string, error) {
|
||||
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
|
||||
}
|
||||
|
||||
func existsWalk(path string) (string, error) {
|
||||
origPath := path
|
||||
owner, err := sysinfo.Owner(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
fpath, err := exists(path)
|
||||
if err == nil {
|
||||
return fpath, nil
|
||||
}
|
||||
|
||||
// Get the parent path/user id
|
||||
parentPath := filepath.Dir(path)
|
||||
parentOwner, err := sysinfo.Owner(parentPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Error if we reached the root directory and still haven't found a file
|
||||
// OR if the user id of the directory changes
|
||||
if path == parentPath || (parentOwner != owner) {
|
||||
return "", fmt.Errorf(`task: No Taskfile found in "%s" (or any of the parent directories). Use "task --init" to create a new one`, origPath)
|
||||
}
|
||||
|
||||
owner = parentOwner
|
||||
path = parentPath
|
||||
}
|
||||
}
|
||||
|
||||
func checkCircularIncludes(node *ReaderNode) error {
|
||||
if node == nil {
|
||||
return errors.New("task: failed to check for include cycle: node was nil")
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package taskfile
|
||||
|
||||
func stringSliceContains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
158
taskfile/task.go
158
taskfile/task.go
@@ -1,5 +1,11 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Tasks represents a group of tasks
|
||||
type Tasks map[string]*Task
|
||||
|
||||
@@ -11,6 +17,7 @@ type Task struct {
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
@@ -18,6 +25,7 @@ type Task struct {
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
@@ -37,61 +45,111 @@ func (t *Task) Name() string {
|
||||
return t.Task
|
||||
}
|
||||
|
||||
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var cmd Cmd
|
||||
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
|
||||
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
// Shortcut syntax for a task with a single command
|
||||
case yaml.ScalarNode:
|
||||
var cmd Cmd
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Cmds = append(t.Cmds, &cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmds []*Cmd
|
||||
if err := unmarshal(&cmds); err == nil && len(cmds) > 0 {
|
||||
// Shortcut syntax for a simple task with a list of commands
|
||||
case yaml.SequenceNode:
|
||||
var cmds []*Cmd
|
||||
if err := node.Decode(&cmds); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Cmds = cmds
|
||||
return nil
|
||||
|
||||
// Full task object
|
||||
case yaml.MappingNode:
|
||||
var task struct {
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Cmds = task.Cmds
|
||||
t.Deps = task.Deps
|
||||
t.Label = task.Label
|
||||
t.Desc = task.Desc
|
||||
t.Summary = task.Summary
|
||||
t.Aliases = task.Aliases
|
||||
t.Sources = task.Sources
|
||||
t.Generates = task.Generates
|
||||
t.Status = task.Status
|
||||
t.Preconditions = task.Preconditions
|
||||
t.Dir = task.Dir
|
||||
t.Vars = task.Vars
|
||||
t.Env = task.Env
|
||||
t.Dotenv = task.Dotenv
|
||||
t.Silent = task.Silent
|
||||
t.Interactive = task.Interactive
|
||||
t.Internal = task.Internal
|
||||
t.Method = task.Method
|
||||
t.Prefix = task.Prefix
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
return nil
|
||||
}
|
||||
|
||||
var task struct {
|
||||
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
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
}
|
||||
if err := unmarshal(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Cmds = task.Cmds
|
||||
t.Deps = task.Deps
|
||||
t.Label = task.Label
|
||||
t.Desc = task.Desc
|
||||
t.Summary = task.Summary
|
||||
t.Sources = task.Sources
|
||||
t.Generates = task.Generates
|
||||
t.Status = task.Status
|
||||
t.Preconditions = task.Preconditions
|
||||
t.Dir = task.Dir
|
||||
t.Vars = task.Vars
|
||||
t.Env = task.Env
|
||||
t.Silent = task.Silent
|
||||
t.Interactive = task.Interactive
|
||||
t.Internal = task.Internal
|
||||
t.Method = task.Method
|
||||
t.Prefix = task.Prefix
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
return nil
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Task and copies
|
||||
// data by value from the source struct.
|
||||
func (t *Task) DeepCopy() *Task {
|
||||
c := &Task{
|
||||
Task: t.Task,
|
||||
Cmds: deepCopySlice(t.Cmds),
|
||||
Deps: deepCopySlice(t.Deps),
|
||||
Label: t.Label,
|
||||
Desc: t.Desc,
|
||||
Summary: t.Summary,
|
||||
Aliases: deepCopySlice(t.Aliases),
|
||||
Sources: deepCopySlice(t.Sources),
|
||||
Generates: deepCopySlice(t.Generates),
|
||||
Status: deepCopySlice(t.Status),
|
||||
Preconditions: deepCopySlice(t.Preconditions),
|
||||
Dir: t.Dir,
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Dotenv: deepCopySlice(t.Dotenv),
|
||||
Silent: t.Silent,
|
||||
Interactive: t.Interactive,
|
||||
Internal: t.Internal,
|
||||
Method: t.Method,
|
||||
Prefix: t.Prefix,
|
||||
IgnoreError: t.IgnoreError,
|
||||
Run: t.Run,
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package taskfile
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Taskfile represents a Taskfile.yml
|
||||
@@ -18,47 +21,55 @@ type Taskfile struct {
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var taskfile struct {
|
||||
Version string
|
||||
Expansions int
|
||||
Output Output
|
||||
Method string
|
||||
Includes *IncludedTaskfiles
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.MappingNode:
|
||||
var taskfile struct {
|
||||
Version string
|
||||
Expansions int
|
||||
Output Output
|
||||
Method string
|
||||
Includes *IncludedTaskfiles
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
}
|
||||
if err := node.Decode(&taskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
tf.Version = taskfile.Version
|
||||
tf.Expansions = taskfile.Expansions
|
||||
tf.Output = taskfile.Output
|
||||
tf.Method = taskfile.Method
|
||||
tf.Includes = taskfile.Includes
|
||||
tf.Vars = taskfile.Vars
|
||||
tf.Env = taskfile.Env
|
||||
tf.Tasks = taskfile.Tasks
|
||||
tf.Silent = taskfile.Silent
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
tf.Interval = taskfile.Interval
|
||||
if tf.Expansions <= 0 {
|
||||
tf.Expansions = 2
|
||||
}
|
||||
if tf.Vars == nil {
|
||||
tf.Vars = &Vars{}
|
||||
}
|
||||
if tf.Env == nil {
|
||||
tf.Env = &Vars{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := unmarshal(&taskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
tf.Version = taskfile.Version
|
||||
tf.Expansions = taskfile.Expansions
|
||||
tf.Output = taskfile.Output
|
||||
tf.Method = taskfile.Method
|
||||
tf.Includes = taskfile.Includes
|
||||
tf.Vars = taskfile.Vars
|
||||
tf.Env = taskfile.Env
|
||||
tf.Tasks = taskfile.Tasks
|
||||
tf.Silent = taskfile.Silent
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
if tf.Expansions <= 0 {
|
||||
tf.Expansions = 2
|
||||
}
|
||||
if tf.Vars == nil {
|
||||
tf.Vars = &Vars{}
|
||||
}
|
||||
if tf.Env == nil {
|
||||
tf.Env = &Vars{}
|
||||
}
|
||||
return nil
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// ParsedVersion returns the version as a float64
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -12,26 +13,39 @@ type Vars struct {
|
||||
Mapping map[string]Var
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
||||
if node.Kind != yaml.MappingNode {
|
||||
return errors.New("task: vars is not a map")
|
||||
}
|
||||
switch node.Kind {
|
||||
|
||||
// NOTE(@andreynering): on this style of custom unmarsheling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
case yaml.MappingNode:
|
||||
// NOTE(@andreynering): on this style of custom unmarshalling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
var v Var
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
var v Var
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
vs.Set(keyNode.Value, v)
|
||||
}
|
||||
vs.Set(keyNode.Value, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variables", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Vars and copies
|
||||
// data by value from the source struct.
|
||||
func (vs *Vars) DeepCopy() *Vars {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
return &Vars{
|
||||
Keys: deepCopySlice(vs.Keys),
|
||||
Mapping: deepCopyMap(vs.Mapping),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges the given Vars into the caller one
|
||||
@@ -47,7 +61,7 @@ func (vs *Vars) Set(key string, value Var) {
|
||||
if vs.Mapping == nil {
|
||||
vs.Mapping = make(map[string]Var, 1)
|
||||
}
|
||||
if !stringSliceContains(vs.Keys, key) {
|
||||
if !slices.Contains(vs.Keys, key) {
|
||||
vs.Keys = append(vs.Keys, key)
|
||||
}
|
||||
vs.Mapping[key] = value
|
||||
@@ -103,20 +117,27 @@ type Var struct {
|
||||
Dir string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var str string
|
||||
if err := unmarshal(&str); err == nil {
|
||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var str string
|
||||
if err := node.Decode(&str); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Static = str
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var sh struct {
|
||||
Sh string
|
||||
}
|
||||
if err := node.Decode(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Sh = sh.Sh
|
||||
return nil
|
||||
}
|
||||
|
||||
var sh struct {
|
||||
Sh string
|
||||
}
|
||||
if err := unmarshal(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Sh = sh.Sh
|
||||
return nil
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variable", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
19
testdata/alias/Taskfile.yml
vendored
Normal file
19
testdata/alias/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: Taskfile2.yml
|
||||
aliases: [inc, i]
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
aliases: [f, x]
|
||||
cmds:
|
||||
- echo "foo"
|
||||
- task: b
|
||||
|
||||
bar:
|
||||
aliases: [b, x]
|
||||
cmds:
|
||||
- echo "bar"
|
||||
- task: inc:q
|
||||
7
testdata/alias/Taskfile2.yml
vendored
Normal file
7
testdata/alias/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
qux:
|
||||
aliases: [q, x]
|
||||
cmds:
|
||||
- echo "qux"
|
||||
11
testdata/alias/alias-summary.txt
vendored
Normal file
11
testdata/alias/alias-summary.txt
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
task: foo
|
||||
|
||||
(task does not have description or summary)
|
||||
|
||||
aliases:
|
||||
- f
|
||||
- x
|
||||
|
||||
commands:
|
||||
- echo "foo"
|
||||
- Task: b
|
||||
6
testdata/alias/alias.txt
vendored
Normal file
6
testdata/alias/alias.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
task: [foo] echo "foo"
|
||||
foo
|
||||
task: [bar] echo "bar"
|
||||
bar
|
||||
task: [included:qux] echo "qux"
|
||||
qux
|
||||
1
testdata/dotenv_task/default/.env
vendored
Normal file
1
testdata/dotenv_task/default/.env
vendored
Normal file
@@ -0,0 +1 @@
|
||||
FOO=foo
|
||||
1
testdata/dotenv_task/default/.gitignore
vendored
Normal file
1
testdata/dotenv_task/default/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.txt
|
||||
28
testdata/dotenv_task/default/Taskfile.yml
vendored
Normal file
28
testdata/dotenv_task/default/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
FOO: global
|
||||
|
||||
tasks:
|
||||
dotenv:
|
||||
dotenv: ['.env']
|
||||
cmds:
|
||||
- echo "$FOO" > dotenv.txt
|
||||
|
||||
dotenv-overridden-by-env:
|
||||
dotenv: ['.env']
|
||||
env:
|
||||
FOO: overridden
|
||||
cmds:
|
||||
- echo "$FOO" > dotenv-overridden-by-env.txt
|
||||
|
||||
dotenv-with-var-name:
|
||||
vars:
|
||||
DOTENV: .env
|
||||
dotenv: ['{{.DOTENV}}']
|
||||
cmds:
|
||||
- echo "$FOO" > dotenv-with-var-name.txt
|
||||
|
||||
no-dotenv:
|
||||
cmds:
|
||||
- echo "$FOO" > no-dotenv.txt
|
||||
4
testdata/includes_incorrect/Taskfile.yml
vendored
Normal file
4
testdata/includes_incorrect/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included: incomplete.yml
|
||||
4
testdata/includes_incorrect/incomplete.yml
vendored
Normal file
4
testdata/includes_incorrect/incomplete.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
name:
|
||||
'test
|
||||
10
testdata/includes_interpolation/Taskfile.yml
vendored
Normal file
10
testdata/includes_interpolation/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
MODULE_NAME: included
|
||||
|
||||
includes:
|
||||
include: './{{.MODULE_NAME}}/Taskfile.yml'
|
||||
include-with-dir:
|
||||
taskfile: './{{.MODULE_NAME}}/Taskfile.yml'
|
||||
dir: '{{.MODULE_NAME}}'
|
||||
6
testdata/includes_interpolation/included/Taskfile.yml
vendored
Normal file
6
testdata/includes_interpolation/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- basename $(pwd)
|
||||
10
testdata/includes_shadowed_default/Taskfile.yml
vendored
Normal file
10
testdata/includes_shadowed_default/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: Taskfile2.yml
|
||||
|
||||
tasks:
|
||||
included:
|
||||
cmds:
|
||||
- echo "shadowed" > file.txt
|
||||
6
testdata/includes_shadowed_default/Taskfile2.yml
vendored
Normal file
6
testdata/includes_shadowed_default/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "included" > file.txt
|
||||
1
testdata/includes_shadowed_default/file.txt
vendored
Normal file
1
testdata/includes_shadowed_default/file.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
shadowed
|
||||
5
testdata/includes_unshadowed_default/Taskfile.yml
vendored
Normal file
5
testdata/includes_unshadowed_default/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: Taskfile2.yml
|
||||
6
testdata/includes_unshadowed_default/Taskfile2.yml
vendored
Normal file
6
testdata/includes_unshadowed_default/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "included" > file.txt
|
||||
1
testdata/includes_unshadowed_default/file.txt
vendored
Normal file
1
testdata/includes_unshadowed_default/file.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
included
|
||||
7
testdata/taskfile_walk/Taskfile.yml
vendored
Normal file
7
testdata/taskfile_walk/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
silent: true
|
||||
0
testdata/taskfile_walk/foo/bar/.gitkeep
vendored
Normal file
0
testdata/taskfile_walk/foo/bar/.gitkeep
vendored
Normal file
7
testdata/user_working_dir/Taskfile.yml
vendored
Normal file
7
testdata/user_working_dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo '{{.USER_WORKING_DIR}}'
|
||||
silent: true
|
||||
1
testdata/watcher_interval/.gitignore
vendored
Normal file
1
testdata/watcher_interval/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/*
|
||||
16
testdata/watcher_interval/Taskfile.yaml
vendored
Normal file
16
testdata/watcher_interval/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# https://taskfile.dev
|
||||
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
interval: "500ms"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
sources:
|
||||
- "src/*"
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
silent: false
|
||||
32
variables.go
32
variables.go
@@ -1,8 +1,11 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/status"
|
||||
@@ -22,13 +25,12 @@ func (e *Executor) FastCompiledTask(call taskfile.Call) (*taskfile.Task, error)
|
||||
}
|
||||
|
||||
func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskfile.Task, error) {
|
||||
origTask, ok := e.Taskfile.Tasks[call.Task]
|
||||
if !ok {
|
||||
return nil, &taskNotFoundError{call.Task}
|
||||
origTask, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vars *taskfile.Vars
|
||||
var err error
|
||||
if evaluateShVars {
|
||||
vars, err = e.Compiler.GetVariables(origTask, call)
|
||||
} else {
|
||||
@@ -50,11 +52,13 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
Label: r.Replace(origTask.Label),
|
||||
Desc: r.Replace(origTask.Desc),
|
||||
Summary: r.Replace(origTask.Summary),
|
||||
Aliases: origTask.Aliases,
|
||||
Sources: r.ReplaceSlice(origTask.Sources),
|
||||
Generates: r.ReplaceSlice(origTask.Generates),
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
Vars: nil,
|
||||
Env: nil,
|
||||
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
||||
Silent: origTask.Silent,
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
@@ -76,8 +80,28 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
new.Prefix = new.Task
|
||||
}
|
||||
|
||||
dotenvEnvs := &taskfile.Vars{}
|
||||
if len(new.Dotenv) > 0 {
|
||||
for _, dotEnvPath := range new.Dotenv {
|
||||
dotEnvPath = filepathext.SmartJoin(new.Dir, dotEnvPath)
|
||||
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
envs, err := godotenv.Read(dotEnvPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range envs {
|
||||
if _, ok := dotenvEnvs.Mapping[key]; !ok {
|
||||
dotenvEnvs.Set(key, taskfile.Var{Static: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new.Env = &taskfile.Vars{}
|
||||
new.Env.Merge(r.ReplaceVars(e.Taskfile.Env))
|
||||
new.Env.Merge(r.ReplaceVars(dotenvEnvs))
|
||||
new.Env.Merge(r.ReplaceVars(origTask.Env))
|
||||
if evaluateShVars {
|
||||
err = new.Env.Range(func(k string, v taskfile.Var) error {
|
||||
|
||||
18
watch.go
18
watch.go
@@ -10,13 +10,14 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/radovskyb/watcher"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/status"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/radovskyb/watcher"
|
||||
)
|
||||
|
||||
const watchInterval = 5 * time.Second
|
||||
const defaultWatchInterval = 5 * time.Second
|
||||
|
||||
// watchTasks start watching the given tasks
|
||||
func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
||||
@@ -24,6 +25,7 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
||||
for i, c := range calls {
|
||||
tasks[i] = c.Task
|
||||
}
|
||||
|
||||
e.Logger.Errf(logger.Green, "task: Started watching for tasks: %s", strings.Join(tasks, ", "))
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -36,6 +38,18 @@ func (e *Executor) watchTasks(calls ...taskfile.Call) error {
|
||||
}()
|
||||
}
|
||||
|
||||
var watchInterval time.Duration
|
||||
switch {
|
||||
case e.Interval != 0:
|
||||
watchInterval = e.Interval
|
||||
case e.Taskfile.Interval != 0:
|
||||
watchInterval = e.Taskfile.Interval
|
||||
default:
|
||||
watchInterval = defaultWatchInterval
|
||||
}
|
||||
|
||||
e.Logger.VerboseOutf(logger.Green, "task: Watching for changes every %v", watchInterval)
|
||||
|
||||
w := watcher.New()
|
||||
defer w.Close()
|
||||
w.SetMaxEvents(1)
|
||||
|
||||
80
watch_test.go
Normal file
80
watch_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
//go:build watch
|
||||
// +build watch
|
||||
|
||||
package task_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
func TestFileWatcherInterval(t *testing.T) {
|
||||
const dir = "testdata/watcher_interval"
|
||||
expectedOutput := strings.TrimSpace(`
|
||||
task: Started watching for tasks: default
|
||||
task: [default] echo "Hello, World!"
|
||||
Hello, World!
|
||||
task: [default] echo "Hello, World!"
|
||||
Hello, World!
|
||||
`)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Watch: true,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
buff.Reset()
|
||||
|
||||
err := os.MkdirAll(filepathext.SmartJoin(dir, "src"), 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := e.Run(ctx, taskfile.Call{Task: "default"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test updated"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(700 * time.Millisecond)
|
||||
cancel()
|
||||
assert.Equal(t, expectedOutput, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
err = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(filepathext.SmartJoin(dir, "src"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user