mirror of
https://github.com/go-task/task.git
synced 2026-06-15 11:51:41 +00:00
Compare commits
299 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7bb3d63b0 | ||
|
|
e6b543c15e | ||
|
|
7dcb3af944 | ||
|
|
10986d3a7c | ||
|
|
bf64259af3 | ||
|
|
6141ba84ce | ||
|
|
329902f0db | ||
|
|
bfcaa7a443 | ||
|
|
45915bf0ed | ||
|
|
df8293bee6 | ||
|
|
935216f179 | ||
|
|
f56bbd46fd | ||
|
|
86be13ff1f | ||
|
|
ee95df0e57 | ||
|
|
442c29f020 | ||
|
|
739037fc37 | ||
|
|
f8252020aa | ||
|
|
38b87439fe | ||
|
|
116879f7ea | ||
|
|
2eafb2f067 | ||
|
|
c36f0f6f7f | ||
|
|
bfc033959b | ||
|
|
814f350b6d | ||
|
|
05db8ce582 | ||
|
|
3fd36a0c72 | ||
|
|
d52d74c64c | ||
|
|
d36f73de01 | ||
|
|
628c4a7b5f | ||
|
|
ca07a663e1 | ||
|
|
66d008391e | ||
|
|
eef84bda26 | ||
|
|
e0defe71aa | ||
|
|
069257151e | ||
|
|
347ecc028f | ||
|
|
94ac60fa09 | ||
|
|
b08b3546d2 | ||
|
|
7453e688fd | ||
|
|
32b097b3f2 | ||
|
|
22394def78 | ||
|
|
68ecb7fbdd | ||
|
|
de98a53b43 | ||
|
|
1c9fbf92c6 | ||
|
|
c068b05232 | ||
|
|
01e9a8f720 | ||
|
|
4bdfe64afb | ||
|
|
ec934ba3c0 | ||
|
|
48add0f293 | ||
|
|
a4685229c9 | ||
|
|
f0bc4d26a0 | ||
|
|
6a4f420187 | ||
|
|
6640632683 | ||
|
|
09d5d802d0 | ||
|
|
fea23ed6d4 | ||
|
|
10a6c4dc7a | ||
|
|
4cdaa72224 | ||
|
|
27bc1ca5d1 | ||
|
|
1ea49188c9 | ||
|
|
3084ef129c | ||
|
|
c0d112f858 | ||
|
|
2265dda84c | ||
|
|
263b094cab | ||
|
|
fbd13614a5 | ||
|
|
0494d7ebe3 | ||
|
|
9a8442c946 | ||
|
|
e1dcd0b441 | ||
|
|
a152db7054 | ||
|
|
b9e092674e | ||
|
|
4162b5f41d | ||
|
|
67ae6f210f | ||
|
|
f6c5a46626 | ||
|
|
d6f7e01c53 | ||
|
|
46463e4e24 | ||
|
|
5c420f3a34 | ||
|
|
393712ead2 | ||
|
|
84da80356d | ||
|
|
bcbb85eac3 | ||
|
|
0e1d8a72e6 | ||
|
|
bbdd698869 | ||
|
|
8f684ffa6d | ||
|
|
9be3666fe7 | ||
|
|
b7785678f4 | ||
|
|
d8005b4cf6 | ||
|
|
52028fc3bc | ||
|
|
5285ec23ae | ||
|
|
3c882e5c57 | ||
|
|
ad569a8a36 | ||
|
|
0d9fdbaac1 | ||
|
|
f5cd3eab9e | ||
|
|
8987cd64a0 | ||
|
|
fac51dcf03 | ||
|
|
01101a4c9b | ||
|
|
d561e40817 | ||
|
|
0cb298ebdf | ||
|
|
a149368725 | ||
|
|
afeefe8259 | ||
|
|
690d3c27a2 | ||
|
|
3d56ea5ce5 | ||
|
|
fdff7f80a3 | ||
|
|
fe6978b107 | ||
|
|
57db6865d2 | ||
|
|
d235d5ab28 | ||
|
|
613dfe06d3 | ||
|
|
a312d61d68 | ||
|
|
e414c1f7b0 | ||
|
|
955359b073 | ||
|
|
26e0c0887a | ||
|
|
4c295b564a | ||
|
|
2eb52da0db | ||
|
|
d8bfb3ab13 | ||
|
|
d970e93507 | ||
|
|
762714de68 | ||
|
|
82a3651a18 | ||
|
|
abe0352de9 | ||
|
|
4cee4aa5a8 | ||
|
|
9c68c7c50b | ||
|
|
0608782cfa | ||
|
|
edeaf3794a | ||
|
|
fe2b8c8afa | ||
|
|
b66bf58064 | ||
|
|
957dfa9cdf | ||
|
|
cc9264854e | ||
|
|
d1463b3e24 | ||
|
|
f1082520e1 | ||
|
|
733c563194 | ||
|
|
0200d043c3 | ||
|
|
9c475c36e7 | ||
|
|
c663c5c507 | ||
|
|
1e93c38307 | ||
|
|
81baf808c9 | ||
|
|
74537689dc | ||
|
|
12ab01d5e6 | ||
|
|
044d3a0ff9 | ||
|
|
659cae6a4c | ||
|
|
bd5882f0f0 | ||
|
|
6ff9ba9df9 | ||
|
|
b2df398a12 | ||
|
|
83d618e1eb | ||
|
|
f0768b3af1 | ||
|
|
0233ce52ed | ||
|
|
6e6f337509 | ||
|
|
1546415b8f | ||
|
|
20725c69bf | ||
|
|
90613220c6 | ||
|
|
659fd2ae93 | ||
|
|
29d899f7da | ||
|
|
902a0a01a9 | ||
|
|
8001fb3915 | ||
|
|
e81e2802f0 | ||
|
|
1ee066ec42 | ||
|
|
53d54d1c4a | ||
|
|
10082b60b8 | ||
|
|
c5b9773922 | ||
|
|
de11323d28 | ||
|
|
9f269e1a95 | ||
|
|
e4204168a0 | ||
|
|
9c350f8ef1 | ||
|
|
db19fdac29 | ||
|
|
d516b238b1 | ||
|
|
f9330f6cd9 | ||
|
|
360da29e1f | ||
|
|
9cfac1642a | ||
|
|
db90e87d10 | ||
|
|
b7564080bc | ||
|
|
1d783bf6c7 | ||
|
|
1025c2e3a1 | ||
|
|
4fd82ab222 | ||
|
|
8eadfc1bf6 | ||
|
|
f66edbad50 | ||
|
|
c7f17b5319 | ||
|
|
23c4adcef6 | ||
|
|
808542bed0 | ||
|
|
93bfd57856 | ||
|
|
7e7e1bccba | ||
|
|
34f6da86c3 | ||
|
|
15c0381c3c | ||
|
|
c2f4a57e02 | ||
|
|
f945cf2343 | ||
|
|
5bca3cfd71 | ||
|
|
26ce4e6886 | ||
|
|
f5f0e0c376 | ||
|
|
9dea1e7f3e | ||
|
|
c2e0f8c81f | ||
|
|
d341bc25ce | ||
|
|
0379e2b51b | ||
|
|
e79026b840 | ||
|
|
fc34d6b56f | ||
|
|
2a1571a99e | ||
|
|
c158608255 | ||
|
|
3ca590b185 | ||
|
|
3f8ee21849 | ||
|
|
845b88a193 | ||
|
|
e252972c7f | ||
|
|
a9012ebfc5 | ||
|
|
5cfd9bbbbd | ||
|
|
c82a7240bb | ||
|
|
a4a20d92a4 | ||
|
|
890996f595 | ||
|
|
474f27c6d3 | ||
|
|
33f3894372 | ||
|
|
24436ac76e | ||
|
|
3ee66ef705 | ||
|
|
a1765e1d33 | ||
|
|
765e3dbf72 | ||
|
|
80f5cee599 | ||
|
|
4dcb124693 | ||
|
|
31ecf167cc | ||
|
|
3999480d64 | ||
|
|
9dbb503c23 | ||
|
|
a98f803d87 | ||
|
|
9e9ffeb5d5 | ||
|
|
33d4ad4d84 | ||
|
|
d05d418c4c | ||
|
|
06d0af7a1d | ||
|
|
9a3b726068 | ||
|
|
2676ab9a59 | ||
|
|
a1837d553e | ||
|
|
fdbc130d8d | ||
|
|
4b3cea3812 | ||
|
|
1c3082ffa6 | ||
|
|
0446cfdba0 | ||
|
|
db1d3183b6 | ||
|
|
fb666394fc | ||
|
|
1054c89a9d | ||
|
|
8dd87dc482 | ||
|
|
b2edbf05a1 | ||
|
|
6fb53a406b | ||
|
|
b05fa0821d | ||
|
|
0a808b1212 | ||
|
|
f1d83e92a7 | ||
|
|
31b60f7f60 | ||
|
|
c0f9af5daa | ||
|
|
b25a9e8884 | ||
|
|
3c0cf3cd55 | ||
|
|
1ac6f17e6a | ||
|
|
399a2b38f3 | ||
|
|
b97221cdb2 | ||
|
|
0164bc21ea | ||
|
|
5a23250d32 | ||
|
|
80d88d9789 | ||
|
|
31ead854c7 | ||
|
|
4b64fcb8a4 | ||
|
|
a951f2403d | ||
|
|
f9adeba7f1 | ||
|
|
5c823d51d0 | ||
|
|
9be7521b83 | ||
|
|
c73ddc3552 | ||
|
|
4b7f058f41 | ||
|
|
07221a1b20 | ||
|
|
13614fb3c4 | ||
|
|
4fa983bde7 | ||
|
|
9cb1db8c0a | ||
|
|
5738436d55 | ||
|
|
5e49b38c33 | ||
|
|
0c94adaff9 | ||
|
|
f8a6c5d06c | ||
|
|
21e66c7c02 | ||
|
|
902f0d3ac4 | ||
|
|
713ecd35f6 | ||
|
|
27b35157cd | ||
|
|
f8fb639870 | ||
|
|
14f41ae619 | ||
|
|
a026d72924 | ||
|
|
2cb070f5b3 | ||
|
|
1dec956e99 | ||
|
|
310394aa60 | ||
|
|
468ff18243 | ||
|
|
44a63580f0 | ||
|
|
4ac1fa43aa | ||
|
|
6f992a3cf7 | ||
|
|
fd4ce656d5 | ||
|
|
9ed2dca427 | ||
|
|
dfb804fe3f | ||
|
|
4f2a84b426 | ||
|
|
14a127b6b3 | ||
|
|
06000533fb | ||
|
|
7722aba403 | ||
|
|
4817d8c67f | ||
|
|
9a062d90d1 | ||
|
|
959eb45373 | ||
|
|
a42f2af9eb | ||
|
|
4ddad68212 | ||
|
|
aac6c5a1c7 | ||
|
|
5572e31fd4 | ||
|
|
233b8bf81a | ||
|
|
2ae3810f80 | ||
|
|
736165876c | ||
|
|
61b3fca9a3 | ||
|
|
469863b7b3 | ||
|
|
5238bc55fd | ||
|
|
57a01aa6ff | ||
|
|
9361dbc39e | ||
|
|
11d257cb26 | ||
|
|
a928ab75e3 | ||
|
|
55a240c82e | ||
|
|
f8aedf438b | ||
|
|
9f1bb9a42e | ||
|
|
0ed7274610 | ||
|
|
df032b09a7 | ||
|
|
780bd08490 |
@@ -7,7 +7,6 @@ insert_final_newline = true
|
|||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 8
|
|
||||||
|
|
||||||
[*.{md,yml,yaml,json,toml,htm,html}]
|
[*.{md,yml,yaml,json,toml,htm,html}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
open_collective: task
|
||||||
|
patreon: andreynering
|
||||||
26
.github/workflows/release.yml
vendored
Normal file
26
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: goreleaser
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.14.x
|
||||||
|
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: release --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
30
.github/workflows/test.yml
vendored
Normal file
30
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Test
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.13.x, 1.14.x]
|
||||||
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
runs-on: ${{matrix.platform}}
|
||||||
|
steps:
|
||||||
|
- name: Set up Go ${{matrix.go-version}}
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: ${{matrix.go-version}}
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Download Go modules
|
||||||
|
run: go mod download
|
||||||
|
env:
|
||||||
|
GOPROXY: https://proxy.golang.org
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -o ./bin/task -v ./cmd/task
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ./bin/task test
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -18,3 +18,11 @@
|
|||||||
dist/
|
dist/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# intellij idea/goland
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# exuberant ctags
|
||||||
|
tags
|
||||||
|
|
||||||
|
/bin
|
||||||
|
|||||||
@@ -14,12 +14,11 @@ build:
|
|||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
|
||||||
archive:
|
archives:
|
||||||
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||||
|
format_overrides:
|
||||||
format_overrides:
|
- goos: windows
|
||||||
- goos: windows
|
format: zip
|
||||||
format: zip
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
draft: true
|
draft: true
|
||||||
@@ -30,15 +29,15 @@ snapshot:
|
|||||||
checksum:
|
checksum:
|
||||||
name_template: "task_checksums.txt"
|
name_template: "task_checksums.txt"
|
||||||
|
|
||||||
nfpm:
|
nfpms:
|
||||||
vendor: Task
|
- vendor: Task
|
||||||
homepage: https://github.com/go-task/task
|
homepage: https://github.com/go-task/task
|
||||||
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
maintainer: Andrey Nering <andrey.nering@gmail.com>
|
||||||
description: Simple task runner written in Go
|
description: Simple task runner written in Go
|
||||||
license: MIT
|
license: MIT
|
||||||
conflicts:
|
conflicts:
|
||||||
- taskwarrior
|
- taskwarrior
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||||
|
|||||||
22
.travis.yml
22
.travis.yml
@@ -1,22 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- rpm
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go install github.com/go-task/task/cmd/task
|
|
||||||
- task ci
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: curl -sL http://git.io/goreleaser | bash
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
condition: $TRAVIS_OS_NAME = linux
|
|
||||||
237
CHANGELOG.md
Normal file
237
CHANGELOG.md
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v2.8.1 - 2019-05-20
|
||||||
|
|
||||||
|
- Fix error code for the `--help` flag
|
||||||
|
([#300](https://github.com/go-task/task/issues/300), [#330](https://github.com/go-task/task/pull/330)).
|
||||||
|
- Print version to stdout instead of stderr
|
||||||
|
([#299](https://github.com/go-task/task/issues/299), [#329](https://github.com/go-task/task/pull/329)).
|
||||||
|
- Supress `context` errors when using the `--watch` flag
|
||||||
|
([#313](https://github.com/go-task/task/issues/313), [#317](https://github.com/go-task/task/pull/317)).
|
||||||
|
- Support templating on description
|
||||||
|
([#276](https://github.com/go-task/task/issues/276), [#283](https://github.com/go-task/task/pull/283)).
|
||||||
|
|
||||||
|
## v2.8.0 - 2019-12-07
|
||||||
|
|
||||||
|
- Add `--parallel` flag (alias `-p`) to run tasks given by the command line in
|
||||||
|
parallel
|
||||||
|
([#266](https://github.com/go-task/task/pull/266)).
|
||||||
|
- Fixed bug where calling the `task` CLI only informing global vars would not
|
||||||
|
execute the `default` task.
|
||||||
|
- Add hability to silent all tasks by adding `silent: true` a the root of the
|
||||||
|
Taskfile.
|
||||||
|
|
||||||
|
## v2.7.1 - 2019-11-10
|
||||||
|
|
||||||
|
- Fix error being raised when `exit 0` was called
|
||||||
|
([#251](https://github.com/go-task/task/issues/251)).
|
||||||
|
|
||||||
|
## v2.7.0 - 2019-09-22
|
||||||
|
|
||||||
|
- Fixed panic bug when assigning a global variable
|
||||||
|
([#229](https://github.com/go-task/task/issues/229), [#243](https://github.com/go-task/task/issues/234)).
|
||||||
|
- A task with `method: checksum` will now re-run if generated files are deleted
|
||||||
|
([#228](https://github.com/go-task/task/pull/228), [#238](https://github.com/go-task/task/issues/238)).
|
||||||
|
|
||||||
|
## v2.6.0 - 2019-07-21
|
||||||
|
|
||||||
|
- Fixed some bugs regarding minor version checks on `version:`.
|
||||||
|
- Add `preconditions:` to task
|
||||||
|
([#205](https://github.com/go-task/task/pull/205)).
|
||||||
|
- Create directory informed on `dir:` if it doesn't exist
|
||||||
|
([#209](https://github.com/go-task/task/issues/209), [#211](https://github.com/go-task/task/pull/211)).
|
||||||
|
- We now have a `--taskfile` flag (alias `-t`), which can be used to run
|
||||||
|
another Taskfile (other than the default `Taskfile.yml`)
|
||||||
|
([#221](https://github.com/go-task/task/pull/221)).
|
||||||
|
- It's now possible to install Task using Homebrew on Linux
|
||||||
|
([go-task/homebrew-tap#1](https://github.com/go-task/homebrew-tap/pull/1)).
|
||||||
|
|
||||||
|
## v2.5.2 - 2019-05-11
|
||||||
|
|
||||||
|
- Reverted YAML upgrade due issues with CRLF on Windows
|
||||||
|
([#201](https://github.com/go-task/task/issues/201), [go-yaml/yaml#450](https://github.com/go-yaml/yaml/issues/450)).
|
||||||
|
- Allow setting global variables through the CLI
|
||||||
|
([#192](https://github.com/go-task/task/issues/192)).
|
||||||
|
|
||||||
|
## 2.5.1 - 2019-04-27
|
||||||
|
|
||||||
|
- Fixed some issues with interactive command line tools, where sometimes
|
||||||
|
the output were not being shown, and similar issues
|
||||||
|
([#114](https://github.com/go-task/task/issues/114), [#190](https://github.com/go-task/task/issues/190), [#200](https://github.com/go-task/task/pull/200)).
|
||||||
|
- Upgraded [go-yaml/yaml](https://github.com/go-yaml/yaml) from v2 to v3.
|
||||||
|
|
||||||
|
## v2.5.0 - 2019-03-16
|
||||||
|
|
||||||
|
- We moved from the taskfile.org domain to the new fancy taskfile.dev domain.
|
||||||
|
While stuff is being redirected, we strongly recommend to everyone that use
|
||||||
|
[this install script](https://taskfile.dev/#/installation?id=install-script)
|
||||||
|
to use the new taskfile.dev domain on scripts from now on.
|
||||||
|
- Fixed to the ZSH completion
|
||||||
|
([#182](https://github.com/go-task/task/pull/182)).
|
||||||
|
- Add [`--summary` flag along with `summary:` task attribute](https://taskfile.org/#/usage?id=display-summary-of-task)
|
||||||
|
([#180](https://github.com/go-task/task/pull/180)).
|
||||||
|
|
||||||
|
## v2.4.0 - 2019-02-21
|
||||||
|
|
||||||
|
- Allow calling a task of the root Taskfile from an included Taskfile
|
||||||
|
by prefixing it with `:`
|
||||||
|
([#161](https://github.com/go-task/task/issues/161), [#172](https://github.com/go-task/task/issues/172)),
|
||||||
|
- Add flag to override the `output` option
|
||||||
|
([#173](https://github.com/go-task/task/pull/173));
|
||||||
|
- Fix bug where Task was persisting the new checksum on the disk when the Dry
|
||||||
|
Mode is enabled
|
||||||
|
([#166](https://github.com/go-task/task/issues/166));
|
||||||
|
- Fix file timestamp issue when the file name has spaces
|
||||||
|
([#176](https://github.com/go-task/task/issues/176));
|
||||||
|
- Mitigating path expanding issues on Windows
|
||||||
|
([#170](https://github.com/go-task/task/pull/170)).
|
||||||
|
|
||||||
|
## v2.3.0 - 2019-01-02
|
||||||
|
|
||||||
|
- On Windows, Task can now be installed using [Scoop](https://scoop.sh/)
|
||||||
|
([#152](https://github.com/go-task/task/pull/152));
|
||||||
|
- Fixed issue with file/directory globing
|
||||||
|
([#153](https://github.com/go-task/task/issues/153));
|
||||||
|
- Added ability to globally set environment variables
|
||||||
|
(
|
||||||
|
[#138](https://github.com/go-task/task/pull/138),
|
||||||
|
[#159](https://github.com/go-task/task/pull/159)
|
||||||
|
).
|
||||||
|
|
||||||
|
## v2.2.1 - 2018-12-09
|
||||||
|
|
||||||
|
- This repository now uses Go Modules (#143). We'll still keep the `vendor` directory in sync for some time, though;
|
||||||
|
- Fixing a bug when the Taskfile has no tasks but includes another Taskfile (#150);
|
||||||
|
- Fix a bug when calling another task or a dependency in an included Taskfile (#151).
|
||||||
|
|
||||||
|
## v2.2.0 - 2018-10-25
|
||||||
|
|
||||||
|
- Added support for [including other Taskfiles](https://taskfile.org/#/usage?id=including-other-taskfiles) (#98)
|
||||||
|
- This should be considered experimental. For now, only including local files is supported, but support for including remote Taskfiles is being discussed. If you have any feedback, please comment on #98.
|
||||||
|
- Task now have a dedicated documentation site: https://taskfile.org
|
||||||
|
- Thanks to [Docsify](https://docsify.js.org/) for making this pretty easy. To check the source code, just take a look at the [docs](https://github.com/go-task/task/tree/master/docs) directory of this repository. Contributions to the documentation is really appreciated.
|
||||||
|
|
||||||
|
## v2.1.1 - 2018-09-17
|
||||||
|
|
||||||
|
- Fix suggestion to use `task --init` not being shown anymore (when a `Taskfile.yml` is not found)
|
||||||
|
- Fix error when using checksum method and no file exists for a source glob (#131)
|
||||||
|
- Fix signal handling when the `--watch` flag is given (#132)
|
||||||
|
|
||||||
|
## v2.1.0 - 2018-08-19
|
||||||
|
|
||||||
|
- Add a `ignore_error` option to task and command (#123)
|
||||||
|
- Add a dry run mode (`--dry` flag) (#126)
|
||||||
|
|
||||||
|
## v2.0.3 - 2018-06-24
|
||||||
|
|
||||||
|
- Expand environment variables on "dir", "sources" and "generates" (#116)
|
||||||
|
- Fix YAML merging syntax (#112)
|
||||||
|
- Add ZSH completion (#111)
|
||||||
|
- Implement new `output` option. Please check out the [documentation](https://github.com/go-task/task#output-syntax)
|
||||||
|
|
||||||
|
## v2.0.2 - 2018-05-01
|
||||||
|
|
||||||
|
- Fix merging of YAML anchors (#112)
|
||||||
|
|
||||||
|
## v2.0.1 - 2018-03-11
|
||||||
|
|
||||||
|
- Fixes panic on `task --list`
|
||||||
|
|
||||||
|
## v2.0.0 - 2018-03-08
|
||||||
|
|
||||||
|
Version 2.0.0 is here, with a new Taskfile format.
|
||||||
|
|
||||||
|
Please, make sure to read the [Taskfile versions](https://github.com/go-task/task/blob/master/TASKFILE_VERSIONS.md) document, since it describes in depth what changed for this version.
|
||||||
|
|
||||||
|
* New Taskfile version 2 (https://github.com/go-task/task/issues/77)
|
||||||
|
* Possibility to have global variables in the `Taskfile.yml` instead of `Taskvars.yml` (https://github.com/go-task/task/issues/66)
|
||||||
|
* Small improvements and fixes
|
||||||
|
|
||||||
|
## v1.4.4 - 2017-11-19
|
||||||
|
|
||||||
|
- Handle SIGINT and SIGTERM (#75);
|
||||||
|
- List: print message with there's no task with description;
|
||||||
|
- Expand home dir ("~" symbol) on paths (#74);
|
||||||
|
- Add Snap as an installation method;
|
||||||
|
- Move examples to its own repo;
|
||||||
|
- Watch: also walk on tasks called on on "cmds", and not only on "deps";
|
||||||
|
- Print logs to stderr instead of stdout (#68);
|
||||||
|
- Remove deprecated `set` keyword;
|
||||||
|
- Add checksum based status check, alternative to timestamp based.
|
||||||
|
|
||||||
|
## v1.4.3 - 2017-09-07
|
||||||
|
|
||||||
|
- Allow assigning variables to tasks at run time via CLI (#33)
|
||||||
|
- Added suport for multiline variables from sh (#64)
|
||||||
|
- Fixes env: remove square braces and evaluate shell (#62)
|
||||||
|
- Watch: change watch library and few fixes and improvements
|
||||||
|
- When use watching, cancel and restart long running process on file change (#59 and #60)
|
||||||
|
|
||||||
|
## v1.4.2 - 2017-07-30
|
||||||
|
|
||||||
|
- Flag to set directory of execution
|
||||||
|
- Always echo command if is verbose mode
|
||||||
|
- Add silent mode to disable echoing of commands
|
||||||
|
- Fixes and improvements of variables (#56)
|
||||||
|
|
||||||
|
## v1.4.1 - 2017-07-15
|
||||||
|
|
||||||
|
- Allow use of YAML for dynamic variables instead of $ prefix
|
||||||
|
- `VAR: {sh: echo Hello}` instead of `VAR: $echo Hello`
|
||||||
|
- Add `--list` (or `-l`) flag to print existing tasks
|
||||||
|
- OS specific Taskvars file (e.g. `Taskvars_windows.yml`, `Taskvars_linux.yml`, etc)
|
||||||
|
- Consider task up-to-date on equal timestamps (#49)
|
||||||
|
- Allow absolute path in generates section (#48)
|
||||||
|
- Bugfix: allow templating when calling deps (#42)
|
||||||
|
- Fix panic for invalid task in cyclic dep detection
|
||||||
|
- Better error output for dynamic variables in Taskvars.yml (#41)
|
||||||
|
- Allow template evaluation in parameters
|
||||||
|
|
||||||
|
## v1.4.0 - 2017-07-06
|
||||||
|
|
||||||
|
- Cache dynamic variables
|
||||||
|
- Add verbose mode (`-v` flag)
|
||||||
|
- Support to task parameters (overriding vars) (#31) (#32)
|
||||||
|
- Print command, also when "set:" is specified (#35)
|
||||||
|
- Improve task command help text (#35)
|
||||||
|
|
||||||
|
## v1.3.1 - 2017-06-14
|
||||||
|
|
||||||
|
- Fix glob not working on commands (#28)
|
||||||
|
- Add ExeExt template function
|
||||||
|
- Add `--init` flag to create a new Taskfile
|
||||||
|
- Add status option to prevent task from running (#27)
|
||||||
|
- Allow interpolation on `generates` and `sources` attributes (#26)
|
||||||
|
|
||||||
|
## v1.3.0 - 2017-04-24
|
||||||
|
|
||||||
|
- Migrate from os/exec.Cmd to a native Go sh/bash interpreter
|
||||||
|
- This is a potentially breaking change if you use Windows.
|
||||||
|
- Now, `cmd` is not used anymore on Windows. Always use Bash-like syntax for your commands, even on Windows.
|
||||||
|
- Add "ToSlash" and "FromSlash" to template functions
|
||||||
|
- Use functions defined on github.com/Masterminds/sprig
|
||||||
|
- Do not redirect stdin while running variables commands
|
||||||
|
- Using `context` and `errgroup` packages (this will make other tasks to be cancelled, if one returned an error)
|
||||||
|
|
||||||
|
## v1.2.0 - 2017-04-02
|
||||||
|
|
||||||
|
- More tests and Travis integration
|
||||||
|
- Watch a task (experimental)
|
||||||
|
- Possibility to call another task
|
||||||
|
- Fix "=" not being reconized in variables/environment variables
|
||||||
|
- Tasks can now have a description, and help will print them (#10)
|
||||||
|
- Task dependencies now run concurrently
|
||||||
|
- Support for a default task (#16)
|
||||||
|
|
||||||
|
## v1.1.0 - 2017-03-08
|
||||||
|
|
||||||
|
- Support for YAML, TOML and JSON (#1)
|
||||||
|
- Support running command in another directory (#4)
|
||||||
|
- `--force` or `-f` flag to force execution of task even when it's up-to-date
|
||||||
|
- Detection of cyclic dependencies (#5)
|
||||||
|
- Support for variables (#6, #9, #14)
|
||||||
|
- Operation System specific commands and variables (#13)
|
||||||
|
|
||||||
|
## v1.0.0 - 2017-02-28
|
||||||
|
|
||||||
|
- Add LICENSE file
|
||||||
19
README.md
19
README.md
@@ -1,12 +1,23 @@
|
|||||||
[](https://travis-ci.org/go-task/task)
|

|
||||||
|

|
||||||
|
|
||||||
# Task
|
# Task
|
||||||
|
|
||||||
Task is a task runner / build tool that aims to be simpler and easier to use
|
Task is a task runner / build tool that aims to be simpler and easier to use
|
||||||
than, for example, [GNU Make][make].
|
than, for example, [GNU Make](https://www.gnu.org/software/make/).
|
||||||
|
|
||||||
|
See [taskfile.dev](https://taskfile.dev) for documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
See [taskfile.org](https://taskfile.org) for documentation.
|
## Sponsors
|
||||||
|
|
||||||
[make]: https://www.gnu.org/software/make/
|
[](https://opencollective.com/task)
|
||||||
|
|
||||||
|
## Backers
|
||||||
|
|
||||||
|
[](https://opencollective.com/task)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
[](https://github.com/go-task/task/graphs/contributors)
|
||||||
|
|||||||
15
Taskfile.yml
15
Taskfile.yml
@@ -1,5 +1,10 @@
|
|||||||
version: '2'
|
version: '2'
|
||||||
|
|
||||||
|
# silent: true
|
||||||
|
|
||||||
|
includes:
|
||||||
|
docs: ./docs
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
GIT_COMMIT:
|
GIT_COMMIT:
|
||||||
sh: git log -n 1 --format=%h
|
sh: git log -n 1 --format=%h
|
||||||
@@ -83,13 +88,3 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- echo '{{.GO_PACKAGES}}'
|
- echo '{{.GO_PACKAGES}}'
|
||||||
silent: true
|
silent: true
|
||||||
|
|
||||||
docs:install:
|
|
||||||
desc: Installs docsify to work the on the documentation site
|
|
||||||
cmds:
|
|
||||||
- npm install docsify-cli -g
|
|
||||||
|
|
||||||
docs:serve:
|
|
||||||
desc: Serves the documentation site locally
|
|
||||||
cmds:
|
|
||||||
- docsify serve docs
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-task/task/v2"
|
"github.com/go-task/task/v2"
|
||||||
@@ -17,7 +19,7 @@ var (
|
|||||||
version = "master"
|
version = "master"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [task...]
|
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]
|
||||||
|
|
||||||
Runs the specified task(s). Falls back to the "default" task if no task name
|
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.
|
was specified, or lists all tasks if an unknown task name was specified.
|
||||||
@@ -48,6 +50,7 @@ func main() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
versionFlag bool
|
versionFlag bool
|
||||||
|
helpFlag bool
|
||||||
init bool
|
init bool
|
||||||
list bool
|
list bool
|
||||||
status bool
|
status bool
|
||||||
@@ -56,10 +59,15 @@ func main() {
|
|||||||
verbose bool
|
verbose bool
|
||||||
silent bool
|
silent bool
|
||||||
dry bool
|
dry bool
|
||||||
|
summary bool
|
||||||
|
parallel bool
|
||||||
dir string
|
dir string
|
||||||
|
entrypoint string
|
||||||
|
output string
|
||||||
)
|
)
|
||||||
|
|
||||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||||
|
pflag.BoolVarP(&helpFlag, "help", "h", false, "shows Task usage")
|
||||||
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
|
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yml in the current folder")
|
||||||
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
|
||||||
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
|
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
|
||||||
@@ -67,12 +75,21 @@ func main() {
|
|||||||
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
|
||||||
pflag.BoolVarP(&verbose, "verbose", "v", false, "enables verbose mode")
|
pflag.BoolVarP(&verbose, "verbose", "v", false, "enables verbose mode")
|
||||||
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
|
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
|
||||||
|
pflag.BoolVarP(¶llel, "parallel", "p", false, "executes tasks provided on command line in parallel")
|
||||||
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
|
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
|
||||||
|
pflag.BoolVar(&summary, "summary", false, "show summary about a task")
|
||||||
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
|
||||||
|
pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`)
|
||||||
|
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
log.Printf("Task version: %s\n", version)
|
fmt.Printf("Task version: %s\n", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if helpFlag {
|
||||||
|
pflag.Usage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,24 +104,33 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
if dir != "" && entrypoint != "" {
|
||||||
if !watch {
|
log.Fatal("task: You can't set both --dir and --taskfile")
|
||||||
ctx = getSignalContext()
|
return
|
||||||
|
}
|
||||||
|
if entrypoint != "" {
|
||||||
|
dir = filepath.Dir(entrypoint)
|
||||||
|
entrypoint = filepath.Base(entrypoint)
|
||||||
|
} else {
|
||||||
|
entrypoint = "Taskfile.yml"
|
||||||
}
|
}
|
||||||
|
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Force: force,
|
Force: force,
|
||||||
Watch: watch,
|
Watch: watch,
|
||||||
Verbose: verbose,
|
Verbose: verbose,
|
||||||
Silent: silent,
|
Silent: silent,
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Dry: dry,
|
Dry: dry,
|
||||||
|
Entrypoint: entrypoint,
|
||||||
Context: ctx,
|
Summary: summary,
|
||||||
|
Parallel: parallel,
|
||||||
|
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
|
|
||||||
|
OutputStyle: output,
|
||||||
}
|
}
|
||||||
if err := e.Setup(); err != nil {
|
if err := e.Setup(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -115,25 +141,24 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments := pflag.Args()
|
calls, globals := args.Parse(pflag.Args()...)
|
||||||
if len(arguments) == 0 {
|
for name, value := range globals {
|
||||||
log.Println("task: No argument given, trying default task")
|
e.Taskfile.Vars[name] = value
|
||||||
arguments = []string{"default"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calls, err := args.Parse(arguments...)
|
ctx := context.Background()
|
||||||
if err != nil {
|
if !watch {
|
||||||
log.Fatal(err)
|
ctx = getSignalContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
if err = e.Status(calls...); err != nil {
|
if err := e.Status(ctx, calls...); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.Run(calls...); err != nil {
|
if err := e.Run(ctx, calls...); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
completion/bash/task.bash
Normal file
21
completion/bash/task.bash
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
_task_completion()
|
||||||
|
{
|
||||||
|
local scripts;
|
||||||
|
local curr_arg;
|
||||||
|
|
||||||
|
# Remove colon from word breaks
|
||||||
|
COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
|
||||||
|
|
||||||
|
scripts=$(task -l | sed '1d' | awk '{ print $2 }' | sed 's/:$//');
|
||||||
|
|
||||||
|
curr_arg="${COMP_WORDS[COMP_CWORD]:-"."}"
|
||||||
|
|
||||||
|
# Do not accept more than 1 argument
|
||||||
|
if [ "${#COMP_WORDS[@]}" != "2" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMPREPLY=($(compgen -c | echo "$scripts" | grep $curr_arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _task_completion task
|
||||||
10
completion/ps/task.ps1
Normal file
10
completion/ps/task.ps1
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
$scriptBlock = {
|
||||||
|
param($commandName, $wordToComplete, $cursorPosition)
|
||||||
|
$curReg = "task{.exe}? (.*?)$"
|
||||||
|
$startsWith = $wordToComplete | Select-String $curReg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||||
|
$reg = "\* ($startsWith.+?):"
|
||||||
|
$listOutput = $(task -l)
|
||||||
|
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value + " " }
|
||||||
|
}
|
||||||
|
|
||||||
|
Register-ArgumentCompleter -Native -CommandName task -ScriptBlock $scriptBlock
|
||||||
2
completion/zsh/_task
Normal file → Executable file
2
completion/zsh/_task
Normal file → Executable file
@@ -5,7 +5,7 @@ function __list() {
|
|||||||
local -a scripts
|
local -a scripts
|
||||||
|
|
||||||
if [ -f Taskfile.yml ]; then
|
if [ -f Taskfile.yml ]; then
|
||||||
scripts=($(task -l | sed '1d' | sed 's/://' | awk '{ print $2 }'))
|
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/g'))
|
||||||
_describe 'script' scripts
|
_describe 'script' scripts
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
taskfile.org
|
taskfile.dev
|
||||||
@@ -28,8 +28,8 @@ guide to check the full schema documentation and Task features.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [Easy installation](installation.md): just download a single binary, add to
|
- [Easy installation](installation.md): just download a single binary, add to
|
||||||
$PATH and you're done! Or you can also install using [Homebrew][homebrew] or
|
$PATH and you're done! Or you can also install using [Homebrew][homebrew],
|
||||||
[Snapcraft][snapcraft] if you want;
|
[Snapcraft][snapcraft], or [Scoop][scoop] if you want;
|
||||||
- Available on CIs: by adding [this simple command](installation.md#install-script)
|
- Available on CIs: by adding [this simple command](installation.md#install-script)
|
||||||
to install on your CI script and you're done to use Task as part of your CI pipeline;
|
to install on your CI script and you're done to use Task as part of your CI pipeline;
|
||||||
- Truly cross-platform: while most build tools only work well on Linux or macOS,
|
- Truly cross-platform: while most build tools only work well on Linux or macOS,
|
||||||
@@ -38,9 +38,22 @@ 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
|
if a given set of files haven't changed since last run (based either on its
|
||||||
timestamp or content).
|
timestamp or content).
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
[](https://opencollective.com/task)
|
||||||
|
|
||||||
|
## Backers
|
||||||
|
|
||||||
|
[](https://opencollective.com/task)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
[](https://github.com/go-task/task/graphs/contributors)
|
||||||
|
|
||||||
[make]: https://www.gnu.org/software/make/
|
[make]: https://www.gnu.org/software/make/
|
||||||
[go]: https://golang.org/
|
[go]: https://golang.org/
|
||||||
[yaml]: http://yaml.org/
|
[yaml]: http://yaml.org/
|
||||||
[homebrew]: https://brew.sh/
|
[homebrew]: https://brew.sh/
|
||||||
[snapcraft]: https://snapcraft.io/
|
[snapcraft]: https://snapcraft.io/
|
||||||
|
[scoop]: https://scoop.sh/
|
||||||
[sh]: https://mvdan.cc/sh
|
[sh]: https://mvdan.cc/sh
|
||||||
|
|||||||
12
docs/Taskfile.yml
Normal file
12
docs/Taskfile.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
install:
|
||||||
|
desc: Installs docsify to work the on the documentation site
|
||||||
|
cmds:
|
||||||
|
- npm install docsify-cli -g
|
||||||
|
|
||||||
|
serve:
|
||||||
|
desc: Serves the documentation site locally
|
||||||
|
cmds:
|
||||||
|
- docsify serve docs
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
- [Installation](installation.md)
|
- [Installation](installation.md)
|
||||||
- [Usage](usage.md)
|
- [Usage](usage.md)
|
||||||
|
- [Styleguide](styleguide.md)
|
||||||
- [Taskfile Versions](taskfile_versions.md)
|
- [Taskfile Versions](taskfile_versions.md)
|
||||||
- [Examples](examples.md)
|
- [Examples](examples.md)
|
||||||
- [Releasing Task](releasing_task.md)
|
- [Releasing Task](releasing_task.md)
|
||||||
- [Alternative Task Runners](alternative_task_runners.md)
|
- [Alternative Task Runners](alternative_task_runners.md)
|
||||||
- [Sponsors and Backers](sponsors_and_backers.md)
|
- [GitHub](https://github.com/go-task/task)
|
||||||
- [Github](https://github.com/go-task/task)
|
|
||||||
|
|||||||
@@ -6,25 +6,8 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
<meta name="description" content="A task runner / simpler Make alternative written in Go">
|
<meta name="description" content="A task runner / simpler Make alternative written in Go">
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<link rel="stylesheet" href="//unpkg.com/docsify-themeable/dist/css/theme-simple.css">
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-themeable/dist/css/theme-simple.css">
|
||||||
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
|
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
|
||||||
<script>
|
|
||||||
var SeedAndDewConfig = {};
|
|
||||||
(function() {
|
|
||||||
SeedAndDewConfig['adClass'] = "snd-ad";
|
|
||||||
/* * * DON'T EDIT BELOW THIS LINE * * */
|
|
||||||
SeedAndDewConfig['projectId'] = '16e0aed0-b265-48c9-9eae-0aad56147553';
|
|
||||||
SeedAndDewConfig['loadStartTime'] = performance.now();
|
|
||||||
SeedAndDewConfig['apiVersion'] = '2018-05-28'
|
|
||||||
SeedAndDewConfig['sessionId'] = Math.random().toString(36).substring(2, 15);
|
|
||||||
var snd = document.createElement('script');
|
|
||||||
snd.type = 'text/javascript';
|
|
||||||
snd.async = true;
|
|
||||||
snd.src = 'https://www.seedanddew.com/static/embed.min.js';
|
|
||||||
(document.getElementsByTagName('head')[0] ||
|
|
||||||
document.getElementsByTagName('body')[0]).appendChild(snd);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
@@ -32,18 +15,17 @@
|
|||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
name: 'Task',
|
name: 'Task',
|
||||||
repo: 'go-task/task',
|
repo: 'go-task/task',
|
||||||
ga: 'UA-126286662-1',
|
themeColor: '#00add8',
|
||||||
themeColor: '#83d0f2',
|
|
||||||
loadSidebar: true,
|
loadSidebar: true,
|
||||||
auto2top: true,
|
auto2top: true,
|
||||||
maxLevel: 3,
|
maxLevel: 3,
|
||||||
subMaxLevel: 3
|
subMaxLevel: 3
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||||
<script src="//unpkg.com/docsify/lib/plugins/ga.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/docsify-themeable/dist/js/docsify-themeable.min.js"></script>
|
||||||
<script src="//unpkg.com/docsify-themeable"></script>
|
<script src="//cdn.jsdelivr.net/npm/docsify-tabs"></script>
|
||||||
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
|
||||||
<script src="//unpkg.com/prismjs/components/prism-yaml.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-yaml.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,76 +1,133 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
## Binary
|
Task offers many installation methods. Check out the available methods below.
|
||||||
|
|
||||||
Or you can download the binary from the [releases][releases] page and add to
|
## Package Managers
|
||||||
your $PATH. DEB and RPM packages are also available.
|
|
||||||
The `task_checksums.txt` file contains the sha256 checksum for each file.
|
|
||||||
|
|
||||||
## Homebrew
|
<!-- tabs:start -->
|
||||||
|
|
||||||
If you're on macOS and have [Homebrew][homebrew] installed, getting Task is
|
#### **Homebrew**
|
||||||
as simple as running:
|
|
||||||
|
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
|
||||||
|
Task is as simple as running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install go-task/tap/go-task
|
brew install go-task/tap/go-task
|
||||||
```
|
```
|
||||||
|
|
||||||
## Snap
|
#### **Snap**
|
||||||
|
|
||||||
Task is available for [Snapcraft][snapcraft], but keep in mind that your
|
Task is available in [Snapcraft][snapcraft], but keep in mind that your
|
||||||
Linux distribution should allow classic confinement for Snaps to Task work
|
Linux distribution should allow classic confinement for Snaps to Task work
|
||||||
right:
|
right:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo snap install task
|
sudo snap install task --classic
|
||||||
```
|
```
|
||||||
|
|
||||||
## Go
|
#### **Scoop**
|
||||||
|
|
||||||
Task now uses [Go Modules](https://github.com/golang/go/wiki/Modules), which
|
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
||||||
means you may have trouble compiling it on older Go versions.
|
to install Task like:
|
||||||
|
|
||||||
For CI environments we recommend using the [Install Script](#install-script)
|
```cmd
|
||||||
instead, which is faster and more stable, since it'll just download the latest
|
scoop bucket add extras
|
||||||
released binary, instead of compiling the edge (master branch) version.
|
scoop install task
|
||||||
|
```
|
||||||
|
|
||||||
Installing in your `$GOPATH`:
|
This installation method is community owned. After a new release of Task, it
|
||||||
|
may take some time until it's available on Scoop.
|
||||||
|
|
||||||
|
#### **AUR**
|
||||||
|
|
||||||
|
If you're on Arch Linux you can install Task from
|
||||||
|
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
|
||||||
|
package manager such as `yay`, `pacaur` or `yaourt`:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
yay -S taskfile-git
|
||||||
|
```
|
||||||
|
|
||||||
|
This installation method is community owned, but since it's `-git` version of
|
||||||
|
the package, it's always latest available version based on the Git repository.
|
||||||
|
|
||||||
|
<!-- tabs:end -->
|
||||||
|
|
||||||
|
## Get The Binary
|
||||||
|
|
||||||
|
<!-- tabs:start -->
|
||||||
|
|
||||||
|
#### **Binary**
|
||||||
|
|
||||||
|
You can download the binary from the [releases page on GitHub][releases] and
|
||||||
|
add to your `$PATH`.
|
||||||
|
|
||||||
|
DEB and RPM packages are also available.
|
||||||
|
|
||||||
|
The `task_checksums.txt` file contains the SHA-256 checksum for each file.
|
||||||
|
|
||||||
|
#### **Install Script**
|
||||||
|
|
||||||
|
We also have a [install script][installscript], which is very useful on
|
||||||
|
scenarios like CIs. Many thanks to [GoDownloader][godownloader] for allowing
|
||||||
|
easily generating this script.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u -v github.com/go-task/task/cmd/task
|
curl -sL https://taskfile.dev/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> This method will download the binary on the local `./bin` directory by default.
|
||||||
|
|
||||||
|
#### **GitHub Actions**
|
||||||
|
|
||||||
|
If you want to install Task in GitHub Actions you can try using
|
||||||
|
[this action](https://github.com/arduino/actions/tree/master/setup-taskfile)
|
||||||
|
by the Arduino team:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install Task
|
||||||
|
uses: Arduino/actions/setup-taskfile@master
|
||||||
|
```
|
||||||
|
|
||||||
|
This installation method is community owned.
|
||||||
|
|
||||||
|
<!-- tabs:end -->
|
||||||
|
|
||||||
|
## Build From Source
|
||||||
|
|
||||||
|
<!-- tabs:start -->
|
||||||
|
|
||||||
|
#### **Go Modules**
|
||||||
|
|
||||||
|
First, make sure you have [Go][go] properly installed and setup.
|
||||||
|
|
||||||
|
Task requires [Go Modules](https://github.com/golang/go/wiki/Modules) and
|
||||||
|
doesn't officially support installing via `go get` anymore.
|
||||||
|
|
||||||
Installing in another directory:
|
Installing in another directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/go-task/task
|
git clone https://github.com/go-task/task
|
||||||
cd task
|
cd task
|
||||||
|
|
||||||
# compiling binary to $GOPATH/bin
|
# Compiling binary to $GOPATH/bin
|
||||||
go install -v
|
go install -v ./cmd/task
|
||||||
|
|
||||||
# compiling it to another location
|
# Compiling it to another location.
|
||||||
# use -o ./task.exe on Windows
|
# Use -o ./task.exe on Windows.
|
||||||
go build -v -o ./task ./cmd/task
|
go build -v -o ./task ./cmd/task
|
||||||
```
|
```
|
||||||
|
|
||||||
Both methods requires having the [Go][go] environment properly setup locally.
|
> For CI environments we recommend using the [Install Script](#get-the-binary)
|
||||||
|
> instead, which is faster and more stable, since it'll just download the latest
|
||||||
|
> released binary, instead of compiling the edge (master branch) version.
|
||||||
|
|
||||||
## Install script
|
<!-- tabs:end -->
|
||||||
|
|
||||||
We also have a [install script][installscript], which is very useful on
|
|
||||||
scanarios like CIs. Many thanks to [godownloader][godownloader] for allowing
|
|
||||||
easily generating this script.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s https://taskfile.org/install.sh | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
> This method will download the binary on the local `./bin` directory by default.
|
|
||||||
|
|
||||||
[go]: https://golang.org/
|
[go]: https://golang.org/
|
||||||
[snapcraft]: https://snapcraft.io/
|
[snapcraft]: https://snapcraft.io/task
|
||||||
[homebrew]: https://brew.sh/
|
[homebrew]: https://brew.sh/
|
||||||
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
|
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
|
||||||
[releases]: https://github.com/go-task/task/releases
|
[releases]: https://github.com/go-task/task/releases
|
||||||
[godownloader]: https://github.com/goreleaser/godownloader
|
[godownloader]: https://github.com/goreleaser/godownloader
|
||||||
|
[scoop]: https://scoop.sh/
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Releasing Task
|
# Releasing Task
|
||||||
|
|
||||||
The release process of Task is done is done with the help of
|
The release process of Task is done with the help of
|
||||||
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
[GoReleaser][goreleaser]. You can test the release process locally by calling
|
||||||
the `test-release` task of the Taskfile.
|
the `test-release` task of the Taskfile.
|
||||||
|
|
||||||
The Travis CI should release automatically when a new
|
[GitHub Actions](https://github.com/go-task/task/actions) should release
|
||||||
Git tag is pushed to master, either for the artifact uploading (raw executables
|
artifacts automatically when a new Git tag is pushed to master
|
||||||
and DEB and RPM packages)
|
(raw executables and DEB and RPM packages).
|
||||||
|
|
||||||
# Homebrew
|
# Homebrew
|
||||||
|
|
||||||
@@ -22,7 +22,14 @@ the binaries:
|
|||||||
|
|
||||||
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
||||||
* Moving both `i386` and `amd64` new artifacts to the stable channel on
|
* Moving both `i386` and `amd64` new artifacts to the stable channel on
|
||||||
the [Snapscraft dashboard][snapcraftdashboard]
|
the [Snapcraft dashboard][snapcraftdashboard]
|
||||||
|
|
||||||
|
# Scoop
|
||||||
|
|
||||||
|
Scoop is a community owned installation method. Scoop owners usually take care
|
||||||
|
of updating versions there by editing
|
||||||
|
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||||
|
If you think its Task version is outdated, open an issue to let us know.
|
||||||
|
|
||||||
[goreleaser]: https://goreleaser.com/#continuous_integration
|
[goreleaser]: https://goreleaser.com/#continuous_integration
|
||||||
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# Sponsors and Backers
|
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
|
|
||||||
[][opencollective]
|
|
||||||
|
|
||||||
## Backers
|
|
||||||
|
|
||||||
[][opencollective]
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
[][contributors]
|
|
||||||
|
|
||||||
[opencollective]: https://opencollective.com/task
|
|
||||||
[contributors]: https://github.com/go-task/task/graphs/contributors
|
|
||||||
213
docs/styleguide.md
Normal file
213
docs/styleguide.md
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
# Styleguide
|
||||||
|
|
||||||
|
This is the official Task styleguide for `Taskfile.yml` files. This guide
|
||||||
|
contains some basic instructions to keep your Taskfile clean and familiar to
|
||||||
|
other users.
|
||||||
|
|
||||||
|
This contains general guidelines, but don't necessarely need to be strictly
|
||||||
|
followed. Feel free to disagree and proceed differently in some point if you
|
||||||
|
need or want to. Also, feel free to open issues or pull requests with
|
||||||
|
improvements to this guide.
|
||||||
|
|
||||||
|
## Use `Taskfile.yml` and not `taskfile.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
taskfile.yml
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
Taskfile.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
This is important especially for Linux users. Windows and macOS have case
|
||||||
|
insensitive filesystems, so `taskfile.yml` will end up working, even that not
|
||||||
|
officially supported. On Linux, only `Taskfile.yml` will work, though.
|
||||||
|
|
||||||
|
## Use the correct order of keywords
|
||||||
|
|
||||||
|
- `version:`
|
||||||
|
- `includes:`
|
||||||
|
- Configuration ones, like `output:`, `expansions:` or `silent:`
|
||||||
|
- `vars:`
|
||||||
|
- `env:`
|
||||||
|
- `tasks:`
|
||||||
|
|
||||||
|
## Use 2 spaces for indentation
|
||||||
|
|
||||||
|
This is the most common convention for YAML files, and Task follows it.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
cmds:
|
||||||
|
- echo 'foo'
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
cmds:
|
||||||
|
- echo 'foo'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Separate with spaces the mains sections
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
version: 2
|
||||||
|
includes:
|
||||||
|
docker: ./docker/Taskfile.yml
|
||||||
|
output: prefixed
|
||||||
|
expansions: 3
|
||||||
|
vars:
|
||||||
|
FOO: bar
|
||||||
|
env:
|
||||||
|
BAR: baz
|
||||||
|
tasks:
|
||||||
|
# ...
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
includes:
|
||||||
|
docker: ./docker/Taskfile.yml
|
||||||
|
|
||||||
|
output: prefixed
|
||||||
|
expansions: 3
|
||||||
|
|
||||||
|
vars:
|
||||||
|
FOO: bar
|
||||||
|
|
||||||
|
env:
|
||||||
|
BAR: baz
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add spaces between tasks
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
cmds:
|
||||||
|
- echo 'foo'
|
||||||
|
bar:
|
||||||
|
cmds:
|
||||||
|
- echo 'bar'
|
||||||
|
baz:
|
||||||
|
cmds:
|
||||||
|
- echo 'baz'
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
cmds:
|
||||||
|
- echo 'foo'
|
||||||
|
|
||||||
|
bar:
|
||||||
|
cmds:
|
||||||
|
- echo 'bar'
|
||||||
|
|
||||||
|
baz:
|
||||||
|
cmds:
|
||||||
|
- echo 'baz'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use upper-case variable names
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
vars:
|
||||||
|
binary_name: myapp
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- go build -o {{.binary_name}} .
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
vars:
|
||||||
|
BINARY_NAME: myapp
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- go build -o {{.BINARY_NAME}} .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Don't wrap vars in spaces when templating
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
greet:
|
||||||
|
cmds:
|
||||||
|
- echo '{{ .MESSAGE }}'
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
greet:
|
||||||
|
cmds:
|
||||||
|
- echo '{{.MESSAGE}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
This convention is also used by most people for any Go templating.
|
||||||
|
|
||||||
|
## Separate task name words with a dash
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# bad
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
do_something_fancy:
|
||||||
|
cmds:
|
||||||
|
- echo 'Do something'
|
||||||
|
|
||||||
|
|
||||||
|
# good
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
do-something-fancy:
|
||||||
|
cmds:
|
||||||
|
- echo 'Do something'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use colon for task namespacing
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# good
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
docker:build:
|
||||||
|
cmds:
|
||||||
|
- docker ...
|
||||||
|
|
||||||
|
docker:run:
|
||||||
|
cmds:
|
||||||
|
- docker-compose ...
|
||||||
|
```
|
||||||
|
|
||||||
|
This is also done automatically when using included Taskfiles.
|
||||||
@@ -9,8 +9,8 @@ The Taskfile version follows the Task version. E.g. the change to Taskfile
|
|||||||
version `2` means that Task `v2.0.0` should be release to support it.
|
version `2` means that Task `v2.0.0` should be release to support it.
|
||||||
|
|
||||||
The `version:` key on Taskfile accepts a semver string, so either `2`, `2.0` or
|
The `version:` key on Taskfile accepts a semver string, so either `2`, `2.0` or
|
||||||
`2.0.0` is accepted. You you choose to use `2.0` Task will not enable future
|
`2.0.0` is accepted. If you choose to use `2.0` Task will not enable future
|
||||||
`2.1` features, but if you choose to use `2`, than any `2.x.x` features will be
|
`2.1` features, but if you choose to use `2`, then any `2.x.x` features will be
|
||||||
available, but not `3.0.0+`.
|
available, but not `3.0.0+`.
|
||||||
|
|
||||||
## Version 1
|
## Version 1
|
||||||
@@ -33,7 +33,7 @@ The variable priority order was also different:
|
|||||||
|
|
||||||
## Version 2.0
|
## Version 2.0
|
||||||
|
|
||||||
At version 2, we introduced the `version:` key, to allow us to envolve Task
|
At version 2, we introduced the `version:` key, to allow us to evolve Task
|
||||||
with new features without breaking existing Taskfiles. The new syntax is as
|
with new features without breaking existing Taskfiles. The new syntax is as
|
||||||
follows:
|
follows:
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ tasks:
|
|||||||
prefix: server
|
prefix: server
|
||||||
```
|
```
|
||||||
|
|
||||||
From this version it's not also possible to ignore errors of a command or task
|
From this version it's also possible to ignore errors of a command or task
|
||||||
(check documentation [here][ignore_errors]):
|
(check documentation [here][ignore_errors]):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -141,6 +141,21 @@ includes:
|
|||||||
docker: ./DockerTasks.yml
|
docker: ./DockerTasks.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Version 2.6
|
||||||
|
|
||||||
|
Version 2.6 comes with `preconditions` stanza in tasks.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
upload_environment:
|
||||||
|
preconditions:
|
||||||
|
- test -f .env
|
||||||
|
cmds:
|
||||||
|
- aws s3 cp .env s3://myenvironment
|
||||||
|
```
|
||||||
|
|
||||||
Please check the [documentation][includes]
|
Please check the [documentation][includes]
|
||||||
|
|
||||||
[output]: usage.md#output-syntax
|
[output]: usage.md#output-syntax
|
||||||
|
|||||||
172
docs/usage.md
172
docs/usage.md
@@ -31,23 +31,41 @@ interpreter. So you can write sh/bash commands and it will work even on
|
|||||||
Windows, where `sh` or `bash` are usually not available. Just remember any
|
Windows, where `sh` or `bash` are usually not available. Just remember any
|
||||||
executable called must be available by the OS or in PATH.
|
executable called must be available by the OS or in PATH.
|
||||||
|
|
||||||
If you ommit a task name, "default" will be assumed.
|
If you omit a task name, "default" will be assumed.
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
You can specify environment variables that are added when running a command:
|
You can use `env` to set custom environment variables for a specific task:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '2'
|
version: '2'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
build:
|
greet:
|
||||||
cmds:
|
cmds:
|
||||||
- echo $hallo
|
- echo $GREETING
|
||||||
env:
|
env:
|
||||||
hallo: welt
|
GREETING: Hey, there!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Additionally, you can set globally environment variables, that'll be available
|
||||||
|
to all tasks:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
env:
|
||||||
|
GREETING: Hey, there!
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
greet:
|
||||||
|
cmds:
|
||||||
|
- echo $GREETING
|
||||||
|
```
|
||||||
|
|
||||||
|
> NOTE: `env` supports expansion and retrieving output from a shell command
|
||||||
|
> just like variables, as you can see on the [Variables](#variables) section.
|
||||||
|
|
||||||
## Operating System specific tasks
|
## Operating System specific tasks
|
||||||
|
|
||||||
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
|
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
|
||||||
@@ -83,7 +101,7 @@ Keep in mind that the version of the files should match. Also, when redefining
|
|||||||
a task the whole task is replaced, properties of the task are not merged.
|
a task the whole task is replaced, properties of the task are not merged.
|
||||||
|
|
||||||
It's also possible to have an OS specific `Taskvars.yml` file, like
|
It's also possible to have an OS specific `Taskvars.yml` file, like
|
||||||
`Taskvars_windows.yml`, `Taskfile_linux.yml`, or `Taskvars_darwin.yml`. See the
|
`Taskvars_windows.yml`, `Taskvars_linux.yml`, or `Taskvars_darwin.yml`. See the
|
||||||
[variables section](#variables) below.
|
[variables section](#variables) below.
|
||||||
|
|
||||||
## Including other Taskfiles
|
## Including other Taskfiles
|
||||||
@@ -130,8 +148,14 @@ tasks:
|
|||||||
- caddy
|
- caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If the directory doesn't exist, `task` creates it.
|
||||||
|
|
||||||
## Task dependencies
|
## Task dependencies
|
||||||
|
|
||||||
|
> Dependencies run in parallel, so dependencies of a task shouldn't depend one
|
||||||
|
> another. If you want to force tasks to run serially take a look at the
|
||||||
|
> [Calling Another Task](#calling-another-task) section below.
|
||||||
|
|
||||||
You may have tasks that depend on others. Just pointing them on `deps` will
|
You may have tasks that depend on others. Just pointing them on `deps` will
|
||||||
make them run automatically before running the parent task:
|
make them run automatically before running the parent task:
|
||||||
|
|
||||||
@@ -173,6 +197,9 @@ tasks:
|
|||||||
If there is more than one dependency, they always run in parallel for better
|
If there is more than one dependency, they always run in parallel for better
|
||||||
performance.
|
performance.
|
||||||
|
|
||||||
|
> You can also make the tasks given by the command line run in parallel by
|
||||||
|
> using the `--parallel` flag (alias `-p`). Example: `task --parallel js css`.
|
||||||
|
|
||||||
If you want to pass information to dependencies, you can do that the same
|
If you want to pass information to dependencies, you can do that the same
|
||||||
manner as you would to [call another task](#calling-another-task):
|
manner as you would to [call another task](#calling-another-task):
|
||||||
|
|
||||||
@@ -240,6 +267,10 @@ tasks:
|
|||||||
|
|
||||||
The above syntax is also supported in `deps`.
|
The above syntax is also supported in `deps`.
|
||||||
|
|
||||||
|
> NOTE: If you want to call a task declared in the root Taskfile from within an
|
||||||
|
> [included Taskfile](#including-other-taskfiles), add a leading `:` like this:
|
||||||
|
> `task: :task-name`.
|
||||||
|
|
||||||
## Prevent unnecessary work
|
## Prevent unnecessary work
|
||||||
|
|
||||||
If a task generates something, you can inform Task the source and generated
|
If a task generates something, you can inform Task the source and generated
|
||||||
@@ -322,12 +353,61 @@ up-to-date.
|
|||||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
||||||
the tasks are not up-to-date.
|
the tasks are not up-to-date.
|
||||||
|
|
||||||
|
If you need a certain set of conditions to be _true_ you can use the
|
||||||
|
`preconditions` stanza. `preconditions` are very similar to `status`
|
||||||
|
lines except they support `sh` expansion and they SHOULD all return 0.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
generate-files:
|
||||||
|
cmds:
|
||||||
|
- mkdir directory
|
||||||
|
- touch directory/file1.txt
|
||||||
|
- touch directory/file2.txt
|
||||||
|
# test existence of files
|
||||||
|
preconditions:
|
||||||
|
- test -f .env
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "One doesn't equal Zero, Halting"
|
||||||
|
```
|
||||||
|
|
||||||
|
Preconditions can set specific failure messages that can tell
|
||||||
|
a user what steps to take using the `msg` field.
|
||||||
|
|
||||||
|
If a task has a dependency on a sub-task with a precondition, and that
|
||||||
|
precondition is not met - the calling task will fail. Note that a task
|
||||||
|
executed with a failing precondition will not run unless `--force` is
|
||||||
|
given.
|
||||||
|
|
||||||
|
Unlike `status` which will skip a task if it is up to date, and continue
|
||||||
|
executing tasks that depend on it, a `precondition` will fail a task, along
|
||||||
|
with any other tasks that depend on it.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
tasks:
|
||||||
|
task_will_fail:
|
||||||
|
preconditions:
|
||||||
|
- sh: "exit 1"
|
||||||
|
|
||||||
|
task_will_also_fail:
|
||||||
|
deps:
|
||||||
|
- task_will_fail
|
||||||
|
|
||||||
|
task_will_still_fail:
|
||||||
|
cmds:
|
||||||
|
- task: task_will_fail
|
||||||
|
- echo "I will not run"
|
||||||
|
```
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
When doing interpolation of variables, Task will look for the below.
|
When doing interpolation of variables, Task will look for the below.
|
||||||
They are listed below in order of importance (e.g. most important first):
|
They are listed below in order of importance (e.g. most important first):
|
||||||
|
|
||||||
- Variables declared locally in the task
|
- Variables declared in the task definition
|
||||||
- Variables given while calling a task from another.
|
- Variables given while calling a task from another.
|
||||||
(See [Calling another task](#calling-another-task) above)
|
(See [Calling another task](#calling-another-task) above)
|
||||||
- Variables declared in the `vars:` option in the `Taskfile`
|
- Variables declared in the `vars:` option in the `Taskfile`
|
||||||
@@ -349,6 +429,12 @@ right before.
|
|||||||
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you want to set global variables using this syntax, give it before any task:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ task OUTPUT=file.txt generate-file
|
||||||
|
```
|
||||||
|
|
||||||
Example of locally declared vars:
|
Example of locally declared vars:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -357,7 +443,7 @@ version: '2'
|
|||||||
tasks:
|
tasks:
|
||||||
print-var:
|
print-var:
|
||||||
cmds:
|
cmds:
|
||||||
echo "{{.VAR}}"
|
- echo "{{.VAR}}"
|
||||||
vars:
|
vars:
|
||||||
VAR: Hello!
|
VAR: Hello!
|
||||||
```
|
```
|
||||||
@@ -455,7 +541,7 @@ Task also adds the following functions:
|
|||||||
- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space.
|
- `catLines`: Replaces Unix (\n) and Windows (\r\n) styled newlines with a space.
|
||||||
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
- `toSlash`: Does nothing on Unix, but on Windows converts a string from `\`
|
||||||
path format to `/`.
|
path format to `/`.
|
||||||
- `fromSlash`: Oposite of `toSlash`. Does nothing on Unix, but on Windows
|
- `fromSlash`: Opposite of `toSlash`. Does nothing on Unix, but on Windows
|
||||||
converts a string from `\` path format to `/`.
|
converts a string from `\` path format to `/`.
|
||||||
- `exeExt`: Returns the right executable extension for the current OS
|
- `exeExt`: Returns the right executable extension for the current OS
|
||||||
(`".exe"` for Windows, `""` for others).
|
(`".exe"` for Windows, `""` for others).
|
||||||
@@ -488,7 +574,7 @@ tasks:
|
|||||||
## Help
|
## Help
|
||||||
|
|
||||||
Running `task --list` (or `task -l`) lists all tasks with a description.
|
Running `task --list` (or `task -l`) lists all tasks with a description.
|
||||||
The following taskfile:
|
The following Taskfile:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '2'
|
version: '2'
|
||||||
@@ -520,6 +606,51 @@ would print the following output:
|
|||||||
* test: Run all the go tests.
|
* test: Run all the go tests.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Display summary of task
|
||||||
|
|
||||||
|
Running `task --summary task-name` will show a summary of a task.
|
||||||
|
The following Taskfile:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
release:
|
||||||
|
deps: [build]
|
||||||
|
summary: |
|
||||||
|
Release your project to github
|
||||||
|
|
||||||
|
It will build your project before starting the release.
|
||||||
|
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||||
|
cmds:
|
||||||
|
- your-release-tool
|
||||||
|
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- your-build-tool
|
||||||
|
```
|
||||||
|
|
||||||
|
with running ``task --summary release`` would print the following output:
|
||||||
|
|
||||||
|
```
|
||||||
|
task: release
|
||||||
|
|
||||||
|
Release your project to github
|
||||||
|
|
||||||
|
It will build your project before starting the release.
|
||||||
|
Please make sure that you have set GITHUB_TOKEN before starting.
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- build
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- your-release-tool
|
||||||
|
```
|
||||||
|
If a summary is missing, the description will be printed.
|
||||||
|
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*.
|
||||||
|
|
||||||
## Silent mode
|
## Silent mode
|
||||||
|
|
||||||
Silent mode disables echoing of commands before Task runs it.
|
Silent mode disables echoing of commands before Task runs it.
|
||||||
@@ -547,7 +678,7 @@ With silent mode on, the below will be print instead:
|
|||||||
Print something
|
Print something
|
||||||
```
|
```
|
||||||
|
|
||||||
There's three ways to enable silent mode:
|
There are four ways to enable silent mode:
|
||||||
|
|
||||||
* At command level:
|
* At command level:
|
||||||
|
|
||||||
@@ -573,9 +704,22 @@ tasks:
|
|||||||
silent: true
|
silent: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Globally at Taskfile level:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
echo:
|
||||||
|
cmds:
|
||||||
|
- echo "Print something"
|
||||||
|
```
|
||||||
|
|
||||||
* Or globally with `--silent` or `-s` flag
|
* Or globally with `--silent` or `-s` flag
|
||||||
|
|
||||||
If you want to suppress stdout instead, just redirect a command to `/dev/null`:
|
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '2'
|
version: '2'
|
||||||
@@ -620,7 +764,7 @@ tasks:
|
|||||||
- echo "Hello World"
|
- echo "Hello World"
|
||||||
```
|
```
|
||||||
|
|
||||||
`ignore_error` can also be set for a task, which mean errors will be supressed
|
`ignore_error` can also be set for a task, which mean errors will be suppressed
|
||||||
for all commands. But keep in mind this option won't propagate to other tasks
|
for all commands. But keep in mind this option won't propagate to other tasks
|
||||||
called either by `deps` or `cmds`!
|
called either by `deps` or `cmds`!
|
||||||
|
|
||||||
@@ -686,6 +830,8 @@ $ task default
|
|||||||
[print-baz] baz
|
[print-baz] baz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> The `output` option can also be specified by the `--output` or `-o` flags.
|
||||||
|
|
||||||
## Watch tasks
|
## Watch tasks
|
||||||
|
|
||||||
If you give a `--watch` or `-w` argument, task will watch for file changes
|
If you give a `--watch` or `-w` argument, task will watch for file changes
|
||||||
|
|||||||
23
go.mod
23
go.mod
@@ -1,24 +1,21 @@
|
|||||||
module github.com/go-task/task/v2
|
module github.com/go-task/task/v2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver v1.4.2
|
github.com/Masterminds/semver v1.4.2 // indirect
|
||||||
github.com/Masterminds/sprig v2.16.0+incompatible
|
github.com/Masterminds/sprig v2.16.0+incompatible
|
||||||
github.com/aokoli/goutils v1.0.1 // indirect
|
github.com/aokoli/goutils v1.0.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/uuid v1.0.0 // indirect
|
github.com/google/uuid v1.0.0 // indirect
|
||||||
github.com/huandu/xstrings v1.1.0 // indirect
|
github.com/huandu/xstrings v1.1.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.6 // indirect
|
github.com/imdario/mergo v0.3.6 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/mattn/go-zglob v0.0.1
|
||||||
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53
|
github.com/radovskyb/watcher v1.0.5
|
||||||
github.com/mitchellh/go-homedir v1.0.0
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/radovskyb/watcher v1.0.2
|
|
||||||
github.com/spf13/pflag v1.0.3
|
github.com/spf13/pflag v1.0.3
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.4.0
|
||||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc // indirect
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 // indirect
|
mvdan.cc/sh/v3 v3.1.1
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
|
||||||
mvdan.cc/sh v0.0.0-20180829163519-3a244a89e2e5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|||||||
64
go.sum
64
go.sum
@@ -4,8 +4,12 @@ github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88
|
|||||||
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
|
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
|
||||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||||
|
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/huandu/xstrings v1.1.0 h1:9oZY6Z/H3A1gytJxzuicbmV5QoR8M1TAPcn9WTg7vqg=
|
github.com/huandu/xstrings v1.1.0 h1:9oZY6Z/H3A1gytJxzuicbmV5QoR8M1TAPcn9WTg7vqg=
|
||||||
@@ -14,32 +18,52 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
|||||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53 h1:tGfIHhDghvEnneeRhODvGYOt305TPwingKt6p90F4MU=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
||||||
|
github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/radovskyb/watcher v1.0.2 h1:9L5TsZUbo1nKhQEQPtICVc+x9UZQ6VPdBepLHyGw/bQ=
|
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
|
||||||
github.com/radovskyb/watcher v1.0.2/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||||
|
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 h1:T8D7l6WB3tLu+VpKvw06ieD/OhBi1XpJmG1U/FtttZg=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867 h1:JoRuNIf+rpHl+VhScRQQvzbHed86tKkqwPMV34T8myw=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407 h1:5zh5atpUEdIc478E/ebrIaHLKcfVvG6dL/fGv7BcMoM=
|
||||||
|
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
mvdan.cc/sh v0.0.0-20180829163519-3a244a89e2e5 h1:FKi9XtQO5aNipfQ/qnnLCoM6gdFwPQY702RRbNRxjK8=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
mvdan.cc/sh v0.0.0-20180829163519-3a244a89e2e5/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
mvdan.cc/editorconfig v0.1.1-0.20200121172147-e40951bde157/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8=
|
||||||
|
mvdan.cc/sh/v3 v3.1.1 h1:niuYC5Ug0KzLuN6CNX3ru37v4MkVD5Wm9T4Mk2eJr9A=
|
||||||
|
mvdan.cc/sh/v3 v3.1.1/go.mod h1:F+Vm4ZxPJxDKExMLhvjuI50oPnedVXpfjNSrusiTOno=
|
||||||
|
|||||||
4
help.go
4
help.go
@@ -29,6 +29,10 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
|||||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||||
for _, task := range e.Taskfile.Tasks {
|
for _, task := range e.Taskfile.Tasks {
|
||||||
if task.Desc != "" {
|
if task.Desc != "" {
|
||||||
|
compiledTask, err := e.CompiledTask(taskfile.Call{Task: task.Task})
|
||||||
|
if err == nil {
|
||||||
|
task = compiledTask
|
||||||
|
}
|
||||||
tasks = append(tasks, task)
|
tasks = append(tasks, task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
init.go
2
init.go
@@ -8,7 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTaskfile = `# https://taskfile.org
|
const defaultTaskfile = `# https://taskfile.dev
|
||||||
|
|
||||||
version: '2'
|
version: '2'
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,47 @@
|
|||||||
package args
|
package args
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrVariableWithoutTask is returned when variables are given before any task
|
|
||||||
ErrVariableWithoutTask = errors.New("task: variable given before any task")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse parses command line argument: tasks and vars of each task
|
// Parse parses command line argument: tasks and vars of each task
|
||||||
func Parse(args ...string) ([]taskfile.Call, error) {
|
func Parse(args ...string) ([]taskfile.Call, taskfile.Vars) {
|
||||||
var calls []taskfile.Call
|
var calls []taskfile.Call
|
||||||
|
var globals taskfile.Vars
|
||||||
|
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if !strings.Contains(arg, "=") {
|
if !strings.Contains(arg, "=") {
|
||||||
calls = append(calls, taskfile.Call{Task: arg})
|
calls = append(calls, taskfile.Call{Task: arg})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(calls) < 1 {
|
if len(calls) < 1 {
|
||||||
return nil, ErrVariableWithoutTask
|
if globals == nil {
|
||||||
}
|
globals = taskfile.Vars{}
|
||||||
|
}
|
||||||
|
|
||||||
if calls[len(calls)-1].Vars == nil {
|
name, value := splitVar(arg)
|
||||||
calls[len(calls)-1].Vars = make(taskfile.Vars)
|
globals[name] = taskfile.Var{Static: value}
|
||||||
}
|
} else {
|
||||||
|
if calls[len(calls)-1].Vars == nil {
|
||||||
|
calls[len(calls)-1].Vars = make(taskfile.Vars)
|
||||||
|
}
|
||||||
|
|
||||||
pair := strings.SplitN(arg, "=", 2)
|
name, value := splitVar((arg))
|
||||||
calls[len(calls)-1].Vars[pair[0]] = taskfile.Var{Static: pair[1]}
|
calls[len(calls)-1].Vars[name] = taskfile.Var{Static: value}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return calls, nil
|
|
||||||
|
if len(calls) == 0 {
|
||||||
|
calls = append(calls, taskfile.Call{Task: "default"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return calls, globals
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitVar(s string) (string, string) {
|
||||||
|
pair := strings.SplitN(s, "=", 2)
|
||||||
|
return pair[0], pair[1]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import (
|
|||||||
|
|
||||||
func TestArgs(t *testing.T) {
|
func TestArgs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Args []string
|
Args []string
|
||||||
Expected []taskfile.Call
|
ExpectedCalls []taskfile.Call
|
||||||
Err error
|
ExpectedGlobals taskfile.Vars
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Args: []string{"task-a", "task-b", "task-c"},
|
Args: []string{"task-a", "task-b", "task-c"},
|
||||||
Expected: []taskfile.Call{
|
ExpectedCalls: []taskfile.Call{
|
||||||
{Task: "task-a"},
|
{Task: "task-a"},
|
||||||
{Task: "task-b"},
|
{Task: "task-b"},
|
||||||
{Task: "task-c"},
|
{Task: "task-c"},
|
||||||
@@ -26,7 +26,7 @@ func TestArgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
||||||
Expected: []taskfile.Call{
|
ExpectedCalls: []taskfile.Call{
|
||||||
{
|
{
|
||||||
Task: "task-a",
|
Task: "task-a",
|
||||||
Vars: taskfile.Vars{
|
Vars: taskfile.Vars{
|
||||||
@@ -45,7 +45,7 @@ func TestArgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||||
Expected: []taskfile.Call{
|
ExpectedCalls: []taskfile.Call{
|
||||||
{
|
{
|
||||||
Task: "task-a",
|
Task: "task-a",
|
||||||
Vars: taskfile.Vars{
|
Vars: taskfile.Vars{
|
||||||
@@ -55,16 +55,44 @@ func TestArgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Args: []string{"FOO=bar", "task-a"},
|
Args: []string{"FOO=bar", "task-a", "task-b"},
|
||||||
Err: args.ErrVariableWithoutTask,
|
ExpectedCalls: []taskfile.Call{
|
||||||
|
{Task: "task-a"},
|
||||||
|
{Task: "task-b"},
|
||||||
|
},
|
||||||
|
ExpectedGlobals: taskfile.Vars{
|
||||||
|
"FOO": {Static: "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Args: nil,
|
||||||
|
ExpectedCalls: []taskfile.Call{
|
||||||
|
{Task: "default"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Args: []string{},
|
||||||
|
ExpectedCalls: []taskfile.Call{
|
||||||
|
{Task: "default"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Args: []string{"FOO=bar", "BAR=baz"},
|
||||||
|
ExpectedCalls: []taskfile.Call{
|
||||||
|
{Task: "default"},
|
||||||
|
},
|
||||||
|
ExpectedGlobals: taskfile.Vars{
|
||||||
|
"FOO": {Static: "bar"},
|
||||||
|
"BAR": {Static: "baz"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||||
calls, err := args.Parse(test.Args...)
|
calls, globals := args.Parse(test.Args...)
|
||||||
assert.Equal(t, test.Err, err)
|
assert.Equal(t, test.ExpectedCalls, calls)
|
||||||
assert.Equal(t, test.Expected, calls)
|
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
internal/execext/devnull.go
Normal file
13
internal/execext/devnull.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package execext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ io.ReadWriteCloser = devNull{}
|
||||||
|
|
||||||
|
type devNull struct{}
|
||||||
|
|
||||||
|
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
|
||||||
|
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
|
||||||
|
func (devNull) Close() error { return nil }
|
||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"mvdan.cc/sh/interp"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/syntax"
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/shell"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunCommandOptions is the options for the RunCommand func
|
// RunCommandOptions is the options for the RunCommand func
|
||||||
@@ -41,17 +44,17 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
if len(environ) == 0 {
|
if len(environ) == 0 {
|
||||||
environ = os.Environ()
|
environ = os.Environ()
|
||||||
}
|
}
|
||||||
env, err := interp.EnvFromList(environ)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := interp.New(
|
r, err := interp.New(
|
||||||
interp.Dir(opts.Dir),
|
interp.Dir(opts.Dir),
|
||||||
interp.Env(env),
|
interp.Env(expand.ListEnviron(environ...)),
|
||||||
|
|
||||||
interp.Module(interp.DefaultExec),
|
interp.OpenHandler(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
interp.Module(interp.OpenDevImpls(interp.DefaultOpen)),
|
if path == "/dev/null" {
|
||||||
|
return devNull{}, nil
|
||||||
|
}
|
||||||
|
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
|
||||||
|
}),
|
||||||
|
|
||||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||||
)
|
)
|
||||||
@@ -63,10 +66,23 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
|
|
||||||
// IsExitError returns true the given error is an exis status error
|
// IsExitError returns true the given error is an exis status error
|
||||||
func IsExitError(err error) bool {
|
func IsExitError(err error) bool {
|
||||||
switch err.(type) {
|
if _, ok := interp.IsExitStatus(err); ok {
|
||||||
case interp.ExitStatus, interp.ShellExitStatus:
|
|
||||||
return true
|
return true
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||||
|
// if available.
|
||||||
|
func Expand(s string) (string, error) {
|
||||||
|
s = filepath.ToSlash(s)
|
||||||
|
s = strings.Replace(s, " ", `\ `, -1)
|
||||||
|
fields, err := shell.Fields(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(fields) > 0 {
|
||||||
|
return fields[0], nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
type Group struct{}
|
type Group struct{}
|
||||||
|
|
||||||
func (Group) WrapWriter(w io.Writer, _ string) io.WriteCloser {
|
func (Group) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||||
return &groupWriter{writer: w}
|
return &groupWriter{writer: w}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,6 @@ import (
|
|||||||
|
|
||||||
type Interleaved struct{}
|
type Interleaved struct{}
|
||||||
|
|
||||||
func (Interleaved) WrapWriter(w io.Writer, _ string) io.WriteCloser {
|
func (Interleaved) WrapWriter(w io.Writer, _ string) io.Writer {
|
||||||
return nopWriterCloser{w: w}
|
return w
|
||||||
}
|
|
||||||
|
|
||||||
type nopWriterCloser struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wc nopWriterCloser) Write(p []byte) (int, error) {
|
|
||||||
return wc.w.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wc nopWriterCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Output interface {
|
type Output interface {
|
||||||
WrapWriter(w io.Writer, prefix string) io.WriteCloser
|
WrapWriter(w io.Writer, prefix string) io.Writer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package output_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/output"
|
"github.com/go-task/task/v2/internal/output"
|
||||||
@@ -24,7 +25,7 @@ func TestInterleaved(t *testing.T) {
|
|||||||
func TestGroup(t *testing.T) {
|
func TestGroup(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Group{}
|
var o output.Output = output.Group{}
|
||||||
var w = o.WrapWriter(&b, "")
|
var w = o.WrapWriter(&b, "").(io.WriteCloser)
|
||||||
|
|
||||||
fmt.Fprintln(w, "foo\nbar")
|
fmt.Fprintln(w, "foo\nbar")
|
||||||
assert.Equal(t, "", b.String())
|
assert.Equal(t, "", b.String())
|
||||||
@@ -37,7 +38,7 @@ func TestGroup(t *testing.T) {
|
|||||||
func TestPrefixed(t *testing.T) {
|
func TestPrefixed(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var o output.Output = output.Prefixed{}
|
var o output.Output = output.Prefixed{}
|
||||||
var w = o.WrapWriter(&b, "prefix")
|
var w = o.WrapWriter(&b, "prefix").(io.WriteCloser)
|
||||||
|
|
||||||
t.Run("simple use cases", func(t *testing.T) {
|
t.Run("simple use cases", func(t *testing.T) {
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
type Prefixed struct{}
|
type Prefixed struct{}
|
||||||
|
|
||||||
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.WriteCloser {
|
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.Writer {
|
||||||
return &prefixWriter{writer: w, prefix: prefix}
|
return &prefixWriter{writer: w, prefix: prefix}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +34,12 @@ func (pw *prefixWriter) Close() error {
|
|||||||
|
|
||||||
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||||
for {
|
for {
|
||||||
line, err := pw.buff.ReadString('\n')
|
switch line, err := pw.buff.ReadString('\n'); err {
|
||||||
if err == nil {
|
case nil:
|
||||||
if err = pw.writeLine(line); err != nil {
|
if err = pw.writeLine(line); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err == io.EOF {
|
case io.EOF:
|
||||||
// if this line was not a complete line, re-add to the buffer
|
// if this line was not a complete line, re-add to the buffer
|
||||||
if !force && !strings.HasSuffix(line, "\n") {
|
if !force && !strings.HasSuffix(line, "\n") {
|
||||||
_, err = pw.buff.WriteString(line)
|
_, err = pw.buff.WriteString(line)
|
||||||
@@ -47,7 +47,7 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pw.writeLine(line)
|
return pw.writeLine(line)
|
||||||
} else {
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import (
|
|||||||
// Checksum validades if a task is up to date by calculating its source
|
// Checksum validades if a task is up to date by calculating its source
|
||||||
// files checksum
|
// files checksum
|
||||||
type Checksum struct {
|
type Checksum struct {
|
||||||
Dir string
|
Dir string
|
||||||
Task string
|
Task string
|
||||||
Sources []string
|
Sources []string
|
||||||
|
Generates []string
|
||||||
|
Dry bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUpToDate implements the Checker interface
|
// IsUpToDate implements the Checker interface
|
||||||
@@ -26,7 +28,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
|||||||
data, _ := ioutil.ReadFile(checksumFile)
|
data, _ := ioutil.ReadFile(checksumFile)
|
||||||
oldMd5 := strings.TrimSpace(string(data))
|
oldMd5 := strings.TrimSpace(string(data))
|
||||||
|
|
||||||
sources, err := glob(c.Dir, c.Sources)
|
sources, err := globs(c.Dir, c.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -36,10 +38,29 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
|
if !c.Dry {
|
||||||
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
|
||||||
return false, err
|
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.Generates) > 0 {
|
||||||
|
// For each specified 'generates' field, check whether the files actually exist
|
||||||
|
for _, g := range c.Generates {
|
||||||
|
generates, err := glob(c.Dir, g)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(generates) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return oldMd5 == newMd5, nil
|
return oldMd5 == newMd5, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,21 +68,14 @@ func (c *Checksum) checksum(files ...string) (string, error) {
|
|||||||
h := md5.New()
|
h := md5.New()
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
|
// also sum the filename, so checksum changes for renaming a file
|
||||||
|
if _, err := io.Copy(h, strings.NewReader(filepath.Base(f))); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
f, err := os.Open(f)
|
f, err := os.Open(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
info, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// also sum the filename, so checksum changes for renaming a file
|
|
||||||
if _, err = io.Copy(h, strings.NewReader(info.Name())); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err = io.Copy(h, f); err != nil {
|
if _, err = io.Copy(h, f); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,50 @@
|
|||||||
package status
|
package status
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
|
|
||||||
"github.com/mattn/go-zglob"
|
"github.com/mattn/go-zglob"
|
||||||
"mvdan.cc/sh/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func glob(dir string, globs []string) (files []string, err error) {
|
func globs(dir string, globs []string) ([]string, error) {
|
||||||
|
files := make([]string, 0)
|
||||||
for _, g := range globs {
|
for _, g := range globs {
|
||||||
if !filepath.IsAbs(g) {
|
f, err := glob(dir, g)
|
||||||
g = filepath.Join(dir, g)
|
|
||||||
}
|
|
||||||
g, err = shell.Expand(g, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err := zglob.Glob(g)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
files = append(files, f...)
|
files = append(files, f...)
|
||||||
}
|
}
|
||||||
sort.Strings(files)
|
sort.Strings(files)
|
||||||
return
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func glob(dir string, g string) ([]string, error) {
|
||||||
|
files := make([]string, 0)
|
||||||
|
if !filepath.IsAbs(g) {
|
||||||
|
g = filepath.Join(dir, g)
|
||||||
|
}
|
||||||
|
g, err := execext.Expand(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fs, err := zglob.Glob(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range fs {
|
||||||
|
info, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sources, err := glob(t.Dir, t.Sources)
|
sources, err := globs(t.Dir, t.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
generates, err := glob(t.Dir, t.Generates)
|
generates, err := globs(t.Dir, t.Generates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
103
internal/summary/summary.go
Normal file
103
internal/summary/summary.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package summary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
|
||||||
|
for i, call := range c {
|
||||||
|
PrintSpaceBetweenSummaries(l, i)
|
||||||
|
PrintTask(l, t.Tasks[call.Task])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||||
|
spaceRequired := i > 0
|
||||||
|
if !spaceRequired {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Outf("")
|
||||||
|
l.Outf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
printTaskName(l, t)
|
||||||
|
printTaskDescribingText(t, l)
|
||||||
|
printTaskDependencies(l, t)
|
||||||
|
printTaskCommands(l, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskDescribingText(t *taskfile.Task, l *logger.Logger) {
|
||||||
|
if hasSummary(t) {
|
||||||
|
printTaskSummary(l, t)
|
||||||
|
} else if hasDescription(t) {
|
||||||
|
printTaskDescription(l, t)
|
||||||
|
} else {
|
||||||
|
printNoDescriptionOrSummary(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSummary(t *taskfile.Task) bool {
|
||||||
|
return t.Summary != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
lines := strings.Split(t.Summary, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
notLastLine := i+1 < len(lines)
|
||||||
|
if notLastLine || line != "" {
|
||||||
|
l.Outf(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
l.Outf("task: %s", t.Task)
|
||||||
|
l.Outf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDescription(t *taskfile.Task) bool {
|
||||||
|
return t.Desc != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
l.Outf(t.Desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNoDescriptionOrSummary(l *logger.Logger) {
|
||||||
|
l.Outf("(task does not have description or summary)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
if len(t.Deps) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Outf("")
|
||||||
|
l.Outf("dependencies:")
|
||||||
|
|
||||||
|
for _, d := range t.Deps {
|
||||||
|
l.Outf(" - %s", d.Task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||||
|
if len(t.Cmds) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Outf("")
|
||||||
|
l.Outf("commands:")
|
||||||
|
for _, c := range t.Cmds {
|
||||||
|
isCommand := c.Cmd != ""
|
||||||
|
if isCommand {
|
||||||
|
l.Outf(" - %s", c.Cmd)
|
||||||
|
} else {
|
||||||
|
l.Outf(" - Task: %s", c.Task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
173
internal/summary/summary_test.go
Normal file
173
internal/summary/summary_test.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package summary_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
|
"github.com/go-task/task/v2/internal/summary"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintsDependenciesIfPresent(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Deps: []*taskfile.Dep{
|
||||||
|
{Task: "dep1"},
|
||||||
|
{Task: "dep2"},
|
||||||
|
{Task: "dep3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "\ndependencies:\n - dep1\n - dep2\n - dep3\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyLogger() (*bytes.Buffer, logger.Logger) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
l := logger.Logger{
|
||||||
|
Stderr: buffer,
|
||||||
|
Stdout: buffer,
|
||||||
|
Verbose: false,
|
||||||
|
}
|
||||||
|
return buffer, l
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Deps: []*taskfile.Dep{},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.NotContains(t, buffer.String(), "dependencies:")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintTaskName(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Task: "my-task-name",
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "task: my-task-name\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Cmds: []*taskfile.Cmd{
|
||||||
|
{Cmd: "command-1"},
|
||||||
|
{Cmd: "command-2"},
|
||||||
|
{Task: "task-1"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "\ncommands:\n")
|
||||||
|
assert.Contains(t, buffer.String(), "\n - command-1\n")
|
||||||
|
assert.Contains(t, buffer.String(), "\n - command-2\n")
|
||||||
|
assert.Contains(t, buffer.String(), "\n - Task: task-1\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Cmds: []*taskfile.Cmd{},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.NotContains(t, buffer.String(), "commands")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLayout(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
task := &taskfile.Task{
|
||||||
|
Task: "sample-task",
|
||||||
|
Summary: "line1\nline2\nline3\n",
|
||||||
|
Deps: []*taskfile.Dep{
|
||||||
|
{Task: "dependency"},
|
||||||
|
},
|
||||||
|
Cmds: []*taskfile.Cmd{
|
||||||
|
{Cmd: "command"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, task)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedOutput(), buffer.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectedOutput() string {
|
||||||
|
expected := `task: sample-task
|
||||||
|
|
||||||
|
line1
|
||||||
|
line2
|
||||||
|
line3
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- dependency
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- command
|
||||||
|
`
|
||||||
|
return expected
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintDescriptionAsFallback(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
taskWithoutSummary := &taskfile.Task{
|
||||||
|
Desc: "description",
|
||||||
|
}
|
||||||
|
|
||||||
|
taskWithSummary := &taskfile.Task{
|
||||||
|
Desc: "description",
|
||||||
|
Summary: "summary",
|
||||||
|
}
|
||||||
|
taskWithoutSummaryOrDescription := &taskfile.Task{}
|
||||||
|
|
||||||
|
summary.PrintTask(&l, taskWithoutSummary)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "description")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
summary.PrintTask(&l, taskWithSummary)
|
||||||
|
|
||||||
|
assert.NotContains(t, buffer.String(), "description")
|
||||||
|
|
||||||
|
buffer.Reset()
|
||||||
|
summary.PrintTask(&l, taskWithoutSummaryOrDescription)
|
||||||
|
|
||||||
|
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintAllWithSpaces(t *testing.T) {
|
||||||
|
buffer, l := createDummyLogger()
|
||||||
|
|
||||||
|
t1 := &taskfile.Task{Task: "t1"}
|
||||||
|
t2 := &taskfile.Task{Task: "t2"}
|
||||||
|
t3 := &taskfile.Task{Task: "t3"}
|
||||||
|
|
||||||
|
tasks := make(taskfile.Tasks, 3)
|
||||||
|
tasks["t1"] = t1
|
||||||
|
tasks["t2"] = t2
|
||||||
|
tasks["t3"] = t3
|
||||||
|
|
||||||
|
summary.PrintTasks(&l,
|
||||||
|
&taskfile.Taskfile{Tasks: tasks},
|
||||||
|
[]taskfile.Call{{Task: "t1"}, {Task: "t2"}, {Task: "t3"}})
|
||||||
|
|
||||||
|
assert.True(t, strings.HasPrefix(buffer.String(), "task: t1"))
|
||||||
|
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t2")
|
||||||
|
assert.Contains(t, buffer.String(), "\n(task does not have description or summary)\n\n\ntask: t3")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,6 +35,13 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
|||||||
t1.Vars[k] = v
|
t1.Vars[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t1.Env == nil {
|
||||||
|
t1.Env = make(Vars)
|
||||||
|
}
|
||||||
|
for k, v := range t2.Env {
|
||||||
|
t1.Env[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
if t1.Tasks == nil {
|
if t1.Tasks == nil {
|
||||||
t1.Tasks = make(Tasks)
|
t1.Tasks = make(Tasks)
|
||||||
}
|
}
|
||||||
@@ -59,5 +66,8 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func taskNameWithNamespace(taskName string, namespaces ...string) string {
|
func taskNameWithNamespace(taskName string, namespaces ...string) string {
|
||||||
|
if strings.HasPrefix(taskName, ":") {
|
||||||
|
return strings.TrimPrefix(taskName, ":")
|
||||||
|
}
|
||||||
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
|
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
|
||||||
}
|
}
|
||||||
|
|||||||
45
internal/taskfile/precondition.go
Normal file
45
internal/taskfile/precondition.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
|
||||||
|
ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Precondition represents a precondition necessary for a task to run
|
||||||
|
type Precondition struct {
|
||||||
|
Sh string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||||
|
func (p *Precondition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var cmd string
|
||||||
|
|
||||||
|
if err := unmarshal(&cmd); err == nil {
|
||||||
|
p.Sh = cmd
|
||||||
|
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||||
|
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
|
||||||
|
}
|
||||||
48
internal/taskfile/precondition_test.go
Normal file
48
internal/taskfile/precondition_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package taskfile_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPreconditionParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
content string
|
||||||
|
v interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"test -f foo.txt",
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: `test -f foo.txt`, Msg: "`test -f foo.txt` failed"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sh: '[ 1 = 0 ]'",
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: "[ 1 = 0 ]", Msg: "[ 1 = 0 ] failed"},
|
||||||
|
},
|
||||||
|
{`
|
||||||
|
sh: "[ 1 = 2 ]"
|
||||||
|
msg: "1 is not 2"
|
||||||
|
`,
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||||
|
},
|
||||||
|
{`
|
||||||
|
sh: "[ 1 = 2 ]"
|
||||||
|
msg: "1 is not 2"
|
||||||
|
`,
|
||||||
|
&taskfile.Precondition{},
|
||||||
|
&taskfile.Precondition{Sh: "[ 1 = 2 ]", Msg: "1 is not 2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
err := yaml.Unmarshal([]byte(test.content), test.v)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, test.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,16 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
var (
|
||||||
var ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
|
||||||
|
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
|
||||||
|
)
|
||||||
|
|
||||||
// Taskfile reads a Taskfile for a given directory
|
// Taskfile reads a Taskfile for a given directory
|
||||||
func Taskfile(dir string) (*taskfile.Taskfile, error) {
|
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
||||||
path := filepath.Join(dir, "Taskfile.yml")
|
path := filepath.Join(dir, entrypoint)
|
||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
return nil, fmt.Errorf(`No Taskfile.yml found. Use "task --init" to create a new one`)
|
return nil, fmt.Errorf(`task: No Taskfile found on "%s". Use "task --init" to create a new one`, path)
|
||||||
}
|
}
|
||||||
t, err := readTaskfile(path)
|
t, err := readTaskfile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
// Tasks representas a group of tasks
|
// Tasks represents a group of tasks
|
||||||
type Tasks map[string]*Task
|
type Tasks map[string]*Task
|
||||||
|
|
||||||
// Task represents a task
|
// Task represents a task
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Task string
|
Task string
|
||||||
Cmds []*Cmd
|
Cmds []*Cmd
|
||||||
Deps []*Dep
|
Deps []*Dep
|
||||||
Desc string
|
Desc string
|
||||||
Sources []string
|
Summary string
|
||||||
Generates []string
|
Sources []string
|
||||||
Status []string
|
Generates []string
|
||||||
Dir string
|
Status []string
|
||||||
Vars Vars
|
Preconditions []*Precondition
|
||||||
Env Vars
|
Dir string
|
||||||
Silent bool
|
Vars Vars
|
||||||
Method string
|
Env Vars
|
||||||
Prefix string
|
Silent bool
|
||||||
IgnoreError bool `yaml:"ignore_error"`
|
Method string
|
||||||
|
Prefix string
|
||||||
|
IgnoreError bool `yaml:"ignore_error"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ type Taskfile struct {
|
|||||||
Output string
|
Output string
|
||||||
Includes map[string]string
|
Includes map[string]string
|
||||||
Vars Vars
|
Vars Vars
|
||||||
|
Env Vars
|
||||||
Tasks Tasks
|
Tasks Tasks
|
||||||
|
Silent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||||
@@ -23,7 +25,9 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
Output string
|
Output string
|
||||||
Includes map[string]string
|
Includes map[string]string
|
||||||
Vars Vars
|
Vars Vars
|
||||||
|
Env Vars
|
||||||
Tasks Tasks
|
Tasks Tasks
|
||||||
|
Silent bool
|
||||||
}
|
}
|
||||||
if err := unmarshal(&taskfile); err != nil {
|
if err := unmarshal(&taskfile); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -33,9 +37,14 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
tf.Output = taskfile.Output
|
tf.Output = taskfile.Output
|
||||||
tf.Includes = taskfile.Includes
|
tf.Includes = taskfile.Includes
|
||||||
tf.Vars = taskfile.Vars
|
tf.Vars = taskfile.Vars
|
||||||
|
tf.Env = taskfile.Env
|
||||||
tf.Tasks = taskfile.Tasks
|
tf.Tasks = taskfile.Tasks
|
||||||
|
tf.Silent = taskfile.Silent
|
||||||
if tf.Expansions <= 0 {
|
if tf.Expansions <= 0 {
|
||||||
tf.Expansions = 2
|
tf.Expansions = 2
|
||||||
}
|
}
|
||||||
|
if tf.Vars == nil {
|
||||||
|
tf.Vars = make(Vars)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Masterminds/semver"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
v1 = mustVersion("1")
|
|
||||||
v2 = mustVersion("2")
|
|
||||||
v21 = mustVersion("2.1")
|
|
||||||
v22 = mustVersion("2.2")
|
|
||||||
v23 = mustVersion("2.3")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsV1 returns if is a given Taskfile version is version 1
|
|
||||||
func IsV1(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV2 returns if is a given Taskfile version is at least version 2
|
|
||||||
func IsV2(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV21 returns if is a given Taskfile version is at least version 2.1
|
|
||||||
func IsV21(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v21)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV22 returns if is a given Taskfile version is at least version 2.2
|
|
||||||
func IsV22(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v22)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsV23 returns if is a given Taskfile version is at least version 2.3
|
|
||||||
func IsV23(v *semver.Constraints) bool {
|
|
||||||
return v.Check(v23)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustVersion(s string) *semver.Version {
|
|
||||||
v, err := semver.NewVersion(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
31
precondition.go
Normal file
31
precondition.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPreconditionFailed is returned when a precondition fails
|
||||||
|
ErrPreconditionFailed = errors.New("task: precondition not met")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||||
|
for _, p := range t.Preconditions {
|
||||||
|
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
|
Command: p.Sh,
|
||||||
|
Dir: t.Dir,
|
||||||
|
Env: getEnviron(t),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
e.Logger.Errf("task: %s", p.Msg)
|
||||||
|
return false, ErrPreconditionFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
28
status.go
28
status.go
@@ -10,13 +10,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Status returns an error if any the of given tasks is not up-to-date
|
// Status returns an error if any the of given tasks is not up-to-date
|
||||||
func (e *Executor) Status(calls ...taskfile.Call) error {
|
func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
|
||||||
for _, call := range calls {
|
for _, call := range calls {
|
||||||
t, err := e.CompiledTask(call)
|
t, err := e.CompiledTask(call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
isUpToDate, err := isTaskUpToDate(e.Context, t)
|
isUpToDate, err := e.isTaskUpToDate(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -27,12 +27,12 @@ func (e *Executor) Status(calls ...taskfile.Call) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||||
if len(t.Status) > 0 {
|
if len(t.Status) > 0 {
|
||||||
return isTaskUpToDateStatus(ctx, t)
|
return e.isTaskUpToDateStatus(ctx, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
checker, err := getStatusChecker(t)
|
checker, err := e.getStatusChecker(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -40,15 +40,15 @@ func isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
|
|||||||
return checker.IsUpToDate()
|
return checker.IsUpToDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusOnError(t *taskfile.Task) error {
|
func (e *Executor) statusOnError(t *taskfile.Task) error {
|
||||||
checker, err := getStatusChecker(t)
|
checker, err := e.getStatusChecker(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return checker.OnError()
|
return checker.OnError()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
||||||
switch t.Method {
|
switch t.Method {
|
||||||
case "", "timestamp":
|
case "", "timestamp":
|
||||||
return &status.Timestamp{
|
return &status.Timestamp{
|
||||||
@@ -58,9 +58,11 @@ func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
case "checksum":
|
case "checksum":
|
||||||
return &status.Checksum{
|
return &status.Checksum{
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Task: t.Task,
|
Task: t.Task,
|
||||||
Sources: t.Sources,
|
Sources: t.Sources,
|
||||||
|
Generates: t.Generates,
|
||||||
|
Dry: e.Dry,
|
||||||
}, nil
|
}, nil
|
||||||
case "none":
|
case "none":
|
||||||
return status.None{}, nil
|
return status.None{}, nil
|
||||||
@@ -69,7 +71,7 @@ func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
||||||
for _, s := range t.Status {
|
for _, s := range t.Status {
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: s,
|
Command: s,
|
||||||
@@ -77,8 +79,10 @@ func isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
|
|||||||
Env: getEnviron(t),
|
Env: getEnviron(t),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
e.Logger.VerboseOutf("task: status command %s exited non-zero: %s", s, err)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
e.Logger.VerboseOutf("task: status command %s exited zero", s)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
183
task.go
183
task.go
@@ -2,9 +2,12 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/compiler"
|
"github.com/go-task/task/v2/internal/compiler"
|
||||||
@@ -13,11 +16,10 @@ import (
|
|||||||
"github.com/go-task/task/v2/internal/execext"
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
"github.com/go-task/task/v2/internal/logger"
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
"github.com/go-task/task/v2/internal/output"
|
"github.com/go-task/task/v2/internal/output"
|
||||||
|
"github.com/go-task/task/v2/internal/summary"
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
"github.com/go-task/task/v2/internal/taskfile/read"
|
"github.com/go-task/task/v2/internal/taskfile/read"
|
||||||
"github.com/go-task/task/v2/internal/taskfile/version"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,30 +32,34 @@ const (
|
|||||||
// Executor executes a Taskfile
|
// Executor executes a Taskfile
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
Taskfile *taskfile.Taskfile
|
Taskfile *taskfile.Taskfile
|
||||||
Dir string
|
|
||||||
Force bool
|
|
||||||
Watch bool
|
|
||||||
Verbose bool
|
|
||||||
Silent bool
|
|
||||||
Dry bool
|
|
||||||
|
|
||||||
Context context.Context
|
Dir string
|
||||||
|
Entrypoint string
|
||||||
|
Force bool
|
||||||
|
Watch bool
|
||||||
|
Verbose bool
|
||||||
|
Silent bool
|
||||||
|
Dry bool
|
||||||
|
Summary bool
|
||||||
|
Parallel bool
|
||||||
|
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Compiler compiler.Compiler
|
Compiler compiler.Compiler
|
||||||
Output output.Output
|
Output output.Output
|
||||||
|
OutputStyle string
|
||||||
|
|
||||||
taskvars taskfile.Vars
|
taskvars taskfile.Vars
|
||||||
|
|
||||||
taskCallCount map[string]*int32
|
taskCallCount map[string]*int32
|
||||||
|
mkdirMutexMap map[string]*sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs Task
|
// Run runs Task
|
||||||
func (e *Executor) Run(calls ...taskfile.Call) error {
|
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||||
// check if given tasks exist
|
// check if given tasks exist
|
||||||
for _, c := range calls {
|
for _, c := range calls {
|
||||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||||
@@ -63,22 +69,44 @@ func (e *Executor) Run(calls ...taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Summary {
|
||||||
|
for i, c := range calls {
|
||||||
|
compiledTask, err := e.CompiledTask(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
summary.PrintSpaceBetweenSummaries(e.Logger, i)
|
||||||
|
summary.PrintTask(e.Logger, compiledTask)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if e.Watch {
|
if e.Watch {
|
||||||
return e.watchTasks(calls...)
|
return e.watchTasks(calls...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
for _, c := range calls {
|
for _, c := range calls {
|
||||||
if err := e.RunTask(e.Context, c); err != nil {
|
c := c
|
||||||
return err
|
if e.Parallel {
|
||||||
|
g.Go(func() error { return e.RunTask(ctx, c) })
|
||||||
|
} else {
|
||||||
|
if err := e.RunTask(ctx, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return g.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup setups Executor's internal state
|
// Setup setups Executor's internal state
|
||||||
func (e *Executor) Setup() error {
|
func (e *Executor) Setup() error {
|
||||||
|
if e.Entrypoint == "" {
|
||||||
|
e.Entrypoint = "Taskfile.yml"
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
e.Taskfile, err = read.Taskfile(e.Dir)
|
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,14 +115,6 @@ func (e *Executor) Setup() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := semver.NewConstraint(e.Taskfile.Version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Context == nil {
|
|
||||||
e.Context = context.Background()
|
|
||||||
}
|
|
||||||
if e.Stdin == nil {
|
if e.Stdin == nil {
|
||||||
e.Stdin = os.Stdin
|
e.Stdin = os.Stdin
|
||||||
}
|
}
|
||||||
@@ -109,14 +129,30 @@ func (e *Executor) Setup() error {
|
|||||||
Stderr: e.Stderr,
|
Stderr: e.Stderr,
|
||||||
Verbose: e.Verbose,
|
Verbose: e.Verbose,
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case version.IsV1(v):
|
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||||
|
}
|
||||||
|
// consider as equal to the greater version if round
|
||||||
|
if v == 2.0 {
|
||||||
|
v = 2.6
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 1 {
|
||||||
|
return fmt.Errorf(`task: Taskfile version should be greater or equal to v1`)
|
||||||
|
}
|
||||||
|
if v > 2.6 {
|
||||||
|
return fmt.Errorf(`task: Taskfile versions greater than v2.6 not implemented in the version of Task`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 2 {
|
||||||
e.Compiler = &compilerv1.CompilerV1{
|
e.Compiler = &compilerv1.CompilerV1{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Vars: e.taskvars,
|
Vars: e.taskvars,
|
||||||
Logger: e.Logger,
|
Logger: e.Logger,
|
||||||
}
|
}
|
||||||
case version.IsV2(v), version.IsV21(v), version.IsV22(v):
|
} else { // v >= 2
|
||||||
e.Compiler = &compilerv2.CompilerV2{
|
e.Compiler = &compilerv2.CompilerV2{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Taskvars: e.taskvars,
|
Taskvars: e.taskvars,
|
||||||
@@ -124,16 +160,18 @@ func (e *Executor) Setup() error {
|
|||||||
Expansions: e.Taskfile.Expansions,
|
Expansions: e.Taskfile.Expansions,
|
||||||
Logger: e.Logger,
|
Logger: e.Logger,
|
||||||
}
|
}
|
||||||
case version.IsV23(v):
|
|
||||||
return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !version.IsV21(v) && e.Taskfile.Output != "" {
|
if v < 2.1 && e.Taskfile.Output != "" {
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||||
}
|
}
|
||||||
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
|
if v < 2.2 && len(e.Taskfile.Includes) > 0 {
|
||||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.OutputStyle != "" {
|
||||||
|
e.Taskfile.Output = e.OutputStyle
|
||||||
|
}
|
||||||
switch e.Taskfile.Output {
|
switch e.Taskfile.Output {
|
||||||
case "", "interleaved":
|
case "", "interleaved":
|
||||||
e.Output = output.Interleaved{}
|
e.Output = output.Interleaved{}
|
||||||
@@ -145,8 +183,8 @@ func (e *Executor) Setup() error {
|
|||||||
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !version.IsV21(v) {
|
if v <= 2.1 {
|
||||||
err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||||
|
|
||||||
for _, task := range e.Taskfile.Tasks {
|
for _, task := range e.Taskfile.Tasks {
|
||||||
if task.IgnoreError {
|
if task.IgnoreError {
|
||||||
@@ -160,9 +198,19 @@ func (e *Executor) Setup() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v < 2.6 {
|
||||||
|
for _, task := range e.Taskfile.Tasks {
|
||||||
|
if len(task.Preconditions) > 0 {
|
||||||
|
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
||||||
|
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
|
||||||
for k := range e.Taskfile.Tasks {
|
for k := range e.Taskfile.Tasks {
|
||||||
e.taskCallCount[k] = new(int32)
|
e.taskCallCount[k] = new(int32)
|
||||||
|
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -182,11 +230,17 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !e.Force {
|
if !e.Force {
|
||||||
upToDate, err := isTaskUpToDate(ctx, t)
|
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if upToDate {
|
|
||||||
|
upToDate, err := e.isTaskUpToDate(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if upToDate && preCondMet {
|
||||||
if !e.Silent {
|
if !e.Silent {
|
||||||
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
|
||||||
}
|
}
|
||||||
@@ -194,9 +248,13 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := e.mkdir(t); err != nil {
|
||||||
|
e.Logger.Errf("task: cannot make directory %q: %v", t.Dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
for i := range t.Cmds {
|
for i := range t.Cmds {
|
||||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||||
if err2 := statusOnError(t); err2 != nil {
|
if err2 := e.statusOnError(t); err2 != nil {
|
||||||
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
|
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +269,23 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) mkdir(t *taskfile.Task) error {
|
||||||
|
if t.Dir == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex := e.mkdirMutexMap[t.Task]
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(t.Dir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
@@ -218,7 +293,11 @@ func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
|
|||||||
d := d
|
d := d
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
err := e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,9 +309,13 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case cmd.Task != "":
|
case cmd.Task != "":
|
||||||
return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
err := e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case cmd.Cmd != "":
|
case cmd.Cmd != "":
|
||||||
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
|
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||||
e.Logger.Errf(cmd.Cmd)
|
e.Logger.Errf(cmd.Cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +325,18 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
|
|
||||||
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
|
||||||
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
|
||||||
defer stdOut.Close()
|
defer func() {
|
||||||
defer stdErr.Close()
|
if _, ok := stdOut.(*os.File); !ok {
|
||||||
|
if closer, ok := stdOut.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := stdErr.(*os.File); !ok {
|
||||||
|
if closer, ok := stdErr.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: cmd.Cmd,
|
Command: cmd.Cmd,
|
||||||
@@ -268,9 +361,9 @@ func getEnviron(t *taskfile.Task) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
envs := os.Environ()
|
environ := os.Environ()
|
||||||
for k, v := range t.Env.ToStringMap() {
|
for k, v := range t.Env.ToStringMap() {
|
||||||
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
environ = append(environ, fmt.Sprintf("%s=%s", k, v))
|
||||||
}
|
}
|
||||||
return envs
|
return environ
|
||||||
}
|
}
|
||||||
|
|||||||
235
task_test.go
235
task_test.go
@@ -2,17 +2,18 @@ package task_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-task/task/v2"
|
"github.com/go-task/task/v2"
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ func (fct fileContentTest) Run(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: fct.Target}), "e.Run(target)")
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: fct.Target}), "e.Run(target)")
|
||||||
|
|
||||||
for name, expectContent := range fct.Files {
|
for name, expectContent := range fct.Files {
|
||||||
t.Run(fct.name(name), func(t *testing.T) {
|
t.Run(fct.name(name), func(t *testing.T) {
|
||||||
@@ -61,7 +62,8 @@ func TestEnv(t *testing.T) {
|
|||||||
Target: "default",
|
Target: "default",
|
||||||
TrimSpace: false,
|
TrimSpace: false,
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"env.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||||
|
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
@@ -177,7 +179,7 @@ func TestVarsInvalidTmpl(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup(), "e.Setup()")
|
assert.NoError(t, e.Setup(), "e.Setup()")
|
||||||
assert.EqualError(t, e.Run(taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParams(t *testing.T) {
|
func TestParams(t *testing.T) {
|
||||||
@@ -229,12 +231,12 @@ func TestDeps(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "default"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
f = filepath.Join(dir, f)
|
f = filepath.Join(dir, f)
|
||||||
if _, err := os.Stat(f); err != nil {
|
if _, err := os.Stat(f); err != nil {
|
||||||
t.Errorf("File %s should exists", f)
|
t.Errorf("File %s should exist", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ func TestStatus(t *testing.T) {
|
|||||||
_ = os.Remove(file)
|
_ = os.Remove(file)
|
||||||
|
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(file); err == nil {
|
||||||
t.Errorf("File should not exists: %v", err)
|
t.Errorf("File should not exist: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
@@ -257,35 +259,79 @@ func TestStatus(t *testing.T) {
|
|||||||
Silent: true,
|
Silent: true,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||||
|
|
||||||
if _, err := os.Stat(file); err != nil {
|
if _, err := os.Stat(file); err != nil {
|
||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exist: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Silent = false
|
e.Silent = false
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||||
|
|
||||||
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrecondition(t *testing.T) {
|
||||||
|
const dir = "testdata/precondition"
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precondition that has been met
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
|
||||||
|
if buff.String() != "" {
|
||||||
|
t.Errorf("Got Output when none was expected: %s", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A precondition that was not met
|
||||||
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "impossible"}))
|
||||||
|
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Calling a task with a precondition in a dependency fails the task
|
||||||
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "depends_on_impossible"}))
|
||||||
|
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Calling a task with a precondition in a cmd fails the task
|
||||||
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "executes_failing_task_as_cmd"}))
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerates(t *testing.T) {
|
func TestGenerates(t *testing.T) {
|
||||||
var srcTask = "sub/src.txt"
|
const (
|
||||||
var relTask = "rel.txt"
|
srcTask = "sub/src.txt"
|
||||||
var absTask = "abs.txt"
|
relTask = "rel.txt"
|
||||||
|
absTask = "abs.txt"
|
||||||
|
fileWithSpaces = "my text file.txt"
|
||||||
|
)
|
||||||
|
|
||||||
// This test does not work with a relative dir.
|
// This test does not work with a relative dir.
|
||||||
dir, err := filepath.Abs("testdata/generates")
|
dir, err := filepath.Abs("testdata/generates")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
var srcFile = filepath.Join(dir, srcTask)
|
var srcFile = filepath.Join(dir, srcTask)
|
||||||
|
|
||||||
for _, task := range []string{srcTask, relTask, absTask} {
|
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
|
||||||
path := filepath.Join(dir, task)
|
path := filepath.Join(dir, task)
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
t.Errorf("File should not exists: %v", err)
|
t.Errorf("File should not exist: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,19 +343,19 @@ func TestGenerates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
for _, theTask := range []string{relTask, absTask} {
|
for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
|
||||||
var destFile = filepath.Join(dir, theTask)
|
var destFile = filepath.Join(dir, theTask)
|
||||||
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
|
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
|
||||||
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
|
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
|
||||||
|
|
||||||
// Run task for the first time.
|
// Run task for the first time.
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
||||||
|
|
||||||
if _, err := os.Stat(srcFile); err != nil {
|
if _, err := os.Stat(srcFile); err != nil {
|
||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exist: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(destFile); err != nil {
|
if _, err := os.Stat(destFile); err != nil {
|
||||||
t.Errorf("File should exists: %v", err)
|
t.Errorf("File should exist: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure task was not incorrectly found to be up-to-date on first run.
|
// Ensure task was not incorrectly found to be up-to-date on first run.
|
||||||
if buff.String() == upToDate {
|
if buff.String() == upToDate {
|
||||||
@@ -318,7 +364,7 @@ func TestGenerates(t *testing.T) {
|
|||||||
buff.Reset()
|
buff.Reset()
|
||||||
|
|
||||||
// Re-run task to ensure it's now found to be up-to-date.
|
// Re-run task to ensure it's now found to be up-to-date.
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
|
||||||
if buff.String() != upToDate {
|
if buff.String() != upToDate {
|
||||||
t.Errorf("Wrong output message: %s", buff.String())
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
}
|
}
|
||||||
@@ -349,14 +395,14 @@ func TestStatusChecksum(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
_, err := os.Stat(filepath.Join(dir, f))
|
_, err := os.Stat(filepath.Join(dir, f))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buff.Reset()
|
buff.Reset()
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +412,7 @@ func TestInit(t *testing.T) {
|
|||||||
|
|
||||||
_ = os.Remove(file)
|
_ = os.Remove(file)
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(file); err == nil {
|
||||||
t.Errorf("Taskfile.yml should not exists")
|
t.Errorf("Taskfile.yml should not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
if err := task.InitTaskfile(ioutil.Discard, dir); err != nil {
|
||||||
@@ -374,7 +420,7 @@ func TestInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(file); err != nil {
|
if _, err := os.Stat(file); err != nil {
|
||||||
t.Errorf("Taskfile.yml should exists")
|
t.Errorf("Taskfile.yml should exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +433,7 @@ func TestCyclicDep(t *testing.T) {
|
|||||||
Stderr: ioutil.Discard,
|
Stderr: ioutil.Discard,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(taskfile.Call{Task: "task-1"}))
|
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTaskVersion(t *testing.T) {
|
func TestTaskVersion(t *testing.T) {
|
||||||
@@ -423,16 +469,16 @@ func TestTaskIgnoreErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
|
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "task-should-pass"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-pass"}))
|
||||||
assert.Error(t, e.Run(taskfile.Call{Task: "task-should-fail"}))
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-fail"}))
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "cmd-should-pass"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-pass"}))
|
||||||
assert.Error(t, e.Run(taskfile.Call{Task: "cmd-should-fail"}))
|
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-fail"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpand(t *testing.T) {
|
func TestExpand(t *testing.T) {
|
||||||
const dir = "testdata/expand"
|
const dir = "testdata/expand"
|
||||||
|
|
||||||
home, err := homedir.Dir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Couldn't get $HOME: %v", err)
|
t.Errorf("Couldn't get $HOME: %v", err)
|
||||||
}
|
}
|
||||||
@@ -444,7 +490,7 @@ func TestExpand(t *testing.T) {
|
|||||||
Stderr: &buff,
|
Stderr: &buff,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "pwd"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "pwd"}))
|
||||||
assert.Equal(t, home, strings.TrimSpace(buff.String()))
|
assert.Equal(t, home, strings.TrimSpace(buff.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,7 +509,7 @@ func TestDry(t *testing.T) {
|
|||||||
Dry: true,
|
Dry: true,
|
||||||
}
|
}
|
||||||
assert.NoError(t, e.Setup())
|
assert.NoError(t, e.Setup())
|
||||||
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||||
|
|
||||||
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
|
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(file); err == nil {
|
||||||
@@ -471,6 +517,32 @@ func TestDry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDryChecksum tests if the checksum file is not being written to disk
|
||||||
|
// if the dry mode is enabled.
|
||||||
|
func TestDryChecksum(t *testing.T) {
|
||||||
|
const dir = "testdata/dry_checksum"
|
||||||
|
|
||||||
|
checksumFile := filepath.Join(dir, ".task/checksum/default")
|
||||||
|
_ = os.Remove(checksumFile)
|
||||||
|
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: ioutil.Discard,
|
||||||
|
Stderr: ioutil.Discard,
|
||||||
|
Dry: true,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||||
|
|
||||||
|
_, err := os.Stat(checksumFile)
|
||||||
|
assert.Error(t, err, "checksum file should not exist")
|
||||||
|
|
||||||
|
e.Dry = false
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||||
|
_, err = os.Stat(checksumFile)
|
||||||
|
assert.NoError(t, err, "checksum file should exist")
|
||||||
|
}
|
||||||
|
|
||||||
func TestIncludes(t *testing.T) {
|
func TestIncludes(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/includes",
|
Dir: "testdata/includes",
|
||||||
@@ -510,3 +582,102 @@ func TestIncludesDependencies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesCallingRoot(t *testing.T) {
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/includes_call_root_task",
|
||||||
|
Target: "included:call-root",
|
||||||
|
TrimSpace: true,
|
||||||
|
Files: map[string]string{
|
||||||
|
"root_task.txt": "root task",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSummary(t *testing.T) {
|
||||||
|
const dir = "testdata/summary"
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Summary: true,
|
||||||
|
Silent: true,
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expectedOutput := string(data)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedOutput, buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
|
||||||
|
const expected = "dir"
|
||||||
|
const dir = "testdata/" + expected
|
||||||
|
var out bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &out,
|
||||||
|
Stderr: &out,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
||||||
|
|
||||||
|
// got should be the "dir" part of "testdata/dir"
|
||||||
|
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||||
|
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
|
||||||
|
const expected = "exists"
|
||||||
|
const dir = "testdata/dir/explicit_exists"
|
||||||
|
var out bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &out,
|
||||||
|
Stderr: &out,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "whereami"}))
|
||||||
|
|
||||||
|
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||||
|
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
||||||
|
const expected = "createme"
|
||||||
|
const dir = "testdata/dir/explicit_doesnt_exist/"
|
||||||
|
const toBeCreated = dir + expected
|
||||||
|
const target = "whereami"
|
||||||
|
var out bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &out,
|
||||||
|
Stderr: &out,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the directory to be created doesn't actually exist.
|
||||||
|
_ = os.Remove(toBeCreated)
|
||||||
|
if _, err := os.Stat(toBeCreated); err == nil {
|
||||||
|
t.Errorf("Directory should not exist: %v", err)
|
||||||
|
}
|
||||||
|
assert.NoError(t, e.Setup())
|
||||||
|
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
|
||||||
|
|
||||||
|
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||||
|
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||||
|
|
||||||
|
// Clean-up after ourselves only if no error.
|
||||||
|
_ = os.Remove(toBeCreated)
|
||||||
|
}
|
||||||
|
|||||||
7
testdata/dir/Taskfile.yml
vendored
Normal file
7
testdata/dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
whereami:
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
|
silent: true
|
||||||
8
testdata/dir/explicit_doesnt_exist/Taskfile.yml
vendored
Normal file
8
testdata/dir/explicit_doesnt_exist/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
whereami:
|
||||||
|
dir: createme
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
|
silent: true
|
||||||
8
testdata/dir/explicit_exists/Taskfile.yml
vendored
Normal file
8
testdata/dir/explicit_exists/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
whereami:
|
||||||
|
dir: exists
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
|
silent: true
|
||||||
0
testdata/dir/explicit_exists/exists/.keep
vendored
Normal file
0
testdata/dir/explicit_exists/exists/.keep
vendored
Normal file
9
testdata/dry_checksum/Taskfile.yml
vendored
Normal file
9
testdata/dry_checksum/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "Working..."
|
||||||
|
sources:
|
||||||
|
- source.txt
|
||||||
|
method: checksum
|
||||||
1
testdata/dry_checksum/source.txt
vendored
Normal file
1
testdata/dry_checksum/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Something...
|
||||||
2
testdata/env/.gitignore
vendored
2
testdata/env/.gitignore
vendored
@@ -1 +1 @@
|
|||||||
env.txt
|
*.txt
|
||||||
|
|||||||
43
testdata/env/Taskfile.yml
vendored
43
testdata/env/Taskfile.yml
vendored
@@ -1,10 +1,33 @@
|
|||||||
default:
|
version: '2'
|
||||||
vars:
|
|
||||||
AMD64: amd64
|
vars:
|
||||||
env:
|
BAZ:
|
||||||
GOOS: linux
|
sh: echo baz
|
||||||
GOARCH: "{{.AMD64}}"
|
|
||||||
CGO_ENABLED:
|
env:
|
||||||
sh: echo '0'
|
FOO: foo
|
||||||
cmds:
|
BAR: bar
|
||||||
- echo "GOOS='$GOOS' GOARCH='$GOARCH' CGO_ENABLED='$CGO_ENABLED'" > env.txt
|
BAZ: "{{.BAZ}}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- task: local
|
||||||
|
- task: global
|
||||||
|
|
||||||
|
local:
|
||||||
|
vars:
|
||||||
|
AMD64: amd64
|
||||||
|
env:
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: "{{.AMD64}}"
|
||||||
|
CGO_ENABLED:
|
||||||
|
sh: echo '0'
|
||||||
|
cmds:
|
||||||
|
- echo "GOOS='$GOOS' GOARCH='$GOARCH' CGO_ENABLED='$CGO_ENABLED'" > local.txt
|
||||||
|
|
||||||
|
global:
|
||||||
|
env:
|
||||||
|
BAR: overriden
|
||||||
|
cmds:
|
||||||
|
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > global.txt
|
||||||
|
|||||||
10
testdata/generates/Taskfile.yml
vendored
10
testdata/generates/Taskfile.yml
vendored
@@ -29,3 +29,13 @@ sub/src.txt:
|
|||||||
- echo "hello world" > sub/src.txt
|
- echo "hello world" > sub/src.txt
|
||||||
status:
|
status:
|
||||||
- test -f sub/src.txt
|
- test -f sub/src.txt
|
||||||
|
|
||||||
|
'my text file.txt':
|
||||||
|
desc: generate file with spaces in the name
|
||||||
|
deps: [sub/src.txt]
|
||||||
|
cmds:
|
||||||
|
- cat sub/src.txt > 'my text file.txt'
|
||||||
|
sources:
|
||||||
|
- sub/src.txt
|
||||||
|
generates:
|
||||||
|
- 'my text file.txt'
|
||||||
|
|||||||
1
testdata/includes_call_root_task/.gitignore
vendored
Normal file
1
testdata/includes_call_root_task/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.txt
|
||||||
9
testdata/includes_call_root_task/Taskfile.yml
vendored
Normal file
9
testdata/includes_call_root_task/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
included: Taskfile2.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
root-task:
|
||||||
|
cmds:
|
||||||
|
- echo "root task" > root_task.txt
|
||||||
6
testdata/includes_call_root_task/Taskfile2.yml
vendored
Normal file
6
testdata/includes_call_root_task/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
call-root:
|
||||||
|
cmds:
|
||||||
|
- task: :root-task
|
||||||
19
testdata/precondition/Taskfile.yml
vendored
Normal file
19
testdata/precondition/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
preconditions:
|
||||||
|
- test -f foo.txt
|
||||||
|
|
||||||
|
impossible:
|
||||||
|
preconditions:
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "1 != 0 obviously!"
|
||||||
|
|
||||||
|
depends_on_impossible:
|
||||||
|
deps:
|
||||||
|
- impossible
|
||||||
|
|
||||||
|
executes_failing_task_as_cmd:
|
||||||
|
cmds:
|
||||||
|
- task: impossible
|
||||||
0
testdata/precondition/foo.txt
vendored
Normal file
0
testdata/precondition/foo.txt
vendored
Normal file
26
testdata/summary/Taskfile.yml
vendored
Normal file
26
testdata/summary/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
task-with-summary:
|
||||||
|
deps: [dependend-task-1, dependend-task-2]
|
||||||
|
summary: |
|
||||||
|
summary of task-with-summary - line 1
|
||||||
|
line 2
|
||||||
|
line 3
|
||||||
|
cmds:
|
||||||
|
- echo 'task-with-summary was executed'
|
||||||
|
- echo 'another command'
|
||||||
|
- exit 0
|
||||||
|
|
||||||
|
other-task-with-summary:
|
||||||
|
summary: summary of other-task-with-summary
|
||||||
|
cmds:
|
||||||
|
- echo 'other-task-with-summary was executed'
|
||||||
|
|
||||||
|
dependend-task-1:
|
||||||
|
cmds:
|
||||||
|
- echo 'dependend-task-1 was executed'
|
||||||
|
|
||||||
|
dependend-task-2:
|
||||||
|
cmds:
|
||||||
|
- echo 'dependend-task-2 was executed'
|
||||||
22
testdata/summary/task-with-summary.txt
vendored
Normal file
22
testdata/summary/task-with-summary.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
task: task-with-summary
|
||||||
|
|
||||||
|
summary of task-with-summary - line 1
|
||||||
|
line 2
|
||||||
|
line 3
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- dependend-task-1
|
||||||
|
- dependend-task-2
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- echo 'task-with-summary was executed'
|
||||||
|
- echo 'another command'
|
||||||
|
- exit 0
|
||||||
|
|
||||||
|
|
||||||
|
task: other-task-with-summary
|
||||||
|
|
||||||
|
summary of other-task-with-summary
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- echo 'other-task-with-summary was executed'
|
||||||
26
variables.go
26
variables.go
@@ -3,10 +3,9 @@ package task
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
"github.com/go-task/task/v2/internal/templater"
|
"github.com/go-task/task/v2/internal/templater"
|
||||||
|
|
||||||
"mvdan.cc/sh/shell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompiledTask returns a copy of a task, but replacing variables in almost all
|
// CompiledTask returns a copy of a task, but replacing variables in almost all
|
||||||
@@ -26,18 +25,19 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
new := taskfile.Task{
|
new := taskfile.Task{
|
||||||
Task: origTask.Task,
|
Task: origTask.Task,
|
||||||
Desc: r.Replace(origTask.Desc),
|
Desc: r.Replace(origTask.Desc),
|
||||||
|
Summary: r.Replace(origTask.Summary),
|
||||||
Sources: r.ReplaceSlice(origTask.Sources),
|
Sources: r.ReplaceSlice(origTask.Sources),
|
||||||
Generates: r.ReplaceSlice(origTask.Generates),
|
Generates: r.ReplaceSlice(origTask.Generates),
|
||||||
Status: r.ReplaceSlice(origTask.Status),
|
Status: r.ReplaceSlice(origTask.Status),
|
||||||
Dir: r.Replace(origTask.Dir),
|
Dir: r.Replace(origTask.Dir),
|
||||||
Vars: nil,
|
Vars: nil,
|
||||||
Env: r.ReplaceVars(origTask.Env),
|
Env: nil,
|
||||||
Silent: origTask.Silent,
|
Silent: origTask.Silent,
|
||||||
Method: r.Replace(origTask.Method),
|
Method: r.Replace(origTask.Method),
|
||||||
Prefix: r.Replace(origTask.Prefix),
|
Prefix: r.Replace(origTask.Prefix),
|
||||||
IgnoreError: origTask.IgnoreError,
|
IgnoreError: origTask.IgnoreError,
|
||||||
}
|
}
|
||||||
new.Dir, err = shell.Expand(new.Dir, nil)
|
new.Dir, err = execext.Expand(new.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,14 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
if new.Prefix == "" {
|
if new.Prefix == "" {
|
||||||
new.Prefix = new.Task
|
new.Prefix = new.Task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new.Env = make(taskfile.Vars, len(e.Taskfile.Env)+len(origTask.Env))
|
||||||
|
for k, v := range r.ReplaceVars(e.Taskfile.Env) {
|
||||||
|
new.Env[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range r.ReplaceVars(origTask.Env) {
|
||||||
|
new.Env[k] = v
|
||||||
|
}
|
||||||
for k, v := range new.Env {
|
for k, v := range new.Env {
|
||||||
static, err := e.Compiler.HandleDynamicVar(v)
|
static, err := e.Compiler.HandleDynamicVar(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,5 +85,15 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(origTask.Preconditions) > 0 {
|
||||||
|
new.Preconditions = make([]*taskfile.Precondition, len(origTask.Preconditions))
|
||||||
|
for i, precond := range origTask.Preconditions {
|
||||||
|
new.Preconditions[i] = &taskfile.Precondition{
|
||||||
|
Sh: r.Replace(precond.Sh),
|
||||||
|
Msg: r.Replace(precond.Msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &new, r.Err()
|
return &new, r.Err()
|
||||||
}
|
}
|
||||||
|
|||||||
21
vendor/github.com/mitchellh/go-homedir/LICENSE
generated
vendored
21
vendor/github.com/mitchellh/go-homedir/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013 Mitchell Hashimoto
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
14
vendor/github.com/mitchellh/go-homedir/README.md
generated
vendored
14
vendor/github.com/mitchellh/go-homedir/README.md
generated
vendored
@@ -1,14 +0,0 @@
|
|||||||
# go-homedir
|
|
||||||
|
|
||||||
This is a Go library for detecting the user's home directory without
|
|
||||||
the use of cgo, so the library can be used in cross-compilation environments.
|
|
||||||
|
|
||||||
Usage is incredibly simple, just call `homedir.Dir()` to get the home directory
|
|
||||||
for a user, and `homedir.Expand()` to expand the `~` in a path to the home
|
|
||||||
directory.
|
|
||||||
|
|
||||||
**Why not just use `os/user`?** The built-in `os/user` package requires
|
|
||||||
cgo on Darwin systems. This means that any Go code that uses that package
|
|
||||||
cannot cross compile. But 99% of the time the use for `os/user` is just to
|
|
||||||
retrieve the home directory, which we can do for the current user without
|
|
||||||
cgo. This library does that, enabling cross-compilation.
|
|
||||||
1
vendor/github.com/mitchellh/go-homedir/go.mod
generated
vendored
1
vendor/github.com/mitchellh/go-homedir/go.mod
generated
vendored
@@ -1 +0,0 @@
|
|||||||
module github.com/mitchellh/go-homedir
|
|
||||||
157
vendor/github.com/mitchellh/go-homedir/homedir.go
generated
vendored
157
vendor/github.com/mitchellh/go-homedir/homedir.go
generated
vendored
@@ -1,157 +0,0 @@
|
|||||||
package homedir
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DisableCache will disable caching of the home directory. Caching is enabled
|
|
||||||
// by default.
|
|
||||||
var DisableCache bool
|
|
||||||
|
|
||||||
var homedirCache string
|
|
||||||
var cacheLock sync.RWMutex
|
|
||||||
|
|
||||||
// Dir returns the home directory for the executing user.
|
|
||||||
//
|
|
||||||
// This uses an OS-specific method for discovering the home directory.
|
|
||||||
// An error is returned if a home directory cannot be detected.
|
|
||||||
func Dir() (string, error) {
|
|
||||||
if !DisableCache {
|
|
||||||
cacheLock.RLock()
|
|
||||||
cached := homedirCache
|
|
||||||
cacheLock.RUnlock()
|
|
||||||
if cached != "" {
|
|
||||||
return cached, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheLock.Lock()
|
|
||||||
defer cacheLock.Unlock()
|
|
||||||
|
|
||||||
var result string
|
|
||||||
var err error
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
result, err = dirWindows()
|
|
||||||
} else {
|
|
||||||
// Unix-like system, so just assume Unix
|
|
||||||
result, err = dirUnix()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
homedirCache = result
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand expands the path to include the home directory if the path
|
|
||||||
// is prefixed with `~`. If it isn't prefixed with `~`, the path is
|
|
||||||
// returned as-is.
|
|
||||||
func Expand(path string) (string, error) {
|
|
||||||
if len(path) == 0 {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if path[0] != '~' {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
|
|
||||||
return "", errors.New("cannot expand user-specific home dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := Dir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(dir, path[1:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dirUnix() (string, error) {
|
|
||||||
homeEnv := "HOME"
|
|
||||||
if runtime.GOOS == "plan9" {
|
|
||||||
// On plan9, env vars are lowercase.
|
|
||||||
homeEnv = "home"
|
|
||||||
}
|
|
||||||
|
|
||||||
// First prefer the HOME environmental variable
|
|
||||||
if home := os.Getenv(homeEnv); home != "" {
|
|
||||||
return home, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
|
|
||||||
// If that fails, try OS specific commands
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`)
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
if err := cmd.Run(); err == nil {
|
|
||||||
result := strings.TrimSpace(stdout.String())
|
|
||||||
if result != "" {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
// If the error is ErrNotFound, we ignore it. Otherwise, return it.
|
|
||||||
if err != exec.ErrNotFound {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
|
|
||||||
// username:password:uid:gid:gecos:home:shell
|
|
||||||
passwdParts := strings.SplitN(passwd, ":", 7)
|
|
||||||
if len(passwdParts) > 5 {
|
|
||||||
return passwdParts[5], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all else fails, try the shell
|
|
||||||
stdout.Reset()
|
|
||||||
cmd := exec.Command("sh", "-c", "cd && pwd")
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.TrimSpace(stdout.String())
|
|
||||||
if result == "" {
|
|
||||||
return "", errors.New("blank output when reading home directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dirWindows() (string, error) {
|
|
||||||
// First prefer the HOME environmental variable
|
|
||||||
if home := os.Getenv("HOME"); home != "" {
|
|
||||||
return home, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer standard environment variable USERPROFILE
|
|
||||||
if home := os.Getenv("USERPROFILE"); home != "" {
|
|
||||||
return home, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
drive := os.Getenv("HOMEDRIVE")
|
|
||||||
path := os.Getenv("HOMEPATH")
|
|
||||||
home := drive + path
|
|
||||||
if drive == "" || path == "" {
|
|
||||||
return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
return home, nil
|
|
||||||
}
|
|
||||||
10
vendor/github.com/radovskyb/watcher/README.md
generated
vendored
10
vendor/github.com/radovskyb/watcher/README.md
generated
vendored
@@ -15,7 +15,8 @@ Events contain the `os.FileInfo` of the file or directory that the event is base
|
|||||||
[Watcher Command](#command)
|
[Watcher Command](#command)
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
Event.Path for Rename and Move events is now returned in the format of `fromPath -> toPath`
|
- Added new file filter hooks (Including a built in regexp filtering hook) [Dec 12, 2018]
|
||||||
|
- Event.Path for Rename and Move events is now returned in the format of `fromPath -> toPath`
|
||||||
|
|
||||||
#### Chmod event is not supported under windows.
|
#### Chmod event is not supported under windows.
|
||||||
|
|
||||||
@@ -68,6 +69,11 @@ func main() {
|
|||||||
// Only notify rename and move events.
|
// Only notify rename and move events.
|
||||||
w.FilterOps(watcher.Rename, watcher.Move)
|
w.FilterOps(watcher.Rename, watcher.Move)
|
||||||
|
|
||||||
|
// Only files that match the regular expression during file listings
|
||||||
|
// will be watched.
|
||||||
|
r := regexp.MustCompile("^abc$")
|
||||||
|
w.AddFilterHook(watcher.RegexFilterHook(r, false))
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -128,6 +134,8 @@ Usage of watcher:
|
|||||||
command to run when an event occurs
|
command to run when an event occurs
|
||||||
-dotfiles
|
-dotfiles
|
||||||
watch dot files (default true)
|
watch dot files (default true)
|
||||||
|
-ignore string
|
||||||
|
comma separated list of paths to ignore
|
||||||
-interval string
|
-interval string
|
||||||
watcher poll interval (default "100ms")
|
watcher poll interval (default "100ms")
|
||||||
-keepalive
|
-keepalive
|
||||||
|
|||||||
12
vendor/github.com/radovskyb/watcher/ishidden.go
generated
vendored
Normal file
12
vendor/github.com/radovskyb/watcher/ishidden.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isHiddenFile(path string) (bool, error) {
|
||||||
|
return strings.HasPrefix(filepath.Base(path), "."), nil
|
||||||
|
}
|
||||||
21
vendor/github.com/radovskyb/watcher/ishidden_windows.go
generated
vendored
Normal file
21
vendor/github.com/radovskyb/watcher/ishidden_windows.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isHiddenFile(path string) (bool, error) {
|
||||||
|
pointer, err := syscall.UTF16PtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes, err := syscall.GetFileAttributes(pointer)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes&syscall.FILE_ATTRIBUTE_HIDDEN != 0, nil
|
||||||
|
}
|
||||||
2
vendor/github.com/radovskyb/watcher/samefile.go
generated
vendored
2
vendor/github.com/radovskyb/watcher/samefile.go
generated
vendored
@@ -4,6 +4,6 @@ package watcher
|
|||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
func SameFile(fi1, fi2 os.FileInfo) bool {
|
func sameFile(fi1, fi2 os.FileInfo) bool {
|
||||||
return os.SameFile(fi1, fi2)
|
return os.SameFile(fi1, fi2)
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/radovskyb/watcher/samefile_windows.go
generated
vendored
2
vendor/github.com/radovskyb/watcher/samefile_windows.go
generated
vendored
@@ -4,7 +4,7 @@ package watcher
|
|||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
func SameFile(fi1, fi2 os.FileInfo) bool {
|
func sameFile(fi1, fi2 os.FileInfo) bool {
|
||||||
return fi1.ModTime() == fi2.ModTime() &&
|
return fi1.ModTime() == fi2.ModTime() &&
|
||||||
fi1.Size() == fi2.Size() &&
|
fi1.Size() == fi2.Size() &&
|
||||||
fi1.Mode() == fi2.Mode() &&
|
fi1.Mode() == fi2.Mode() &&
|
||||||
|
|||||||
116
vendor/github.com/radovskyb/watcher/watcher.go
generated
vendored
116
vendor/github.com/radovskyb/watcher/watcher.go
generated
vendored
@@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -24,6 +25,10 @@ var (
|
|||||||
// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
|
// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
|
||||||
// being watched has been deleted.
|
// being watched has been deleted.
|
||||||
ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")
|
ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")
|
||||||
|
|
||||||
|
// ErrSkip is less of an error, but more of a way for path hooks to skip a file or
|
||||||
|
// directory.
|
||||||
|
ErrSkip = errors.New("error: skipping file")
|
||||||
)
|
)
|
||||||
|
|
||||||
// An Op is a type that is used to describe what type
|
// An Op is a type that is used to describe what type
|
||||||
@@ -69,16 +74,43 @@ type Event struct {
|
|||||||
// String returns a string depending on what type of event occurred and the
|
// String returns a string depending on what type of event occurred and the
|
||||||
// file name associated with the event.
|
// file name associated with the event.
|
||||||
func (e Event) String() string {
|
func (e Event) String() string {
|
||||||
if e.FileInfo != nil {
|
if e.FileInfo == nil {
|
||||||
pathType := "FILE"
|
return "???"
|
||||||
if e.IsDir() {
|
|
||||||
pathType = "DIRECTORY"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
|
|
||||||
}
|
}
|
||||||
return "???"
|
|
||||||
|
pathType := "FILE"
|
||||||
|
if e.IsDir() {
|
||||||
|
pathType = "DIRECTORY"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterFileHookFunc is a function that is called to filter files during listings.
|
||||||
|
// If a file is ok to be listed, nil is returned otherwise ErrSkip is returned.
|
||||||
|
type FilterFileHookFunc func(info os.FileInfo, fullPath string) error
|
||||||
|
|
||||||
|
// RegexFilterHook is a function that accepts or rejects a file
|
||||||
|
// for listing based on whether it's filename or full path matches
|
||||||
|
// a regular expression.
|
||||||
|
func RegexFilterHook(r *regexp.Regexp, useFullPath bool) FilterFileHookFunc {
|
||||||
|
return func(info os.FileInfo, fullPath string) error {
|
||||||
|
str := info.Name()
|
||||||
|
|
||||||
|
if useFullPath {
|
||||||
|
str = fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match
|
||||||
|
if r.MatchString(str) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No match.
|
||||||
|
return ErrSkip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watcher describes a process that watches files for changes.
|
||||||
type Watcher struct {
|
type Watcher struct {
|
||||||
Event chan Event
|
Event chan Event
|
||||||
Error chan error
|
Error chan error
|
||||||
@@ -88,6 +120,7 @@ type Watcher struct {
|
|||||||
|
|
||||||
// mu protects the following.
|
// mu protects the following.
|
||||||
mu *sync.Mutex
|
mu *sync.Mutex
|
||||||
|
ffh []FilterFileHookFunc
|
||||||
running bool
|
running bool
|
||||||
names map[string]bool // bool for recursive or not.
|
names map[string]bool // bool for recursive or not.
|
||||||
files map[string]os.FileInfo // map of files.
|
files map[string]os.FileInfo // map of files.
|
||||||
@@ -125,6 +158,13 @@ func (w *Watcher) SetMaxEvents(delta int) {
|
|||||||
w.mu.Unlock()
|
w.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddFilterHook
|
||||||
|
func (w *Watcher) AddFilterHook(f FilterFileHookFunc) {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.ffh = append(w.ffh, f)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// IgnoreHiddenFiles sets the watcher to ignore any file or directory
|
// IgnoreHiddenFiles sets the watcher to ignore any file or directory
|
||||||
// that starts with a dot.
|
// that starts with a dot.
|
||||||
func (w *Watcher) IgnoreHiddenFiles(ignore bool) {
|
func (w *Watcher) IgnoreHiddenFiles(ignore bool) {
|
||||||
@@ -157,7 +197,13 @@ func (w *Watcher) Add(name string) (err error) {
|
|||||||
// If name is on the ignored list or if hidden files are
|
// If name is on the ignored list or if hidden files are
|
||||||
// ignored and name is a hidden file or directory, simply return.
|
// ignored and name is a hidden file or directory, simply return.
|
||||||
_, ignored := w.ignored[name]
|
_, ignored := w.ignored[name]
|
||||||
if ignored || (w.ignoreHidden && strings.HasPrefix(name, ".")) {
|
|
||||||
|
isHidden, err := isHiddenFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignored || (w.ignoreHidden && isHidden) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,18 +246,36 @@ func (w *Watcher) list(name string) (map[string]os.FileInfo, error) {
|
|||||||
// Add all of the files in the directory to the file list as long
|
// Add all of the files in the directory to the file list as long
|
||||||
// as they aren't on the ignored list or are hidden files if ignoreHidden
|
// as they aren't on the ignored list or are hidden files if ignoreHidden
|
||||||
// is set to true.
|
// is set to true.
|
||||||
|
outer:
|
||||||
for _, fInfo := range fInfoList {
|
for _, fInfo := range fInfoList {
|
||||||
path := filepath.Join(name, fInfo.Name())
|
path := filepath.Join(name, fInfo.Name())
|
||||||
_, ignored := w.ignored[path]
|
_, ignored := w.ignored[path]
|
||||||
if ignored || (w.ignoreHidden && strings.HasPrefix(fInfo.Name(), ".")) {
|
|
||||||
|
isHidden, err := isHiddenFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignored || (w.ignoreHidden && isHidden) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, f := range w.ffh {
|
||||||
|
err := f(fInfo, path)
|
||||||
|
if err == ErrSkip {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fileList[path] = fInfo
|
fileList[path] = fInfo
|
||||||
}
|
}
|
||||||
return fileList, nil
|
return fileList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds either a single file or directory recursively to the file list.
|
// AddRecursive adds either a single file or directory recursively to the file list.
|
||||||
func (w *Watcher) AddRecursive(name string) (err error) {
|
func (w *Watcher) AddRecursive(name string) (err error) {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
@@ -242,10 +306,27 @@ func (w *Watcher) listRecursive(name string) (map[string]os.FileInfo, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, f := range w.ffh {
|
||||||
|
err := f(info, path)
|
||||||
|
if err == ErrSkip {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If path is ignored and it's a directory, skip the directory. If it's
|
// If path is ignored and it's a directory, skip the directory. If it's
|
||||||
// ignored and it's a single file, skip the file.
|
// ignored and it's a single file, skip the file.
|
||||||
_, ignored := w.ignored[path]
|
_, ignored := w.ignored[path]
|
||||||
if ignored || (w.ignoreHidden && strings.HasPrefix(info.Name(), ".")) {
|
|
||||||
|
isHidden, err := isHiddenFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignored || (w.ignoreHidden && isHidden) {
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
@@ -292,7 +373,7 @@ func (w *Watcher) Remove(name string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes either a single file or a directory recursively from
|
// RemoveRecursive removes either a single file or a directory recursively from
|
||||||
// the file's list.
|
// the file's list.
|
||||||
func (w *Watcher) RemoveRecursive(name string) (err error) {
|
func (w *Watcher) RemoveRecursive(name string) (err error) {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
@@ -346,11 +427,17 @@ func (w *Watcher) Ignore(paths ...string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WatchedFiles returns a map of files added to a Watcher.
|
||||||
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
|
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
return w.files
|
files := make(map[string]os.FileInfo)
|
||||||
|
for k, v := range w.files {
|
||||||
|
files[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileInfo is an implementation of os.FileInfo that can be used
|
// fileInfo is an implementation of os.FileInfo that can be used
|
||||||
@@ -560,7 +647,7 @@ func (w *Watcher) pollEvents(files map[string]os.FileInfo, evt chan Event,
|
|||||||
// Check for renames and moves.
|
// Check for renames and moves.
|
||||||
for path1, info1 := range removes {
|
for path1, info1 := range removes {
|
||||||
for path2, info2 := range creates {
|
for path2, info2 := range creates {
|
||||||
if SameFile(info1, info2) {
|
if sameFile(info1, info2) {
|
||||||
e := Event{
|
e := Event{
|
||||||
Op: Move,
|
Op: Move,
|
||||||
Path: fmt.Sprintf("%s -> %s", path1, path2),
|
Path: fmt.Sprintf("%s -> %s", path1, path2),
|
||||||
@@ -606,6 +693,7 @@ func (w *Watcher) Wait() {
|
|||||||
w.wg.Wait()
|
w.wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close stops a Watcher and unlocks its mutex, then sends a close signal.
|
||||||
func (w *Watcher) Close() {
|
func (w *Watcher) Close() {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
if !w.running {
|
if !w.running {
|
||||||
|
|||||||
35
vendor/github.com/stretchr/testify/LICENSE
generated
vendored
35
vendor/github.com/stretchr/testify/LICENSE
generated
vendored
@@ -1,22 +1,21 @@
|
|||||||
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
|
MIT License
|
||||||
|
|
||||||
Please consider promoting this project if you find it useful.
|
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
obtaining a copy of this software and associated documentation
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
files (the "Software"), to deal in the Software without restriction,
|
in the Software without restriction, including without limitation the rights
|
||||||
including without limitation the rights to use, copy, modify, merge,
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
and to permit persons to whom the Software is furnished to do so,
|
furnished to do so, subject to the following conditions:
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
The above copyright notice and this permission notice shall be included in all
|
||||||
in all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
SOFTWARE.
|
||||||
|
|||||||
82
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
82
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
@@ -113,6 +113,17 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
|||||||
return Error(t, err, append([]interface{}{msg}, args...)...)
|
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick.
|
||||||
|
//
|
||||||
|
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||||
|
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Exactlyf asserts that two objects are equal in value and type.
|
// Exactlyf asserts that two objects are equal in value and type.
|
||||||
//
|
//
|
||||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
||||||
@@ -157,6 +168,31 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
|
|||||||
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Greaterf asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||||
|
// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1))
|
||||||
|
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||||
|
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||||
|
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||||
// body that contains a string.
|
// body that contains a string.
|
||||||
//
|
//
|
||||||
@@ -289,6 +325,14 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int
|
|||||||
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||||
|
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Lenf asserts that the specified object has specific length.
|
// Lenf asserts that the specified object has specific length.
|
||||||
// Lenf also fails if the object has a type that len() not accept.
|
// Lenf also fails if the object has a type that len() not accept.
|
||||||
//
|
//
|
||||||
@@ -300,6 +344,31 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf
|
|||||||
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lessf asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||||
|
// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2))
|
||||||
|
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||||
|
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||||
|
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Nilf asserts that the specified object is nil.
|
// Nilf asserts that the specified object is nil.
|
||||||
//
|
//
|
||||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||||
@@ -444,6 +513,19 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in
|
|||||||
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Samef asserts that two pointers reference the same object.
|
||||||
|
//
|
||||||
|
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||||
// elements given in the specified subset(array, slice...).
|
// elements given in the specified subset(array, slice...).
|
||||||
//
|
//
|
||||||
|
|||||||
164
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
164
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
@@ -215,6 +215,28 @@ func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
|
|||||||
return Errorf(a.t, err, msg, args...)
|
return Errorf(a.t, err, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eventually asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick.
|
||||||
|
//
|
||||||
|
// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond)
|
||||||
|
func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Eventually(a.t, condition, waitFor, tick, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick.
|
||||||
|
//
|
||||||
|
// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||||
|
func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Eventuallyf(a.t, condition, waitFor, tick, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// Exactly asserts that two objects are equal in value and type.
|
// Exactly asserts that two objects are equal in value and type.
|
||||||
//
|
//
|
||||||
// a.Exactly(int32(123), int64(123))
|
// a.Exactly(int32(123), int64(123))
|
||||||
@@ -303,6 +325,56 @@ func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) b
|
|||||||
return FileExistsf(a.t, path, msg, args...)
|
return FileExistsf(a.t, path, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Greater asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// a.Greater(2, 1)
|
||||||
|
// a.Greater(float64(2), float64(1))
|
||||||
|
// a.Greater("b", "a")
|
||||||
|
func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Greater(a.t, e1, e2, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// a.GreaterOrEqual(2, 1)
|
||||||
|
// a.GreaterOrEqual(2, 2)
|
||||||
|
// a.GreaterOrEqual("b", "a")
|
||||||
|
// a.GreaterOrEqual("b", "b")
|
||||||
|
func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return GreaterOrEqual(a.t, e1, e2, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// a.GreaterOrEqualf(2, 1, "error message %s", "formatted")
|
||||||
|
// a.GreaterOrEqualf(2, 2, "error message %s", "formatted")
|
||||||
|
// a.GreaterOrEqualf("b", "a", "error message %s", "formatted")
|
||||||
|
// a.GreaterOrEqualf("b", "b", "error message %s", "formatted")
|
||||||
|
func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return GreaterOrEqualf(a.t, e1, e2, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greaterf asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// a.Greaterf(2, 1, "error message %s", "formatted")
|
||||||
|
// a.Greaterf(float64(2, "error message %s", "formatted"), float64(1))
|
||||||
|
// a.Greaterf("b", "a", "error message %s", "formatted")
|
||||||
|
func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Greaterf(a.t, e1, e2, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPBodyContains asserts that a specified handler returns a
|
// HTTPBodyContains asserts that a specified handler returns a
|
||||||
// body that contains a string.
|
// body that contains a string.
|
||||||
//
|
//
|
||||||
@@ -567,6 +639,22 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ..
|
|||||||
return JSONEqf(a.t, expected, actual, msg, args...)
|
return JSONEqf(a.t, expected, actual, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YAMLEq asserts that two YAML strings are equivalent.
|
||||||
|
func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return YAMLEq(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||||
|
func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return YAMLEqf(a.t, expected, actual, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// Len asserts that the specified object has specific length.
|
// Len asserts that the specified object has specific length.
|
||||||
// Len also fails if the object has a type that len() not accept.
|
// Len also fails if the object has a type that len() not accept.
|
||||||
//
|
//
|
||||||
@@ -589,6 +677,56 @@ func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...in
|
|||||||
return Lenf(a.t, object, length, msg, args...)
|
return Lenf(a.t, object, length, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Less asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// a.Less(1, 2)
|
||||||
|
// a.Less(float64(1), float64(2))
|
||||||
|
// a.Less("a", "b")
|
||||||
|
func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Less(a.t, e1, e2, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// a.LessOrEqual(1, 2)
|
||||||
|
// a.LessOrEqual(2, 2)
|
||||||
|
// a.LessOrEqual("a", "b")
|
||||||
|
// a.LessOrEqual("b", "b")
|
||||||
|
func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return LessOrEqual(a.t, e1, e2, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// a.LessOrEqualf(1, 2, "error message %s", "formatted")
|
||||||
|
// a.LessOrEqualf(2, 2, "error message %s", "formatted")
|
||||||
|
// a.LessOrEqualf("a", "b", "error message %s", "formatted")
|
||||||
|
// a.LessOrEqualf("b", "b", "error message %s", "formatted")
|
||||||
|
func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return LessOrEqualf(a.t, e1, e2, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lessf asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// a.Lessf(1, 2, "error message %s", "formatted")
|
||||||
|
// a.Lessf(float64(1, "error message %s", "formatted"), float64(2))
|
||||||
|
// a.Lessf("a", "b", "error message %s", "formatted")
|
||||||
|
func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Lessf(a.t, e1, e2, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// Nil asserts that the specified object is nil.
|
// Nil asserts that the specified object is nil.
|
||||||
//
|
//
|
||||||
// a.Nil(err)
|
// a.Nil(err)
|
||||||
@@ -877,6 +1015,32 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args .
|
|||||||
return Regexpf(a.t, rx, str, msg, args...)
|
return Regexpf(a.t, rx, str, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same asserts that two pointers reference the same object.
|
||||||
|
//
|
||||||
|
// a.Same(ptr1, ptr2)
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Same(a.t, expected, actual, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samef asserts that two pointers reference the same object.
|
||||||
|
//
|
||||||
|
// a.Samef(ptr1, ptr2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Samef(a.t, expected, actual, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// Subset asserts that the specified list(array, slice...) contains all
|
// Subset asserts that the specified list(array, slice...) contains all
|
||||||
// elements given in the specified subset(array, slice...).
|
// elements given in the specified subset(array, slice...).
|
||||||
//
|
//
|
||||||
|
|||||||
309
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
Normal file
309
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
|
||||||
|
switch kind {
|
||||||
|
case reflect.Int:
|
||||||
|
{
|
||||||
|
intobj1 := obj1.(int)
|
||||||
|
intobj2 := obj2.(int)
|
||||||
|
if intobj1 > intobj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if intobj1 == intobj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if intobj1 < intobj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int8:
|
||||||
|
{
|
||||||
|
int8obj1 := obj1.(int8)
|
||||||
|
int8obj2 := obj2.(int8)
|
||||||
|
if int8obj1 > int8obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if int8obj1 == int8obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if int8obj1 < int8obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
{
|
||||||
|
int16obj1 := obj1.(int16)
|
||||||
|
int16obj2 := obj2.(int16)
|
||||||
|
if int16obj1 > int16obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if int16obj1 == int16obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if int16obj1 < int16obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
{
|
||||||
|
int32obj1 := obj1.(int32)
|
||||||
|
int32obj2 := obj2.(int32)
|
||||||
|
if int32obj1 > int32obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if int32obj1 == int32obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if int32obj1 < int32obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
{
|
||||||
|
int64obj1 := obj1.(int64)
|
||||||
|
int64obj2 := obj2.(int64)
|
||||||
|
if int64obj1 > int64obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if int64obj1 == int64obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if int64obj1 < int64obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint:
|
||||||
|
{
|
||||||
|
uintobj1 := obj1.(uint)
|
||||||
|
uintobj2 := obj2.(uint)
|
||||||
|
if uintobj1 > uintobj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if uintobj1 == uintobj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if uintobj1 < uintobj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
{
|
||||||
|
uint8obj1 := obj1.(uint8)
|
||||||
|
uint8obj2 := obj2.(uint8)
|
||||||
|
if uint8obj1 > uint8obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if uint8obj1 == uint8obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if uint8obj1 < uint8obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
{
|
||||||
|
uint16obj1 := obj1.(uint16)
|
||||||
|
uint16obj2 := obj2.(uint16)
|
||||||
|
if uint16obj1 > uint16obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if uint16obj1 == uint16obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if uint16obj1 < uint16obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
{
|
||||||
|
uint32obj1 := obj1.(uint32)
|
||||||
|
uint32obj2 := obj2.(uint32)
|
||||||
|
if uint32obj1 > uint32obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if uint32obj1 == uint32obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if uint32obj1 < uint32obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
{
|
||||||
|
uint64obj1 := obj1.(uint64)
|
||||||
|
uint64obj2 := obj2.(uint64)
|
||||||
|
if uint64obj1 > uint64obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if uint64obj1 == uint64obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if uint64obj1 < uint64obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float32:
|
||||||
|
{
|
||||||
|
float32obj1 := obj1.(float32)
|
||||||
|
float32obj2 := obj2.(float32)
|
||||||
|
if float32obj1 > float32obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if float32obj1 == float32obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if float32obj1 < float32obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
{
|
||||||
|
float64obj1 := obj1.(float64)
|
||||||
|
float64obj2 := obj2.(float64)
|
||||||
|
if float64obj1 > float64obj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if float64obj1 == float64obj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if float64obj1 < float64obj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
{
|
||||||
|
stringobj1 := obj1.(string)
|
||||||
|
stringobj2 := obj2.(string)
|
||||||
|
if stringobj1 > stringobj2 {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
if stringobj1 == stringobj2 {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
if stringobj1 < stringobj2 {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greater asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// assert.Greater(t, 2, 1)
|
||||||
|
// assert.Greater(t, float64(2), float64(1))
|
||||||
|
// assert.Greater(t, "b", "a")
|
||||||
|
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
e1Kind := reflect.ValueOf(e1).Kind()
|
||||||
|
e2Kind := reflect.ValueOf(e2).Kind()
|
||||||
|
if e1Kind != e2Kind {
|
||||||
|
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, isComparable := compare(e1, e2, e1Kind)
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res != -1 {
|
||||||
|
return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.GreaterOrEqual(t, 2, 1)
|
||||||
|
// assert.GreaterOrEqual(t, 2, 2)
|
||||||
|
// assert.GreaterOrEqual(t, "b", "a")
|
||||||
|
// assert.GreaterOrEqual(t, "b", "b")
|
||||||
|
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
e1Kind := reflect.ValueOf(e1).Kind()
|
||||||
|
e2Kind := reflect.ValueOf(e2).Kind()
|
||||||
|
if e1Kind != e2Kind {
|
||||||
|
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, isComparable := compare(e1, e2, e1Kind)
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res != -1 && res != 0 {
|
||||||
|
return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// assert.Less(t, 1, 2)
|
||||||
|
// assert.Less(t, float64(1), float64(2))
|
||||||
|
// assert.Less(t, "a", "b")
|
||||||
|
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
e1Kind := reflect.ValueOf(e1).Kind()
|
||||||
|
e2Kind := reflect.ValueOf(e2).Kind()
|
||||||
|
if e1Kind != e2Kind {
|
||||||
|
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, isComparable := compare(e1, e2, e1Kind)
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res != 1 {
|
||||||
|
return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.LessOrEqual(t, 1, 2)
|
||||||
|
// assert.LessOrEqual(t, 2, 2)
|
||||||
|
// assert.LessOrEqual(t, "a", "b")
|
||||||
|
// assert.LessOrEqual(t, "b", "b")
|
||||||
|
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
e1Kind := reflect.ValueOf(e1).Kind()
|
||||||
|
e2Kind := reflect.ValueOf(e2).Kind()
|
||||||
|
if e1Kind != e2Kind {
|
||||||
|
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, isComparable := compare(e1, e2, e1Kind)
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res != 1 && res != 0 {
|
||||||
|
return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
128
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
128
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
@@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/pmezard/go-difflib/difflib"
|
"github.com/pmezard/go-difflib/difflib"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_format.go.tmpl
|
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_format.go.tmpl
|
||||||
@@ -39,7 +40,7 @@ type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool
|
|||||||
// for table driven tests.
|
// for table driven tests.
|
||||||
type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
|
type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
|
||||||
|
|
||||||
// ValuesAssertionFunc is a common function prototype when validating an error value. Can be useful
|
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
|
||||||
// for table driven tests.
|
// for table driven tests.
|
||||||
type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
|
type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
|
||||||
|
|
||||||
@@ -179,7 +180,11 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if len(msgAndArgs) == 1 {
|
if len(msgAndArgs) == 1 {
|
||||||
return msgAndArgs[0].(string)
|
msg := msgAndArgs[0]
|
||||||
|
if msgAsStr, ok := msg.(string); ok {
|
||||||
|
return msgAsStr
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%+v", msg)
|
||||||
}
|
}
|
||||||
if len(msgAndArgs) > 1 {
|
if len(msgAndArgs) > 1 {
|
||||||
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
|
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
|
||||||
@@ -346,6 +351,37 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{})
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same asserts that two pointers reference the same object.
|
||||||
|
//
|
||||||
|
// assert.Same(t, ptr1, ptr2)
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPtr, actualPtr := reflect.ValueOf(expected), reflect.ValueOf(actual)
|
||||||
|
if expectedPtr.Kind() != reflect.Ptr || actualPtr.Kind() != reflect.Ptr {
|
||||||
|
return Fail(t, "Invalid operation: both arguments must be pointers", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedType, actualType := reflect.TypeOf(expected), reflect.TypeOf(actual)
|
||||||
|
if expectedType != actualType {
|
||||||
|
return Fail(t, fmt.Sprintf("Pointer expected to be of type %v, but was %v",
|
||||||
|
expectedType, actualType), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected != actual {
|
||||||
|
return Fail(t, fmt.Sprintf("Not same: \n"+
|
||||||
|
"expected: %p %#v\n"+
|
||||||
|
"actual : %p %#v", expected, expected, actual, actual), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// formatUnequalValues takes two values of arbitrary types and returns string
|
// formatUnequalValues takes two values of arbitrary types and returns string
|
||||||
// representations appropriate to be presented to the user.
|
// representations appropriate to be presented to the user.
|
||||||
//
|
//
|
||||||
@@ -415,6 +451,17 @@ func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|||||||
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
|
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containsKind checks if a specified kind in the slice of kinds.
|
||||||
|
func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool {
|
||||||
|
for i := 0; i < len(kinds); i++ {
|
||||||
|
if kind == kinds[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// isNil checks if a specified object is nil or not, without Failing.
|
// isNil checks if a specified object is nil or not, without Failing.
|
||||||
func isNil(object interface{}) bool {
|
func isNil(object interface{}) bool {
|
||||||
if object == nil {
|
if object == nil {
|
||||||
@@ -423,7 +470,14 @@ func isNil(object interface{}) bool {
|
|||||||
|
|
||||||
value := reflect.ValueOf(object)
|
value := reflect.ValueOf(object)
|
||||||
kind := value.Kind()
|
kind := value.Kind()
|
||||||
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
|
isNilableKind := containsKind(
|
||||||
|
[]reflect.Kind{
|
||||||
|
reflect.Chan, reflect.Func,
|
||||||
|
reflect.Interface, reflect.Map,
|
||||||
|
reflect.Ptr, reflect.Slice},
|
||||||
|
kind)
|
||||||
|
|
||||||
|
if isNilableKind && value.IsNil() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,14 +511,14 @@ func isEmpty(object interface{}) bool {
|
|||||||
// collection types are empty when they have no element
|
// collection types are empty when they have no element
|
||||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||||
return objValue.Len() == 0
|
return objValue.Len() == 0
|
||||||
// pointers are empty if nil or if the value they point to is empty
|
// pointers are empty if nil or if the value they point to is empty
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if objValue.IsNil() {
|
if objValue.IsNil() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
deref := objValue.Elem().Interface()
|
deref := objValue.Elem().Interface()
|
||||||
return isEmpty(deref)
|
return isEmpty(deref)
|
||||||
// for all other types, compare against the zero value
|
// for all other types, compare against the zero value
|
||||||
default:
|
default:
|
||||||
zero := reflect.Zero(objValue.Type())
|
zero := reflect.Zero(objValue.Type())
|
||||||
return reflect.DeepEqual(object, zero.Interface())
|
return reflect.DeepEqual(object, zero.Interface())
|
||||||
@@ -607,7 +661,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{
|
|||||||
func includeElement(list interface{}, element interface{}) (ok, found bool) {
|
func includeElement(list interface{}, element interface{}) (ok, found bool) {
|
||||||
|
|
||||||
listValue := reflect.ValueOf(list)
|
listValue := reflect.ValueOf(list)
|
||||||
elementValue := reflect.ValueOf(element)
|
listKind := reflect.TypeOf(list).Kind()
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
ok = false
|
ok = false
|
||||||
@@ -615,11 +669,12 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if reflect.TypeOf(list).Kind() == reflect.String {
|
if listKind == reflect.String {
|
||||||
|
elementValue := reflect.ValueOf(element)
|
||||||
return true, strings.Contains(listValue.String(), elementValue.String())
|
return true, strings.Contains(listValue.String(), elementValue.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflect.TypeOf(list).Kind() == reflect.Map {
|
if listKind == reflect.Map {
|
||||||
mapKeys := listValue.MapKeys()
|
mapKeys := listValue.MapKeys()
|
||||||
for i := 0; i < len(mapKeys); i++ {
|
for i := 0; i < len(mapKeys); i++ {
|
||||||
if ObjectsAreEqual(mapKeys[i].Interface(), element) {
|
if ObjectsAreEqual(mapKeys[i].Interface(), element) {
|
||||||
@@ -1315,6 +1370,24 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{
|
|||||||
return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...)
|
return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YAMLEq asserts that two YAML strings are equivalent.
|
||||||
|
func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
var expectedYAMLAsInterface, actualYAMLAsInterface interface{}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal([]byte(expected), &expectedYAMLAsInterface); err != nil {
|
||||||
|
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil {
|
||||||
|
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Equal(t, expectedYAMLAsInterface, actualYAMLAsInterface, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
|
func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
|
||||||
t := reflect.TypeOf(v)
|
t := reflect.TypeOf(v)
|
||||||
k := t.Kind()
|
k := t.Kind()
|
||||||
@@ -1327,7 +1400,7 @@ func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// diff returns a diff of both values as long as both are of the same type and
|
// diff returns a diff of both values as long as both are of the same type and
|
||||||
// are a struct, map, slice or array. Otherwise it returns an empty string.
|
// are a struct, map, slice, array or string. Otherwise it returns an empty string.
|
||||||
func diff(expected interface{}, actual interface{}) string {
|
func diff(expected interface{}, actual interface{}) string {
|
||||||
if expected == nil || actual == nil {
|
if expected == nil || actual == nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -1345,12 +1418,12 @@ func diff(expected interface{}, actual interface{}) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var e, a string
|
var e, a string
|
||||||
if ek != reflect.String {
|
if et != reflect.TypeOf("") {
|
||||||
e = spewConfig.Sdump(expected)
|
e = spewConfig.Sdump(expected)
|
||||||
a = spewConfig.Sdump(actual)
|
a = spewConfig.Sdump(actual)
|
||||||
} else {
|
} else {
|
||||||
e = expected.(string)
|
e = reflect.ValueOf(expected).String()
|
||||||
a = actual.(string)
|
a = reflect.ValueOf(actual).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
||||||
@@ -1392,3 +1465,34 @@ var spewConfig = spew.ConfigState{
|
|||||||
type tHelper interface {
|
type tHelper interface {
|
||||||
Helper()
|
Helper()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eventually asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick.
|
||||||
|
//
|
||||||
|
// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond)
|
||||||
|
func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := time.NewTimer(waitFor)
|
||||||
|
ticker := time.NewTicker(tick)
|
||||||
|
checkPassed := make(chan bool)
|
||||||
|
defer timer.Stop()
|
||||||
|
defer ticker.Stop()
|
||||||
|
defer close(checkPassed)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
||||||
|
case result := <-checkPassed:
|
||||||
|
if result {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
go func() {
|
||||||
|
checkPassed <- condition()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
99
vendor/golang.org/x/crypto/scrypt/scrypt.go
generated
vendored
99
vendor/golang.org/x/crypto/scrypt/scrypt.go
generated
vendored
@@ -10,6 +10,7 @@ package scrypt // import "golang.org/x/crypto/scrypt"
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
@@ -29,7 +30,7 @@ func blockXOR(dst, src []uint32, n int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in,
|
// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in,
|
||||||
// and puts the result into both both tmp and out.
|
// and puts the result into both tmp and out.
|
||||||
func salsaXOR(tmp *[16]uint32, in, out []uint32) {
|
func salsaXOR(tmp *[16]uint32, in, out []uint32) {
|
||||||
w0 := tmp[0] ^ in[0]
|
w0 := tmp[0] ^ in[0]
|
||||||
w1 := tmp[1] ^ in[1]
|
w1 := tmp[1] ^ in[1]
|
||||||
@@ -52,77 +53,45 @@ func salsaXOR(tmp *[16]uint32, in, out []uint32) {
|
|||||||
x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15
|
x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15
|
||||||
|
|
||||||
for i := 0; i < 8; i += 2 {
|
for i := 0; i < 8; i += 2 {
|
||||||
u := x0 + x12
|
x4 ^= bits.RotateLeft32(x0+x12, 7)
|
||||||
x4 ^= u<<7 | u>>(32-7)
|
x8 ^= bits.RotateLeft32(x4+x0, 9)
|
||||||
u = x4 + x0
|
x12 ^= bits.RotateLeft32(x8+x4, 13)
|
||||||
x8 ^= u<<9 | u>>(32-9)
|
x0 ^= bits.RotateLeft32(x12+x8, 18)
|
||||||
u = x8 + x4
|
|
||||||
x12 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x12 + x8
|
|
||||||
x0 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x5 + x1
|
x9 ^= bits.RotateLeft32(x5+x1, 7)
|
||||||
x9 ^= u<<7 | u>>(32-7)
|
x13 ^= bits.RotateLeft32(x9+x5, 9)
|
||||||
u = x9 + x5
|
x1 ^= bits.RotateLeft32(x13+x9, 13)
|
||||||
x13 ^= u<<9 | u>>(32-9)
|
x5 ^= bits.RotateLeft32(x1+x13, 18)
|
||||||
u = x13 + x9
|
|
||||||
x1 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x1 + x13
|
|
||||||
x5 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x10 + x6
|
x14 ^= bits.RotateLeft32(x10+x6, 7)
|
||||||
x14 ^= u<<7 | u>>(32-7)
|
x2 ^= bits.RotateLeft32(x14+x10, 9)
|
||||||
u = x14 + x10
|
x6 ^= bits.RotateLeft32(x2+x14, 13)
|
||||||
x2 ^= u<<9 | u>>(32-9)
|
x10 ^= bits.RotateLeft32(x6+x2, 18)
|
||||||
u = x2 + x14
|
|
||||||
x6 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x6 + x2
|
|
||||||
x10 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x15 + x11
|
x3 ^= bits.RotateLeft32(x15+x11, 7)
|
||||||
x3 ^= u<<7 | u>>(32-7)
|
x7 ^= bits.RotateLeft32(x3+x15, 9)
|
||||||
u = x3 + x15
|
x11 ^= bits.RotateLeft32(x7+x3, 13)
|
||||||
x7 ^= u<<9 | u>>(32-9)
|
x15 ^= bits.RotateLeft32(x11+x7, 18)
|
||||||
u = x7 + x3
|
|
||||||
x11 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x11 + x7
|
|
||||||
x15 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x0 + x3
|
x1 ^= bits.RotateLeft32(x0+x3, 7)
|
||||||
x1 ^= u<<7 | u>>(32-7)
|
x2 ^= bits.RotateLeft32(x1+x0, 9)
|
||||||
u = x1 + x0
|
x3 ^= bits.RotateLeft32(x2+x1, 13)
|
||||||
x2 ^= u<<9 | u>>(32-9)
|
x0 ^= bits.RotateLeft32(x3+x2, 18)
|
||||||
u = x2 + x1
|
|
||||||
x3 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x3 + x2
|
|
||||||
x0 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x5 + x4
|
x6 ^= bits.RotateLeft32(x5+x4, 7)
|
||||||
x6 ^= u<<7 | u>>(32-7)
|
x7 ^= bits.RotateLeft32(x6+x5, 9)
|
||||||
u = x6 + x5
|
x4 ^= bits.RotateLeft32(x7+x6, 13)
|
||||||
x7 ^= u<<9 | u>>(32-9)
|
x5 ^= bits.RotateLeft32(x4+x7, 18)
|
||||||
u = x7 + x6
|
|
||||||
x4 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x4 + x7
|
|
||||||
x5 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x10 + x9
|
x11 ^= bits.RotateLeft32(x10+x9, 7)
|
||||||
x11 ^= u<<7 | u>>(32-7)
|
x8 ^= bits.RotateLeft32(x11+x10, 9)
|
||||||
u = x11 + x10
|
x9 ^= bits.RotateLeft32(x8+x11, 13)
|
||||||
x8 ^= u<<9 | u>>(32-9)
|
x10 ^= bits.RotateLeft32(x9+x8, 18)
|
||||||
u = x8 + x11
|
|
||||||
x9 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x9 + x8
|
|
||||||
x10 ^= u<<18 | u>>(32-18)
|
|
||||||
|
|
||||||
u = x15 + x14
|
x12 ^= bits.RotateLeft32(x15+x14, 7)
|
||||||
x12 ^= u<<7 | u>>(32-7)
|
x13 ^= bits.RotateLeft32(x12+x15, 9)
|
||||||
u = x12 + x15
|
x14 ^= bits.RotateLeft32(x13+x12, 13)
|
||||||
x13 ^= u<<9 | u>>(32-9)
|
x15 ^= bits.RotateLeft32(x14+x13, 18)
|
||||||
u = x13 + x12
|
|
||||||
x14 ^= u<<13 | u>>(32-13)
|
|
||||||
u = x14 + x13
|
|
||||||
x15 ^= u<<18 | u>>(32-18)
|
|
||||||
}
|
}
|
||||||
x0 += w0
|
x0 += w0
|
||||||
x1 += w1
|
x1 += w1
|
||||||
|
|||||||
951
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
951
vendor/golang.org/x/crypto/ssh/terminal/terminal.go
generated
vendored
@@ -1,951 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package terminal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EscapeCodes contains escape sequences that can be written to the terminal in
|
|
||||||
// order to achieve different styles of text.
|
|
||||||
type EscapeCodes struct {
|
|
||||||
// Foreground colors
|
|
||||||
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
|
|
||||||
|
|
||||||
// Reset all attributes
|
|
||||||
Reset []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var vt100EscapeCodes = EscapeCodes{
|
|
||||||
Black: []byte{keyEscape, '[', '3', '0', 'm'},
|
|
||||||
Red: []byte{keyEscape, '[', '3', '1', 'm'},
|
|
||||||
Green: []byte{keyEscape, '[', '3', '2', 'm'},
|
|
||||||
Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
|
|
||||||
Blue: []byte{keyEscape, '[', '3', '4', 'm'},
|
|
||||||
Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
|
|
||||||
Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
|
|
||||||
White: []byte{keyEscape, '[', '3', '7', 'm'},
|
|
||||||
|
|
||||||
Reset: []byte{keyEscape, '[', '0', 'm'},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
|
||||||
// reading lines of input.
|
|
||||||
type Terminal struct {
|
|
||||||
// AutoCompleteCallback, if non-null, is called for each keypress with
|
|
||||||
// the full input line and the current position of the cursor (in
|
|
||||||
// bytes, as an index into |line|). If it returns ok=false, the key
|
|
||||||
// press is processed normally. Otherwise it returns a replacement line
|
|
||||||
// and the new cursor position.
|
|
||||||
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
|
||||||
|
|
||||||
// Escape contains a pointer to the escape codes for this terminal.
|
|
||||||
// It's always a valid pointer, although the escape codes themselves
|
|
||||||
// may be empty if the terminal doesn't support them.
|
|
||||||
Escape *EscapeCodes
|
|
||||||
|
|
||||||
// lock protects the terminal and the state in this object from
|
|
||||||
// concurrent processing of a key press and a Write() call.
|
|
||||||
lock sync.Mutex
|
|
||||||
|
|
||||||
c io.ReadWriter
|
|
||||||
prompt []rune
|
|
||||||
|
|
||||||
// line is the current line being entered.
|
|
||||||
line []rune
|
|
||||||
// pos is the logical position of the cursor in line
|
|
||||||
pos int
|
|
||||||
// echo is true if local echo is enabled
|
|
||||||
echo bool
|
|
||||||
// pasteActive is true iff there is a bracketed paste operation in
|
|
||||||
// progress.
|
|
||||||
pasteActive bool
|
|
||||||
|
|
||||||
// cursorX contains the current X value of the cursor where the left
|
|
||||||
// edge is 0. cursorY contains the row number where the first row of
|
|
||||||
// the current line is 0.
|
|
||||||
cursorX, cursorY int
|
|
||||||
// maxLine is the greatest value of cursorY so far.
|
|
||||||
maxLine int
|
|
||||||
|
|
||||||
termWidth, termHeight int
|
|
||||||
|
|
||||||
// outBuf contains the terminal data to be sent.
|
|
||||||
outBuf []byte
|
|
||||||
// remainder contains the remainder of any partial key sequences after
|
|
||||||
// a read. It aliases into inBuf.
|
|
||||||
remainder []byte
|
|
||||||
inBuf [256]byte
|
|
||||||
|
|
||||||
// history contains previously entered commands so that they can be
|
|
||||||
// accessed with the up and down keys.
|
|
||||||
history stRingBuffer
|
|
||||||
// historyIndex stores the currently accessed history entry, where zero
|
|
||||||
// means the immediately previous entry.
|
|
||||||
historyIndex int
|
|
||||||
// When navigating up and down the history it's possible to return to
|
|
||||||
// the incomplete, initial line. That value is stored in
|
|
||||||
// historyPending.
|
|
||||||
historyPending string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
|
||||||
// a local terminal, that terminal must first have been put into raw mode.
|
|
||||||
// prompt is a string that is written at the start of each input line (i.e.
|
|
||||||
// "> ").
|
|
||||||
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
|
||||||
return &Terminal{
|
|
||||||
Escape: &vt100EscapeCodes,
|
|
||||||
c: c,
|
|
||||||
prompt: []rune(prompt),
|
|
||||||
termWidth: 80,
|
|
||||||
termHeight: 24,
|
|
||||||
echo: true,
|
|
||||||
historyIndex: -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
keyCtrlD = 4
|
|
||||||
keyCtrlU = 21
|
|
||||||
keyEnter = '\r'
|
|
||||||
keyEscape = 27
|
|
||||||
keyBackspace = 127
|
|
||||||
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
|
||||||
keyUp
|
|
||||||
keyDown
|
|
||||||
keyLeft
|
|
||||||
keyRight
|
|
||||||
keyAltLeft
|
|
||||||
keyAltRight
|
|
||||||
keyHome
|
|
||||||
keyEnd
|
|
||||||
keyDeleteWord
|
|
||||||
keyDeleteLine
|
|
||||||
keyClearScreen
|
|
||||||
keyPasteStart
|
|
||||||
keyPasteEnd
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
crlf = []byte{'\r', '\n'}
|
|
||||||
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
|
|
||||||
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
|
|
||||||
)
|
|
||||||
|
|
||||||
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
|
||||||
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
|
||||||
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
|
||||||
if len(b) == 0 {
|
|
||||||
return utf8.RuneError, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pasteActive {
|
|
||||||
switch b[0] {
|
|
||||||
case 1: // ^A
|
|
||||||
return keyHome, b[1:]
|
|
||||||
case 5: // ^E
|
|
||||||
return keyEnd, b[1:]
|
|
||||||
case 8: // ^H
|
|
||||||
return keyBackspace, b[1:]
|
|
||||||
case 11: // ^K
|
|
||||||
return keyDeleteLine, b[1:]
|
|
||||||
case 12: // ^L
|
|
||||||
return keyClearScreen, b[1:]
|
|
||||||
case 23: // ^W
|
|
||||||
return keyDeleteWord, b[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b[0] != keyEscape {
|
|
||||||
if !utf8.FullRune(b) {
|
|
||||||
return utf8.RuneError, b
|
|
||||||
}
|
|
||||||
r, l := utf8.DecodeRune(b)
|
|
||||||
return r, b[l:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
|
|
||||||
switch b[2] {
|
|
||||||
case 'A':
|
|
||||||
return keyUp, b[3:]
|
|
||||||
case 'B':
|
|
||||||
return keyDown, b[3:]
|
|
||||||
case 'C':
|
|
||||||
return keyRight, b[3:]
|
|
||||||
case 'D':
|
|
||||||
return keyLeft, b[3:]
|
|
||||||
case 'H':
|
|
||||||
return keyHome, b[3:]
|
|
||||||
case 'F':
|
|
||||||
return keyEnd, b[3:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
|
||||||
switch b[5] {
|
|
||||||
case 'C':
|
|
||||||
return keyAltRight, b[6:]
|
|
||||||
case 'D':
|
|
||||||
return keyAltLeft, b[6:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
|
|
||||||
return keyPasteStart, b[6:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
|
|
||||||
return keyPasteEnd, b[6:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here then we have a key that we don't recognise, or a
|
|
||||||
// partial sequence. It's not clear how one should find the end of a
|
|
||||||
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
|
||||||
// appears at the end of a sequence.
|
|
||||||
for i, c := range b[0:] {
|
|
||||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
|
|
||||||
return keyUnknown, b[i+1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return utf8.RuneError, b
|
|
||||||
}
|
|
||||||
|
|
||||||
// queue appends data to the end of t.outBuf
|
|
||||||
func (t *Terminal) queue(data []rune) {
|
|
||||||
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
|
|
||||||
var space = []rune{' '}
|
|
||||||
|
|
||||||
func isPrintable(key rune) bool {
|
|
||||||
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
|
||||||
return key >= 32 && !isInSurrogateArea
|
|
||||||
}
|
|
||||||
|
|
||||||
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
|
||||||
// given, logical position in the text.
|
|
||||||
func (t *Terminal) moveCursorToPos(pos int) {
|
|
||||||
if !t.echo {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
x := visualLength(t.prompt) + pos
|
|
||||||
y := x / t.termWidth
|
|
||||||
x = x % t.termWidth
|
|
||||||
|
|
||||||
up := 0
|
|
||||||
if y < t.cursorY {
|
|
||||||
up = t.cursorY - y
|
|
||||||
}
|
|
||||||
|
|
||||||
down := 0
|
|
||||||
if y > t.cursorY {
|
|
||||||
down = y - t.cursorY
|
|
||||||
}
|
|
||||||
|
|
||||||
left := 0
|
|
||||||
if x < t.cursorX {
|
|
||||||
left = t.cursorX - x
|
|
||||||
}
|
|
||||||
|
|
||||||
right := 0
|
|
||||||
if x > t.cursorX {
|
|
||||||
right = x - t.cursorX
|
|
||||||
}
|
|
||||||
|
|
||||||
t.cursorX = x
|
|
||||||
t.cursorY = y
|
|
||||||
t.move(up, down, left, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) move(up, down, left, right int) {
|
|
||||||
movement := make([]rune, 3*(up+down+left+right))
|
|
||||||
m := movement
|
|
||||||
for i := 0; i < up; i++ {
|
|
||||||
m[0] = keyEscape
|
|
||||||
m[1] = '['
|
|
||||||
m[2] = 'A'
|
|
||||||
m = m[3:]
|
|
||||||
}
|
|
||||||
for i := 0; i < down; i++ {
|
|
||||||
m[0] = keyEscape
|
|
||||||
m[1] = '['
|
|
||||||
m[2] = 'B'
|
|
||||||
m = m[3:]
|
|
||||||
}
|
|
||||||
for i := 0; i < left; i++ {
|
|
||||||
m[0] = keyEscape
|
|
||||||
m[1] = '['
|
|
||||||
m[2] = 'D'
|
|
||||||
m = m[3:]
|
|
||||||
}
|
|
||||||
for i := 0; i < right; i++ {
|
|
||||||
m[0] = keyEscape
|
|
||||||
m[1] = '['
|
|
||||||
m[2] = 'C'
|
|
||||||
m = m[3:]
|
|
||||||
}
|
|
||||||
|
|
||||||
t.queue(movement)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) clearLineToRight() {
|
|
||||||
op := []rune{keyEscape, '[', 'K'}
|
|
||||||
t.queue(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxLineLength = 4096
|
|
||||||
|
|
||||||
func (t *Terminal) setLine(newLine []rune, newPos int) {
|
|
||||||
if t.echo {
|
|
||||||
t.moveCursorToPos(0)
|
|
||||||
t.writeLine(newLine)
|
|
||||||
for i := len(newLine); i < len(t.line); i++ {
|
|
||||||
t.writeLine(space)
|
|
||||||
}
|
|
||||||
t.moveCursorToPos(newPos)
|
|
||||||
}
|
|
||||||
t.line = newLine
|
|
||||||
t.pos = newPos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) advanceCursor(places int) {
|
|
||||||
t.cursorX += places
|
|
||||||
t.cursorY += t.cursorX / t.termWidth
|
|
||||||
if t.cursorY > t.maxLine {
|
|
||||||
t.maxLine = t.cursorY
|
|
||||||
}
|
|
||||||
t.cursorX = t.cursorX % t.termWidth
|
|
||||||
|
|
||||||
if places > 0 && t.cursorX == 0 {
|
|
||||||
// Normally terminals will advance the current position
|
|
||||||
// when writing a character. But that doesn't happen
|
|
||||||
// for the last character in a line. However, when
|
|
||||||
// writing a character (except a new line) that causes
|
|
||||||
// a line wrap, the position will be advanced two
|
|
||||||
// places.
|
|
||||||
//
|
|
||||||
// So, if we are stopping at the end of a line, we
|
|
||||||
// need to write a newline so that our cursor can be
|
|
||||||
// advanced to the next line.
|
|
||||||
t.outBuf = append(t.outBuf, '\r', '\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) eraseNPreviousChars(n int) {
|
|
||||||
if n == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.pos < n {
|
|
||||||
n = t.pos
|
|
||||||
}
|
|
||||||
t.pos -= n
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
|
|
||||||
copy(t.line[t.pos:], t.line[n+t.pos:])
|
|
||||||
t.line = t.line[:len(t.line)-n]
|
|
||||||
if t.echo {
|
|
||||||
t.writeLine(t.line[t.pos:])
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
t.queue(space)
|
|
||||||
}
|
|
||||||
t.advanceCursor(n)
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// countToLeftWord returns then number of characters from the cursor to the
|
|
||||||
// start of the previous word.
|
|
||||||
func (t *Terminal) countToLeftWord() int {
|
|
||||||
if t.pos == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := t.pos - 1
|
|
||||||
for pos > 0 {
|
|
||||||
if t.line[pos] != ' ' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pos--
|
|
||||||
}
|
|
||||||
for pos > 0 {
|
|
||||||
if t.line[pos] == ' ' {
|
|
||||||
pos++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pos--
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.pos - pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// countToRightWord returns then number of characters from the cursor to the
|
|
||||||
// start of the next word.
|
|
||||||
func (t *Terminal) countToRightWord() int {
|
|
||||||
pos := t.pos
|
|
||||||
for pos < len(t.line) {
|
|
||||||
if t.line[pos] == ' ' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
for pos < len(t.line) {
|
|
||||||
if t.line[pos] != ' ' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
return pos - t.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// visualLength returns the number of visible glyphs in s.
|
|
||||||
func visualLength(runes []rune) int {
|
|
||||||
inEscapeSeq := false
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
for _, r := range runes {
|
|
||||||
switch {
|
|
||||||
case inEscapeSeq:
|
|
||||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
|
|
||||||
inEscapeSeq = false
|
|
||||||
}
|
|
||||||
case r == '\x1b':
|
|
||||||
inEscapeSeq = true
|
|
||||||
default:
|
|
||||||
length++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleKey processes the given key and, optionally, returns a line of text
|
|
||||||
// that the user has entered.
|
|
||||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|
||||||
if t.pasteActive && key != keyEnter {
|
|
||||||
t.addKeyToLine(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case keyBackspace:
|
|
||||||
if t.pos == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.eraseNPreviousChars(1)
|
|
||||||
case keyAltLeft:
|
|
||||||
// move left by a word.
|
|
||||||
t.pos -= t.countToLeftWord()
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyAltRight:
|
|
||||||
// move right by a word.
|
|
||||||
t.pos += t.countToRightWord()
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyLeft:
|
|
||||||
if t.pos == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.pos--
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyRight:
|
|
||||||
if t.pos == len(t.line) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.pos++
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyHome:
|
|
||||||
if t.pos == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.pos = 0
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyEnd:
|
|
||||||
if t.pos == len(t.line) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.pos = len(t.line)
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyUp:
|
|
||||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
if t.historyIndex == -1 {
|
|
||||||
t.historyPending = string(t.line)
|
|
||||||
}
|
|
||||||
t.historyIndex++
|
|
||||||
runes := []rune(entry)
|
|
||||||
t.setLine(runes, len(runes))
|
|
||||||
case keyDown:
|
|
||||||
switch t.historyIndex {
|
|
||||||
case -1:
|
|
||||||
return
|
|
||||||
case 0:
|
|
||||||
runes := []rune(t.historyPending)
|
|
||||||
t.setLine(runes, len(runes))
|
|
||||||
t.historyIndex--
|
|
||||||
default:
|
|
||||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
|
||||||
if ok {
|
|
||||||
t.historyIndex--
|
|
||||||
runes := []rune(entry)
|
|
||||||
t.setLine(runes, len(runes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case keyEnter:
|
|
||||||
t.moveCursorToPos(len(t.line))
|
|
||||||
t.queue([]rune("\r\n"))
|
|
||||||
line = string(t.line)
|
|
||||||
ok = true
|
|
||||||
t.line = t.line[:0]
|
|
||||||
t.pos = 0
|
|
||||||
t.cursorX = 0
|
|
||||||
t.cursorY = 0
|
|
||||||
t.maxLine = 0
|
|
||||||
case keyDeleteWord:
|
|
||||||
// Delete zero or more spaces and then one or more characters.
|
|
||||||
t.eraseNPreviousChars(t.countToLeftWord())
|
|
||||||
case keyDeleteLine:
|
|
||||||
// Delete everything from the current cursor position to the
|
|
||||||
// end of line.
|
|
||||||
for i := t.pos; i < len(t.line); i++ {
|
|
||||||
t.queue(space)
|
|
||||||
t.advanceCursor(1)
|
|
||||||
}
|
|
||||||
t.line = t.line[:t.pos]
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
case keyCtrlD:
|
|
||||||
// Erase the character under the current position.
|
|
||||||
// The EOF case when the line is empty is handled in
|
|
||||||
// readLine().
|
|
||||||
if t.pos < len(t.line) {
|
|
||||||
t.pos++
|
|
||||||
t.eraseNPreviousChars(1)
|
|
||||||
}
|
|
||||||
case keyCtrlU:
|
|
||||||
t.eraseNPreviousChars(t.pos)
|
|
||||||
case keyClearScreen:
|
|
||||||
// Erases the screen and moves the cursor to the home position.
|
|
||||||
t.queue([]rune("\x1b[2J\x1b[H"))
|
|
||||||
t.queue(t.prompt)
|
|
||||||
t.cursorX, t.cursorY = 0, 0
|
|
||||||
t.advanceCursor(visualLength(t.prompt))
|
|
||||||
t.setLine(t.line, t.pos)
|
|
||||||
default:
|
|
||||||
if t.AutoCompleteCallback != nil {
|
|
||||||
prefix := string(t.line[:t.pos])
|
|
||||||
suffix := string(t.line[t.pos:])
|
|
||||||
|
|
||||||
t.lock.Unlock()
|
|
||||||
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
|
|
||||||
t.lock.Lock()
|
|
||||||
|
|
||||||
if completeOk {
|
|
||||||
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isPrintable(key) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(t.line) == maxLineLength {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.addKeyToLine(key)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// addKeyToLine inserts the given key at the current position in the current
|
|
||||||
// line.
|
|
||||||
func (t *Terminal) addKeyToLine(key rune) {
|
|
||||||
if len(t.line) == cap(t.line) {
|
|
||||||
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
|
|
||||||
copy(newLine, t.line)
|
|
||||||
t.line = newLine
|
|
||||||
}
|
|
||||||
t.line = t.line[:len(t.line)+1]
|
|
||||||
copy(t.line[t.pos+1:], t.line[t.pos:])
|
|
||||||
t.line[t.pos] = key
|
|
||||||
if t.echo {
|
|
||||||
t.writeLine(t.line[t.pos:])
|
|
||||||
}
|
|
||||||
t.pos++
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) writeLine(line []rune) {
|
|
||||||
for len(line) != 0 {
|
|
||||||
remainingOnLine := t.termWidth - t.cursorX
|
|
||||||
todo := len(line)
|
|
||||||
if todo > remainingOnLine {
|
|
||||||
todo = remainingOnLine
|
|
||||||
}
|
|
||||||
t.queue(line[:todo])
|
|
||||||
t.advanceCursor(visualLength(line[:todo]))
|
|
||||||
line = line[todo:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
|
||||||
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
|
|
||||||
for len(buf) > 0 {
|
|
||||||
i := bytes.IndexByte(buf, '\n')
|
|
||||||
todo := len(buf)
|
|
||||||
if i >= 0 {
|
|
||||||
todo = i
|
|
||||||
}
|
|
||||||
|
|
||||||
var nn int
|
|
||||||
nn, err = w.Write(buf[:todo])
|
|
||||||
n += nn
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
buf = buf[todo:]
|
|
||||||
|
|
||||||
if i >= 0 {
|
|
||||||
if _, err = w.Write(crlf); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
buf = buf[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) Write(buf []byte) (n int, err error) {
|
|
||||||
t.lock.Lock()
|
|
||||||
defer t.lock.Unlock()
|
|
||||||
|
|
||||||
if t.cursorX == 0 && t.cursorY == 0 {
|
|
||||||
// This is the easy case: there's nothing on the screen that we
|
|
||||||
// have to move out of the way.
|
|
||||||
return writeWithCRLF(t.c, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have a prompt and possibly user input on the screen. We
|
|
||||||
// have to clear it first.
|
|
||||||
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
|
|
||||||
t.cursorX = 0
|
|
||||||
t.clearLineToRight()
|
|
||||||
|
|
||||||
for t.cursorY > 0 {
|
|
||||||
t.move(1 /* up */, 0, 0, 0)
|
|
||||||
t.cursorY--
|
|
||||||
t.clearLineToRight()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = t.c.Write(t.outBuf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.outBuf = t.outBuf[:0]
|
|
||||||
|
|
||||||
if n, err = writeWithCRLF(t.c, buf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.writeLine(t.prompt)
|
|
||||||
if t.echo {
|
|
||||||
t.writeLine(t.line)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
|
|
||||||
if _, err = t.c.Write(t.outBuf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.outBuf = t.outBuf[:0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPassword temporarily changes the prompt and reads a password, without
|
|
||||||
// echo, from the terminal.
|
|
||||||
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
|
||||||
t.lock.Lock()
|
|
||||||
defer t.lock.Unlock()
|
|
||||||
|
|
||||||
oldPrompt := t.prompt
|
|
||||||
t.prompt = []rune(prompt)
|
|
||||||
t.echo = false
|
|
||||||
|
|
||||||
line, err = t.readLine()
|
|
||||||
|
|
||||||
t.prompt = oldPrompt
|
|
||||||
t.echo = true
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadLine returns a line of input from the terminal.
|
|
||||||
func (t *Terminal) ReadLine() (line string, err error) {
|
|
||||||
t.lock.Lock()
|
|
||||||
defer t.lock.Unlock()
|
|
||||||
|
|
||||||
return t.readLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) readLine() (line string, err error) {
|
|
||||||
// t.lock must be held at this point
|
|
||||||
|
|
||||||
if t.cursorX == 0 && t.cursorY == 0 {
|
|
||||||
t.writeLine(t.prompt)
|
|
||||||
t.c.Write(t.outBuf)
|
|
||||||
t.outBuf = t.outBuf[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
lineIsPasted := t.pasteActive
|
|
||||||
|
|
||||||
for {
|
|
||||||
rest := t.remainder
|
|
||||||
lineOk := false
|
|
||||||
for !lineOk {
|
|
||||||
var key rune
|
|
||||||
key, rest = bytesToKey(rest, t.pasteActive)
|
|
||||||
if key == utf8.RuneError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !t.pasteActive {
|
|
||||||
if key == keyCtrlD {
|
|
||||||
if len(t.line) == 0 {
|
|
||||||
return "", io.EOF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if key == keyPasteStart {
|
|
||||||
t.pasteActive = true
|
|
||||||
if len(t.line) == 0 {
|
|
||||||
lineIsPasted = true
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if key == keyPasteEnd {
|
|
||||||
t.pasteActive = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !t.pasteActive {
|
|
||||||
lineIsPasted = false
|
|
||||||
}
|
|
||||||
line, lineOk = t.handleKey(key)
|
|
||||||
}
|
|
||||||
if len(rest) > 0 {
|
|
||||||
n := copy(t.inBuf[:], rest)
|
|
||||||
t.remainder = t.inBuf[:n]
|
|
||||||
} else {
|
|
||||||
t.remainder = nil
|
|
||||||
}
|
|
||||||
t.c.Write(t.outBuf)
|
|
||||||
t.outBuf = t.outBuf[:0]
|
|
||||||
if lineOk {
|
|
||||||
if t.echo {
|
|
||||||
t.historyIndex = -1
|
|
||||||
t.history.Add(line)
|
|
||||||
}
|
|
||||||
if lineIsPasted {
|
|
||||||
err = ErrPasteIndicator
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// t.remainder is a slice at the beginning of t.inBuf
|
|
||||||
// containing a partial key sequence
|
|
||||||
readBuf := t.inBuf[len(t.remainder):]
|
|
||||||
var n int
|
|
||||||
|
|
||||||
t.lock.Unlock()
|
|
||||||
n, err = t.c.Read(readBuf)
|
|
||||||
t.lock.Lock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.remainder = t.inBuf[:n+len(t.remainder)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
|
||||||
func (t *Terminal) SetPrompt(prompt string) {
|
|
||||||
t.lock.Lock()
|
|
||||||
defer t.lock.Unlock()
|
|
||||||
|
|
||||||
t.prompt = []rune(prompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
|
|
||||||
// Move cursor to column zero at the start of the line.
|
|
||||||
t.move(t.cursorY, 0, t.cursorX, 0)
|
|
||||||
t.cursorX, t.cursorY = 0, 0
|
|
||||||
t.clearLineToRight()
|
|
||||||
for t.cursorY < numPrevLines {
|
|
||||||
// Move down a line
|
|
||||||
t.move(0, 1, 0, 0)
|
|
||||||
t.cursorY++
|
|
||||||
t.clearLineToRight()
|
|
||||||
}
|
|
||||||
// Move back to beginning.
|
|
||||||
t.move(t.cursorY, 0, 0, 0)
|
|
||||||
t.cursorX, t.cursorY = 0, 0
|
|
||||||
|
|
||||||
t.queue(t.prompt)
|
|
||||||
t.advanceCursor(visualLength(t.prompt))
|
|
||||||
t.writeLine(t.line)
|
|
||||||
t.moveCursorToPos(t.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) SetSize(width, height int) error {
|
|
||||||
t.lock.Lock()
|
|
||||||
defer t.lock.Unlock()
|
|
||||||
|
|
||||||
if width == 0 {
|
|
||||||
width = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
oldWidth := t.termWidth
|
|
||||||
t.termWidth, t.termHeight = width, height
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case width == oldWidth:
|
|
||||||
// If the width didn't change then nothing else needs to be
|
|
||||||
// done.
|
|
||||||
return nil
|
|
||||||
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
|
|
||||||
// If there is nothing on current line and no prompt printed,
|
|
||||||
// just do nothing
|
|
||||||
return nil
|
|
||||||
case width < oldWidth:
|
|
||||||
// Some terminals (e.g. xterm) will truncate lines that were
|
|
||||||
// too long when shinking. Others, (e.g. gnome-terminal) will
|
|
||||||
// attempt to wrap them. For the former, repainting t.maxLine
|
|
||||||
// works great, but that behaviour goes badly wrong in the case
|
|
||||||
// of the latter because they have doubled every full line.
|
|
||||||
|
|
||||||
// We assume that we are working on a terminal that wraps lines
|
|
||||||
// and adjust the cursor position based on every previous line
|
|
||||||
// wrapping and turning into two. This causes the prompt on
|
|
||||||
// xterms to move upwards, which isn't great, but it avoids a
|
|
||||||
// huge mess with gnome-terminal.
|
|
||||||
if t.cursorX >= t.termWidth {
|
|
||||||
t.cursorX = t.termWidth - 1
|
|
||||||
}
|
|
||||||
t.cursorY *= 2
|
|
||||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
|
|
||||||
case width > oldWidth:
|
|
||||||
// If the terminal expands then our position calculations will
|
|
||||||
// be wrong in the future because we think the cursor is
|
|
||||||
// |t.pos| chars into the string, but there will be a gap at
|
|
||||||
// the end of any wrapped line.
|
|
||||||
//
|
|
||||||
// But the position will actually be correct until we move, so
|
|
||||||
// we can move back to the beginning and repaint everything.
|
|
||||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := t.c.Write(t.outBuf)
|
|
||||||
t.outBuf = t.outBuf[:0]
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type pasteIndicatorError struct{}
|
|
||||||
|
|
||||||
func (pasteIndicatorError) Error() string {
|
|
||||||
return "terminal: ErrPasteIndicator not correctly handled"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
|
||||||
// to valid line data. It indicates that bracketed paste mode is enabled and
|
|
||||||
// that the returned line consists only of pasted data. Programs may wish to
|
|
||||||
// interpret pasted data more literally than typed data.
|
|
||||||
var ErrPasteIndicator = pasteIndicatorError{}
|
|
||||||
|
|
||||||
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
|
||||||
// with markers. Not all terminals support this but, if it is supported, then
|
|
||||||
// enabling this mode will stop any autocomplete callback from running due to
|
|
||||||
// pastes. Additionally, any lines that are completely pasted will be returned
|
|
||||||
// from ReadLine with the error set to ErrPasteIndicator.
|
|
||||||
func (t *Terminal) SetBracketedPasteMode(on bool) {
|
|
||||||
if on {
|
|
||||||
io.WriteString(t.c, "\x1b[?2004h")
|
|
||||||
} else {
|
|
||||||
io.WriteString(t.c, "\x1b[?2004l")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stRingBuffer is a ring buffer of strings.
|
|
||||||
type stRingBuffer struct {
|
|
||||||
// entries contains max elements.
|
|
||||||
entries []string
|
|
||||||
max int
|
|
||||||
// head contains the index of the element most recently added to the ring.
|
|
||||||
head int
|
|
||||||
// size contains the number of elements in the ring.
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stRingBuffer) Add(a string) {
|
|
||||||
if s.entries == nil {
|
|
||||||
const defaultNumEntries = 100
|
|
||||||
s.entries = make([]string, defaultNumEntries)
|
|
||||||
s.max = defaultNumEntries
|
|
||||||
}
|
|
||||||
|
|
||||||
s.head = (s.head + 1) % s.max
|
|
||||||
s.entries[s.head] = a
|
|
||||||
if s.size < s.max {
|
|
||||||
s.size++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
|
||||||
// If n is zero then the immediately prior value is returned, if one, then the
|
|
||||||
// next most recent, and so on. If such an element doesn't exist then ok is
|
|
||||||
// false.
|
|
||||||
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
|
||||||
if n >= s.size {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
index := s.head - n
|
|
||||||
if index < 0 {
|
|
||||||
index += s.max
|
|
||||||
}
|
|
||||||
return s.entries[index], true
|
|
||||||
}
|
|
||||||
|
|
||||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
|
||||||
// The slice returned does not include the \n.
|
|
||||||
// readPasswordLine also ignores any \r it finds.
|
|
||||||
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
|
||||||
var buf [1]byte
|
|
||||||
var ret []byte
|
|
||||||
|
|
||||||
for {
|
|
||||||
n, err := reader.Read(buf[:])
|
|
||||||
if n > 0 {
|
|
||||||
switch buf[0] {
|
|
||||||
case '\n':
|
|
||||||
return ret, nil
|
|
||||||
case '\r':
|
|
||||||
// remove \r from passwords on Windows
|
|
||||||
default:
|
|
||||||
ret = append(ret, buf[0])
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF && len(ret) > 0 {
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
114
vendor/golang.org/x/crypto/ssh/terminal/util.go
generated
vendored
114
vendor/golang.org/x/crypto/ssh/terminal/util.go
generated
vendored
@@ -1,114 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
|
||||||
|
|
||||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||||
// commonly found on UNIX systems.
|
|
||||||
//
|
|
||||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||||
//
|
|
||||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer terminal.Restore(0, oldState)
|
|
||||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// State contains the state of a terminal.
|
|
||||||
type State struct {
|
|
||||||
termios unix.Termios
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldState := State{termios: *termios}
|
|
||||||
|
|
||||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
|
||||||
// the termios(3) manpage.
|
|
||||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
|
||||||
termios.Oflag &^= unix.OPOST
|
|
||||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
|
||||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
|
||||||
termios.Cflag |= unix.CS8
|
|
||||||
termios.Cc[unix.VMIN] = 1
|
|
||||||
termios.Cc[unix.VTIME] = 0
|
|
||||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &State{termios: *termios}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, state *State) error {
|
|
||||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
|
||||||
if err != nil {
|
|
||||||
return -1, -1, err
|
|
||||||
}
|
|
||||||
return int(ws.Col), int(ws.Row), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
|
||||||
type passwordReader int
|
|
||||||
|
|
||||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
|
||||||
return unix.Read(int(r), buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||||
// returned does not include the \n.
|
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := *termios
|
|
||||||
newState.Lflag &^= unix.ECHO
|
|
||||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
|
||||||
newState.Iflag |= unix.ICRNL
|
|
||||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
|
|
||||||
|
|
||||||
return readPasswordLine(passwordReader(fd))
|
|
||||||
}
|
|
||||||
58
vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go
generated
vendored
58
vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go
generated
vendored
@@ -1,58 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||||
// commonly found on UNIX systems.
|
|
||||||
//
|
|
||||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||||
//
|
|
||||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer terminal.Restore(0, oldState)
|
|
||||||
package terminal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type State struct{}
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, state *State) error {
|
|
||||||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||||
// returned does not include the \n.
|
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
|
||||||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
124
vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go
generated
vendored
124
vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go
generated
vendored
@@ -1,124 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build solaris
|
|
||||||
|
|
||||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"io"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// State contains the state of a terminal.
|
|
||||||
type State struct {
|
|
||||||
termios unix.Termios
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||||
// returned does not include the \n.
|
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
|
||||||
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
|
||||||
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
oldState := *val
|
|
||||||
|
|
||||||
newState := oldState
|
|
||||||
newState.Lflag &^= syscall.ECHO
|
|
||||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
|
||||||
newState.Iflag |= syscall.ICRNL
|
|
||||||
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
|
||||||
|
|
||||||
var buf [16]byte
|
|
||||||
var ret []byte
|
|
||||||
for {
|
|
||||||
n, err := syscall.Read(fd, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
if len(ret) == 0 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if buf[n-1] == '\n' {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
ret = append(ret, buf[:n]...)
|
|
||||||
if n < len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldState := State{termios: *termios}
|
|
||||||
|
|
||||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
|
||||||
termios.Oflag &^= unix.OPOST
|
|
||||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
|
||||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
|
||||||
termios.Cflag |= unix.CS8
|
|
||||||
termios.Cc[unix.VMIN] = 1
|
|
||||||
termios.Cc[unix.VTIME] = 0
|
|
||||||
|
|
||||||
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, oldState *State) error {
|
|
||||||
return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &State{termios: *termios}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
return int(ws.Col), int(ws.Row), nil
|
|
||||||
}
|
|
||||||
103
vendor/golang.org/x/crypto/ssh/terminal/util_windows.go
generated
vendored
103
vendor/golang.org/x/crypto/ssh/terminal/util_windows.go
generated
vendored
@@ -1,103 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||||
// commonly found on UNIX systems.
|
|
||||||
//
|
|
||||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||||
//
|
|
||||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer terminal.Restore(0, oldState)
|
|
||||||
package terminal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
mode uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
var st uint32
|
|
||||||
err := windows.GetConsoleMode(windows.Handle(fd), &st)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
var st uint32
|
|
||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
|
||||||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &State{st}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
var st uint32
|
|
||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &State{st}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, state *State) error {
|
|
||||||
return windows.SetConsoleMode(windows.Handle(fd), state.mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
var info windows.ConsoleScreenBufferInfo
|
|
||||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
return int(info.Size.X), int(info.Size.Y), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||||
// returned does not include the \n.
|
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
|
||||||
var st uint32
|
|
||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
old := st
|
|
||||||
|
|
||||||
st &^= (windows.ENABLE_ECHO_INPUT)
|
|
||||||
st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
|
||||||
if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer windows.SetConsoleMode(windows.Handle(fd), old)
|
|
||||||
|
|
||||||
var h windows.Handle
|
|
||||||
p, _ := windows.GetCurrentProcess()
|
|
||||||
if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f := os.NewFile(uintptr(h), "stdin")
|
|
||||||
defer f.Close()
|
|
||||||
return readPasswordLine(f)
|
|
||||||
}
|
|
||||||
56
vendor/golang.org/x/net/context/context.go
generated
vendored
56
vendor/golang.org/x/net/context/context.go
generated
vendored
@@ -1,56 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package context defines the Context type, which carries deadlines,
|
|
||||||
// cancelation signals, and other request-scoped values across API boundaries
|
|
||||||
// and between processes.
|
|
||||||
// As of Go 1.7 this package is available in the standard library under the
|
|
||||||
// name context. https://golang.org/pkg/context.
|
|
||||||
//
|
|
||||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
|
||||||
// servers should accept a Context. The chain of function calls between must
|
|
||||||
// propagate the Context, optionally replacing it with a modified copy created
|
|
||||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
|
||||||
//
|
|
||||||
// Programs that use Contexts should follow these rules to keep interfaces
|
|
||||||
// consistent across packages and enable static analysis tools to check context
|
|
||||||
// propagation:
|
|
||||||
//
|
|
||||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
|
||||||
// explicitly to each function that needs it. The Context should be the first
|
|
||||||
// parameter, typically named ctx:
|
|
||||||
//
|
|
||||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
|
||||||
// // ... use ctx ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
|
||||||
// if you are unsure about which Context to use.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
//
|
|
||||||
// The same Context may be passed to functions running in different goroutines;
|
|
||||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/context for example code for a server that uses
|
|
||||||
// Contexts.
|
|
||||||
package context // import "golang.org/x/net/context"
|
|
||||||
|
|
||||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
|
||||||
// values, and has no deadline. It is typically used by the main function,
|
|
||||||
// initialization, and tests, and as the top-level Context for incoming
|
|
||||||
// requests.
|
|
||||||
func Background() Context {
|
|
||||||
return background
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
|
||||||
// it's unclear which Context to use or it is not yet available (because the
|
|
||||||
// surrounding function has not yet been extended to accept a Context
|
|
||||||
// parameter). TODO is recognized by static analysis tools that determine
|
|
||||||
// whether Contexts are propagated correctly in a program.
|
|
||||||
func TODO() Context {
|
|
||||||
return todo
|
|
||||||
}
|
|
||||||
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
@@ -1,72 +0,0 @@
|
|||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context" // standard library's context, as of Go 1.7
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
todo = context.TODO()
|
|
||||||
background = context.Background()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
|
||||||
var Canceled = context.Canceled
|
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
|
||||||
// deadline passes.
|
|
||||||
var DeadlineExceeded = context.DeadlineExceeded
|
|
||||||
|
|
||||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
|
||||||
// context's Done channel is closed when the returned cancel function is called
|
|
||||||
// or when the parent context's Done channel is closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|
||||||
ctx, f := context.WithCancel(parent)
|
|
||||||
return ctx, CancelFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
|
||||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
|
||||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
|
||||||
// context's Done channel is closed when the deadline expires, when the returned
|
|
||||||
// cancel function is called, or when the parent context's Done channel is
|
|
||||||
// closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
|
||||||
ctx, f := context.WithDeadline(parent, deadline)
|
|
||||||
return ctx, CancelFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete:
|
|
||||||
//
|
|
||||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
|
||||||
// return slowOperation(ctx)
|
|
||||||
// }
|
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|
||||||
return WithDeadline(parent, time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValue returns a copy of parent in which the value associated with key is
|
|
||||||
// val.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
|
||||||
return context.WithValue(parent, key, val)
|
|
||||||
}
|
|
||||||
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
@@ -1,20 +0,0 @@
|
|||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import "context" // standard library's context, as of Go 1.7
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context = context.Context
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc = context.CancelFunc
|
|
||||||
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
@@ -1,300 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
|
||||||
// struct{}, since vars of this type must have distinct addresses.
|
|
||||||
type emptyCtx int
|
|
||||||
|
|
||||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Done() <-chan struct{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Err() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emptyCtx) String() string {
|
|
||||||
switch e {
|
|
||||||
case background:
|
|
||||||
return "context.Background"
|
|
||||||
case todo:
|
|
||||||
return "context.TODO"
|
|
||||||
}
|
|
||||||
return "unknown empty Context"
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
background = new(emptyCtx)
|
|
||||||
todo = new(emptyCtx)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
|
||||||
var Canceled = errors.New("context canceled")
|
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
|
||||||
// deadline passes.
|
|
||||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
|
||||||
|
|
||||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
|
||||||
// context's Done channel is closed when the returned cancel function is called
|
|
||||||
// or when the parent context's Done channel is closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|
||||||
c := newCancelCtx(parent)
|
|
||||||
propagateCancel(parent, c)
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCancelCtx returns an initialized cancelCtx.
|
|
||||||
func newCancelCtx(parent Context) *cancelCtx {
|
|
||||||
return &cancelCtx{
|
|
||||||
Context: parent,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// propagateCancel arranges for child to be canceled when parent is.
|
|
||||||
func propagateCancel(parent Context, child canceler) {
|
|
||||||
if parent.Done() == nil {
|
|
||||||
return // parent is never canceled
|
|
||||||
}
|
|
||||||
if p, ok := parentCancelCtx(parent); ok {
|
|
||||||
p.mu.Lock()
|
|
||||||
if p.err != nil {
|
|
||||||
// parent has already been canceled
|
|
||||||
child.cancel(false, p.err)
|
|
||||||
} else {
|
|
||||||
if p.children == nil {
|
|
||||||
p.children = make(map[canceler]bool)
|
|
||||||
}
|
|
||||||
p.children[child] = true
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
} else {
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-parent.Done():
|
|
||||||
child.cancel(false, parent.Err())
|
|
||||||
case <-child.Done():
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parentCancelCtx follows a chain of parent references until it finds a
|
|
||||||
// *cancelCtx. This function understands how each of the concrete types in this
|
|
||||||
// package represents its parent.
|
|
||||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
|
||||||
for {
|
|
||||||
switch c := parent.(type) {
|
|
||||||
case *cancelCtx:
|
|
||||||
return c, true
|
|
||||||
case *timerCtx:
|
|
||||||
return c.cancelCtx, true
|
|
||||||
case *valueCtx:
|
|
||||||
parent = c.Context
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeChild removes a context from its parent.
|
|
||||||
func removeChild(parent Context, child canceler) {
|
|
||||||
p, ok := parentCancelCtx(parent)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.mu.Lock()
|
|
||||||
if p.children != nil {
|
|
||||||
delete(p.children, child)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A canceler is a context type that can be canceled directly. The
|
|
||||||
// implementations are *cancelCtx and *timerCtx.
|
|
||||||
type canceler interface {
|
|
||||||
cancel(removeFromParent bool, err error)
|
|
||||||
Done() <-chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
|
||||||
// that implement canceler.
|
|
||||||
type cancelCtx struct {
|
|
||||||
Context
|
|
||||||
|
|
||||||
done chan struct{} // closed by the first cancel call.
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
children map[canceler]bool // set to nil by the first cancel call
|
|
||||||
err error // set to non-nil by the first cancel call
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) Done() <-chan struct{} {
|
|
||||||
return c.done
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) Err() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel closes c.done, cancels each of c's children, and, if
|
|
||||||
// removeFromParent is true, removes c from its parent's children.
|
|
||||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
|
||||||
if err == nil {
|
|
||||||
panic("context: internal error: missing cancel error")
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.err != nil {
|
|
||||||
c.mu.Unlock()
|
|
||||||
return // already canceled
|
|
||||||
}
|
|
||||||
c.err = err
|
|
||||||
close(c.done)
|
|
||||||
for child := range c.children {
|
|
||||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
|
||||||
child.cancel(false, err)
|
|
||||||
}
|
|
||||||
c.children = nil
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if removeFromParent {
|
|
||||||
removeChild(c.Context, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
|
||||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
|
||||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
|
||||||
// context's Done channel is closed when the deadline expires, when the returned
|
|
||||||
// cancel function is called, or when the parent context's Done channel is
|
|
||||||
// closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
|
||||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
|
||||||
// The current deadline is already sooner than the new one.
|
|
||||||
return WithCancel(parent)
|
|
||||||
}
|
|
||||||
c := &timerCtx{
|
|
||||||
cancelCtx: newCancelCtx(parent),
|
|
||||||
deadline: deadline,
|
|
||||||
}
|
|
||||||
propagateCancel(parent, c)
|
|
||||||
d := deadline.Sub(time.Now())
|
|
||||||
if d <= 0 {
|
|
||||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.err == nil {
|
|
||||||
c.timer = time.AfterFunc(d, func() {
|
|
||||||
c.cancel(true, DeadlineExceeded)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
|
||||||
// implement Done and Err. It implements cancel by stopping its timer then
|
|
||||||
// delegating to cancelCtx.cancel.
|
|
||||||
type timerCtx struct {
|
|
||||||
*cancelCtx
|
|
||||||
timer *time.Timer // Under cancelCtx.mu.
|
|
||||||
|
|
||||||
deadline time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
|
||||||
return c.deadline, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
|
||||||
c.cancelCtx.cancel(false, err)
|
|
||||||
if removeFromParent {
|
|
||||||
// Remove this timerCtx from its parent cancelCtx's children.
|
|
||||||
removeChild(c.cancelCtx.Context, c)
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.timer != nil {
|
|
||||||
c.timer.Stop()
|
|
||||||
c.timer = nil
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete:
|
|
||||||
//
|
|
||||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
|
||||||
// return slowOperation(ctx)
|
|
||||||
// }
|
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|
||||||
return WithDeadline(parent, time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValue returns a copy of parent in which the value associated with key is
|
|
||||||
// val.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
|
||||||
return &valueCtx{parent, key, val}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
|
||||||
// delegates all other calls to the embedded Context.
|
|
||||||
type valueCtx struct {
|
|
||||||
Context
|
|
||||||
key, val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *valueCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
|
||||||
if c.key == key {
|
|
||||||
return c.val
|
|
||||||
}
|
|
||||||
return c.Context.Value(key)
|
|
||||||
}
|
|
||||||
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
@@ -1,109 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.9
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context interface {
|
|
||||||
// Deadline returns the time when work done on behalf of this context
|
|
||||||
// should be canceled. Deadline returns ok==false when no deadline is
|
|
||||||
// set. Successive calls to Deadline return the same results.
|
|
||||||
Deadline() (deadline time.Time, ok bool)
|
|
||||||
|
|
||||||
// Done returns a channel that's closed when work done on behalf of this
|
|
||||||
// context should be canceled. Done may return nil if this context can
|
|
||||||
// never be canceled. Successive calls to Done return the same value.
|
|
||||||
//
|
|
||||||
// WithCancel arranges for Done to be closed when cancel is called;
|
|
||||||
// WithDeadline arranges for Done to be closed when the deadline
|
|
||||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
|
||||||
// elapses.
|
|
||||||
//
|
|
||||||
// Done is provided for use in select statements:
|
|
||||||
//
|
|
||||||
// // Stream generates values with DoSomething and sends them to out
|
|
||||||
// // until DoSomething returns an error or ctx.Done is closed.
|
|
||||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
|
||||||
// for {
|
|
||||||
// v, err := DoSomething(ctx)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// select {
|
|
||||||
// case <-ctx.Done():
|
|
||||||
// return ctx.Err()
|
|
||||||
// case out <- v:
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
|
||||||
// a Done channel for cancelation.
|
|
||||||
Done() <-chan struct{}
|
|
||||||
|
|
||||||
// Err returns a non-nil error value after Done is closed. Err returns
|
|
||||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
|
||||||
// context's deadline passed. No other values for Err are defined.
|
|
||||||
// After Done is closed, successive calls to Err return the same value.
|
|
||||||
Err() error
|
|
||||||
|
|
||||||
// Value returns the value associated with this context for key, or nil
|
|
||||||
// if no value is associated with key. Successive calls to Value with
|
|
||||||
// the same key returns the same result.
|
|
||||||
//
|
|
||||||
// Use context values only for request-scoped data that transits
|
|
||||||
// processes and API boundaries, not for passing optional parameters to
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// A key identifies a specific value in a Context. Functions that wish
|
|
||||||
// to store values in Context typically allocate a key in a global
|
|
||||||
// variable then use that key as the argument to context.WithValue and
|
|
||||||
// Context.Value. A key can be any type that supports equality;
|
|
||||||
// packages should define keys as an unexported type to avoid
|
|
||||||
// collisions.
|
|
||||||
//
|
|
||||||
// Packages that define a Context key should provide type-safe accessors
|
|
||||||
// for the values stores using that key:
|
|
||||||
//
|
|
||||||
// // Package user defines a User type that's stored in Contexts.
|
|
||||||
// package user
|
|
||||||
//
|
|
||||||
// import "golang.org/x/net/context"
|
|
||||||
//
|
|
||||||
// // User is the type of value stored in the Contexts.
|
|
||||||
// type User struct {...}
|
|
||||||
//
|
|
||||||
// // key is an unexported type for keys defined in this package.
|
|
||||||
// // This prevents collisions with keys defined in other packages.
|
|
||||||
// type key int
|
|
||||||
//
|
|
||||||
// // userKey is the key for user.User values in Contexts. It is
|
|
||||||
// // unexported; clients use user.NewContext and user.FromContext
|
|
||||||
// // instead of using this key directly.
|
|
||||||
// var userKey key = 0
|
|
||||||
//
|
|
||||||
// // NewContext returns a new Context that carries value u.
|
|
||||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
|
||||||
// return context.WithValue(ctx, userKey, u)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // FromContext returns the User value stored in ctx, if any.
|
|
||||||
// func FromContext(ctx context.Context) (*User, bool) {
|
|
||||||
// u, ok := ctx.Value(userKey).(*User)
|
|
||||||
// return u, ok
|
|
||||||
// }
|
|
||||||
Value(key interface{}) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc func()
|
|
||||||
3
vendor/golang.org/x/sync/errgroup/errgroup.go
generated
vendored
3
vendor/golang.org/x/sync/errgroup/errgroup.go
generated
vendored
@@ -7,9 +7,8 @@
|
|||||||
package errgroup
|
package errgroup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Group is a collection of goroutines working on subtasks that are part of
|
// A Group is a collection of goroutines working on subtasks that are part of
|
||||||
|
|||||||
8
vendor/golang.org/x/sys/plan9/asm.s
generated
vendored
Normal file
8
vendor/golang.org/x/sys/plan9/asm.s
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·use(SB),NOSPLIT,$0
|
||||||
|
RET
|
||||||
30
vendor/golang.org/x/sys/plan9/asm_plan9_386.s
generated
vendored
Normal file
30
vendor/golang.org/x/sys/plan9/asm_plan9_386.s
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System call support for 386, Plan 9
|
||||||
|
//
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-32
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-44
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·seek(SB),NOSPLIT,$0-36
|
||||||
|
JMP syscall·seek(SB)
|
||||||
|
|
||||||
|
TEXT ·exit(SB),NOSPLIT,$4-4
|
||||||
|
JMP syscall·exit(SB)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user