Compare commits

...

95 Commits

Author SHA1 Message Date
Andrey Nering
347fe87229 v2.1.1 2018-09-16 22:17:36 -03:00
Andrey Nering
b65a0a3a8d Fix signal handling when --watch flag is given
Closes #132
2018-09-16 21:59:00 -03:00
Andrey Nering
687b4ec837 Fix error when using checksum method and no file exists for a source glob
Closes #131
2018-09-01 11:23:38 -03:00
Andrey Nering
8bdf5c554d Fixes after updating mvdan.cc/sh 2018-09-01 11:02:23 -03:00
Andrey Nering
f4a18e531f Update dependencies 2018-09-01 11:00:49 -03:00
Andrey Nering
df951a0c7c Fix suggestion to use task --init not being shown anymore 2018-08-19 16:23:32 -03:00
Andrey Nering
a6cac2691b Delete unused errors 2018-08-19 16:15:19 -03:00
Andrey Nering
a9f5179066 README: Fix title case 2018-08-19 16:12:53 -03:00
Andrey Nering
687e2699cf Improvements on documentation 2018-08-19 15:47:21 -03:00
Andrey Nering
491da0ceb9 "ignore_error" is only available on version >= 2.1 2018-08-19 15:46:07 -03:00
Andrey Nering
1bac40bc58 README: Update alternatives 2018-08-18 17:50:40 -03:00
Andrey Nering
fb9061480d Merge branch 'Eun-master' 2018-08-05 13:02:22 -03:00
Andrey Nering
a04cf100b4 Improve README a bit 2018-08-05 13:01:30 -03:00
Andrey Nering
76253bf516 Reduce code duplication on test 2018-08-05 12:56:55 -03:00
Andrey Nering
feaf70922d Allow ignore_error at task level 2018-08-05 12:53:42 -03:00
Andrey Nering
c70343a5bc Handle ignore_error one level up on the code 2018-08-05 12:40:11 -03:00
Andrey Nering
550c116aea Merge branch 'master' into master 2018-08-05 11:32:07 -03:00
Andrey Nering
a5f31a4280 Dry run small code style changes 2018-08-05 11:28:02 -03:00
Andrey Nering
27fc4c4ca8 Merge pull request #126 from bebbs/125-dry-run
Implemented dry run mode
2018-08-05 11:21:52 -03:00
Tobias Salzmann
90a5f17f58 Merge branch 'master' into master 2018-08-01 10:47:25 +02:00
Tobias Salzmann
108cb91d95 IgnoreError
* Document ignore_error
* ignore_error only for commands
2018-08-01 10:44:53 +02:00
Josh Bebbington
00a0755ff3 Added test for executor.DryRun 2018-07-31 23:17:21 +01:00
Josh Bebbington
3f7e8c88eb Changed --dry-run flag to --dry 2018-07-29 22:03:22 +01:00
Josh Bebbington
1c7ca94d49 Implemented dry run mode
Added a --dry-run flag that compiles and steps through each task, but does not execute them. The commands that would have been run are printed. See #125.
2018-07-29 00:39:25 +01:00
Andrey Nering
31273cd6ff Update README 2018-07-22 18:15:11 -03:00
Andrey Nering
14e39dd745 Update dependencies 2018-07-22 18:05:13 -03:00
Andrey Nering
cc6f7b6088 Manually implement merging of Taskfiles and remove dependency on github.com/imdario/mergo
I was carreful enough to check the behavior keeps the same
2018-07-22 17:54:44 -03:00
Andrey Nering
da1b0c9558 Error if Taskfile_{{GOOS}}.yml version doesn't match Taskfile.yml version
This change is not backward incompatible because merging was already not
working if versions 1 and 2 coexist in Taskfile.yml and Taskfile_{{GOOS}}.yml
2018-07-22 17:36:30 -03:00
Andrey Nering
9f294b4d10 Refactor and move logic of reading Taskfiles to its own package
Idea to making things easier to start implementing #98
2018-07-22 16:10:20 -03:00
Andrey Nering
13f60bae41 Merge pull request #124 from go-task/shell-expand
Move path expanding logic to shell.Expand
2018-07-22 15:16:21 -03:00
Andrey Nering
67105b332f Move path expanding logic to shell.Expand 2018-07-15 15:49:00 -03:00
Andrey Nering
18961e3d07 README improvements 2018-07-15 15:47:38 -03:00
Andrey Nering
fe31f5050d Taskfile.yml improvements 2018-07-15 15:42:46 -03:00
Andrey Nering
ab8549adea Update dependencies 2018-07-15 14:38:39 -03:00
Tobias Salzmann
05600601ff add ignoreError option 2018-07-10 10:44:58 +02:00
Andrey Nering
c541356289 Fix typo 2018-06-24 11:05:54 -03:00
Andrey Nering
467c4360ca v2.0.3 2018-06-24 10:52:19 -03:00
Andrey Nering
3b152a38b0 .goreleaser.yml: Use CGO_ENABLED=0 2018-06-24 10:51:04 -03:00
Andrey Nering
f4d3855528 Update deps 2018-06-24 10:40:44 -03:00
Andrey Nering
09eab770a7 Skip empty commands
Fixes #120
2018-06-24 10:29:46 -03:00
Andrey Nering
102f8ab74e Expand environment variables on "dir", "sources" and "generates"
So a path like this works: $GOPATH/src/github.com/go-task/task

Allowing of "~" was also implemented. See #74 and baac067a1a

Fixes #116
2018-06-16 14:30:40 -03:00
Andrey Nering
a830dba5da Taskfile: Disable CGO 2018-06-16 14:24:50 -03:00
Andrey Nering
3f13a50ca3 Merge pull request #115 from AlekSi/patch-1
Use latest patch releases of Go
2018-05-31 18:24:06 -03:00
Alexey Palazhchenko
7c456f2ab9 Use latest patch releases of Go 2018-05-31 15:43:52 +00:00
Andrey Nering
bae95cd6f6 Travis: Remove Go 1.8 2018-04-28 15:44:35 -03:00
Andrey Nering
bbd6e443b0 YAML: Don't use strict unmarshaling anymore
Fixes #112
2018-04-28 15:39:54 -03:00
Andrey Nering
db0d847e03 Fix compilation after updating mvdan/sh 2018-04-28 15:36:01 -03:00
Andrey Nering
0afb453fed Update dependencies 2018-04-28 15:35:33 -03:00
Andrey Nering
dbc79b4311 Merge pull request #109 from go-task/feature/parallel-task-output-#104
Add "output" option to Taskfile to control how stuff are printed to stdout/stderr
2018-04-28 15:18:05 -03:00
Andrey Nering
ac6008c33c Merge pull request #111 from sawadashota/add-zsh-completion
Add zsh completion
2018-04-28 15:07:06 -03:00
Shota Sawada
e540e752f2 Add zsh completion 2018-04-27 17:50:56 +09:00
Andrey Nering
cdbe821eb8 Write documentation for output types 2018-04-22 22:13:31 -03:00
Andrey Nering
6be994f1ca Write test for ouput types 2018-04-22 21:40:03 -03:00
Andrey Nering
a407b0a8eb First step implementing "prefixed" output option
Ref #104
2018-04-22 15:41:53 -03:00
Andrey Nering
051ff35878 Add "output" option to Taskfile to control how stuff are printed to stdout/stderr
First step for #104
2018-04-15 14:37:44 -03:00
Andrey Nering
8b3c34c308 Add "output" options to the Taskfile
Also, fix handling of Taskfile by making the version an instance of
`semver.Constraints` instead of `semver.Version`. This makes the version
works as described on TASKFILE_VERSIONS.md document, i.e. version "2" will
include "2.x" features but version "2.0" not.
2018-04-15 11:11:07 -03:00
Andrey Nering
2cb2668803 Fix Travis CI 2018-04-07 15:36:37 -03:00
Andrey Nering
4dccdb95b9 README: Add image links 2018-04-07 15:15:35 -03:00
Andrey Nering
96db9a9410 Using godownloader to generate a install script
Closes #78
2018-04-07 15:07:43 -03:00
Andrey Nering
0cd34bbebc Move variables from Taskvars to Taskfile 2018-04-07 14:42:24 -03:00
Andrey Nering
15f50c0e58 Update README.md and CONTRIBUTING.md 2018-04-07 14:33:10 -03:00
Andrey Nering
7fca9732e7 Merge pull request #105 from michael-k/patch-1
Fix some typos in readme
2018-03-18 21:35:35 -03:00
Michael
0ea8c3ed28 Fix some typos in readme 2018-03-16 06:32:14 +00:00
Andrey Nering
0af9600e92 Update the release process once again:
- Release both Homebrew and Snapcraft packages manually for now;
- Fix some artifacts file names to keep them consistent.
2018-03-11 15:58:32 -03:00
Andrey Nering
328e3725e5 Merge pull request #102 from go-task/develop
v2.0.1
2018-03-11 15:37:18 -03:00
Andrey Nering
2183e1e9f5 Improve release process and testing automatic release on new tag using Travis 2018-03-11 15:31:27 -03:00
Andrey Nering
120d0be84c Fixes panic on task --list
Fixes #99
2018-03-11 14:39:40 -03:00
Andrey Nering
5649f75a8d Merge pull request #97 from seblw/patch-1
Editorial changes in TASKFILE_VERSIONS.md
2018-03-08 08:07:31 -03:00
Sebastian Lawniczak
a209f7d6be Editorial changes in TASKFILE_VERSIONS.md 2018-03-08 10:18:27 +01:00
Andrey Nering
d48a2f3ccf Documentation changes for v2.0.0 release 2018-03-07 22:54:37 -03:00
Andrey Nering
c1ae36866e Remove warning for version 2 2018-03-04 16:27:16 -03:00
Andrey Nering
4f368923a5 Upgrade own Taskfile to version 2 2018-03-04 16:20:26 -03:00
Andrey Nering
7c02097d93 Improve --init flag
- Update generated Taskfile to version 2
- Don't loop anymore on extensions since we only support YAML
- Use 644 for file permission
2018-03-04 16:19:52 -03:00
Andrey Nering
51998f706f Allow customizable number of expansions
Updates #66
2018-03-04 15:50:03 -03:00
Andrey Nering
1a3df08aca Allow global variables in the Taskfile
Closes #66
2018-03-04 15:39:14 -03:00
Andrey Nering
975f262ac0 Fix "test-release" task
Goreleaser changed some flags
2018-03-04 11:06:19 -03:00
Andrey Nering
1cb4a3b8d5 Fix CI
Goreleaser now requires Go 1.10, but we still support Go 1.8
2018-03-04 10:57:46 -03:00
Andrey Nering
35f4b2f686 Travis: Test Go 1.10 2018-03-03 19:28:13 -03:00
Andrey Nering
407ec91ca7 Update dependencies 2018-03-03 19:28:08 -03:00
Andrey Nering
12c0d18932 Move setting of default version to "taskfile" package 2018-03-03 18:56:15 -03:00
Andrey Nering
2d4ca37226 Use semver for Taskfile versions 2018-03-03 18:54:42 -03:00
Andrey Nering
afe6744e97 Merge pull request #93 from go-task/v2_refactor
Re-organization and refactoring targeting v2
2018-02-18 10:14:00 -03:00
Andrey Nering
19d4b8b7f7 Add warning about Taskfile version 2 until v2.0.0 release 2018-02-18 10:04:17 -03:00
Andrey Nering
3556942516 Improve nested variables support
Closes #76 #89 #77 #83
2018-02-18 09:50:39 -03:00
Andrey Nering
87a200e42c Extract some functionality to its own packages
Like variable and template handling, and logging
2018-02-17 16:12:41 -02:00
Andrey Nering
152fc0ad38 Move all structs related to Taskfile to its own package 2018-02-17 14:22:18 -02:00
Andrey Nering
3212ae4713 Update dependencies 2018-02-11 22:02:22 -02:00
Andrey Nering
040cef1479 Fix usage of "dep"
Now, "dep prune" should not be run, and instead pruning options should be set on Gopkg.toml
2018-02-11 22:00:40 -02:00
Andrey Nering
f5f70d7a75 README: Document Homebrew as an installation method 2018-01-27 14:44:20 -02:00
Andrey Nering
42509cf2f5 Update dependencies
Fixes #86
2018-01-21 09:39:15 -02:00
atar-axis
6f74c2d823 Added "passing variables to dependencies" (#85) 2018-01-11 10:10:12 -02:00
Andrey Nering
e23a6dc9f1 Update dependencies 2018-01-03 15:12:40 -02:00
Andrey Nering
134c6b79c4 Add Taskfile struct and start implementing new format
Updates #54, #66 and #77
2017-12-29 18:46:15 -02:00
Andrey Nering
00ff1447ee Update README.md
Includes removing unmaintained alternatives
2017-12-26 21:53:57 -02:00
Andrey Nering
78f6cb08d8 Add --status flag to check is a task is up-to-date
Closes #81
2017-12-26 21:43:52 -02:00
462 changed files with 50691 additions and 27452 deletions

View File

@@ -1,12 +1,6 @@
* Bug reports and feature requests are welcome in [the issues][issues]
* For questions and discussion there's the [Slack room][slack] ([invititation here][slackinvite])
* Pull Requests are welcome. For more complex changes and features it's
recommended to open an issue first
* About 3 or 4 pull requests accepted one gets write access to the repo.
Even then, possible backward incompatible changes should be discussed first
in an issue or pull request
recommended to open an issue with the feature request first
* Documentation contributions are as important as code contributions
[issues]: https://github.com/go-task/task/issues
[slack]: https://gophers.slack.com/messages/task
[slackinvite]: https://invite.slack.golangbridge.org/

View File

@@ -1,7 +1,4 @@
<!--
For questions and general talk there's the Slack room: https://gophers.slack.com/messages/task
Invite to the Slack is available in this link: https://invite.slack.golangbridge.org/
If relevant, include the following information:
- Task version
- OS

2
.gitignore vendored
View File

@@ -16,4 +16,4 @@
./task
dist/
vendor/**/*_test.go
.DS_Store

View File

@@ -11,6 +11,8 @@ build:
ignore:
- goos: darwin
goarch: 386
env:
- CGO_ENABLED=0
archive:
name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
@@ -22,12 +24,21 @@ archive:
release:
draft: true
fpm:
snapshot:
name_template: "{{.Tag}}"
checksum:
name_template: "task_checksums.txt"
nfpm:
vendor: Task
homepage: https://github.com/go-task/task
maintainer: Andrey Nering <andrey.nering@gmail.com>
description: Simple task runner written in Go
license: MIT
conflicts:
- taskwarrior
formats:
- deb
- rpm
name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"

View File

@@ -1,9 +1,22 @@
language: go
go:
- 1.8
- 1.9
- 1.9.x
- 1.10.x
addons:
apt:
packages:
- rpm
script:
- go install github.com/go-task/task/cmd/task
- task dl-deps
- task lint
- task test
- task ci
deploy:
- provider: script
skip_cleanup: true
script: curl -sL http://git.io/goreleaser | bash
on:
tags: true
condition: $TRAVIS_OS_NAME = linux

139
Gopkg.lock generated
View File

@@ -2,122 +2,187 @@
[[projects]]
branch = "master"
digest = "1:f3960e064201714a3507bf96183be246b3d941568af01dc5cff2a388ac4c7515"
name = "github.com/Masterminds/semver"
packages = ["."]
revision = "15d8430ab86497c5c0da827b748823945e1cf1e1"
version = "v1.4.0"
pruneopts = "NUT"
revision = "c84ddcca87bf5a941b138dde832a7e20b0159ad8"
[[projects]]
branch = "master"
digest = "1:58551c80f84fd3ba2954668f5f850a8179dc3c5bb4885fc7eed5a8e2ae815c99"
name = "github.com/Masterminds/sprig"
packages = ["."]
revision = "82f6f19d47b416d27ae039939b44afaa0575860e"
pruneopts = "NUT"
revision = "77bb58b7f5e10889a1195c21b9e7a96ee166f199"
[[projects]]
digest = "1:975108e8d4f5dab096fc991326e96a5716ee8d02e5e7386bb4796171afc4ab9a"
name = "github.com/aokoli/goutils"
packages = ["."]
pruneopts = "NUT"
revision = "3391d3790d23d03408670993e957e8f408993c34"
version = "v1.0.1"
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
pruneopts = "NUT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
digest = "1:a1578f7323eca2b88021fdc9a79a99833d40b12c32a5ea4f284e2fad19ea2657"
name = "github.com/google/uuid"
packages = ["."]
pruneopts = "NUT"
revision = "d460ce9f8df2e77fb1ba55ca87fafed96c607494"
version = "v1.0.0"
[[projects]]
digest = "1:ed24122ea28f4f65f82f5b4703389fdbb5f246efc971e4d74f01f7c47c0b81b2"
name = "github.com/huandu/xstrings"
packages = ["."]
pruneopts = "NUT"
revision = "55ae428c2ac4f74d7430952ef528631e656ac92c"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/huandu/xstrings"
packages = ["."]
revision = "d6590c0c31d16526217fa60fbd2067f7afcd78c5"
[[projects]]
digest = "1:9a52adf44086cead3b384e5d0dbf7a1c1cce65e67552ee3383a8561c42a18cd3"
name = "github.com/imdario/mergo"
packages = ["."]
revision = "7fe0c75c13abdee74b09fcacef5ea1c6bba6a874"
version = "0.2.4"
pruneopts = "NUT"
revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4"
version = "v0.3.6"
[[projects]]
branch = "master"
digest = "1:729c1199021f057f38487f7e64432f3539f9efac444185e64b4b7e121414ac68"
name = "github.com/mattn/go-zglob"
packages = [".","fastwalk"]
revision = "4b74c24375b3b1ee226867156e01996f4e19a8d6"
packages = [
".",
"fastwalk",
]
pruneopts = "NUT"
revision = "2ea3427bfa539cca900ca2768d8663ecc8a708c1"
[[projects]]
branch = "master"
digest = "1:a4df73029d2c42fabcb6b41e327d2f87e685284ec03edf76921c267d9cfc9c23"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
pruneopts = "NUT"
revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4"
version = "v1.0.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "NUT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:5089085e1b27a57be4fb7acf32bfa71fb6236dc0e8371165651c9e15285a9ce0"
name = "github.com/radovskyb/watcher"
packages = ["."]
revision = "6145e1439b9de93806925353403f91d2abbad8a5"
version = "v1.0.2"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "879c5887cd475cd7864858769793b2ceb0d44feb"
version = "v1.1.0"
pruneopts = "NUT"
revision = "0d9d32686dbf6395752c9b209398a59e302a7f1e"
[[projects]]
branch = "master"
digest = "1:9d8420bbf131d1618bde6530af37c3799340d3762cc47210c1d9532a4c3a2779"
name = "github.com/spf13/pflag"
packages = ["."]
revision = "4c012f6dcd9546820e378d0bdda4d8fc772cdfea"
pruneopts = "NUT"
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
[[projects]]
digest = "1:bacb8b590716ab7c33f2277240972c9582d389593ee8d66fc10074e0508b8126"
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4"
pruneopts = "NUT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
digest = "1:69b3fcb7a41b18436a85471cbdcfc70ad10ba3206f8c87563e1c773610e1bcad"
name = "golang.org/x/crypto"
packages = ["pbkdf2","scrypt","ssh/terminal"]
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
packages = [
"pbkdf2",
"scrypt",
"ssh/terminal",
]
pruneopts = "NUT"
revision = "182538f80094b6a8efaade63a8fd8e0d9d5843dd"
[[projects]]
branch = "master"
digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70"
name = "golang.org/x/net"
packages = ["context"]
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
pruneopts = "NUT"
revision = "8a410e7b638dca158bf9e766925842f6651ff828"
[[projects]]
branch = "master"
digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239"
name = "golang.org/x/sync"
packages = ["errgroup"]
revision = "fd80eb99c8f653c847d294a001bdf2a3a6f768f5"
pruneopts = "NUT"
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
digest = "1:8270f14d85e8d36e852e018872ac923f7a2067648bfe8428d01be288818aa337"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "0ac51a24ef1c37380f98ba8b98f56e3bffd59850"
packages = [
"unix",
"windows",
]
pruneopts = "NUT"
revision = "fa5fdf94c78965f1aa8423f0cc50b8b8d728b05a"
[[projects]]
branch = "v2"
digest = "1:7c95b35057a0ff2e19f707173cc1a947fa43a6eb5c4d300d196ece0334046082"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5"
pruneopts = "NUT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
[[projects]]
branch = "master"
digest = "1:e647f1f4fcc94a6baad3965a347c1ac4ed669f31c87c826516314e415d1a4ad2"
name = "mvdan.cc/sh"
packages = ["interp","syntax"]
revision = "5758e57655f2f2242603195aaaad08d1cb9f8f85"
packages = [
"interp",
"shell",
"syntax",
]
pruneopts = "NUT"
revision = "3a244a89e2e520d2777e94693d51bef800761879"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "da52cb2c602c1362c303cf241aa18dfd6199f30484bb12684adb0b6927391cbf"
input-imports = [
"github.com/Masterminds/semver",
"github.com/Masterminds/sprig",
"github.com/mattn/go-zglob",
"github.com/mitchellh/go-homedir",
"github.com/radovskyb/watcher",
"github.com/spf13/pflag",
"github.com/stretchr/testify/assert",
"golang.org/x/sync/errgroup",
"gopkg.in/yaml.v2",
"mvdan.cc/sh/interp",
"mvdan.cc/sh/shell",
"mvdan.cc/sh/syntax",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,79 +1,12 @@
## Gopkg.toml example (these lines may be deleted)
## "metadata" defines metadata about the project that could be used by other independent
## systems. The metadata defined here will be ignored by dep.
# [metadata]
# key1 = "value that convey data to other systems"
# system1-data = "value that is used by a system"
# system2-data = "value that is used by another system"
## "required" lists a set of packages (not projects) that must be included in
## Gopkg.lock. This list is merged with the set of packages imported by the current
## project. Use it when your project needs a package it doesn't explicitly import -
## including "main" packages.
# required = ["github.com/user/thing/cmd/thing"]
## "ignored" lists a set of packages (not projects) that are ignored when
## dep statically analyzes source code. Ignored packages can be in this project,
## or in a dependency.
# ignored = ["github.com/user/project/badpkg"]
## Constraints are rules for how directly imported projects
## may be incorporated into the depgraph. They are respected by
## dep whether coming from the Gopkg.toml of the current project or a dependency.
# [[constraint]]
## Required: the root import path of the project being constrained.
# name = "github.com/user/project"
#
## Recommended: the version constraint to enforce for the project.
## Only one of "branch", "version" or "revision" can be specified.
# version = "1.0.0"
# branch = "master"
# revision = "abc123"
#
## Optional: an alternate location (URL or import path) for the project's source.
# source = "https://github.com/myfork/package.git"
#
## "metadata" defines metadata about the dependency or override that could be used
## by other independent systems. The metadata defined here will be ignored by dep.
# [metadata]
# key1 = "value that convey data to other systems"
# system1-data = "value that is used by a system"
# system2-data = "value that is used by another system"
## Overrides have the same structure as [[constraint]], but supersede all
## [[constraint]] declarations from all projects. Only [[override]] from
## the current project's are applied.
##
## Overrides are a sledgehammer. Use them only as a last resort.
# [[override]]
## Required: the root import path of the project being constrained.
# name = "github.com/user/project"
#
## Optional: specifying a version constraint override will cause all other
## constraints on this project to be ignored; only the overridden constraint
## need be satisfied.
## Again, only one of "branch", "version" or "revision" can be specified.
# version = "1.0.0"
# branch = "master"
# revision = "abc123"
#
## Optional: specifying an alternate source location as an override will
## enforce that the alternate location is used for that project, regardless of
## what source location any dependent projects specify.
# source = "https://github.com/myfork/package.git"
[prune]
unused-packages = true
non-go = true
go-tests = true
[[constraint]]
branch = "master"
name = "github.com/Masterminds/sprig"
[[constraint]]
branch = "master"
name = "github.com/imdario/mergo"
[[constraint]]
branch = "master"
name = "github.com/mattn/go-zglob"
@@ -100,4 +33,4 @@
[[constraint]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
name = "github.com/Masterminds/semver"

665
README.md
View File

@@ -1,14 +1,18 @@
[![Join Slack room](https://img.shields.io/badge/%23task-chat%20room-blue.svg)](https://gophers.slack.com/messages/task)
[![Build Status](https://travis-ci.org/go-task/task.svg?branch=master)](https://travis-ci.org/go-task/task)
# Task - A task runner / simpler Make alternative written in Go
> We recently released version 2.0.0 of Task. The Taskfile changed a bit.
Please, check the [Taskfile versions](TASKFILE_VERSIONS.md) document to see
what changed and how to upgrade.
Task is a simple tool that allows you to easily run development and build
tasks. Task is written in Golang, but can be used to develop any language.
It aims to be simpler and easier to use then [GNU Make][make].
- [Installation](#installation)
- [Go](#go)
- [Homebrew](#homebrew)
- [Snap](#snap)
- [Binary](#binary)
- [Usage](#usage)
@@ -23,9 +27,11 @@ It aims to be simpler and easier to use then [GNU Make][make].
- [Go's template engine](#gos-template-engine)
- [Help](#help)
- [Silent mode](#silent-mode)
- [Dry run mode](#dry-run-mode)
- [Ignore errors](#ignore-errors)
- [Output syntax](#output-syntax)
- [Watch tasks](#watch-tasks-experimental)
- [Examples](#examples)
- [Task in the wild](#task-in-the-wild)
- [Alternative task runners](#alternative-task-runners)
## Installation
@@ -38,6 +44,15 @@ If you have a [Golang][golang] environment setup, you can simply run:
go get -u -v github.com/go-task/task/cmd/task
```
### Homebrew
If you're on macOS and have [Homebrew][homebrew] installed, getting Task is
as simple as running:
```bash
brew install go-task/tap/go-task
```
### Snap
Task is available for [Snapcraft][snapcraft], but keep in mind that your
@@ -48,6 +63,16 @@ right:
sudo snap install task
```
### Install script
We also have a [install script][installscript], which is very useful on
scanarios like CIs. Many thanks to [godownloader][godownloader] for easily
generating this script.
```bash
curl -s https://raw.githubusercontent.com/go-task/task/master/install-task.sh | sh
```
### Binary
Or you can download the binary from the [releases][releases] page and add to
@@ -56,19 +81,22 @@ The `task_checksums.txt` file contains the sha256 checksum for each file.
## Usage
Create a file called `Taskfile.yml` in the root of the project.
The `cmds` attribute should contains the commands of a task.
The example below allows compile a Go app and uses [Minify][minify] to concat
Create a file called `Taskfile.yml` in the root of your project.
The `cmds` attribute should contain the commands of a task.
The example below allows compiling a Go app and uses [Minify][minify] to concat
and minify multiple CSS files into a single one.
```yml
build:
cmds:
- go build -v -i main.go
version: '2'
assets:
cmds:
- minify -o public/style.css src/css
tasks:
build:
cmds:
- go build -v -i main.go
assets:
cmds:
- minify -o public/style.css src/css
```
Running the tasks is as simple as running:
@@ -79,7 +107,7 @@ task assets build
Task uses [github.com/mvdan/sh](https://github.com/mvdan/sh), a native Go sh
interpreter. So you can write sh/bash commands and it will work even on
Windows, where `sh` or `bash` is 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.
If you ommit a task name, "default" will be assumed.
@@ -89,16 +117,19 @@ If you ommit a task name, "default" will be assumed.
You can specify environment variables that are added when running a command:
```yml
build:
cmds:
- echo $hallo
env:
hallo: welt
version: '2'
tasks:
build:
cmds:
- echo $hallo
env:
hallo: welt
```
### OS specific task
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
based on the operating system.
Example:
@@ -106,23 +137,32 @@ Example:
Taskfile.yml:
```yml
build:
cmds:
- echo "default"
version: '2'
tasks:
build:
cmds:
- echo "default"
```
Taskfile_linux.yml:
```yml
build:
cmds:
- echo "linux"
version: '2'
tasks:
build:
cmds:
- echo "linux"
```
Will print out `linux` and not default.
Will print out `linux` and not `default`.
It's also possible to have OS specific `Taskvars.yml` file, like
`Taskvars_windows.yml`, `Taskfile_linux.yml` or `Taskvars_darwin.yml`. See the
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.
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
[variables section](#variables) below.
### Task directory
@@ -132,27 +172,33 @@ located. But you can easily make the task run in another folder informing
`dir`:
```yml
serve:
dir: public/www
cmds:
# run http server
- caddy
version: '2'
tasks:
serve:
dir: public/www
cmds:
# run http server
- caddy
```
### Task dependencies
You may have tasks that depends 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:
```yml
build:
deps: [assets]
cmds:
- go build -v -i main.go
version: '2'
assets:
cmds:
- minify -o public/style.css src/css
tasks:
build:
deps: [assets]
cmds:
- go build -v -i main.go
assets:
cmds:
- minify -o public/style.css src/css
```
In the above example, `assets` will always run right before `build` if you run
@@ -161,21 +207,45 @@ In the above example, `assets` will always run right before `build` if you run
A task can have only dependencies and no commands to group tasks together:
```yml
assets:
deps: [js, css]
version: '2'
js:
cmds:
- minify -o public/script.js src/js
tasks:
assets:
deps: [js, css]
css:
cmds:
- minify -o public/style.css src/css
js:
cmds:
- minify -o public/script.js src/js
css:
cmds:
- minify -o public/style.css src/css
```
If there are 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.
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):
```yml
version: '2'
tasks:
default:
deps:
- task: echo_sth
vars: {TEXT: "before 1"}
- task: echo_sth
vars: {TEXT: "before 2"}
cmds:
- echo "after"
echo_sth:
cmds:
- echo {{.TEXT}}
```
### Calling another task
When a task has many dependencies, they are executed concurrently. This will
@@ -183,78 +253,74 @@ often result in a faster build pipeline. But in some situations you may need
to call other tasks serially. In this case, just use the following syntax:
```yml
main-task:
cmds:
- task: task-to-be-called
- task: another-task
- echo "Both done"
version: '2'
task-to-be-called:
cmds:
- echo "Task to be called"
tasks:
main-task:
cmds:
- task: task-to-be-called
- task: another-task
- echo "Both done"
another-task:
cmds:
- echo "Another task"
task-to-be-called:
cmds:
- echo "Task to be called"
another-task:
cmds:
- echo "Another task"
```
Overriding variables in the called task is as simple as informing `vars`
attribute:
```yml
main-task:
cmds:
- task: write-file
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
- task: write-file
vars: {FILE: "world.txt", CONTENT: "World!"}
version: '2'
write-file:
cmds:
- echo "{{.CONTENT}}" > {{.FILE}}
tasks:
main-task:
cmds:
- task: write-file
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
- task: write-file
vars: {FILE: "world.txt", CONTENT: "World!"}
write-file:
cmds:
- echo "{{.CONTENT}}" > {{.FILE}}
```
The above syntax is also supported in `deps`.
> NOTE: It's also possible to call a task without any param prefixing it
with `^`, but this syntax is deprecated:
```yml
a-task:
cmds:
- ^another-task
another-task:
cmds:
- echo "Another task"
```
### Prevent unnecessary work
If a task generates something, you can inform Task the source and generated
files, so Task will prevent to run them if not necessary.
```yml
build:
deps: [js, css]
cmds:
- go build -v -i main.go
version: '2'
js:
cmds:
- minify -o public/script.js src/js
sources:
- src/js/**/*.js
generates:
- public/script.js
tasks:
build:
deps: [js, css]
cmds:
- go build -v -i main.go
css:
cmds:
- minify -o public/style.css src/css
sources:
- src/css/**/*.css
generates:
- public/style.css
js:
cmds:
- minify -o public/script.js src/js
sources:
- src/js/**/*.js
generates:
- public/script.js
css:
cmds:
- minify -o public/style.css src/css
sources:
- src/css/**/*.css
generates:
- public/style.css
```
`sources` and `generates` can be files or file patterns. When both are given,
@@ -266,17 +332,19 @@ If you prefer this check to be made by the content of the files, instead of
its timestamp, just set the `method` property to `checksum`.
You will probably want to ignore the `.task` folder in your `.gitignore` file
(It's there that Task stores the last checksum).
This feature is still experimental and can change until it's stable.
```yml
build:
cmds:
- go build .
sources:
- ./*.go
generates:
- app{{exeExt}}
method: checksum
version: '2'
tasks:
build:
cmds:
- go build .
sources:
- ./*.go
generates:
- app{{exeExt}}
method: checksum
```
> TIP: method `none` skips any validation and always run the task.
@@ -285,33 +353,40 @@ Alternatively, you can inform a sequence of tests as `status`. If no error
is returned (exit status 0), the task is considered up-to-date:
```yml
generate-files:
cmds:
- mkdir directory
- touch directory/file1.txt
- touch directory/file2.txt
# test existence of files
status:
- test -d directory
- test -f directory/file1.txt
- test -f directory/file2.txt
version: '2'
tasks:
generate-files:
cmds:
- mkdir directory
- touch directory/file1.txt
- touch directory/file2.txt
# test existence of files
status:
- test -d directory
- test -f directory/file1.txt
- test -f directory/file2.txt
```
You can use `--force` or `-f` if you want to force a task to run even when
up-to-date.
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
the tasks are not up-to-date.
### Variables
When doing interpolation of variables, Task will look for the below.
They are listed below in order of importance (e.g. most important first):
- Variables declared locally in the task
- Variables given while calling a task from another.
(See [Calling another task](#calling-another-task) above)
- Environment variables
- Variables declared locally in the task
- Variables declared in the `vars:` option in the `Taskfile`
- Variables available in the `Taskvars.yml` file
- Environment variables
Example of overriding with environment variables:
Example of sending parameters with environment variables:
```bash
$ TASK_VARIABLE=a-value task do-something
@@ -329,11 +404,28 @@ $ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!
Example of locally declared vars:
```yml
print-var:
cmds:
echo "{{.VAR}}"
vars:
VAR: Hello!
version: '2'
tasks:
print-var:
cmds:
echo "{{.VAR}}"
vars:
VAR: Hello!
```
Example of global vars in a `Taskfile.yml`:
```yml
version: '2'
vars:
GREETING: Hello from Taskfile!
tasks:
greet:
cmds:
- echo "{{.GREETING}}"
```
Example of `Taskvars.yml` file:
@@ -344,6 +436,30 @@ DEV_MODE: production
GIT_COMMIT: {sh: git log -n 1 --format=%h}
```
#### Variables expansion
Variables are expanded 2 times by default. You can change that by setting the
`expansions:` option. Change that will be necessary if you compose many
variables together:
```yml
version: '2'
expansions: 3
vars:
FOO: foo
BAR: bar
BAZ: baz
FOOBAR: "{{.FOO}}{{.BAR}}"
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
tasks:
default:
cmds:
- echo "{{.FOOBARBAZ}}"
```
#### Dynamic variables
The below syntax (`sh:` prop in a variable) is considered a dynamic variable.
@@ -351,30 +467,19 @@ The value will be treated as a command and the output assigned. If there is one
or more trailing newlines, the last newline will be trimmed.
```yml
build:
cmds:
- go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
vars:
GIT_COMMIT:
sh: git log -n 1 --format=%h
version: '2'
tasks:
build:
cmds:
- go build -ldflags="-X main.Version={{.GIT_COMMIT}}" main.go
vars:
GIT_COMMIT:
sh: git log -n 1 --format=%h
```
This works for all types of variables.
> It's also possible to prefix the variable with `$` to have a dynamic
variable, but this is now considered deprecated:
```yml
# Taskvars.yml
# recommended
GIT_COMMIT:
sh: git log -n 1 --format=%h
# deprecated
GIT_COMMIT: $git log -n 1 --format=%h
```
### Go's template engine
Task parse commands as [Go's template engine][gotemplate] before executing
@@ -384,9 +489,12 @@ All functions by the Go's [sprig lib](http://masterminds.github.io/sprig/)
are available. The following example gets the current date in a given format:
```yml
print-date:
cmds:
- echo {{now | date "2006-01-02"}}
version: '2'
tasks:
print-date:
cmds:
- echo {{now | date "2006-01-02"}}
```
Task also adds the following functions:
@@ -407,27 +515,27 @@ Task also adds the following functions:
Example:
```yml
print-os:
cmds:
- echo '{{OS}} {{ARCH}}'
- echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}'
# This will be path/to/file on Unix but path\to\file on Windows
- echo '{{fromSlash "path/to/file"}}'
enumerated-file:
vars:
CONTENT: |
foo
bar
cmds:
- |
cat << EOF > output.txt
{{range $i, $line := .CONTENT | splitLines -}}
{{printf "%3d" $i}}: {{$line}}
{{end}}EOF
```
version: '2'
> NOTE: There are some deprecated function names still available: `ToSlash`,
`FromSlash` and `ExeExt`. These where changed for consistency with sprig lib.
tasks:
print-os:
cmds:
- echo '{{OS}} {{ARCH}}'
- echo '{{if eq OS "windows"}}windows-command{{else}}unix-command{{end}}'
# This will be path/to/file on Unix but path\to\file on Windows
- echo '{{fromSlash "path/to/file"}}'
enumerated-file:
vars:
CONTENT: |
foo
bar
cmds:
- |
cat << EOF > output.txt
{{range $i, $line := .CONTENT | splitLines -}}
{{printf "%3d" $i}}: {{$line}}
{{end}}EOF
```
### Help
@@ -435,23 +543,26 @@ Running `task --list` (or `task -l`) lists all tasks with a description.
The following taskfile:
```yml
build:
desc: Build the go binary.
cmds:
- go build -v -i main.go
version: '2'
test:
desc: Run all the go tests.
cmds:
- go test -race ./...
tasks:
build:
desc: Build the go binary.
cmds:
- go build -v -i main.go
js:
cmds:
- minify -o public/script.js src/js
test:
desc: Run all the go tests.
cmds:
- go test -race ./...
css:
cmds:
- minify -o public/style.css src/css
js:
cmds:
- minify -o public/script.js src/js
css:
cmds:
- minify -o public/style.css src/css
```
would print the following output:
@@ -467,9 +578,12 @@ Silent mode disables echoing of commands before Task runs it.
For the following Taskfile:
```yml
echo:
cmds:
- echo "Print something"
version: '2'
tasks:
echo:
cmds:
- echo "Print something"
```
Normally this will be print:
@@ -490,19 +604,25 @@ There's three ways to enable silent mode:
* At command level:
```yml
echo:
cmds:
- cmd: echo "Print something"
silent: true
version: '2'
tasks:
echo:
cmds:
- cmd: echo "Print something"
silent: true
```
* At task level:
```yml
echo:
cmds:
- echo "Print something"
silent: true
version: '2'
tasks:
echo:
cmds:
- echo "Print something"
silent: true
```
* Or globally with `--silent` or `-s` flag
@@ -510,14 +630,117 @@ echo:
If you want to suppress stdout instead, just redirect a command to `/dev/null`:
```yml
echo:
cmds:
- echo "This will print nothing" > /dev/null
version: '2'
tasks:
echo:
cmds:
- echo "This will print nothing" > /dev/null
```
## Watch tasks (experimental)
## Dry run mode
If you give a `--watch` or `-w` argument, task will watch for files changes
Dry run mode (`--dry`) compiles and steps through each task, printing the commands
that would be run without executing them. This is useful for debugging your Taskfiles.
## Ignore errors
You have the option to ignore errors during command execution.
Given the following Taskfile:
```yml
version: '2'
tasks:
echo:
cmds:
- exit 1
- echo "Hello World"
```
Task will abort the execution after running `exit 1` because the status code `1` stands for `EXIT_FAILURE`.
However it is possible to continue with execution using `ignore_error`:
```yml
version: '2'
tasks:
echo:
cmds:
- cmd: exit 1
ignore_error: true
- echo "Hello World"
```
`ignore_error` can also be set for a task, which mean errors will be supressed
for all commands. But keep in mind this option won't propagate to other tasks
called either by `deps` or `cmds`!
## Output syntax
By default, Task just redirect the STDOUT and STDERR of the running commands
to the shell in real time. This is good for having live feedback for log
printed by commands, but the output can become messy if you have multiple
commands running at the same time and printing lots of stuff.
To make this more customizable, there are currently three different output
options you can choose:
- `interleaved` (default)
- `group`
- `prefixed`
To choose another one, just set it to root in the Taskfile:
```yml
version: '2'
output: 'group'
tasks:
# ...
```
The `group` output will print the entire output of a command once, after it
finishes, so you won't have live feedback for commands that take a long time
to run.
The `prefix` output will prefix every line printed by a command with
`[task-name] ` as the prefix, but you can customize the prefix for a command
with the `prefix:` attribute:
```yml
version: '2'
output: prefixed
tasks:
default:
deps:
- task: print
vars: {TEXT: foo}
- task: print
vars: {TEXT: bar}
- task: print
vars: {TEXT: baz}
print:
cmds:
- echo "{{.TEXT}}"
prefix: "print-{{.TEXT}}"
silent: true
```
```bash
$ task default
[print-foo] foo
[print-bar] bar
[print-baz] baz
```
## Watch tasks
If you give a `--watch` or `-w` argument, task will watch for file changes
and run the task again. This requires the `sources` attribute to be given,
so task know which files to watch.
@@ -527,42 +750,40 @@ The [go-task/examples][examples] intends to be a collection of Taskfiles for
various use cases.
(It still lacks many examples, though. Contributions are welcome).
## Task in the wild
- [How I Build My Static Assets for Hugo][post-hugo]
## Alternative task runners
- YAML based:
- [tj/robo][robo]
- [dogtools/dog][dog]
- [goeuro/myke][myke]
- [dreadl0ck/zeus][zeus]
- [rliebz/tusk][tusk]
- Go based:
- [go-godo/godo][godo]
- [markbates/grift][grift]
- [nstratos/make.go][make.go]
- [magefile/mage][mage]
- Make based:
- [tj/mmake][mmake]
- Make based or similar:
- [casey/just][just]
### Sponsors
[![Sponsors](https://opencollective.com/task/sponsors.svg?width=890)][opencollective]
### Backers
[![Backers](https://opencollective.com/task/backers.svg?width=890)][opencollective]
### Contributors
[![Contributors](https://opencollective.com/task/contributors.svg?width=890)][contributors]
[make]: https://www.gnu.org/software/make/
[releases]: https://github.com/go-task/task/releases
[golang]: https://golang.org/
[gotemplate]: https://golang.org/pkg/text/template/
[robo]: https://github.com/tj/robo
[dog]: https://github.com/dogtools/dog
[myke]: https://github.com/goeuro/myke
[zeus]: https://github.com/dreadl0ck/zeus
[tusk]: https://github.com/rliebz/tusk
[godo]: https://github.com/go-godo/godo
[grift]: https://github.com/markbates/grift
[make.go]: https://github.com/nstratos/make.go
[mage]: https://github.com/magefile/mage
[mmake]: https://github.com/tj/mmake
[just]: https://github.com/casey/just
[sh]: https://github.com/mvdan/sh
[post-hugo]: https://blog.carlmjohnson.net/post/2017/hugo-asset-pipeline/
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
[examples]: https://github.com/go-task/examples
[snapcraft]: https://snapcraft.io/
[homebrew]: https://brew.sh/
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
[godownloader]: https://github.com/goreleaser/godownloader
[opencollective]: https://opencollective.com/task
[contributors]: https://github.com/go-task/task/graphs/contributors

32
RELEASING_TASK.md Normal file
View File

@@ -0,0 +1,32 @@
# Releasing Task
The release process of Task is done is done with the help of
[GoReleaser][goreleaser]. You can test the release process locally by calling
the `test-release` task of the Taskfile.
The Travis CI should release automatically when a new
Git tag is pushed to master, either for the artifact uploading (raw executables
and DEB and RPM packages)
# Homebrew
To release a new version on the [Homebrew tap][homebrewtap] edit the
[Formula/go-task.rb][gotaskrb] file, updating with the new version, download
URL and sha256.
# Snapcraft
The exception is the publishing of a new version of the
[snap package][snappackage]. This current require two steps after publishing
the binaries:
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
* Moving both `i386` and `amd64` new artifacts to the stable channel on
the [Snapscraft dashboard][snapcraftdashboard]
[goreleaser]: https://goreleaser.com/#continuous_integration
[homebrewtap]: https://github.com/go-task/homebrew-tap
[gotaskrb]: https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb
[snappackage]: https://github.com/go-task/snap
[snapcraftyaml]: https://github.com/go-task/snap/blob/master/snap/snapcraft.yaml#L2
[snapcraftdashboard]: https://dashboard.snapcraft.io/

132
TASKFILE_VERSIONS.md Normal file
View File

@@ -0,0 +1,132 @@
# Taskfile version
The Taskfile syntax and features changed with time. This document explains what
changed on each version and how to upgrade your Taskfile.
# What the Taskfile version mean
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.
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.1` features, but if you choose to use `2`, than any `2.x.x` features will be
available, but not `3.0.0+`.
## Version 1
In the first version of the `Taskfile`, the `version:` key was not available,
because the tasks was in the root of the YAML document. Like this:
```yml
echo:
cmds:
- echo "Hello, World!"
```
The variable priority order was also different:
1. Call variables
2. Environment
3. Task variables
4. `Taskvars.yml` variables
## Version 2.0
At version 2, we introduced the `version:` key, to allow us to envolve Task
with new features without breaking existing Taskfiles. The new syntax is as
follows:
```yml
version: '2'
tasks:
echo:
cmds:
- echo "Hello, World!"
```
Version 2 allows you to write global variables directly in the Taskfile,
if you don't want to create a `Taskvars.yml`:
```yml
version: '2'
vars:
GREETING: Hello, World!
tasks:
greet:
cmds:
- echo "{{.GREETING}}"
```
The variable priority order changed to the following:
1. Task variables
2. Call variables
3. Taskfile variables
4. Taskvars file variables
5. Environment variables
A new global option was added to configure the number of variables expansions
(which default to 2):
```yml
version: '2'
expansions: 3
vars:
FOO: foo
BAR: bar
BAZ: baz
FOOBAR: "{{.FOO}}{{.BAR}}"
FOOBARBAZ: "{{.FOOBAR}}{{.BAZ}}"
tasks:
default:
cmds:
- echo "{{.FOOBARBAZ}}"
```
## Version 2.1
Version 2.1 includes a global `output` option, to allow having more control
over how commands output are printed to the console
(see [documentation][output] for more info):
```yml
version: '2'
output: prefixed
tasks:
server:
cmds:
- go run main.go
prefix: server
```
From this version it's not also possible to ignore errors of a command or task
(check documentatio [here][ignore_errors]):
```yml
version: '2'
tasks:
example-1:
cmds:
- cmd: exit 1
ignore_error: true
- echo "This will be print"
example-2:
cmds:
- exit 1
- echo "This will be print"
ignore_error: true
```
[output]: https://github.com/go-task/task#output-syntax
[ignore_errors]: https://github.com/go-task/task#ignore-errors

View File

@@ -1,55 +1,81 @@
# compiles current source code and make "task" executable available on
# $GOPATH/bin/task{.exe}
install:
desc: Installs Task
cmds:
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
version: '2'
dl-deps:
desc: Downloads cli dependencies
cmds:
- go get -u github.com/golang/lint/golint
- go get -u github.com/goreleaser/goreleaser
- go get -u github.com/asticode/go-astitodo/astitodo
- go get -u github.com/golang/dep/cmd/dep
vars:
GIT_COMMIT:
sh: git log -n 1 --format=%h
update-deps:
desc: Updates dependencies
cmds:
- dep ensure
- dep ensure -update
- dep prune
GO_PACKAGES:
sh: go list ./...
clean:
desc: Cleans temp files and folders
cmds:
- rm -rf dist/
tasks:
default:
cmds:
- task: test
lint:
desc: Runs golint
cmds:
- golint {{.GO_PACKAGES}}
silent: true
install:
desc: Installs Task
cmds:
- go install -v -ldflags="-w -s -X main.version={{.GIT_COMMIT}}" ./cmd/task
env:
CGO_ENABLED: '0'
test:
desc: Runs test suite
deps: [install]
cmds:
- go test {{.GO_PACKAGES}}
dl-deps:
desc: Downloads cli dependencies
cmds:
- task: go-get
vars: {REPO: github.com/golang/lint/golint}
- task: go-get
vars: {REPO: github.com/golang/dep/cmd/dep}
- task: go-get
vars: {REPO: github.com/goreleaser/goreleaser}
- task: go-get
vars: {REPO: github.com/goreleaser/godownloader}
# https://github.com/goreleaser/goreleaser
release:
desc: Release Task
cmds:
- goreleaser
update-deps:
desc: Updates dependencies
cmds:
- dep ensure
- dep ensure -update
test-release:
desc: Tests release process without publishing
cmds:
- goreleaser --skip-validate --skip-publish
clean:
desc: Cleans temp files and folders
cmds:
- rm -rf dist/
todo:
desc: Prints TODO comments present in the code
cmds:
- astitodo {{.GO_PACKAGES}}
silent: true
lint:
desc: Runs golint
cmds:
- golint {{catLines .GO_PACKAGES}}
silent: true
test:
desc: Runs test suite
deps: [install]
cmds:
- go test {{catLines .GO_PACKAGES}}
test-release:
desc: Tests release process without publishing
cmds:
- goreleaser --snapshot --rm-dist
generate-install-script:
desc: Generate install script using https://githbub.com/goreleaser/godownloader
cmds:
- godownloader --repo go-task/task -o install-task.sh
ci:
cmds:
- task: go-get
vars: {REPO: github.com/golang/lint/golint}
- task: lint
- task: test
go-get:
cmds:
- go get -u {{.REPO}}
packages:
cmds:
- echo '{{.GO_PACKAGES}}'
silent: true

View File

@@ -1,9 +0,0 @@
GIT_COMMIT:
sh: git log -n 1 --format=%h
GO_PACKAGES:
.
./cmd/task
./internal/args
./internal/execext
./internal/status

View File

@@ -17,7 +17,7 @@ var (
version = "master"
)
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [task...]
const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [task...]
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.
@@ -50,20 +50,24 @@ func main() {
versionFlag bool
init bool
list bool
status bool
force bool
watch bool
verbose bool
silent bool
dry bool
dir string
)
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
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.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
pflag.BoolVarP(&verbose, "verbose", "v", false, "enables verbose mode")
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
pflag.Parse()
@@ -83,20 +87,26 @@ func main() {
return
}
ctx := context.Background()
if !watch {
ctx = getSignalContext()
}
e := task.Executor{
Force: force,
Watch: watch,
Verbose: verbose,
Silent: silent,
Dir: dir,
Dry: dry,
Context: getSignalContext(),
Context: ctx,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if err := e.ReadTaskfile(); err != nil {
if err := e.Setup(); err != nil {
log.Fatal(err)
}
@@ -116,6 +126,13 @@ func main() {
log.Fatal(err)
}
if status {
if err = e.Status(calls...); err != nil {
log.Fatal(err)
}
return
}
if err := e.Run(calls...); err != nil {
log.Fatal(err)
}

25
completion/zsh/_task Normal file
View File

@@ -0,0 +1,25 @@
#compdef task
# Listing commands from Taskfile.yml
function __list() {
local -a scripts
if [ -f Taskfile.yml ]; then
scripts=($(task -l | sed '1d' | sed 's/://' | awk '{ print $2 }'))
_describe 'script' scripts
fi
}
_arguments \
'(-d --dir)'{-d,--dir}': :_files' \
'(--dry)'--dry \
'(-f --force)'{-f,--force} \
'(-i --init)'{-i,--init} \
'(-l --list)'{-l,--list} \
'(-s --silent)'{-s,--silent} \
'(--status)'--status \
'(-v --verbose)'{-v,--verbose} \
'(--version)'--version \
'(-w --watch)'{-w,--watch} \
'(- *)'{-h,--help} \
'*: :__list' \

View File

@@ -10,14 +10,6 @@ var (
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
)
type taskFileNotFound struct {
taskFile string
}
func (err taskFileNotFound) Error() string {
return fmt.Sprintf(`task: No task file found (is it named "%s"?). Use "task --init" to create a new one`, err.taskFile)
}
type taskNotFoundError struct {
taskName string
}
@@ -35,31 +27,6 @@ func (err *taskRunError) Error() string {
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
}
type cyclicDepError struct {
taskName string
}
func (err *cyclicDepError) Error() string {
return fmt.Sprintf(`task: Cyclic dependency of task "%s" detected`, err.taskName)
}
type cantWatchNoSourcesError struct {
taskName string
}
func (err *cantWatchNoSourcesError) Error() string {
return fmt.Sprintf(`task: Can't watch task "%s" because it has no specified sources`, err.taskName)
}
type dynamicVarError struct {
cause error
cmd string
}
func (err *dynamicVarError) Error() string {
return fmt.Sprintf(`task: Command "%s" in taskvars file failed: %s`, err.cmd, err.cause)
}
// MaximumTaskCallExceededError is returned when a task is called too
// many times. In this case you probably have a cyclic dependendy or
// infinite loop

17
help.go
View File

@@ -4,31 +4,34 @@ import (
"fmt"
"sort"
"text/tabwriter"
"github.com/go-task/task/internal/taskfile"
)
// PrintTasksHelp prints help os tasks that have a description
func (e *Executor) PrintTasksHelp() {
tasks := e.tasksWithDesc()
if len(tasks) == 0 {
e.outf("task: No tasks with description available")
e.Logger.Outf("task: No tasks with description available")
return
}
e.outf("task: Available tasks for this project:")
e.Logger.Outf("task: Available tasks for this project:")
// Format in tab-separated columns with a tab stop of 8.
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
for _, task := range tasks {
fmt.Fprintln(w, fmt.Sprintf("* %s: \t%s", task, e.Tasks[task].Desc))
fmt.Fprintf(w, "* %s: \t%s\n", task.Task, task.Desc)
}
w.Flush()
}
func (e *Executor) tasksWithDesc() (tasks []string) {
for name, task := range e.Tasks {
func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
for _, task := range e.Taskfile.Tasks {
if task.Desc != "" {
tasks = append(tasks, name)
tasks = append(tasks, task)
}
}
sort.Strings(tasks)
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
return
}

27
init.go
View File

@@ -10,22 +10,27 @@ import (
const defaultTaskfile = `# github.com/go-task/task
default:
cmds:
- echo "Hello, World!"
version: '2'
vars:
GREETING: Hello, World!
tasks:
default:
cmds:
- echo "{{.GREETING}}"
silent: true
`
// InitTaskfile Taskfile creates a new Taskfile
func InitTaskfile(w io.Writer, path string) error {
for _, f := range []string{"Taskfile.yml", "Taskfile.toml", "Taskfile.json"} {
f = filepath.Join(path, f)
if _, err := os.Stat(f); err == nil {
return ErrTaskfileAlreadyExists
}
func InitTaskfile(w io.Writer, dir string) error {
f := filepath.Join(dir, "Taskfile.yml")
if _, err := os.Stat(f); err == nil {
return ErrTaskfileAlreadyExists
}
f := filepath.Join(path, "Taskfile.yml")
if err := ioutil.WriteFile(f, []byte(defaultTaskfile), 0666); err != nil {
if err := ioutil.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
return err
}
fmt.Fprintf(w, "Taskfile.yml created in the current directory\n")

390
install-task.sh Executable file
View File

@@ -0,0 +1,390 @@
#!/bin/sh
set -e
# Code generated by godownloader on 2018-04-07T17:47:38Z. DO NOT EDIT.
#
usage() {
this=$1
cat <<EOF
$this: download go binaries for go-task/task
Usage: $this [-b] bindir [-d] [tag]
-b sets bindir or installation directory, Defaults to ./bin
-d turns on debug logging
[tag] is a tag from
https://github.com/go-task/task/releases
If tag is missing, then the latest will be used.
Generated by godownloader
https://github.com/goreleaser/godownloader
EOF
exit 2
}
parse_args() {
#BINDIR is ./bin unless set be ENV
# over-ridden by flag below
BINDIR=${BINDIR:-./bin}
while getopts "b:dh?" arg; do
case "$arg" in
b) BINDIR="$OPTARG" ;;
d) log_set_priority 10 ;;
h | \?) usage "$0" ;;
esac
done
shift $((OPTIND - 1))
TAG=$1
}
# this function wraps all the destructive operations
# if a curl|bash cuts off the end of the script due to
# network, either nothing will happen or will syntax error
# out preventing half-done work
execute() {
tmpdir=$(mktmpdir)
log_debug "downloading files into ${tmpdir}"
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
srcdir="${tmpdir}"
(cd "${tmpdir}" && untar "${TARBALL}")
install -d "${BINDIR}"
for binexe in "task" ; do
if [ "$OS" = "windows" ]; then
binexe="${binexe}.exe"
fi
install "${srcdir}/${binexe}" "${BINDIR}/"
log_info "installed ${BINDIR}/${binexe}"
done
}
is_supported_platform() {
platform=$1
found=1
case "$platform" in
windows/386) found=0 ;;
windows/amd64) found=0 ;;
darwin/386) found=0 ;;
darwin/amd64) found=0 ;;
linux/386) found=0 ;;
linux/amd64) found=0 ;;
esac
case "$platform" in
darwin/386) found=1 ;;
esac
return $found
}
check_platform() {
if is_supported_platform "$PLATFORM"; then
# optional logging goes here
true
else
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
exit 1
fi
}
tag_to_version() {
if [ -z "${TAG}" ]; then
log_info "checking GitHub for latest tag"
else
log_info "checking GitHub for tag '${TAG}'"
fi
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
if test -z "$REALTAG"; then
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
exit 1
fi
# if version starts with 'v', remove it
TAG="$REALTAG"
VERSION=${TAG#v}
}
adjust_format() {
# change format (tar.gz or zip) based on ARCH
case ${ARCH} in
windows) FORMAT=zip ;;
esac
true
}
adjust_os() {
# adjust archive name based on OS
true
}
adjust_arch() {
# adjust archive name based on ARCH
true
}
cat /dev/null <<EOF
------------------------------------------------------------------------
https://github.com/client9/shlib - portable posix shell functions
Public domain - http://unlicense.org
https://github.com/client9/shlib/blob/master/LICENSE.md
but credit (and pull requests) appreciated.
------------------------------------------------------------------------
EOF
is_command() {
command -v "$1" >/dev/null
}
echoerr() {
echo "$@" 1>&2
}
log_prefix() {
echo "$0"
}
_logp=6
log_set_priority() {
_logp="$1"
}
log_priority() {
if test -z "$1"; then
echo "$_logp"
return
fi
[ "$1" -le "$_logp" ]
}
log_tag() {
case $1 in
0) echo "emerg" ;;
1) echo "alert" ;;
2) echo "crit" ;;
3) echo "err" ;;
4) echo "warning" ;;
5) echo "notice" ;;
6) echo "info" ;;
7) echo "debug" ;;
*) echo "$1" ;;
esac
}
log_debug() {
log_priority 7 || return 0
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
}
log_info() {
log_priority 6 || return 0
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
}
log_err() {
log_priority 3 || return 0
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
}
log_crit() {
log_priority 2 || return 0
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
}
uname_os() {
os=$(uname -s | tr '[:upper:]' '[:lower:]')
case "$os" in
msys_nt) os="windows" ;;
esac
echo "$os"
}
uname_arch() {
arch=$(uname -m)
case $arch in
x86_64) arch="amd64" ;;
x86) arch="386" ;;
i686) arch="386" ;;
i386) arch="386" ;;
aarch64) arch="arm64" ;;
armv5*) arch="arm5" ;;
armv6*) arch="arm6" ;;
armv7*) arch="arm7" ;;
esac
echo ${arch}
}
uname_os_check() {
os=$(uname_os)
case "$os" in
darwin) return 0 ;;
dragonfly) return 0 ;;
freebsd) return 0 ;;
linux) return 0 ;;
android) return 0 ;;
nacl) return 0 ;;
netbsd) return 0 ;;
openbsd) return 0 ;;
plan9) return 0 ;;
solaris) return 0 ;;
windows) return 0 ;;
esac
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
return 1
}
uname_arch_check() {
arch=$(uname_arch)
case "$arch" in
386) return 0 ;;
amd64) return 0 ;;
arm64) return 0 ;;
armv5) return 0 ;;
armv6) return 0 ;;
armv7) return 0 ;;
ppc64) return 0 ;;
ppc64le) return 0 ;;
mips) return 0 ;;
mipsle) return 0 ;;
mips64) return 0 ;;
mips64le) return 0 ;;
s390x) return 0 ;;
amd64p32) return 0 ;;
esac
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
return 1
}
untar() {
tarball=$1
case "${tarball}" in
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
*.tar) tar -xf "${tarball}" ;;
*.zip) unzip "${tarball}" ;;
*)
log_err "untar unknown archive format for ${tarball}"
return 1
;;
esac
}
mktmpdir() {
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
mkdir -p "${TMPDIR}"
echo "${TMPDIR}"
}
http_download_curl() {
local_file=$1
source_url=$2
header=$3
if [ -z "$header" ]; then
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
else
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
fi
if [ "$code" != "200" ]; then
log_debug "http_download_curl received HTTP status $code"
return 1
fi
return 0
}
http_download_wget() {
local_file=$1
source_url=$2
header=$3
if [ -z "$header" ]; then
wget -q -O "$local_file" "$source_url"
else
wget -q --header "$header" -O "$local_file" "$source_url"
fi
}
http_download() {
log_debug "http_download $2"
if is_command curl; then
http_download_curl "$@"
return
elif is_command wget; then
http_download_wget "$@"
return
fi
log_crit "http_download unable to find wget or curl"
return 1
}
http_copy() {
tmp=$(mktemp)
http_download "${tmp}" "$1" "$2" || return 1
body=$(cat "$tmp")
rm -f "${tmp}"
echo "$body"
}
github_release() {
owner_repo=$1
version=$2
test -z "$version" && version="latest"
giturl="https://github.com/${owner_repo}/releases/${version}"
json=$(http_copy "$giturl" "Accept:application/json")
test -z "$json" && return 1
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
test -z "$version" && return 1
echo "$version"
}
hash_sha256() {
TARGET=${1:-/dev/stdin}
if is_command gsha256sum; then
hash=$(gsha256sum "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command sha256sum; then
hash=$(sha256sum "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command shasum; then
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command openssl; then
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f a
else
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
return 1
fi
}
hash_sha256_verify() {
TARGET=$1
checksums=$2
if [ -z "$checksums" ]; then
log_err "hash_sha256_verify checksum file not specified in arg2"
return 1
fi
BASENAME=${TARGET##*/}
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
if [ -z "$want" ]; then
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
return 1
fi
got=$(hash_sha256 "$TARGET")
if [ "$want" != "$got" ]; then
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
return 1
fi
}
cat /dev/null <<EOF
------------------------------------------------------------------------
End of functions from https://github.com/client9/shlib
------------------------------------------------------------------------
EOF
PROJECT_NAME="task"
OWNER=go-task
REPO="task"
BINARY=task
FORMAT=tar.gz
OS=$(uname_os)
ARCH=$(uname_arch)
PREFIX="$OWNER/$REPO"
# use in logging routines
log_prefix() {
echo "$PREFIX"
}
PLATFORM="${OS}/${ARCH}"
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
uname_os_check "$OS"
uname_arch_check "$ARCH"
parse_args "$@"
check_platform
tag_to_version
adjust_format
adjust_os
adjust_arch
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
NAME=${BINARY}_${OS}_${ARCH}
TARBALL=${NAME}.${FORMAT}
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
CHECKSUM=task_checksums.txt
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
execute

View File

@@ -4,7 +4,7 @@ import (
"errors"
"strings"
"github.com/go-task/task"
"github.com/go-task/task/internal/taskfile"
)
var (
@@ -13,12 +13,12 @@ var (
)
// Parse parses command line argument: tasks and vars of each task
func Parse(args ...string) ([]task.Call, error) {
var calls []task.Call
func Parse(args ...string) ([]taskfile.Call, error) {
var calls []taskfile.Call
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, task.Call{Task: arg})
calls = append(calls, taskfile.Call{Task: arg})
continue
}
if len(calls) < 1 {
@@ -26,11 +26,11 @@ func Parse(args ...string) ([]task.Call, error) {
}
if calls[len(calls)-1].Vars == nil {
calls[len(calls)-1].Vars = make(task.Vars)
calls[len(calls)-1].Vars = make(taskfile.Vars)
}
pair := strings.SplitN(arg, "=", 2)
calls[len(calls)-1].Vars[pair[0]] = task.Var{Static: pair[1]}
calls[len(calls)-1].Vars[pair[0]] = taskfile.Var{Static: pair[1]}
}
return calls, nil
}

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
"github.com/go-task/task"
"github.com/go-task/task/internal/args"
"github.com/go-task/task/internal/taskfile"
"github.com/stretchr/testify/assert"
)
@@ -13,12 +13,12 @@ import (
func TestArgs(t *testing.T) {
tests := []struct {
Args []string
Expected []task.Call
Expected []taskfile.Call
Err error
}{
{
Args: []string{"task-a", "task-b", "task-c"},
Expected: []task.Call{
Expected: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
@@ -26,30 +26,30 @@ func TestArgs(t *testing.T) {
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
Expected: []task.Call{
Expected: []taskfile.Call{
{
Task: "task-a",
Vars: task.Vars{
"FOO": task.Var{Static: "bar"},
Vars: taskfile.Vars{
"FOO": taskfile.Var{Static: "bar"},
},
},
{Task: "task-b"},
{
Task: "task-c",
Vars: task.Vars{
"BAR": task.Var{Static: "baz"},
"BAZ": task.Var{Static: "foo"},
Vars: taskfile.Vars{
"BAR": taskfile.Var{Static: "baz"},
"BAZ": taskfile.Var{Static: "foo"},
},
},
},
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
Expected: []task.Call{
Expected: []taskfile.Call{
{
Task: "task-a",
Vars: task.Vars{
"CONTENT": task.Var{Static: "with some spaces"},
Vars: taskfile.Vars{
"CONTENT": taskfile.Var{Static: "with some spaces"},
},
},
},

View File

@@ -0,0 +1,12 @@
package compiler
import (
"github.com/go-task/task/internal/taskfile"
)
// Compiler handles compilation of a task before its execution.
// E.g. variable merger, template processing, etc.
type Compiler interface {
GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error)
HandleDynamicVar(v taskfile.Var) (string, error)
}

24
internal/compiler/env.go Normal file
View File

@@ -0,0 +1,24 @@
package compiler
import (
"os"
"strings"
"github.com/go-task/task/internal/taskfile"
)
// GetEnviron the all return all environment variables encapsulated on a
// taskfile.Vars
func GetEnviron() taskfile.Vars {
var (
env = os.Environ()
m = make(taskfile.Vars, len(env))
)
for _, e := range env {
keyVal := strings.SplitN(e, "=", 2)
key, val := keyVal[0], keyVal[1]
m[key] = taskfile.Var{Static: val}
}
return m
}

View File

@@ -0,0 +1,137 @@
package v1
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"github.com/go-task/task/internal/compiler"
"github.com/go-task/task/internal/execext"
"github.com/go-task/task/internal/logger"
"github.com/go-task/task/internal/taskfile"
"github.com/go-task/task/internal/templater"
)
var _ compiler.Compiler = &CompilerV1{}
type CompilerV1 struct {
Dir string
Vars taskfile.Vars
Logger *logger.Logger
dynamicCache map[string]string
muDynamicCache sync.Mutex
}
// GetVariables returns fully resolved variables following the priority order:
// 1. Call variables (should already have been resolved)
// 2. Environment (should not need to be resolved)
// 3. Task variables, resolved with access to:
// - call, taskvars and environment variables
// 4. Taskvars variables, resolved with access to:
// - environment variables
func (c *CompilerV1) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
merge := func(dest taskfile.Vars, srcs ...taskfile.Vars) {
for _, src := range srcs {
for k, v := range src {
dest[k] = v
}
}
}
varsKeys := func(srcs ...taskfile.Vars) []string {
m := make(map[string]struct{})
for _, src := range srcs {
for k := range src {
m[k] = struct{}{}
}
}
lst := make([]string, 0, len(m))
for k := range m {
lst = append(lst, k)
}
return lst
}
replaceVars := func(dest taskfile.Vars, keys []string) error {
r := templater.Templater{Vars: dest}
for _, k := range keys {
v := dest[k]
dest[k] = taskfile.Var{
Static: r.Replace(v.Static),
Sh: r.Replace(v.Sh),
}
}
return r.Err()
}
resolveShell := func(dest taskfile.Vars, keys []string) error {
for _, k := range keys {
v := dest[k]
static, err := c.HandleDynamicVar(v)
if err != nil {
return err
}
dest[k] = taskfile.Var{Static: static}
}
return nil
}
update := func(dest taskfile.Vars, srcs ...taskfile.Vars) error {
merge(dest, srcs...)
// updatedKeys ensures template evaluation is run only once.
updatedKeys := varsKeys(srcs...)
if err := replaceVars(dest, updatedKeys); err != nil {
return err
}
return resolveShell(dest, updatedKeys)
}
// Resolve taskvars variables to "result" with environment override variables.
override := compiler.GetEnviron()
result := make(taskfile.Vars, len(c.Vars)+len(t.Vars)+len(override))
if err := update(result, c.Vars, override); err != nil {
return nil, err
}
// Resolve task variables to "result" with environment and call override variables.
merge(override, call.Vars)
if err := update(result, t.Vars, override); err != nil {
return nil, err
}
return result, nil
}
func (c *CompilerV1) HandleDynamicVar(v taskfile.Var) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
if c.dynamicCache == nil {
c.dynamicCache = make(map[string]string, 30)
}
if result, ok := c.dynamicCache[v.Sh]; ok {
return result, nil
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
if err := execext.RunCommand(context.Background(), opts); err != nil {
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command
// output easier to use in shell commands.
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -0,0 +1,109 @@
package v2
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"github.com/go-task/task/internal/compiler"
"github.com/go-task/task/internal/execext"
"github.com/go-task/task/internal/logger"
"github.com/go-task/task/internal/taskfile"
"github.com/go-task/task/internal/templater"
)
var _ compiler.Compiler = &CompilerV2{}
type CompilerV2 struct {
Dir string
Taskvars taskfile.Vars
TaskfileVars taskfile.Vars
Expansions int
Logger *logger.Logger
dynamicCache map[string]string
muDynamicCache sync.Mutex
}
// GetVariables returns fully resolved variables following the priority order:
// 1. Task variables
// 2. Call variables
// 3. Taskfile variables
// 4. Taskvars file variables
// 5. Environment variables
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (taskfile.Vars, error) {
vr := varResolver{c: c, vars: compiler.GetEnviron()}
for _, vars := range []taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
for i := 0; i < c.Expansions; i++ {
vr.merge(vars)
}
}
return vr.vars, vr.err
}
type varResolver struct {
c *CompilerV2
vars taskfile.Vars
err error
}
func (vr *varResolver) merge(vars taskfile.Vars) {
if vr.err != nil {
return
}
tr := templater.Templater{Vars: vr.vars}
for k, v := range vars {
v = taskfile.Var{
Static: tr.Replace(v.Static),
Sh: tr.Replace(v.Sh),
}
static, err := vr.c.HandleDynamicVar(v)
if err != nil {
vr.err = err
return
}
vr.vars[k] = taskfile.Var{Static: static}
}
vr.err = tr.Err()
}
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
if c.dynamicCache == nil {
c.dynamicCache = make(map[string]string, 30)
}
if result, ok := c.dynamicCache[v.Sh]; ok {
return result, nil
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
if err := execext.RunCommand(context.Background(), opts); err != nil {
return "", fmt.Errorf(`task: Command "%s" in taskvars file failed: %s`, opts.Command, err)
}
// Trim a single trailing newline from the result to make most command
// output easier to use in shell commands.
result := strings.TrimSuffix(stdout.String(), "\n")
c.dynamicCache[v.Sh] = result
c.Logger.VerboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
"os"
"strings"
"mvdan.cc/sh/interp"
@@ -12,7 +13,6 @@ import (
// RunCommandOptions is the options for the RunCommand func
type RunCommandOptions struct {
Context context.Context
Command string
Dir string
Env []string
@@ -27,7 +27,7 @@ var (
)
// RunCommand runs a shell command
func RunCommand(opts *RunCommandOptions) error {
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
if opts == nil {
return ErrNilOptions
}
@@ -37,20 +37,36 @@ func RunCommand(opts *RunCommandOptions) error {
return err
}
r := interp.Runner{
Context: opts.Context,
Dir: opts.Dir,
Env: opts.Env,
Exec: interp.DefaultExec,
Open: interp.OpenDevImpls(interp.DefaultOpen),
Stdin: opts.Stdin,
Stdout: opts.Stdout,
Stderr: opts.Stderr,
environ := opts.Env
if len(environ) == 0 {
environ = os.Environ()
}
if err = r.Reset(); err != nil {
env, err := interp.EnvFromList(environ)
if err != nil {
return err
}
return r.Run(p)
r, err := interp.New(
interp.Dir(opts.Dir),
interp.Env(env),
interp.Module(interp.DefaultExec),
interp.Module(interp.OpenDevImpls(interp.DefaultOpen)),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
)
if err != nil {
return err
}
return r.Run(ctx, p)
}
// IsExitError returns true the given error is an exis status error
func IsExitError(err error) bool {
switch err.(type) {
case interp.ExitStatus, interp.ShellExitStatus:
return true
default:
return false
}
}

38
internal/logger/logger.go Normal file
View File

@@ -0,0 +1,38 @@
package logger
import (
"fmt"
"io"
)
type Logger struct {
Stdout io.Writer
Stderr io.Writer
Verbose bool
}
func (l *Logger) Outf(s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stdout, s+"\n", args...)
}
func (l *Logger) VerboseOutf(s string, args ...interface{}) {
if l.Verbose {
l.Outf(s, args...)
}
}
func (l *Logger) Errf(s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(l.Stderr, s+"\n", args...)
}
func (l *Logger) VerboseErrf(s string, args ...interface{}) {
if l.Verbose {
l.Errf(s, args...)
}
}

26
internal/output/group.go Normal file
View File

@@ -0,0 +1,26 @@
package output
import (
"bytes"
"io"
)
type Group struct{}
func (Group) WrapWriter(w io.Writer, _ string) io.WriteCloser {
return &groupWriter{writer: w}
}
type groupWriter struct {
writer io.Writer
buff bytes.Buffer
}
func (gw *groupWriter) Write(p []byte) (int, error) {
return gw.buff.Write(p)
}
func (gw *groupWriter) Close() error {
_, err := io.Copy(gw.writer, &gw.buff)
return err
}

View File

@@ -0,0 +1,23 @@
package output
import (
"io"
)
type Interleaved struct{}
func (Interleaved) WrapWriter(w io.Writer, _ string) io.WriteCloser {
return nopWriterCloser{w: 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
}

View File

@@ -0,0 +1,9 @@
package output
import (
"io"
)
type Output interface {
WrapWriter(w io.Writer, prefix string) io.WriteCloser
}

View File

@@ -0,0 +1,62 @@
package output_test
import (
"bytes"
"fmt"
"testing"
"github.com/go-task/task/internal/output"
"github.com/stretchr/testify/assert"
)
func TestInterleaved(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Interleaved{}
var w = o.WrapWriter(&b, "")
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, "foo\nbar\n", b.String())
fmt.Fprintln(w, "baz")
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
}
func TestGroup(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Group{}
var w = o.WrapWriter(&b, "")
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, "", b.String())
fmt.Fprintln(w, "baz")
assert.Equal(t, "", b.String())
assert.NoError(t, w.Close())
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
}
func TestPrefixed(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Prefixed{}
var w = o.WrapWriter(&b, "prefix")
t.Run("simple use cases", func(t *testing.T) {
b.Reset()
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
fmt.Fprintln(w, "baz")
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
})
t.Run("multiple writes for a single line", func(t *testing.T) {
b.Reset()
for _, char := range []string{"T", "e", "s", "t", "!"} {
fmt.Fprint(w, char)
assert.Equal(t, "", b.String())
}
assert.NoError(t, w.Close())
assert.Equal(t, "[prefix] Test!\n", b.String())
})
}

View File

@@ -0,0 +1,65 @@
package output
import (
"bytes"
"fmt"
"io"
"strings"
)
type Prefixed struct{}
func (Prefixed) WrapWriter(w io.Writer, prefix string) io.WriteCloser {
return &prefixWriter{writer: w, prefix: prefix}
}
type prefixWriter struct {
writer io.Writer
prefix string
buff bytes.Buffer
}
func (pw *prefixWriter) Write(p []byte) (int, error) {
n, err := pw.buff.Write(p)
if err != nil {
return n, err
}
return n, pw.writeOutputLines(false)
}
func (pw *prefixWriter) Close() error {
return pw.writeOutputLines(true)
}
func (pw *prefixWriter) writeOutputLines(force bool) error {
for {
line, err := pw.buff.ReadString('\n')
if err == nil {
if err = pw.writeLine(line); err != nil {
return err
}
} else if err == io.EOF {
// if this line was not a complete line, re-add to the buffer
if !force && !strings.HasSuffix(line, "\n") {
_, err = pw.buff.WriteString(line)
return err
}
return pw.writeLine(line)
} else {
return err
}
}
}
func (pw *prefixWriter) writeLine(line string) error {
if line == "" {
return nil
}
if !strings.HasSuffix(line, "\n") {
line += "\n"
}
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
return err
}

View File

@@ -5,7 +5,7 @@ import (
"sort"
"github.com/mattn/go-zglob"
"github.com/mitchellh/go-homedir"
"mvdan.cc/sh/shell"
)
func glob(dir string, globs []string) (files []string, err error) {
@@ -13,13 +13,13 @@ func glob(dir string, globs []string) (files []string, err error) {
if !filepath.IsAbs(g) {
g = filepath.Join(dir, g)
}
g, err = homedir.Expand(g)
g, err = shell.Expand(g, nil)
if err != nil {
return nil, err
}
f, err := zglob.Glob(g)
if err != nil {
return nil, err
continue
}
files = append(files, f...)
}

View File

@@ -0,0 +1,7 @@
package taskfile
// Call is the parameters to a task call
type Call struct {
Task string
Vars Vars
}

View File

@@ -1,4 +1,4 @@
package task
package taskfile
import (
"errors"
@@ -7,10 +7,11 @@ import (
// Cmd is a task command
type Cmd struct {
Cmd string
Silent bool
Task string
Vars Vars
Cmd string
Silent bool
Task string
Vars Vars
IgnoreError bool
}
// Dep is a task dependency
@@ -38,12 +39,14 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}
var cmdStruct struct {
Cmd string
Silent bool
Cmd string
Silent bool
IgnoreError bool `yaml:"ignore_error"`
}
if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" {
c.Cmd = cmdStruct.Cmd
c.Silent = cmdStruct.Silent
c.IgnoreError = cmdStruct.IgnoreError
return nil
}
var taskCall struct {
@@ -76,9 +79,3 @@ func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
return ErrCantUnmarshalDep
}
// Call is the parameters to a task call
type Call struct {
Task string
Vars Vars
}

View File

@@ -0,0 +1,27 @@
package taskfile
import (
"fmt"
)
// Merge merges the second Taskfile into the first
func Merge(t1, t2 *Taskfile) error {
if t1.Version != t2.Version {
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
}
if t2.Expansions != 0 && t2.Expansions != 2 {
t1.Expansions = t2.Expansions
}
if t2.Output != "" {
t1.Output = t2.Output
}
for k, v := range t2.Vars {
t1.Vars[k] = v
}
for k, v := range t2.Tasks {
t1.Tasks[k] = v
}
return nil
}

View File

@@ -0,0 +1,47 @@
package read
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/go-task/task/internal/taskfile"
"gopkg.in/yaml.v2"
)
// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, "Taskfile.yml")
t, err := readTaskfile(path)
if err != nil {
return nil, fmt.Errorf(`No Taskfile.yml found. Use "task --init" to create a new one`)
}
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
}
if err = taskfile.Merge(t, osTaskfile); err != nil {
return nil, err
}
}
for name, task := range t.Tasks {
task.Task = name
}
return t, nil
}
func readTaskfile(file string) (*taskfile.Taskfile, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
var t taskfile.Taskfile
return &t, yaml.NewDecoder(f).Decode(&t)
}

View File

@@ -0,0 +1,52 @@
package read
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/go-task/task/internal/taskfile"
"gopkg.in/yaml.v2"
)
// Taskvars reads a Taskvars for a given directory
func Taskvars(dir string) (taskfile.Vars, error) {
vars := make(taskfile.Vars)
path := filepath.Join(dir, "Taskvars.yml")
if _, err := os.Stat(path); err == nil {
vars, err = readTaskvars(path)
if err != nil {
return nil, err
}
}
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
if _, err := os.Stat(path); err == nil {
osVars, err := readTaskvars(path)
if err != nil {
return nil, err
}
if vars == nil {
vars = osVars
} else {
for k, v := range osVars {
vars[k] = v
}
}
}
return vars, nil
}
func readTaskvars(file string) (taskfile.Vars, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
var vars taskfile.Vars
return vars, yaml.NewDecoder(f).Decode(&vars)
}

22
internal/taskfile/task.go Normal file
View File

@@ -0,0 +1,22 @@
package taskfile
// Tasks representas a group of tasks
type Tasks map[string]*Task
// Task represents a task
type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Desc string
Sources []string
Generates []string
Status []string
Dir string
Vars Vars
Env Vars
Silent bool
Method string
Prefix string
IgnoreError bool `yaml:"ignore_error"`
}

View File

@@ -0,0 +1,38 @@
package taskfile
// Taskfile represents a Taskfile.yml
type Taskfile struct {
Version string
Expansions int
Output string
Vars Vars
Tasks Tasks
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&tf.Tasks); err == nil {
tf.Version = "1"
return nil
}
var taskfile struct {
Version string
Expansions int
Output string
Vars Vars
Tasks Tasks
}
if err := unmarshal(&taskfile); err != nil {
return err
}
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Vars = taskfile.Vars
tf.Tasks = taskfile.Tasks
if tf.Expansions <= 0 {
tf.Expansions = 2
}
return nil
}

View File

@@ -1,9 +1,9 @@
package task_test
package taskfile_test
import (
"testing"
"github.com/go-task/task"
"github.com/go-task/task/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
@@ -27,28 +27,28 @@ vars:
}{
{
yamlCmd,
&task.Cmd{},
&task.Cmd{Cmd: `echo "a string command"`},
&taskfile.Cmd{},
&taskfile.Cmd{Cmd: `echo "a string command"`},
},
{
yamlTaskCall,
&task.Cmd{},
&task.Cmd{Task: "another-task", Vars: task.Vars{
"PARAM1": task.Var{Static: "VALUE1"},
"PARAM2": task.Var{Static: "VALUE2"},
&taskfile.Cmd{},
&taskfile.Cmd{Task: "another-task", Vars: taskfile.Vars{
"PARAM1": taskfile.Var{Static: "VALUE1"},
"PARAM2": taskfile.Var{Static: "VALUE2"},
}},
},
{
yamlDep,
&task.Dep{},
&task.Dep{Task: "task-name"},
&taskfile.Dep{},
&taskfile.Dep{Task: "task-name"},
},
{
yamlTaskCall,
&task.Dep{},
&task.Dep{Task: "another-task", Vars: task.Vars{
"PARAM1": task.Var{Static: "VALUE1"},
"PARAM2": task.Var{Static: "VALUE2"},
&taskfile.Dep{},
&taskfile.Dep{Task: "another-task", Vars: taskfile.Vars{
"PARAM1": taskfile.Var{Static: "VALUE1"},
"PARAM2": taskfile.Var{Static: "VALUE2"},
}},
},
}

58
internal/taskfile/var.go Normal file
View File

@@ -0,0 +1,58 @@
package taskfile
import (
"errors"
"strings"
)
var (
// ErrCantUnmarshalVar is returned for invalid var YAML.
ErrCantUnmarshalVar = errors.New("task: can't unmarshal var value")
)
// Vars is a string[string] variables map.
type Vars map[string]Var
// ToStringMap converts Vars to a string map containing only the static
// variables
func (vs Vars) ToStringMap() (m map[string]string) {
m = make(map[string]string, len(vs))
for k, v := range vs {
if v.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
continue
}
m[k] = v.Static
}
return
}
// Var represents either a static or dynamic variable.
type Var struct {
Static string
Sh string
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
if strings.HasPrefix(str, "$") {
v.Sh = strings.TrimPrefix(str, "$")
} else {
v.Static = str
}
return nil
}
var sh struct {
Sh string
}
if err := unmarshal(&sh); err == nil {
v.Sh = sh.Sh
return nil
}
return ErrCantUnmarshalVar
}

View File

@@ -0,0 +1,40 @@
package version
import (
"github.com/Masterminds/semver"
)
var (
v1 = mustVersion("1")
v2 = mustVersion("2")
v21 = mustVersion("2.1")
v22 = mustVersion("2.2")
)
// 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)
}
func mustVersion(s string) *semver.Version {
v, err := semver.NewVersion(s)
if err != nil {
panic(err)
}
return v
}

View File

@@ -0,0 +1,52 @@
package templater
import (
"path/filepath"
"runtime"
"strings"
"text/template"
"github.com/Masterminds/sprig"
)
var (
templateFuncs template.FuncMap
)
func init() {
taskFuncs := template.FuncMap{
"OS": func() string { return runtime.GOOS },
"ARCH": func() string { return runtime.GOARCH },
"catLines": func(s string) string {
s = strings.Replace(s, "\r\n", " ", -1)
return strings.Replace(s, "\n", " ", -1)
},
"splitLines": func(s string) []string {
s = strings.Replace(s, "\r\n", "\n", -1)
return strings.Split(s, "\n")
},
"fromSlash": func(path string) string {
return filepath.FromSlash(path)
},
"toSlash": func(path string) string {
return filepath.ToSlash(path)
},
"exeExt": func() string {
if runtime.GOOS == "windows" {
return ".exe"
}
return ""
},
// IsSH is deprecated.
"IsSH": func() bool { return true },
}
// Deprecated aliases for renamed functions.
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
templateFuncs = sprig.TxtFuncMap()
for k, v := range taskFuncs {
templateFuncs[k] = v
}
}

View File

@@ -0,0 +1,73 @@
package templater
import (
"bytes"
"text/template"
"github.com/go-task/task/internal/taskfile"
)
// Templater is a help struct that allow us to call "replaceX" funcs multiple
// times, without having to check for error each time. The first error that
// happen will be assigned to r.err, and consecutive calls to funcs will just
// return the zero value.
type Templater struct {
Vars taskfile.Vars
strMap map[string]string
err error
}
func (r *Templater) Replace(str string) string {
if r.err != nil || str == "" {
return ""
}
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
if err != nil {
r.err = err
return ""
}
if r.strMap == nil {
r.strMap = r.Vars.ToStringMap()
}
var b bytes.Buffer
if err = templ.Execute(&b, r.strMap); err != nil {
r.err = err
return ""
}
return b.String()
}
func (r *Templater) ReplaceSlice(strs []string) []string {
if r.err != nil || len(strs) == 0 {
return nil
}
new := make([]string, len(strs))
for i, str := range strs {
new[i] = r.Replace(str)
}
return new
}
func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars {
if r.err != nil || len(vars) == 0 {
return nil
}
new := make(taskfile.Vars, len(vars))
for k, v := range vars {
new[k] = taskfile.Var{
Static: r.Replace(v.Static),
Sh: r.Replace(v.Sh),
}
}
return new
}
func (r *Templater) Err() error {
return r.err
}

31
log.go
View File

@@ -1,31 +0,0 @@
package task
import (
"fmt"
)
func (e *Executor) outf(s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(e.Stdout, s+"\n", args...)
}
func (e *Executor) verboseOutf(s string, args ...interface{}) {
if e.Verbose {
e.outf(s, args...)
}
}
func (e *Executor) errf(s string, args ...interface{}) {
if len(args) == 0 {
s, args = "%s", []interface{}{s}
}
fmt.Fprintf(e.Stderr, s+"\n", args...)
}
func (e *Executor) verboseErrf(s string, args ...interface{}) {
if e.Verbose {
e.errf(s, args...)
}
}

View File

@@ -6,14 +6,33 @@ import (
"github.com/go-task/task/internal/execext"
"github.com/go-task/task/internal/status"
"github.com/go-task/task/internal/taskfile"
)
func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
// Status returns an error if any the of given tasks is not up-to-date
func (e *Executor) Status(calls ...taskfile.Call) error {
for _, call := range calls {
t, ok := e.Taskfile.Tasks[call.Task]
if !ok {
return &taskNotFoundError{taskName: call.Task}
}
isUpToDate, err := isTaskUpToDate(e.Context, t)
if err != nil {
return err
}
if !isUpToDate {
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Task)
}
}
return nil
}
func isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
if len(t.Status) > 0 {
return t.isUpToDateStatus(ctx)
return isTaskUpToDateStatus(ctx, t)
}
checker, err := t.getStatusChecker()
checker, err := getStatusChecker(t)
if err != nil {
return false, err
}
@@ -21,15 +40,15 @@ func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
return checker.IsUpToDate()
}
func (t *Task) statusOnError() error {
checker, err := t.getStatusChecker()
func statusOnError(t *taskfile.Task) error {
checker, err := getStatusChecker(t)
if err != nil {
return err
}
return checker.OnError()
}
func (t *Task) getStatusChecker() (status.Checker, error) {
func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
switch t.Method {
case "", "timestamp":
return &status.Timestamp{
@@ -50,13 +69,12 @@ func (t *Task) getStatusChecker() (status.Checker, error) {
}
}
func (t *Task) isUpToDateStatus(ctx context.Context) (bool, error) {
func isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
for _, s := range t.Status {
err := execext.RunCommand(&execext.RunCommandOptions{
Context: ctx,
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: s,
Dir: t.Dir,
Env: t.getEnviron(),
Env: getEnviron(t),
})
if err != nil {
return false, nil

240
task.go
View File

@@ -5,17 +5,23 @@ import (
"fmt"
"io"
"os"
"sync"
"sync/atomic"
"github.com/go-task/task/internal/compiler"
compilerv1 "github.com/go-task/task/internal/compiler/v1"
compilerv2 "github.com/go-task/task/internal/compiler/v2"
"github.com/go-task/task/internal/execext"
"github.com/go-task/task/internal/logger"
"github.com/go-task/task/internal/output"
"github.com/go-task/task/internal/taskfile"
"github.com/go-task/task/internal/taskfile/read"
"github.com/go-task/task/internal/taskfile/version"
"github.com/Masterminds/semver"
"golang.org/x/sync/errgroup"
)
const (
// TaskFilePath is the default Taskfile
TaskFilePath = "Taskfile"
// MaximumTaskCall is the max number of times a task can be called.
// This exists to prevent infinite loops on cyclic dependencies
MaximumTaskCall = 100
@@ -23,12 +29,13 @@ const (
// Executor executes a Taskfile
type Executor struct {
Tasks Tasks
Dir string
Force bool
Watch bool
Verbose bool
Silent bool
Taskfile *taskfile.Taskfile
Dir string
Force bool
Watch bool
Verbose bool
Silent bool
Dry bool
Context context.Context
@@ -36,60 +43,20 @@ type Executor struct {
Stdout io.Writer
Stderr io.Writer
taskvars Vars
Logger *logger.Logger
Compiler compiler.Compiler
Output output.Output
taskvars taskfile.Vars
taskCallCount map[string]*int32
dynamicCache map[string]string
muDynamicCache sync.Mutex
}
// Tasks representas a group of tasks
type Tasks map[string]*Task
// Task represents a task
type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Desc string
Sources []string
Generates []string
Status []string
Dir string
Vars Vars
Env Vars
Silent bool
Method string
}
// Run runs Task
func (e *Executor) Run(calls ...Call) error {
if e.Context == nil {
e.Context = context.Background()
}
if e.Stdin == nil {
e.Stdin = os.Stdin
}
if e.Stdout == nil {
e.Stdout = os.Stdout
}
if e.Stderr == nil {
e.Stderr = os.Stderr
}
e.taskCallCount = make(map[string]*int32, len(e.Tasks))
for k := range e.Tasks {
e.taskCallCount[k] = new(int32)
}
if e.dynamicCache == nil {
e.dynamicCache = make(map[string]string, 10)
}
func (e *Executor) Run(calls ...taskfile.Call) error {
// check if given tasks exist
for _, c := range calls {
if _, ok := e.Tasks[c.Task]; !ok {
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
// FIXME: move to the main package
e.PrintTasksHelp()
return &taskNotFoundError{taskName: c.Task}
@@ -108,8 +75,97 @@ func (e *Executor) Run(calls ...Call) error {
return nil
}
// Setup setups Executor's internal state
func (e *Executor) Setup() error {
var err error
e.Taskfile, err = read.Taskfile(e.Dir)
if err != nil {
return err
}
e.taskvars, err = read.Taskvars(e.Dir)
if err != nil {
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 {
e.Stdin = os.Stdin
}
if e.Stdout == nil {
e.Stdout = os.Stdout
}
if e.Stderr == nil {
e.Stderr = os.Stderr
}
e.Logger = &logger.Logger{
Stdout: e.Stdout,
Stderr: e.Stderr,
Verbose: e.Verbose,
}
switch {
case version.IsV1(v):
e.Compiler = &compilerv1.CompilerV1{
Dir: e.Dir,
Vars: e.taskvars,
Logger: e.Logger,
}
case version.IsV2(v), version.IsV21(v):
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
TaskfileVars: e.Taskfile.Vars,
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
case version.IsV22(v):
return fmt.Errorf(`task: Taskfile versions greater than v2.1 not implemented in the version of Task`)
}
if !version.IsV21(v) && e.Taskfile.Output != "" {
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
}
switch e.Taskfile.Output {
case "", "interleaved":
e.Output = output.Interleaved{}
case "group":
e.Output = output.Group{}
case "prefixed":
e.Output = output.Prefixed{}
default:
return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
}
if !version.IsV21(v) {
err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
for _, task := range e.Taskfile.Tasks {
if task.IgnoreError {
return err
}
for _, cmd := range task.Cmds {
if cmd.IgnoreError {
return err
}
}
}
}
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
for k := range e.Taskfile.Tasks {
e.taskCallCount[k] = new(int32)
}
return nil
}
// RunTask runs a task by its name
func (e *Executor) RunTask(ctx context.Context, call Call) error {
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
t, err := e.CompiledTask(call)
if err != nil {
return err
@@ -123,13 +179,13 @@ func (e *Executor) RunTask(ctx context.Context, call Call) error {
}
if !e.Force {
upToDate, err := t.isUpToDate(ctx)
upToDate, err := isTaskUpToDate(ctx, t)
if err != nil {
return err
}
if upToDate {
if !e.Silent {
e.errf(`task: Task "%s" is up to date`, t.Task)
e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
}
return nil
}
@@ -137,58 +193,80 @@ func (e *Executor) RunTask(ctx context.Context, call Call) error {
for i := range t.Cmds {
if err := e.runCommand(ctx, t, call, i); err != nil {
if err2 := t.statusOnError(); err2 != nil {
e.verboseErrf("task: error cleaning status on error: %v", err2)
if err2 := statusOnError(t); err2 != nil {
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf("task: task error ignored: %v", err)
continue
}
return &taskRunError{t.Task, err}
}
}
return nil
}
func (e *Executor) runDeps(ctx context.Context, t *Task) error {
func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
g, ctx := errgroup.WithContext(ctx)
for _, d := range t.Deps {
d := d
g.Go(func() error {
return e.RunTask(ctx, Call{Task: d.Task, Vars: d.Vars})
return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
})
}
return g.Wait()
}
func (e *Executor) runCommand(ctx context.Context, t *Task, call Call, i int) error {
func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
cmd := t.Cmds[i]
if cmd.Cmd == "" {
return e.RunTask(ctx, Call{Task: cmd.Task, Vars: cmd.Vars})
}
switch {
case cmd.Task != "":
return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
case cmd.Cmd != "":
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
e.Logger.Errf(cmd.Cmd)
}
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
e.errf(cmd.Cmd)
}
if e.Dry {
return nil
}
return execext.RunCommand(&execext.RunCommandOptions{
Context: ctx,
Command: cmd.Cmd,
Dir: t.Dir,
Env: t.getEnviron(),
Stdin: e.Stdin,
Stdout: e.Stdout,
Stderr: e.Stderr,
})
stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
defer stdOut.Close()
defer stdErr.Close()
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: cmd.Cmd,
Dir: t.Dir,
Env: getEnviron(t),
Stdin: e.Stdin,
Stdout: stdOut,
Stderr: stdErr,
})
if execext.IsExitError(err) && cmd.IgnoreError {
e.Logger.VerboseErrf("task: command error ignored: %v", err)
return nil
}
return err
default:
return nil
}
}
func (t *Task) getEnviron() []string {
func getEnviron(t *taskfile.Task) []string {
if t.Env == nil {
return nil
}
envs := 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))
}
return envs

View File

@@ -10,7 +10,9 @@ import (
"testing"
"github.com/go-task/task"
"github.com/go-task/task/internal/taskfile"
"github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
)
@@ -37,8 +39,8 @@ func (fct fileContentTest) Run(t *testing.T) {
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.ReadTaskfile(), "e.ReadTaskfile()")
assert.NoError(t, e.Run(task.Call{Task: fct.Target}), "e.Run(target)")
assert.NoError(t, e.Setup(), "e.Setup()")
assert.NoError(t, e.Run(taskfile.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
@@ -51,7 +53,6 @@ func (fct fileContentTest) Run(t *testing.T) {
assert.Equal(t, expectContent, s, "unexpected file content")
})
}
}
func TestEnv(t *testing.T) {
@@ -66,9 +67,9 @@ func TestEnv(t *testing.T) {
tt.Run(t)
}
func TestVars(t *testing.T) {
func TestVarsV1(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars",
Dir: "testdata/vars/v1",
Target: "default",
TrimSpace: true,
Files: map[string]string{
@@ -102,30 +103,70 @@ func TestVars(t *testing.T) {
tt.Target = "hello"
tt.Run(t)
}
func TestMultilineVars(t *testing.T) {
func TestVarsV2(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/vars/multiline",
Dir: "testdata/vars/v2",
Target: "default",
TrimSpace: false,
TrimSpace: true,
Files: map[string]string{
// Note:
// - task does not strip a trailing newline from var entries
// - task strips one trailing newline from shell output
// - the cat command adds a trailing newline
"echo_foobar.txt": "foo\nbar\n",
"echo_n_foobar.txt": "foo\nbar\n",
"echo_n_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n",
"var_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n\n",
"var_catlines.txt": " foo bar foobar baz \n",
"var_enumfile.txt": "0:\n1:\n2:foo\n3: bar\n4:foobar\n5:\n6:baz\n7:\n8:\n",
"foo.txt": "foo",
"bar.txt": "bar",
"baz.txt": "baz",
"tmpl_foo.txt": "foo",
"tmpl_bar.txt": "bar",
"tmpl_foo2.txt": "foo2",
"tmpl_bar2.txt": "bar2",
"shtmpl_foo.txt": "foo",
"shtmpl_foo2.txt": "foo2",
"nestedtmpl_foo.txt": "<no value>",
"nestedtmpl_foo2.txt": "foo2",
"foo2.txt": "foo2",
"bar2.txt": "bar2",
"baz2.txt": "baz2",
"tmpl2_foo.txt": "<no value>",
"tmpl2_foo2.txt": "foo2",
"tmpl2_bar.txt": "<no value>",
"tmpl2_bar2.txt": "bar2",
"shtmpl2_foo.txt": "<no value>",
"shtmpl2_foo2.txt": "foo2",
"nestedtmpl2_foo2.txt": "<no value>",
"override.txt": "bar",
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
},
}
tt.Run(t)
// Ensure identical results when running hello task directly.
tt.Target = "hello"
tt.Run(t)
}
func TestMultilineVars(t *testing.T) {
for _, dir := range []string{"testdata/vars/v1/multiline", "testdata/vars/v2/multiline"} {
tt := fileContentTest{
Dir: dir,
Target: "default",
TrimSpace: false,
Files: map[string]string{
// Note:
// - task does not strip a trailing newline from var entries
// - task strips one trailing newline from shell output
// - the cat command adds a trailing newline
"echo_foobar.txt": "foo\nbar\n",
"echo_n_foobar.txt": "foo\nbar\n",
"echo_n_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n",
"var_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n\n",
"var_catlines.txt": " foo bar foobar baz \n",
"var_enumfile.txt": "0:\n1:\n2:foo\n3: bar\n4:foobar\n5:\n6:baz\n7:\n8:\n",
},
}
tt.Run(t)
}
}
func TestVarsInvalidTmpl(t *testing.T) {
const (
dir = "testdata/vars"
dir = "testdata/vars/v1"
target = "invalid-var-tmpl"
expectError = "template: :1: unexpected EOF"
)
@@ -135,8 +176,8 @@ func TestVarsInvalidTmpl(t *testing.T) {
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.ReadTaskfile(), "e.ReadTaskfile()")
assert.EqualError(t, e.Run(task.Call{Task: target}), expectError, "e.Run(target)")
assert.NoError(t, e.Setup(), "e.Setup()")
assert.EqualError(t, e.Run(taskfile.Call{Task: target}), expectError, "e.Run(target)")
}
func TestParams(t *testing.T) {
@@ -187,8 +228,8 @@ func TestDeps(t *testing.T) {
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.ReadTaskfile())
assert.NoError(t, e.Run(task.Call{Task: "default"}))
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "default"}))
for _, f := range files {
f = filepath.Join(dir, f)
@@ -208,21 +249,22 @@ func TestStatus(t *testing.T) {
t.Errorf("File should not exists: %v", err)
}
var buff bytes.Buffer
e := &task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
assert.NoError(t, e.ReadTaskfile())
assert.NoError(t, e.Run(task.Call{Task: "gen-foo"}))
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
if _, err := os.Stat(file); err != nil {
t.Errorf("File should exists: %v", err)
}
buff := bytes.NewBuffer(nil)
e.Stdout, e.Stderr = buff, buff
assert.NoError(t, e.Run(task.Call{Task: "gen-foo"}))
e.Silent = false
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
t.Errorf("Wrong output message: %s", buff.String())
@@ -253,7 +295,7 @@ func TestGenerates(t *testing.T) {
Stdout: buff,
Stderr: buff,
}
assert.NoError(t, e.ReadTaskfile())
assert.NoError(t, e.Setup())
for _, theTask := range []string{relTask, absTask} {
var destFile = filepath.Join(dir, theTask)
@@ -261,7 +303,7 @@ func TestGenerates(t *testing.T) {
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
// Run task for the first time.
assert.NoError(t, e.Run(task.Call{Task: theTask}))
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
if _, err := os.Stat(srcFile); err != nil {
t.Errorf("File should exists: %v", err)
@@ -276,7 +318,7 @@ func TestGenerates(t *testing.T) {
buff.Reset()
// Re-run task to ensure it's now found to be up-to-date.
assert.NoError(t, e.Run(task.Call{Task: theTask}))
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
if buff.String() != upToDate {
t.Errorf("Wrong output message: %s", buff.String())
}
@@ -305,16 +347,16 @@ func TestStatusChecksum(t *testing.T) {
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.ReadTaskfile())
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(task.Call{Task: "build"}))
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
for _, f := range files {
_, err := os.Stat(filepath.Join(dir, f))
assert.NoError(t, err)
}
buff.Reset()
assert.NoError(t, e.Run(task.Call{Task: "build"}))
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
}
@@ -344,6 +386,87 @@ func TestCyclicDep(t *testing.T) {
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.ReadTaskfile())
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(task.Call{Task: "task-1"}))
assert.NoError(t, e.Setup())
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(taskfile.Call{Task: "task-1"}))
}
func TestTaskVersion(t *testing.T) {
tests := []struct {
Dir string
Version string
}{
{"testdata/version/v1", "1"},
{"testdata/version/v2", "2"},
}
for _, test := range tests {
t.Run(test.Dir, func(t *testing.T) {
e := task.Executor{
Dir: test.Dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup())
assert.Equal(t, test.Version, e.Taskfile.Version)
assert.Equal(t, 2, len(e.Taskfile.Tasks))
})
}
}
func TestTaskIgnoreErrors(t *testing.T) {
const dir = "testdata/ignore_errors"
e := task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "task-should-pass"}))
assert.Error(t, e.Run(taskfile.Call{Task: "task-should-fail"}))
assert.NoError(t, e.Run(taskfile.Call{Task: "cmd-should-pass"}))
assert.Error(t, e.Run(taskfile.Call{Task: "cmd-should-fail"}))
}
func TestExpand(t *testing.T) {
const dir = "testdata/expand"
home, err := homedir.Dir()
if err != nil {
t.Errorf("Couldn't get $HOME: %v", err)
}
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "pwd"}))
assert.Equal(t, home, strings.TrimSpace(buff.String()))
}
func TestDry(t *testing.T) {
const dir = "testdata/dry"
file := filepath.Join(dir, "file.txt")
_ = os.Remove(file)
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Dry: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
t.Errorf("File should not exist %s", file)
}
}

View File

@@ -1,70 +0,0 @@
package task
import (
"fmt"
"io/ioutil"
"path/filepath"
"runtime"
"github.com/imdario/mergo"
"gopkg.in/yaml.v2"
)
// ReadTaskfile parses Taskfile from the disk
func (e *Executor) ReadTaskfile() error {
path := filepath.Join(e.Dir, TaskFilePath)
var err error
e.Tasks, err = e.readTaskfileData(path)
if err != nil {
return err
}
osTasks, err := e.readTaskfileData(fmt.Sprintf("%s_%s", path, runtime.GOOS))
if err != nil {
switch err.(type) {
case taskFileNotFound:
default:
return err
}
}
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
return err
}
for name, task := range e.Tasks {
task.Task = name
}
return e.readTaskvars()
}
func (e *Executor) readTaskfileData(path string) (tasks map[string]*Task, err error) {
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
return tasks, yaml.UnmarshalStrict(b, &tasks)
}
return nil, taskFileNotFound{path}
}
func (e *Executor) readTaskvars() error {
var (
file = filepath.Join(e.Dir, TaskvarsFilePath)
osSpecificFile = fmt.Sprintf("%s_%s", file, runtime.GOOS)
)
if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
if err := yaml.UnmarshalStrict(b, &e.taskvars); err != nil {
return err
}
}
if b, err := ioutil.ReadFile(osSpecificFile + ".yml"); err == nil {
osTaskvars := make(Vars, 10)
if err := yaml.UnmarshalStrict(b, &osTaskvars); err != nil {
return err
}
for k, v := range osTaskvars {
e.taskvars[k] = v
}
}
return nil
}

View File

@@ -2,6 +2,7 @@ build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./**/glob-with-inexistent-file.txt
- ./source.txt
generates:
- ./generated.txt

6
testdata/dry/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: '2'
tasks:
build:
cmds:
- touch file.txt

8
testdata/expand/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: '2'
tasks:
pwd:
cmds:
- pwd
dir: '~'
silent: true

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

@@ -0,0 +1,20 @@
version: '2'
tasks:
task-should-pass:
cmds:
- exit 1
ignore_error: true
task-should-fail:
cmds:
- exit 1
cmd-should-pass:
cmds:
- cmd: exit 1
ignore_error: true
cmd-should-fail:
cmds:
- cmd: exit 1

1
testdata/vars/v2/.gitignore vendored Normal file
View File

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

56
testdata/vars/v2/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
version: '2'
vars:
NESTED2: "{{.NESTED1}}-TaskfileVars"
tasks:
default:
deps: [hello]
hello:
cmds:
- echo {{.FOO}} > foo.txt
- echo {{.BAR}} > bar.txt
- echo {{.BAZ}} > baz.txt
- echo '{{.TMPL_FOO}}' > tmpl_foo.txt
- echo '{{.TMPL_BAR}}' > tmpl_bar.txt
- echo '{{.TMPL_FOO2}}' > tmpl_foo2.txt
- echo '{{.TMPL_BAR2}}' > tmpl_bar2.txt
- echo '{{.SHTMPL_FOO}}' > shtmpl_foo.txt
- echo '{{.SHTMPL_FOO2}}' > shtmpl_foo2.txt
- echo '{{.NESTEDTMPL_FOO}}' > nestedtmpl_foo.txt
- echo '{{.NESTEDTMPL_FOO2}}' > nestedtmpl_foo2.txt
- echo {{.FOO2}} > foo2.txt
- echo {{.BAR2}} > bar2.txt
- echo {{.BAZ2}} > baz2.txt
- echo '{{.TMPL2_FOO}}' > tmpl2_foo.txt
- echo '{{.TMPL2_BAR}}' > tmpl2_bar.txt
- echo '{{.TMPL2_FOO2}}' > tmpl2_foo2.txt
- echo '{{.TMPL2_BAR2}}' > tmpl2_bar2.txt
- echo '{{.SHTMPL2_FOO}}' > shtmpl2_foo.txt
- echo '{{.SHTMPL2_FOO2}}' > shtmpl2_foo2.txt
- echo '{{.NESTEDTMPL2_FOO2}}' > nestedtmpl2_foo2.txt
- echo {{.OVERRIDE}} > override.txt
- echo '{{.NESTED3}}' > nested.txt
vars:
FOO: foo
BAR: $echo bar
BAZ:
sh: echo baz
TMPL_FOO: "{{.FOO}}"
TMPL_BAR: "{{.BAR}}"
TMPL_FOO2: "{{.FOO2}}"
TMPL_BAR2: "{{.BAR2}}"
SHTMPL_FOO:
sh: "echo '{{.FOO}}'"
SHTMPL_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL_FOO: "{{.TMPL_FOO}}"
NESTEDTMPL_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "bar"
NESTED3: "{{.NESTED2}}-TaskVars"
invalid-var-tmpl:
vars:
CHARS: "abcd"
INVALID: "{{range .CHARS}}no end"

13
testdata/vars/v2/Taskvars.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
FOO2: foo2
BAR2: $echo bar2
BAZ2:
sh: echo baz2
TMPL2_FOO: "{{.FOO}}"
TMPL2_BAR: "{{.BAR}}"
TMPL2_FOO2: "{{.FOO2}}"
TMPL2_BAR2: "{{.BAR2}}"
SHTMPL2_FOO2:
sh: "echo '{{.FOO2}}'"
NESTEDTMPL2_FOO2: "{{.TMPL2_FOO2}}"
OVERRIDE: "foo"
NESTED1: "Taskvars"

45
testdata/vars/v2/multiline/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
version: '2'
tasks:
default:
vars:
MULTILINE: "\n\nfoo\n bar\nfoobar\n\nbaz\n\n"
cmds:
- task: file
vars:
CONTENT:
sh: "echo 'foo\nbar'"
FILE: "echo_foobar.txt"
- task: file
vars:
CONTENT:
sh: "echo -n 'foo\nbar'"
FILE: "echo_n_foobar.txt"
- task: file
vars:
CONTENT:
sh: echo -n "{{.MULTILINE}}"
FILE: "echo_n_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE}}"
FILE: "var_multiline.txt"
- task: file
vars:
CONTENT: "{{.MULTILINE | catLines}}"
FILE: "var_catlines.txt"
- task: enumfile
vars:
LINES: "{{.MULTILINE}}"
FILE: "var_enumfile.txt"
file:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{.CONTENT}}
EOF
enumfile:
cmds:
- |
cat << EOF > '{{.FILE}}'
{{range $i, $line := .LINES| splitLines}}{{$i}}:{{$line}}
{{end}}EOF

9
testdata/version/v1/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: 1
tasks:
foo:
cmds:
- echo "Foo"
bar:
cmds:
- echo "Bar"

9
testdata/version/v2/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: 2
tasks:
foo:
cmds:
- echo "Foo"
bar:
cmds:
- echo "Bar"

View File

@@ -1,364 +1,81 @@
package task
import (
"bytes"
"errors"
"os"
"path/filepath"
"runtime"
"strings"
"text/template"
"github.com/go-task/task/internal/execext"
"github.com/go-task/task/internal/taskfile"
"github.com/go-task/task/internal/templater"
"github.com/Masterminds/sprig"
"github.com/mitchellh/go-homedir"
"mvdan.cc/sh/shell"
)
var (
// TaskvarsFilePath file containing additional variables.
TaskvarsFilePath = "Taskvars"
)
var (
// ErrCantUnmarshalVar is returned for invalid var YAML.
ErrCantUnmarshalVar = errors.New("task: can't unmarshal var value")
)
// Vars is a string[string] variables map.
type Vars map[string]Var
func getEnvironmentVariables() Vars {
var (
env = os.Environ()
m = make(Vars, len(env))
)
for _, e := range env {
keyVal := strings.SplitN(e, "=", 2)
key, val := keyVal[0], keyVal[1]
m[key] = Var{Static: val}
}
return m
}
func (vs Vars) toStringMap() (m map[string]string) {
m = make(map[string]string, len(vs))
for k, v := range vs {
if v.Sh != "" {
// Dynamic variable is not yet resolved; trigger
// <no value> to be used in templates.
continue
}
m[k] = v.Static
}
return
}
// Var represents either a static or dynamic variable.
type Var struct {
Static string
Sh string
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (v *Var) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
if strings.HasPrefix(str, "$") {
v.Sh = strings.TrimPrefix(str, "$")
} else {
v.Static = str
}
return nil
}
var sh struct {
Sh string
}
if err := unmarshal(&sh); err == nil {
v.Sh = sh.Sh
return nil
}
return ErrCantUnmarshalVar
}
// getVariables returns fully resolved variables following the priority order:
// 1. Call variables (should already have been resolved)
// 2. Environment (should not need to be resolved)
// 3. Task variables, resolved with access to:
// - call, taskvars and environment variables
// 4. Taskvars variables, resolved with access to:
// - environment variables
func (e *Executor) getVariables(call Call) (Vars, error) {
t, ok := e.Tasks[call.Task]
if !ok {
return nil, &taskNotFoundError{call.Task}
}
merge := func(dest Vars, srcs ...Vars) {
for _, src := range srcs {
for k, v := range src {
dest[k] = v
}
}
}
varsKeys := func(srcs ...Vars) []string {
m := make(map[string]struct{})
for _, src := range srcs {
for k := range src {
m[k] = struct{}{}
}
}
lst := make([]string, 0, len(m))
for k := range m {
lst = append(lst, k)
}
return lst
}
replaceVars := func(dest Vars, keys []string) error {
r := varReplacer{vars: dest}
for _, k := range keys {
v := dest[k]
dest[k] = Var{
Static: r.replace(v.Static),
Sh: r.replace(v.Sh),
}
}
return r.err
}
resolveShell := func(dest Vars, keys []string) error {
for _, k := range keys {
v := dest[k]
static, err := e.handleShVar(v)
if err != nil {
return err
}
dest[k] = Var{Static: static}
}
return nil
}
update := func(dest Vars, srcs ...Vars) error {
merge(dest, srcs...)
// updatedKeys ensures template evaluation is run only once.
updatedKeys := varsKeys(srcs...)
if err := replaceVars(dest, updatedKeys); err != nil {
return err
}
return resolveShell(dest, updatedKeys)
}
// Resolve taskvars variables to "result" with environment override variables.
override := getEnvironmentVariables()
result := make(Vars, len(e.taskvars)+len(t.Vars)+len(override))
if err := update(result, e.taskvars, override); err != nil {
return nil, err
}
// Resolve task variables to "result" with environment and call override variables.
merge(override, call.Vars)
if err := update(result, t.Vars, override); err != nil {
return nil, err
}
return result, nil
}
func (e *Executor) handleShVar(v Var) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
e.muDynamicCache.Lock()
defer e.muDynamicCache.Unlock()
if result, ok := e.dynamicCache[v.Sh]; ok {
return result, nil
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: e.Dir,
Stdout: &stdout,
Stderr: e.Stderr,
}
if err := execext.RunCommand(opts); err != nil {
return "", &dynamicVarError{cause: err, cmd: opts.Command}
}
// Trim a single trailing newline from the result to make most command
// output easier to use in shell commands.
result := strings.TrimSuffix(stdout.String(), "\n")
e.dynamicCache[v.Sh] = result
e.verboseErrf(`task: dynamic variable: '%s' result: '%s'`, v.Sh, result)
return result, nil
}
// CompiledTask returns a copy of a task, but replacing variables in almost all
// properties using the Go template package.
func (e *Executor) CompiledTask(call Call) (*Task, error) {
origTask, ok := e.Tasks[call.Task]
func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
origTask, ok := e.Taskfile.Tasks[call.Task]
if !ok {
return nil, &taskNotFoundError{call.Task}
}
vars, err := e.getVariables(call)
vars, err := e.Compiler.GetVariables(origTask, call)
if err != nil {
return nil, err
}
r := varReplacer{vars: vars}
r := templater.Templater{Vars: vars}
new := Task{
Task: origTask.Task,
Desc: r.replace(origTask.Desc),
Sources: r.replaceSlice(origTask.Sources),
Generates: r.replaceSlice(origTask.Generates),
Status: r.replaceSlice(origTask.Status),
Dir: r.replace(origTask.Dir),
Vars: nil,
Env: r.replaceVars(origTask.Env),
Silent: origTask.Silent,
Method: r.replace(origTask.Method),
new := taskfile.Task{
Task: origTask.Task,
Desc: r.Replace(origTask.Desc),
Sources: r.ReplaceSlice(origTask.Sources),
Generates: r.ReplaceSlice(origTask.Generates),
Status: r.ReplaceSlice(origTask.Status),
Dir: r.Replace(origTask.Dir),
Vars: nil,
Env: r.ReplaceVars(origTask.Env),
Silent: origTask.Silent,
Method: r.Replace(origTask.Method),
Prefix: r.Replace(origTask.Prefix),
IgnoreError: origTask.IgnoreError,
}
new.Dir, err = homedir.Expand(new.Dir)
new.Dir, err = shell.Expand(new.Dir, nil)
if err != nil {
return nil, err
}
if e.Dir != "" && !filepath.IsAbs(new.Dir) {
new.Dir = filepath.Join(e.Dir, new.Dir)
}
if new.Prefix == "" {
new.Prefix = new.Task
}
for k, v := range new.Env {
static, err := e.handleShVar(v)
static, err := e.Compiler.HandleDynamicVar(v)
if err != nil {
return nil, err
}
new.Env[k] = Var{Static: static}
new.Env[k] = taskfile.Var{Static: static}
}
if len(origTask.Cmds) > 0 {
new.Cmds = make([]*Cmd, len(origTask.Cmds))
new.Cmds = make([]*taskfile.Cmd, len(origTask.Cmds))
for i, cmd := range origTask.Cmds {
new.Cmds[i] = &Cmd{
Task: r.replace(cmd.Task),
Silent: cmd.Silent,
Cmd: r.replace(cmd.Cmd),
Vars: r.replaceVars(cmd.Vars),
new.Cmds[i] = &taskfile.Cmd{
Task: r.Replace(cmd.Task),
Silent: cmd.Silent,
Cmd: r.Replace(cmd.Cmd),
Vars: r.ReplaceVars(cmd.Vars),
IgnoreError: cmd.IgnoreError,
}
}
}
if len(origTask.Deps) > 0 {
new.Deps = make([]*Dep, len(origTask.Deps))
new.Deps = make([]*taskfile.Dep, len(origTask.Deps))
for i, dep := range origTask.Deps {
new.Deps[i] = &Dep{
Task: r.replace(dep.Task),
Vars: r.replaceVars(dep.Vars),
new.Deps[i] = &taskfile.Dep{
Task: r.Replace(dep.Task),
Vars: r.ReplaceVars(dep.Vars),
}
}
}
return &new, r.err
}
// varReplacer is a help struct that allow us to call "replaceX" funcs multiple
// times, without having to check for error each time. The first error that
// happen will be assigned to r.err, and consecutive calls to funcs will just
// return the zero value.
type varReplacer struct {
vars Vars
strMap map[string]string
err error
}
func (r *varReplacer) replace(str string) string {
if r.err != nil || str == "" {
return ""
}
templ, err := template.New("").Funcs(templateFuncs).Parse(str)
if err != nil {
r.err = err
return ""
}
if r.strMap == nil {
r.strMap = r.vars.toStringMap()
}
var b bytes.Buffer
if err = templ.Execute(&b, r.strMap); err != nil {
r.err = err
return ""
}
return b.String()
}
func (r *varReplacer) replaceSlice(strs []string) []string {
if r.err != nil || len(strs) == 0 {
return nil
}
new := make([]string, len(strs))
for i, str := range strs {
new[i] = r.replace(str)
}
return new
}
func (r *varReplacer) replaceVars(vars Vars) Vars {
if r.err != nil || len(vars) == 0 {
return nil
}
new := make(Vars, len(vars))
for k, v := range vars {
new[k] = Var{
Static: r.replace(v.Static),
Sh: r.replace(v.Sh),
}
}
return new
}
var (
templateFuncs template.FuncMap
)
func init() {
taskFuncs := template.FuncMap{
"OS": func() string { return runtime.GOOS },
"ARCH": func() string { return runtime.GOARCH },
"catLines": func(s string) string {
s = strings.Replace(s, "\r\n", " ", -1)
return strings.Replace(s, "\n", " ", -1)
},
"splitLines": func(s string) []string {
s = strings.Replace(s, "\r\n", "\n", -1)
return strings.Split(s, "\n")
},
"fromSlash": func(path string) string {
return filepath.FromSlash(path)
},
"toSlash": func(path string) string {
return filepath.ToSlash(path)
},
"exeExt": func() string {
if runtime.GOOS == "windows" {
return ".exe"
}
return ""
},
// IsSH is deprecated.
"IsSH": func() bool { return true },
}
// Deprecated aliases for renamed functions.
taskFuncs["FromSlash"] = taskFuncs["fromSlash"]
taskFuncs["ToSlash"] = taskFuncs["toSlash"]
taskFuncs["ExeExt"] = taskFuncs["exeExt"]
templateFuncs = sprig.TxtFuncMap()
for k, v := range taskFuncs {
templateFuncs[k] = v
}
return &new, r.Err()
}

View File

@@ -1,25 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
# Setting sudo access to false will let Travis CI use containers rather than
# VMs to run the tests. For more details see:
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
sudo: false
script:
- GO15VENDOREXPERIMENT=1 make setup
- GO15VENDOREXPERIMENT=1 make test
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,72 +0,0 @@
# 1.4.0 (2017-10-04)
## Changed
- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill)
# 1.3.1 (2017-07-10)
## Fixed
- Fixed #57: number comparisons in prerelease sometimes inaccurate
# 1.3.0 (2017-05-02)
## Added
- #45: Added json (un)marshaling support (thanks @mh-cbon)
- Stability marker. See https://masterminds.github.io/stability/
## Fixed
- #51: Fix handling of single digit tilde constraint (thanks @dgodd)
## Changed
- #55: The godoc icon moved from png to svg
# 1.2.3 (2017-04-03)
## Fixed
- #46: Fixed 0.x.x and 0.0.x in constraints being treated as *
# Release 1.2.2 (2016-12-13)
## Fixed
- #34: Fixed issue where hyphen range was not working with pre-release parsing.
# Release 1.2.1 (2016-11-28)
## Fixed
- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha"
properly.
# Release 1.2.0 (2016-11-04)
## Added
- #20: Added MustParse function for versions (thanks @adamreese)
- #15: Added increment methods on versions (thanks @mh-cbon)
## Fixed
- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and
might not satisfy the intended compatibility. The change here ignores pre-releases
on constraint checks (e.g., ~ or ^) when a pre-release is not part of the
constraint. For example, `^1.2.3` will ignore pre-releases while
`^1.2.3-alpha` will include them.
# Release 1.1.1 (2016-06-30)
## Changed
- Issue #9: Speed up version comparison performance (thanks @sdboyer)
- Issue #8: Added benchmarks (thanks @sdboyer)
- Updated Go Report Card URL to new location
- Updated Readme to add code snippet formatting (thanks @mh-cbon)
- Updating tagging to v[SemVer] structure for compatibility with other tools.
# Release 1.1.0 (2016-03-11)
- Issue #2: Implemented validation to provide reasons a versions failed a
constraint.
# Release 1.0.1 (2015-12-31)
- Fixed #1: * constraint failing on valid versions.
# Release 1.0.0 (2015-10-20)
- Initial release

View File

@@ -1,36 +0,0 @@
.PHONY: setup
setup:
go get -u gopkg.in/alecthomas/gometalinter.v1
gometalinter.v1 --install
.PHONY: test
test: validate lint
@echo "==> Running tests"
go test -v
.PHONY: validate
validate:
@echo "==> Running static validations"
@gometalinter.v1 \
--disable-all \
--enable deadcode \
--severity deadcode:error \
--enable gofmt \
--enable gosimple \
--enable ineffassign \
--enable misspell \
--enable vet \
--tests \
--vendor \
--deadline 60s \
./... || exit_code=1
.PHONY: lint
lint:
@echo "==> Running linters"
@gometalinter.v1 \
--disable-all \
--enable golint \
--vendor \
--deadline 60s \
./... || :

View File

@@ -1,165 +0,0 @@
# SemVer
The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to:
* Parse semantic versions
* Sort semantic versions
* Check if a semantic version fits within a set of constraints
* Optionally work with a `v` prefix
[![Stability:
Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)
[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.svg)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver)
## Parsing Semantic Versions
To parse a semantic version use the `NewVersion` function. For example,
```go
v, err := semver.NewVersion("1.2.3-beta.1+build345")
```
If there is an error the version wasn't parseable. The version object has methods
to get the parts of the version, compare it to other versions, convert the
version back into a string, and get the original string. For more details
please see the [documentation](https://godoc.org/github.com/Masterminds/semver).
## Sorting Semantic Versions
A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/)
package from the standard library. For example,
```go
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
vs := make([]*semver.Version, len(raw))
for i, r := range raw {
v, err := semver.NewVersion(r)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
vs[i] = v
}
sort.Sort(semver.Collection(vs))
```
## Checking Version Constraints
Checking a version against version constraints is one of the most featureful
parts of the package.
```go
c, err := semver.NewConstraint(">= 1.2.3")
if err != nil {
// Handle constraint not being parseable.
}
v, _ := semver.NewVersion("1.3")
if err != nil {
// Handle version not being parseable.
}
// Check if the version meets the constraints. The a variable will be true.
a := c.Check(v)
```
## Basic Comparisons
There are two elements to the comparisons. First, a comparison string is a list
of comma separated and comparisons. These are then separated by || separated or
comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
greater than or equal to 4.2.3.
The basic comparisons are:
* `=`: equal (aliased to no operator)
* `!=`: not equal
* `>`: greater than
* `<`: less than
* `>=`: greater than or equal to
* `<=`: less than or equal to
_Note, according to the Semantic Version specification pre-releases may not be
API compliant with their release counterpart. It says,_
> _A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version._
_SemVer comparisons without a pre-release value will skip pre-release versions.
For example, `>1.2.3` will skip pre-releases when looking at a list of values
while `>1.2.3-alpha.1` will evaluate pre-releases._
## Hyphen Range Comparisons
There are multiple methods to handle ranges and the first is hyphens ranges.
These look like:
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5`
## Wildcards In Comparisons
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
for all comparison operators. When used on the `=` operator it falls
back to the pack level comparison (see tilde below). For example,
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
* `>= 1.2.x` is equivalent to `>= 1.2.0`
* `<= 2.x` is equivalent to `<= 3`
* `*` is equivalent to `>= 0.0.0`
## Tilde Range Comparisons (Patch)
The tilde (`~`) comparison operator is for patch level ranges when a minor
version is specified and major level changes when the minor number is missing.
For example,
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
* `~1` is equivalent to `>= 1, < 2`
* `~2.3` is equivalent to `>= 2.3, < 2.4`
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
* `~1.x` is equivalent to `>= 1, < 2`
## Caret Range Comparisons (Major)
The caret (`^`) comparison operator is for major level changes. This is useful
when comparisons of API versions as a major change is API breaking. For example,
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
* `^2.3` is equivalent to `>= 2.3, < 3`
* `^2.x` is equivalent to `>= 2.0.0, < 3`
# Validation
In addition to testing a version against a constraint, a version can be validated
against a constraint. When validation fails a slice of errors containing why a
version didn't meet the constraint is returned. For example,
```go
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
if err != nil {
// Handle constraint not being parseable.
}
v, _ := semver.NewVersion("1.3")
if err != nil {
// Handle version not being parseable.
}
// Validate a version against a constraint.
a, msgs := c.Validate(v)
// a is false
for _, m := range msgs {
fmt.Println(m)
// Loops over the errors which would read
// "1.3 is greater than 1.2.3"
// "1.3 is less than 1.4"
}
```
# Contribute
If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues)
or [create a pull request](https://github.com/Masterminds/semver/pulls).

View File

@@ -1,44 +0,0 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\Masterminds\semver
shallow_clone: true
environment:
GOPATH: C:\gopath
platform:
- x64
install:
- go version
- go env
- go get -u gopkg.in/alecthomas/gometalinter.v1
- set PATH=%PATH%;%GOPATH%\bin
- gometalinter.v1.exe --install
build_script:
- go install -v ./...
test_script:
- "gometalinter.v1 \
--disable-all \
--enable deadcode \
--severity deadcode:error \
--enable gofmt \
--enable gosimple \
--enable ineffassign \
--enable misspell \
--enable vet \
--tests \
--vendor \
--deadline 60s \
./... || exit_code=1"
- "gometalinter.v1 \
--disable-all \
--enable golint \
--vendor \
--deadline 60s \
./... || :"
- go test -v
deploy: off

View File

@@ -233,12 +233,6 @@ func constraintNotEqual(v *Version, c *constraint) bool {
func constraintGreaterThan(v *Version, c *constraint) bool {
// An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease
// exists. This that case.
if !isNonZero(c.con) && isNonZero(v) {
return true
}
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
// more details.
@@ -271,11 +265,6 @@ func constraintLessThan(v *Version, c *constraint) bool {
}
func constraintGreaterThanEqual(v *Version, c *constraint) bool {
// An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease
// exists. This that case.
if !isNonZero(c.con) && isNonZero(v) {
return true
}
// If there is a pre-release on the version but the constraint isn't looking
// for them assume that pre-releases are not compatible. See issue 21 for
@@ -415,12 +404,3 @@ func rewriteRange(i string) string {
return o
}
// Detect if a version is not zero (0.0.0)
func isNonZero(v *Version) bool {
if v.Major() != 0 || v.Minor() != 0 || v.Patch() != 0 || v.Prerelease() != "" {
return true
}
return false
}

View File

@@ -47,7 +47,7 @@ parts of the package.
// Handle constraint not being parseable.
}
v, _ := semver.NewVersion("1.3")
v, err := semver.NewVersion("1.3")
if err != nil {
// Handle version not being parseable.
}

View File

@@ -106,7 +106,7 @@ func MustParse(v string) *Version {
// Note, if the original version contained a leading v this version will not.
// See the Original() method to retrieve the original value. Semantic Versions
// don't contain a leading v per the spec. Instead it's optional on
// impelementation.
// implementation.
func (v *Version) String() string {
var buf bytes.Buffer
@@ -379,16 +379,15 @@ func comparePrePart(s, o string) int {
// When s or o are empty we can use the other in an attempt to determine
// the response.
if o == "" {
_, n := strconv.ParseInt(s, 10, 64)
if n != nil {
if s == "" {
if o != "" {
return -1
}
return 1
}
if s == "" {
_, n := strconv.ParseInt(o, 10, 64)
if n != nil {
if o == "" {
if s != "" {
return 1
}
return -1

View File

@@ -1,2 +0,0 @@
vendor/
/.glide

View File

@@ -1,24 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
# Setting sudo access to false will let Travis CI use containers rather than
# VMs to run the tests. For more details see:
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
sudo: false
script:
- make setup test
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,16 +0,0 @@
# Release 1.2.0 (2016-02-01)
- Added quote and squote
- Added b32enc and b32dec
- add now takes varargs
- biggest now takes varargs
# Release 1.1.0 (2015-12-29)
- Added #4: Added contains function. strings.Contains, but with the arguments
switched to simplify common pipelines. (thanks krancour)
- Added Travis-CI testing support
# Release 1.0.0 (2015-12-23)
- Initial release

View File

@@ -1,13 +0,0 @@
HAS_GLIDE := $(shell command -v glide;)
.PHONY: test
test:
go test -v .
.PHONY: setup
setup:
ifndef HAS_GLIDE
go get -u github.com/Masterminds/glide
endif
glide install

View File

@@ -1,81 +0,0 @@
# Sprig: Template functions for Go templates
[![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html)
[![Build Status](https://travis-ci.org/Masterminds/sprig.svg?branch=master)](https://travis-ci.org/Masterminds/sprig)
The Go language comes with a [built-in template
language](http://golang.org/pkg/text/template/), but not
very many template functions. This library provides a group of commonly
used template functions.
It is inspired by the template functions found in
[Twig](http://twig.sensiolabs.org/documentation) and also in various
JavaScript libraries, such as [underscore.js](http://underscorejs.org/).
## Usage
Template developers can read the [Sprig function documentation](http://masterminds.github.io/sprig/) to
learn about the >100 template functions available.
For Go developers wishing to include Sprig as a library in their programs,
API documentation is available [at GoDoc.org](http://godoc.org/github.com/Masterminds/sprig), but
read on for standard usage.
### Load the Sprig library
To load the Sprig `FuncMap`:
```go
import (
"github.com/Masterminds/sprig"
"html/template"
)
// This example illustrates that the FuncMap *must* be set before the
// templates themselves are loaded.
tpl := template.Must(
template.New("base").Funcs(sprig.FuncMap()).ParseGlob("*.html")
)
```
### Call the functions inside of templates
By convention, all functions are lowercase. This seems to follow the Go
idiom for template functions (as opposed to template methods, which are
TitleCase).
Example:
```
{{ "hello!" | upper | repeat 5 }}
```
Produces:
```
HELLO!HELLO!HELLO!HELLO!HELLO!
```
## Principles:
The following principles were used in deciding on which functions to add, and
determining how to implement them.
- Template functions should be used to build layout. Therefore, the following
types of operations are within the domain of template functions:
- Formatting
- Layout
- Simple type conversions
- Utilities that assist in handling common formatting and layout needs (e.g. arithmetic)
- Template functions should not return errors unless there is no way to print
a sensible value. For example, converting a string to an integer should not
produce an error if conversion fails. Instead, it should display a default
value that can be displayed.
- Simple math is necessary for grid layouts, pagers, and so on. Complex math
(anything other than arithmetic) should be done outside of templates.
- Template functions only deal with the data passed into them. They never retrieve
data from a source.
- Finally, do not override core Go template functions.

View File

@@ -8,10 +8,12 @@ import (
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/pem"
@@ -21,7 +23,7 @@ import (
"net"
"time"
uuid "github.com/satori/go.uuid"
"github.com/google/uuid"
"golang.org/x/crypto/scrypt"
)
@@ -30,9 +32,14 @@ func sha256sum(input string) string {
return hex.EncodeToString(hash[:])
}
func sha1sum(input string) string {
hash := sha1.Sum([]byte(input))
return hex.EncodeToString(hash[:])
}
// uuidv4 provides a safe and secure UUID v4 implementation
func uuidv4() string {
return fmt.Sprintf("%s", uuid.NewV4())
return fmt.Sprintf("%s", uuid.New())
}
var master_password_seed = "com.lyndir.masterpassword"
@@ -156,6 +163,49 @@ type certificate struct {
Key string
}
func buildCustomCertificate(b64cert string, b64key string) (certificate, error) {
crt := certificate{}
cert, err := base64.StdEncoding.DecodeString(b64cert)
if err != nil {
return crt, errors.New("unable to decode base64 certificate")
}
key, err := base64.StdEncoding.DecodeString(b64key)
if err != nil {
return crt, errors.New("unable to decode base64 private key")
}
decodedCert, _ := pem.Decode(cert)
if decodedCert == nil {
return crt, errors.New("unable to decode certificate")
}
_, err = x509.ParseCertificate(decodedCert.Bytes)
if err != nil {
return crt, fmt.Errorf(
"error parsing certificate: decodedCert.Bytes: %s",
err,
)
}
decodedKey, _ := pem.Decode(key)
if decodedKey == nil {
return crt, errors.New("unable to decode key")
}
_, err = x509.ParsePKCS1PrivateKey(decodedKey.Bytes)
if err != nil {
return crt, fmt.Errorf(
"error parsing prive key: decodedKey.Bytes: %s",
err,
)
}
crt.Cert = string(cert)
crt.Key = string(key)
return crt, nil
}
func generateCertificateAuthority(
cn string,
daysValid int,
@@ -319,8 +369,13 @@ func getBaseCertTemplate(
if err != nil {
return nil, err
}
serialNumberUpperBound := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberUpperBound)
if err != nil {
return nil, err
}
return &x509.Certificate{
SerialNumber: big.NewInt(1),
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: cn,
},

View File

@@ -66,7 +66,7 @@ func dateAgo(date interface{}) string {
t = time.Unix(int64(date), 0)
}
// Drop resolution to seconds
duration := time.Since(t) / time.Second * time.Second
duration := time.Since(t).Round(time.Second)
return duration.String()
}

View File

@@ -49,7 +49,6 @@ func empty(given interface{}) bool {
case reflect.Struct:
return false
}
return true
}
// coalesce returns the first non-empty value.
@@ -73,3 +72,12 @@ func toPrettyJson(v interface{}) string {
output, _ := json.MarshalIndent(v, "", " ")
return string(output)
}
// ternary returns the first value if the last value is true, otherwise returns the second value.
func ternary(vt interface{}, vf interface{}, v bool) interface{} {
if v {
return vt
}
return vf
}

View File

@@ -27,10 +27,12 @@ func pluck(key string, d ...map[string]interface{}) []interface{} {
return res
}
func keys(dict map[string]interface{}) []string {
func keys(dicts ...map[string]interface{}) []string {
k := []string{}
for key := range dict {
k = append(k, key)
for _, dict := range dicts {
for key := range dict {
k = append(k, key)
}
}
return k
}
@@ -84,3 +86,12 @@ func merge(dst map[string]interface{}, srcs ...map[string]interface{}) interface
}
return dst
}
func values(dict map[string]interface{}) []interface{} {
values := []interface{}{}
for _, value := range dict {
values = append(values, value)
}
return values
}

View File

@@ -110,6 +110,9 @@ Defaults:
because it is an empty value.
- compact: Return a copy of a list with all of the empty values removed.
'list 0 1 2 "" | compact' will return '[1 2]'
- ternary: Given a value,'true | ternary "b" "c"' will return "b".
'false | ternary "b" "c"' will return '"c"'. Similar to the JavaScript ternary
operator.
OS:
- env: Resolve an environment variable
@@ -160,7 +163,7 @@ Data Structures:
be assigned the empty string. Non-string keys are converted to strings as
follows: []byte are converted, fmt.Stringers will have String() called.
errors will have Error() called. All others will be passed through
fmt.Sprtinf("%v").
fmt.Sprintf("%v").
Lists Functions:
@@ -189,9 +192,10 @@ These are used to manipulate dicts.
- hasKey: Takes a dict and a key, and returns boolean true if the key is in
the dict.
- pluck: Given a key and one or more maps, get all of the values for that key.
- keys: Get an array of all of the keys in a dict.
- keys: Get an array of all of the keys in one or more dicts.
- pick: Select just the given keys out of the dict, and return a new dict.
- omit: Return a dict without the given keys.
- values: Returns a list (interface{}) of all dictionary values.
Math Functions:

View File

@@ -142,6 +142,7 @@ var genericMap = map[string]interface{}{
"nindent": nindent,
"replace": replace,
"plural": plural,
"sha1sum": sha1sum,
"sha256sum": sha256sum,
"toString": strval,
@@ -159,6 +160,8 @@ var genericMap = map[string]interface{}{
// split "/" foo/bar returns map[int]string{0: foo, 1: bar}
"split": split,
"splitList": func(sep, orig string) []string { return strings.Split(orig, sep) },
// splitn "/" foo/bar/fuu returns map[int]string{0: foo, 1: bar/fuu}
"splitn": splitn,
"toStrings": strslice,
"until": until,
@@ -202,6 +205,7 @@ var genericMap = map[string]interface{}{
"compact": compact,
"toJson": toJson,
"toPrettyJson": toPrettyJson,
"ternary": ternary,
// Reflection
"typeOf": typeOf,
@@ -239,6 +243,7 @@ var genericMap = map[string]interface{}{
"pick": pick,
"omit": omit,
"merge": merge,
"values": values,
"append": push, "push": push,
"prepend": prepend,
@@ -249,11 +254,13 @@ var genericMap = map[string]interface{}{
"reverse": reverse,
"uniq": uniq,
"without": without,
"has": func(needle interface{}, haystack []interface{}) bool { return inList(haystack, needle) },
"has": has,
"slice": slice,
// Crypto:
"genPrivateKey": generatePrivateKey,
"derivePassword": derivePassword,
"buildCustomCert": buildCustomCertificate,
"genCA": generateCertificateAuthority,
"genSelfSignedCert": generateSelfSignedCertificate,
"genSignedCert": generateSignedCertificate,

View File

@@ -1,33 +0,0 @@
hash: b9cc40bfd6dde74a94103b96700df1a9ab29a7fff5650216cf5a05f4fe72fb73
updated: 2017-05-02T16:01:04.617727646-06:00
imports:
- name: github.com/aokoli/goutils
version: 9c37978a95bd5c709a15883b6242714ea6709e64
- name: github.com/huandu/xstrings
version: 3959339b333561bf62a38b424fd41517c2c90f40
- name: github.com/imdario/mergo
version: 3e95a51e0639b4cf372f2ccf74c86749d747fbdc
- name: github.com/Masterminds/goutils
version: 45307ec16e3cd47cd841506c081f7afd8237d210
- name: github.com/Masterminds/semver
version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/stretchr/testify
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert
- name: golang.org/x/crypto
version: d172538b2cfce0c13cee31e647d0367aa8cd2486
subpackages:
- pbkdf2
- scrypt
testImports:
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib

View File

@@ -1,15 +0,0 @@
package: github.com/Masterminds/sprig
import:
- package: github.com/Masterminds/goutils
version: ^1.0.0
- package: github.com/satori/go.uuid
version: ^1.1.0
- package: golang.org/x/crypto
subpackages:
- scrypt
- package: github.com/Masterminds/semver
version: v1.2.2
- package: github.com/stretchr/testify
- package: github.com/imdario/mergo
version: ~0.2.2
- package: github.com/huandu/xstrings

View File

@@ -1,50 +1,135 @@
package sprig
import (
"fmt"
"reflect"
"sort"
)
// Reflection is used in these functions so that slices and arrays of strings,
// ints, and other types not implementing []interface{} can be worked with.
// For example, this is useful if you need to work on the output of regexs.
func list(v ...interface{}) []interface{} {
return v
}
func push(list []interface{}, v interface{}) []interface{} {
return append(list, v)
}
func push(list interface{}, v interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
func prepend(list []interface{}, v interface{}) []interface{} {
return append([]interface{}{v}, list...)
}
l := l2.Len()
nl := make([]interface{}, l)
for i := 0; i < l; i++ {
nl[i] = l2.Index(i).Interface()
}
func last(list []interface{}) interface{} {
l := len(list)
if l == 0 {
return nil
return append(nl, v)
default:
panic(fmt.Sprintf("Cannot push on type %s", tp))
}
return list[l-1]
}
func first(list []interface{}) interface{} {
if len(list) == 0 {
return nil
func prepend(list interface{}, v interface{}) []interface{} {
//return append([]interface{}{v}, list...)
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
nl := make([]interface{}, l)
for i := 0; i < l; i++ {
nl[i] = l2.Index(i).Interface()
}
return append([]interface{}{v}, nl...)
default:
panic(fmt.Sprintf("Cannot prepend on type %s", tp))
}
return list[0]
}
func rest(list []interface{}) []interface{} {
if len(list) == 0 {
return list
func last(list interface{}) interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
return l2.Index(l - 1).Interface()
default:
panic(fmt.Sprintf("Cannot find last on type %s", tp))
}
return list[1:]
}
func initial(list []interface{}) []interface{} {
l := len(list)
if l == 0 {
return list
func first(list interface{}) interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
return l2.Index(0).Interface()
default:
panic(fmt.Sprintf("Cannot find first on type %s", tp))
}
}
func rest(list interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
nl := make([]interface{}, l-1)
for i := 1; i < l; i++ {
nl[i-1] = l2.Index(i).Interface()
}
return nl
default:
panic(fmt.Sprintf("Cannot find rest on type %s", tp))
}
}
func initial(list interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
nl := make([]interface{}, l-1)
for i := 0; i < l-1; i++ {
nl[i] = l2.Index(i).Interface()
}
return nl
default:
panic(fmt.Sprintf("Cannot find initial on type %s", tp))
}
return list[:l-1]
}
func sortAlpha(list interface{}) []string {
@@ -59,34 +144,67 @@ func sortAlpha(list interface{}) []string {
return []string{strval(list)}
}
func reverse(v []interface{}) []interface{} {
// We do not sort in place because the incomming array should not be altered.
l := len(v)
c := make([]interface{}, l)
for i := 0; i < l; i++ {
c[l-i-1] = v[i]
func reverse(v interface{}) []interface{} {
tp := reflect.TypeOf(v).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(v)
l := l2.Len()
// We do not sort in place because the incoming array should not be altered.
nl := make([]interface{}, l)
for i := 0; i < l; i++ {
nl[l-i-1] = l2.Index(i).Interface()
}
return nl
default:
panic(fmt.Sprintf("Cannot find reverse on type %s", tp))
}
return c
}
func compact(list []interface{}) []interface{} {
res := []interface{}{}
for _, item := range list {
if !empty(item) {
res = append(res, item)
func compact(list interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
nl := []interface{}{}
var item interface{}
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if !empty(item) {
nl = append(nl, item)
}
}
return nl
default:
panic(fmt.Sprintf("Cannot compact on type %s", tp))
}
return res
}
func uniq(list []interface{}) []interface{} {
dest := []interface{}{}
for _, item := range list {
if !inList(dest, item) {
dest = append(dest, item)
func uniq(list interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
dest := []interface{}{}
var item interface{}
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if !inList(dest, item) {
dest = append(dest, item)
}
}
return dest
default:
panic(fmt.Sprintf("Cannot find uniq on type %s", tp))
}
return dest
}
func inList(haystack []interface{}, needle interface{}) bool {
@@ -98,12 +216,76 @@ func inList(haystack []interface{}, needle interface{}) bool {
return false
}
func without(list []interface{}, omit ...interface{}) []interface{} {
res := []interface{}{}
for _, i := range list {
if !inList(omit, i) {
res = append(res, i)
func without(list interface{}, omit ...interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
res := []interface{}{}
var item interface{}
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if !inList(omit, item) {
res = append(res, item)
}
}
return res
default:
panic(fmt.Sprintf("Cannot find without on type %s", tp))
}
}
func has(needle interface{}, haystack interface{}) bool {
tp := reflect.TypeOf(haystack).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(haystack)
var item interface{}
l := l2.Len()
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if reflect.DeepEqual(needle, item) {
return true
}
}
return false
default:
panic(fmt.Sprintf("Cannot find has on type %s", tp))
}
}
// $list := [1, 2, 3, 4, 5]
// slice $list -> list[0:5] = list[:]
// slice $list 0 3 -> list[0:3] = list[:3]
// slice $list 3 5 -> list[3:5]
// slice $list 3 -> list[3:5] = list[3:]
func slice(list interface{}, indices ...interface{}) interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
var start, end int
if len(indices) > 0 {
start = toInt(indices[0])
}
if len(indices) < 2 {
end = l
} else {
end = toInt(indices[1])
}
return l2.Slice(start, end).Interface()
default:
panic(fmt.Sprintf("list should be type of slice or array but %s", tp))
}
return res
}

View File

@@ -156,4 +156,4 @@ func round(a interface{}, p int, r_opt ...float64) float64 {
round = math.Floor(digit)
}
return round / pow
}
}

View File

@@ -32,4 +32,4 @@ func regexReplaceAllLiteral(regex string, s string, repl string) string {
func regexSplit(regex string, s string, n int) []string {
r := regexp.MustCompile(regex)
return r.Split(s, n)
}
}

View File

@@ -183,6 +183,15 @@ func split(sep, orig string) map[string]string {
return res
}
func splitn(sep string, n int, orig string) map[string]string {
parts := strings.SplitN(orig, sep, n)
res := make(map[string]string, len(parts))
for i, v := range parts {
res["_"+strconv.Itoa(i)] = v
}
return res
}
// substring creates a substring of the given string.
//
// If start is < 0, this calls string[:length].

View File

@@ -1,18 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
script:
- go test -v
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,8 +0,0 @@
# 1.0.1 (2017-05-31)
## Fixed
- #21: Fix generation of alphanumeric strings (thanks @dbarranco)
# 1.0.0 (2014-04-30)
- Initial release.

View File

@@ -1,70 +0,0 @@
GoUtils
===========
[![Stability: Maintenance](https://masterminds.github.io/stability/maintenance.svg)](https://masterminds.github.io/stability/maintenance.html)
[![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils) [![Build Status](https://travis-ci.org/Masterminds/goutils.svg?branch=master)](https://travis-ci.org/Masterminds/goutils) [![Build status](https://ci.appveyor.com/api/projects/status/sc2b1ew0m7f0aiju?svg=true)](https://ci.appveyor.com/project/mattfarina/goutils)
GoUtils provides users with utility functions to manipulate strings in various ways. It is a Go implementation of some
string manipulation libraries of Java Apache Commons. GoUtils includes the following Java Apache Commons classes:
* WordUtils
* RandomStringUtils
* StringUtils (partial implementation)
## Installation
If you have Go set up on your system, from the GOPATH directory within the command line/terminal, enter this:
go get github.com/Masterminds/goutils
If you do not have Go set up on your system, please follow the [Go installation directions from the documenation](http://golang.org/doc/install), and then follow the instructions above to install GoUtils.
## Documentation
GoUtils doc is available here: [![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils)
## Usage
The code snippets below show examples of how to use GoUtils. Some functions return errors while others do not. The first instance below, which does not return an error, is the `Initials` function (located within the `wordutils.go` file).
package main
import (
"fmt"
"github.com/Masterminds/goutils"
)
func main() {
// EXAMPLE 1: A goutils function which returns no errors
fmt.Println (goutils.Initials("John Doe Foo")) // Prints out "JDF"
}
Some functions return errors mainly due to illegal arguements used as parameters. The code example below illustrates how to deal with function that returns an error. In this instance, the function is the `Random` function (located within the `randomstringutils.go` file).
package main
import (
"fmt"
"github.com/Masterminds/goutils"
)
func main() {
// EXAMPLE 2: A goutils function which returns an error
rand1, err1 := goutils.Random (-1, 0, 0, true, true)
if err1 != nil {
fmt.Println(err1) // Prints out error message because -1 was entered as the first parameter in goutils.Random(...)
} else {
fmt.Println(rand1)
}
}
## License
GoUtils is licensed under the Apache License, Version 2.0. Please check the LICENSE.txt file or visit http://www.apache.org/licenses/LICENSE-2.0 for a copy of the license.
## Issue Reporting
Make suggestions or report issues using the Git issue tracker: https://github.com/Masterminds/goutils/issues
## Website
* [GoUtils webpage](http://Masterminds.github.io/goutils/)

View File

@@ -1,21 +0,0 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\Masterminds\goutils
shallow_clone: true
environment:
GOPATH: C:\gopath
platform:
- x64
build: off
install:
- go version
- go env
test_script:
- go test -v
deploy: off

View File

@@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@@ -1,14 +0,0 @@
language: go
go:
- 1.5.4
- 1.6.3
- 1.7
install:
- go get -v golang.org/x/tools/cmd/cover
script:
- go test -v -tags=safe ./spew
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
after_success:
- go get -v github.com/mattn/goveralls
- export PATH=$PATH:$HOME/gopath/bin
- goveralls -coverprofile=profile.cov -service=travis-ci

View File

@@ -2,7 +2,7 @@ ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

View File

@@ -1,205 +0,0 @@
go-spew
=======
[![Build Status](https://img.shields.io/travis/davecgh/go-spew.svg)]
(https://travis-ci.org/davecgh/go-spew) [![ISC License]
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
(https://coveralls.io/r/davecgh/go-spew?branch=master)
Go-spew implements a deep pretty printer for Go data structures to aid in
debugging. A comprehensive suite of tests with 100% test coverage is provided
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
report. Go-spew is licensed under the liberal ISC license, so it may be used in
open source or commercial projects.
If you're interested in reading about how this package came to life and some
of the challenges involved in providing a deep pretty printer, there is a blog
post about it
[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
## Documentation
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
(http://godoc.org/github.com/davecgh/go-spew/spew)
Full `go doc` style documentation for the project can be viewed online without
installing this package by using the excellent GoDoc site here:
http://godoc.org/github.com/davecgh/go-spew/spew
You can also view the documentation locally once the package is installed with
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
## Installation
```bash
$ go get -u github.com/davecgh/go-spew/spew
```
## Quick Start
Add this import line to the file you're working in:
```Go
import "github.com/davecgh/go-spew/spew"
```
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
```Go
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
```
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
and pointer addresses):
```Go
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
```
## Debugging a Web Application Example
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
```Go
package main
import (
"fmt"
"html"
"net/http"
"github.com/davecgh/go-spew/spew"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
```
## Sample Dump Output
```
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) {
(string) "one": (bool) true
}
}
([]uint8) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
```
## Sample Formatter Output
Double pointer to a uint8:
```
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
```
Pointer to circular struct with a uint8 field and a pointer to itself:
```
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
```
## Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available via the
spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
```
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables. This option
relies on access to the unsafe package, so it will not have any effect when
running in environments without access to the unsafe package such as Google
App Engine or with the "safe" build tag specified.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of capacities
for arrays, slices, maps and channels. This is useful when diffing data
structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are supported,
with other types sorted according to the reflect.Value.String() output
which guarantees display stability. Natural map order is used by
default.
* SpewKeys
SpewKeys specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only considered
if SortKeys is true.
```
## Unsafe Package Dependency
This package relies on the unsafe package to perform some of the more advanced
features, however it also supports a "limited" mode which allows it to work in
environments where the unsafe package is not available. By default, it will
operate in this mode on Google App Engine and when compiled with GopherJS. The
"safe" build tag may also be specified to force the package to build without
using the unsafe package.
## License
Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.

View File

@@ -1,22 +0,0 @@
#!/bin/sh
# This script uses gocov to generate a test coverage report.
# The gocov tool my be obtained with the following command:
# go get github.com/axw/gocov/gocov
#
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
# Check for gocov.
if ! type gocov >/dev/null 2>&1; then
echo >&2 "This script requires the gocov tool."
echo >&2 "You may obtain it with the following command:"
echo >&2 "go get github.com/axw/gocov/gocov"
exit 1
fi
# Only run the cgo tests if gcc is installed.
if type gcc >/dev/null 2>&1; then
(cd spew && gocov test -tags testcgo | gocov report)
else
(cd spew && gocov test | gocov report)
fi

View File

@@ -16,7 +16,9 @@
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
// Go versions prior to 1.4 are disabled because they use a different layout
// for interfaces which make the implementation of unsafeReflectValue more complex.
// +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew
@@ -34,80 +36,49 @@ const (
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
type flag uintptr
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
var (
// flagRO indicates whether the value field of a reflect.Value
// is read-only.
flagRO flag
// flagAddr indicates whether the address of the reflect.Value's
// value may be taken.
flagAddr flag
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// flagKindMask holds the bits that make up the kind
// part of the flags field. In all the supported versions,
// it is in the lower 5 bits.
const flagKindMask = flag(0x1f)
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Different versions of Go have used different
// bit layouts for the flags type. This table
// records the known combinations.
var okFlags = []struct {
ro, addr flag
}{{
// From Go 1.4 to 1.5
ro: 1 << 5,
addr: 1 << 7,
}, {
// Up to Go tip.
ro: 1<<5 | 1<<6,
addr: 1 << 8,
}}
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
var flagValOffset = func() uintptr {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
return field.Offset
}()
// flagField returns a pointer to the flag field of a reflect.Value.
func flagField(v *reflect.Value) *flag {
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
@@ -119,34 +90,56 @@ func init() {
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
func unsafeReflectValue(v reflect.Value) reflect.Value {
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
return v
}
flagFieldPtr := flagField(&v)
*flagFieldPtr &^= flagRO
*flagFieldPtr |= flagAddr
return v
}
// Sanity checks against future reflect package changes
// to the type or semantics of the Value.flag field.
func init() {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
panic("reflect.Value flag field has changed kind")
}
type t0 int
var t struct {
A t0
// t0 will have flagEmbedRO set.
t0
// a will have flagStickyRO set
a t0
}
vA := reflect.ValueOf(t).FieldByName("A")
va := reflect.ValueOf(t).FieldByName("a")
vt0 := reflect.ValueOf(t).FieldByName("t0")
// Infer flagRO from the difference between the flags
// for the (otherwise identical) fields in t.
flagPublic := *flagField(&vA)
flagWithRO := *flagField(&va) | *flagField(&vt0)
flagRO = flagPublic ^ flagWithRO
// Infer flagAddr from the difference between a value
// taken from a pointer and not.
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
flagNoPtr := *flagField(&vA)
flagPtr := *flagField(&vPtrA)
flagAddr = flagNoPtr ^ flagPtr
// Check that the inferred flags tally with one of the known versions.
for _, f := range okFlags {
if flagRO == f.ro && flagAddr == f.addr {
return
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
panic("reflect.Value read-only flag has changed semantics")
}

View File

@@ -16,7 +16,7 @@
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe
// +build js appengine safe disableunsafe !go1.4
package spew

Some files were not shown because too many files have changed in this diff Show More