Compare commits

...

89 Commits

Author SHA1 Message Date
Andrey Nering
b095ca5756 v3.2.1 2021-01-09 13:57:20 -03:00
Andrey Nering
4afc0e8ed0 Fixed some bugs and regressions regarding dynamic variables and directories
Closes #426
2021-01-09 13:51:06 -03:00
Andrey Nering
141b377b4e Partly revert 59d2733b88
Keep the old behavior on v2
2021-01-09 10:55:18 -03:00
Andrey Nering
402a478785 Update CHANGELOG 2021-01-09 10:46:53 -03:00
Andrey Nering
73680584f3 Upgrade github.com/go-task/slim-sprig 2021-01-07 13:56:07 -03:00
Andrey Nering
45dbbcd179 v3.2.0 2021-01-07 13:08:07 -03:00
Andrey Nering
83d25bfa00 Refactor: Fix import order
It should be: stdlib > libs > app
2021-01-07 11:48:33 -03:00
Andrey Nering
299e27af15 Fix build 2021-01-07 11:39:36 -03:00
Andrey Nering
ec4cd5ed48 Fix .task directory location
Closes #247
2021-01-07 11:36:09 -03:00
Andrey Nering
59d2733b88 Make dynamic variables run on the right directory
It was always running in the main Taskfile dir, even when the variable was
declared in an included taskfile in another directory or when task had a
custom dir.

Closes #384
2021-01-07 11:26:11 -03:00
Andrey Nering
cbdd088188 Remove manual event trigger 2021-01-05 11:36:30 -03:00
Andrey Nering
2d52485d7b Watch: Clear vars cache between runs
Closes #365
2021-01-05 11:19:34 -03:00
Andrey Nering
d830178ef8 Do more watch fixes
This improves the work done on #423
2021-01-05 10:48:04 -03:00
James Wendel
049984b4cc Update watch.go 2021-01-03 21:26:09 -07:00
James Wendel
d261a986ab Update watch.go
Watch: Stop removing and addings files all the time.
2021-01-03 17:08:16 -07:00
Andrey Nering
9b2e25735b v3.1.0 2021-01-03 19:37:09 -03:00
Andrey Nering
e09e75b0ba Fix a bug when the checksum up-to-date resolution is used by a task with a custom label: attribute
Closes #412

Co-authored-by: Adam Wasila <adam.wasila@gmail.com>
2021-01-03 19:22:38 -03:00
Andrey Nering
6630113fef Release ARMv6 and ARM64 binaries on Linux
Closes #375
Closes #418
2021-01-03 19:12:38 -03:00
Andrey Nering
b2f08c9c20 Merge pull request #415 from felladrin/patch-1
Fix duplicate task name on "Short task syntax" example
2021-01-02 11:02:49 -03:00
Andrey Nering
6a4315b7e7 Merge pull request #407 from bryceschober/patch-1
Use {{default}} for an overriding usage
2021-01-01 19:21:50 -03:00
Andrey Nering
8b3e62ff6d Add forgotten CHANGELOG entry for #358
Ref #121
2021-01-01 19:06:00 -03:00
Andrey Nering
f1d3f6740d Update CHANGELOG.md 2021-01-01 18:57:28 -03:00
Andrey Nering
9ccd1d920c .gitignore: Add /tmp 2021-01-01 18:30:14 -03:00
Andrey Nering
9674d75ff6 Evaluate included taskfiles in order of declaration
Closes #393
2021-01-01 18:27:50 -03:00
Andrey Nering
22fd74846d Use interp.Params("-e") intead of running "set -e" manually
This is an improvement for ac8e344173
2021-01-01 17:32:42 -03:00
Andrey Nering
777645888a New logo and color 2020-12-27 19:07:38 -03:00
Andrey Nering
ac8e344173 Run "set -e" automatically for every command
Without this, multiline command strings won't always exit when they fail.

Closes #403
2020-12-27 17:15:12 -03:00
Andrey Nering
16fad60833 execext: Small refactor 2020-12-27 16:51:00 -03:00
Andrey Nering
cb96a39b46 v3.0.1 2020-12-26 15:34:17 -03:00
Andrey Nering
a540634b5b Add about: to issue templates 2020-12-26 15:27:15 -03:00
Andrey Nering
e15576bc47 Update issue templates 2020-12-26 15:24:46 -03:00
Andrey Nering
95359760ae Documentation: go get is actually supported
Closes #395
2020-12-26 15:06:39 -03:00
Andrey Nering
be209cb7b6 Merge pull request #406 from ezhukov/patch-2
Update curl arguments in installation.md
2020-12-26 15:00:04 -03:00
Victor Nogueira
f5eb80759b Fix duplicate task name on "Short task syntax" example
To make it a valid YAML file and avoid the error:

```
yaml: unmarshal errors:
  line 6: mapping key "build" already defined at line 4
```
2020-12-07 13:00:25 +02:00
Andrey Nering
9f125502f8 Update Taskfile.yml
We now use modules. Dep is history
2020-12-05 19:10:49 -03:00
Andrey Nering
3f856c4b1c Merge pull request #414 from go-task/mvdan/sh-3.2.1
Upgrade mvdan/sh to v3.2.1
2020-12-05 19:04:48 -03:00
Andrey Nering
f55fb1e3a5 Upgrade mvdan/sh to v3.2.1 2020-12-05 18:59:34 -03:00
Bryce Schober
bf88bd5da5 Use {{default}} for an overriding usage
Fixes documentation confusion raised in #376.
2020-11-18 17:09:44 -08:00
Eugene Zhukov
b7112e02db Update curl arguments in installation.md
Double -s argument does not make sense according to curl manual page.
-s stands for silent, while -S (capital S) stands for showing the error.
When used in combination, curl shows an error message if it fails, but disables progress meter.

Finally, in the end of sh command there is -d, which stands for debug and contradicts -sS curl arguments.
I suggest to remove curl silencers all together, because more debug is better in CIs. I also suggest to use --location instead of -L for clarity.
2020-11-17 10:02:06 +02:00
Andrey Nering
b136166fc9 Merge pull request #387 from sheldonhull/patch-1
feat: improve installation script documentation for shell installs
2020-11-08 11:46:58 -03:00
sheldonhull
75727c3d68 fix: improve curl command install
- works on macOS
- works in Linux (tested in docker container)
2020-10-16 18:12:26 -05:00
sheldonhull
6c625b3359 fix: typo on curl install comment
fix from url to curl
2020-10-14 22:30:19 -05:00
Andrey Nering
60759a4e3b Post-fixes and CHANGELOG for #385 2020-10-12 21:16:09 -03:00
Andrey Nering
582a66bb2f Merge pull request #385 from chris-garrett/dev/378-missing-env
Resolves #378 - allow for missing env files as they may be bootstrapped.
2020-10-12 21:12:08 -03:00
Andrey Nering
d78f78bb5c Fix panic for empty tasks
Closes #338
Closes #362

Co-authored-by: Bharath Kumar <shettybharath4@gmail.com>
2020-10-12 21:03:13 -03:00
sheldonhull
71b7d062d5 feat: improve installation script documentation for shell installs
- The default didn't work well for me out of the gate.
- This is the modified version to support passing in the arguments easily as well as an example for installing to `/usr/local/bin` for using in Codespaces or equivalent development workflow.
2020-10-07 14:57:25 -05:00
Chris Garrett
c6138a0660 #378 - allow for missing env files as they may be bootstrapped. 2020-10-03 16:39:58 -06:00
Andrey Nering
ce4ac97269 Documentation updates 2020-09-06 14:33:28 -03:00
Andrey Nering
2088a86512 Merge pull request #372 from go-task/go-1-15
CI: Update Go version to 1.15.x
2020-09-05 17:59:55 -03:00
Andrey Nering
e296fe2b98 Merge pull request #371 from onedr0p/patch-1
Make fish completions compatible with MacOS
2020-09-05 17:59:24 -03:00
Andrey Nering
96b8890ecc CI: Update Go version to 1.15.x 2020-09-05 17:57:24 -03:00
Devin Buhl
db6fae2f5b Make completion compatible with MacOS 2020-09-03 18:52:36 -04:00
Andrey Nering
6743cdbb65 README: Add downloads badge 2020-08-25 18:15:51 -03:00
Andrey Nering
71466c9a27 Merge pull request #358 from damian-szulc/feature/allow-to-use-as-external-lib
Allow usage as an external library
2020-08-24 22:14:57 -03:00
Andrey Nering
1bdf7e3192 v3: Post-release improvements to the documentation
Closes #367
2020-08-24 21:43:08 -03:00
Andrey Nering
7285f3c844 Merge pull request #364 from shilangyu/fish-completions
Added fish completions
2020-08-24 21:20:04 -03:00
Andrey Nering
eb257d3aa7 Merge pull request #366 from jonasagx/patch-1
typo and grammar fixes
2020-08-24 21:17:36 -03:00
Jonas Galvão Xavier
87f11491d9 typo and grammar fixes 2020-08-22 13:33:04 -07:00
shilangyu
5735a02473 added fish completions 2020-08-22 11:59:17 +00:00
Damian Szulc
47dd9b5a03 Move args and taskfile packages to root directory 2020-08-19 10:59:58 +02:00
Andrey Nering
7652d7889b Some documentation improvements after the release of v3
Thanks @marco-m
2020-08-17 09:46:55 -03:00
Andrey Nering
dd2116c897 v3.0.0 2020-08-16 22:07:14 -03:00
Andrey Nering
c5566b3e94 Update version in the home page 2020-08-16 22:01:49 -03:00
Andrey Nering
30cbf02bff Update documentation to reflect the new default "method" 2020-08-16 21:59:42 -03:00
Andrey Nering
9e4e9b4f1a Doc: Update "Taskfile versions" documentation 2020-08-16 21:56:21 -03:00
Andrey Nering
6f290f28b6 On documentation: version: '2' -> version: '3' 2020-08-16 21:34:33 -03:00
Andrey Nering
6ff3c9015b On v3, treat all CLI variables as global variables
Closes #336
Ref #341

Co-authored-by: Egor Kovetskiy <e.kovetskiy@gmail.com>
2020-08-16 21:27:11 -03:00
Andrey Nering
e28b82b2b7 Upgrade mvdan.cc/sh to v3.1.2 2020-08-16 21:05:52 -03:00
Andrey Nering
3edf124f96 Merge pull request #220 from go-task/v3
v3 base branch
2020-08-16 15:54:20 -03:00
Andrey Nering
fb72b46a3c github.com/go-task/task/v2 -> github.com/go-task/task/v3 2020-08-16 15:48:19 -03:00
Andrey Nering
49bf395f61 Remove the vendor/ directory 2020-08-16 15:46:22 -03:00
Andrey Nering
eab14b6c49 Some improvements to #356 2020-08-15 19:13:30 -03:00
Chris Garrett
8b962fb8e8 #324 implement dotenv 2020-08-03 16:18:38 -06:00
Chris Garrett
e5a3c861cb Merge remote-tracking branch 'upstream/v3' into v3 2020-08-03 12:04:36 -06:00
Andrey Nering
c7bb3d63b0 Merge pull request #349 from central182/fix-typo
Fix some typos in the documentation
2020-07-19 16:02:58 -03:00
Arnold
e6b543c15e Fix some typos in the documentation 2020-07-07 16:25:27 +09:00
Andrey Nering
8137517d93 Merge branch 'master' into v3 2020-06-14 17:13:46 -03:00
Andrey Nering
572f6a7fab CHANGELOG, docs and nits for #321 and #337 2020-06-14 17:12:20 -03:00
Andrey Nering
c6d9201680 Merge pull request #337 from adamwasila/subtask-alias
Allow overriding the task name in the logger output
2020-06-14 17:01:28 -03:00
Andrey Nering
7dcb3af944 Merge pull request #347 from ronvanderheijden/master
Completion file honors includes
2020-06-14 16:38:13 -03:00
Adam Wasila
4bc183a8a1 Add basic unit tests for label attribute 2020-06-14 15:12:48 +02:00
Adam Wasila
9f83311931 Add label field to task definition
Label is an alternative name for task that replace it when printed in following context eg.:

- log: when given task is up to date and is skipped from execution
- log: when given task is NOT up to date (`--status` command)
- in `--summary` and `--list` commands output
2020-06-14 13:42:20 +02:00
Ron van der Heijden
10986d3a7c Accept : multiple times 2020-06-13 17:01:14 +02:00
Andrey Nering
f4f6efa547 Skip cleanup if task doesn't have any sources listed
Ref #333
2020-05-31 15:48:23 -03:00
Andrey Nering
bf64259af3 taskfile.dev: Add note about parallel deps
Reference: #331
2020-05-23 14:13:15 -03:00
Andrey Nering
6141ba84ce taskfile.dev: Switch completely from unpkg.com to jsdelivr.net 2020-05-23 14:00:38 -03:00
Andrey Nering
329902f0db taskfile.dev: Improve the installation page by using tabs 2020-05-23 13:46:03 -03:00
Andrey Nering
bfcaa7a443 taskfile.dev: Install Docsify Tabs plugin 2020-05-23 13:45:57 -03:00
Andrey Nering
45915bf0ed taskfile.dev: Remove tracking stuff 2020-05-23 13:45:50 -03:00
662 changed files with 1310 additions and 298664 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,3 @@
open_collective: task
patreon: andreynering
custom: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A&currency_code=BRL&source=url'

View File

@@ -1,6 +0,0 @@
<!--
If relevant, include the following information:
- Task version
- OS
- Example Taskfile showing the issue
-->

14
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,14 @@
---
name: Bug Report
about: Use the template to report bugs and issues
labels: bug
---
- Task version:
- Operating System:
### Example Taskfile showing the issue
```yaml
```

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions, Ideas and General Discussions
url: https://github.com/go-task/task/discussions
about: Ask questions and discuss general ideas with the community

View File

@@ -0,0 +1,12 @@
---
name: Feature Request
about: Use the template to make feature requests
labels: feature
---
Describe in detail what feature do you want to see in Task.
Give examples if possible.
Please, search if this wasn't proposed before, and if this is more like an idea
than a strong feature request, consider opening a
[discussion](https://github.com/go-task/task/discussions) instead.

View File

@@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14.x
go-version: 1.15.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v1

View File

@@ -5,7 +5,7 @@ jobs:
name: Test
strategy:
matrix:
go-version: [1.13.x, 1.14.x]
go-version: [1.14.x, 1.15.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:

1
.gitignore vendored
View File

@@ -27,3 +27,4 @@ tags
/bin
/testdata/vars/v1
/tmp

View File

@@ -8,6 +8,10 @@ build:
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 6
ignore:
- goos: darwin
goarch: 386

View File

@@ -1,7 +1,59 @@
# Changelog
# v3.0.0 - Preview 4
## v3.2.1
- Fixed some bugs and regressions regarding dynamic variables and directories
([#426](https://github.com/go-task/task/issues/426)).
- The [slim-sprig](https://github.com/go-task/slim-sprig) package was updated
with the upstream [sprig](https://github.com/Masterminds/sprig).
## v3.2.0
- Fix the `.task` directory being created in the task directory instead of the
Taskfile directory
([#247](https://github.com/go-task/task/issues/247)).
- Fix a bug where dynamic variables (those declared with `sh:`) were not
running in the task directory when the task has a custom dir or it was
in an included Taskfile
([#384](https://github.com/go-task/task/issues/384)).
- The watch feature (via the `--watch` flag) got a few different bug fixes and
should be more stable now
([#423](https://github.com/go-task/task/pull/423), [#365](https://github.com/go-task/task/issues/365)).
## v3.1.0
- Fix a bug when the checksum up-to-date resolution is used by a task
with a custom `label:` attribute
([#412](https://github.com/go-task/task/issues/412)).
- Starting from this release, we're releasing official ARMv6 and ARM64 binaries
for Linux
([#375](https://github.com/go-task/task/issues/375), [#418](https://github.com/go-task/task/issues/418)).
- Task now respects the order of declaration of included Taskfiles when
evaluating variables declaring by them
([#393](https://github.com/go-task/task/issues/393)).
- `set -e` is now automatically set on every command. This was done to fix an
issue where multiline string commands wouldn't really fail unless the
sentence was in the last line
([#403](https://github.com/go-task/task/issues/403)).
## v3.0.1
- Allow use as a library by moving the required packages out of the `internal`
directory
([#358](https://github.com/go-task/task/pull/358)).
- Do not error if a specified dotenv file does not exist
([#378](https://github.com/go-task/task/issues/378), [#385](https://github.com/go-task/task/pull/385)).
- Fix panic when you have empty tasks in your Taskfile
([#338](https://github.com/go-task/task/issues/338), [#362](https://github.com/go-task/task/pull/362)).
## v3.0.0
- On `v3`, all CLI variables will be considered global variables
([#336](https://github.com/go-task/task/issues/336), [#341](https://github.com/go-task/task/pull/341))
- Add support to `.env` like files
([#324](https://github.com/go-task/task/issues/324), [#356](https://github.com/go-task/task/pull/356)).
- Add `label:` to task so you can override the task name in the logs
([#321](https://github.com/go-task/task/issues/321]), [#337](https://github.com/go-task/task/pull/337)).
- Refactor how variables work on version 3
([#311](https://github.com/go-task/task/pull/311)).
- Disallow `expansions` on v3 since it has no effect.
@@ -9,18 +61,12 @@
- `Taskfile_{{OS}}.yml` is not automatically included anymore.
- Allow interpolation on `includes`, so you can manually include a Taskfile
based on operation system, for example.
# v3.0.0 - Preview 3
- Expose `.TASK` variable in templates with the task name
([#252](https://github.com/go-task/task/issues/252)).
- Implement short task syntax
([#194](https://github.com/go-task/task/issues/194), [#240](https://github.com/go-task/task/pull/240)).
- Added option to make included Taskfile run commands on its own directory
([#260](https://github.com/go-task/task/issues/260), [#144](https://github.com/go-task/task/issues/144))
# v3.0.0 - Preview 2
- Taskfiles in version 1 are not supported anymore
([#237](https://github.com/go-task/task/pull/237)).
- Added global `method:` option. With this option, you can set a default
@@ -35,9 +81,6 @@
This is useful for manual checking when using external, or even remote,
artifacts when using `status:`
([#216](https://github.com/go-task/task/pull/216)).
## v3.0.0 - Preview 1
- We're now using [slim-sprig](https://github.com/go-task/slim-sprig) instead of
[sprig](https://github.com/Masterminds/sprig), which allowed a file size
reduction of about 22%

View File

@@ -1,23 +1,15 @@
![Test](https://github.com/go-task/task/workflows/Test/badge.svg)
![GoReleaser](https://github.com/go-task/task/workflows/goreleaser/badge.svg)
<div align="center">
<a href="https://taskfile.dev">
<img src="docs/Logo.png" width="200px" height="200px" />
</a>
# Task
<h1>Task</h1>
Task is a task runner / build tool that aims to be simpler and easier to use
than, for example, [GNU Make](https://www.gnu.org/software/make/).
<p>
Task is a task runner / build tool that aims to be simpler and easier to use than, for example, <a href="https://www.gnu.org/software/make/">GNU Make<a>.
</p>
See [taskfile.dev](https://taskfile.dev) for documentation.
---
## Sponsors
[![Sponsors](https://opencollective.com/task/sponsors.svg?width=890)](https://opencollective.com/task)
## Backers
[![Backers](https://opencollective.com/task/backers.svg?width=890)](https://opencollective.com/task)
## Contributors
[![Contributors](https://opencollective.com/task/contributors.svg?width=890)](https://github.com/go-task/task/graphs/contributors)
<p>
See <a href="https://taskfile.dev">taskfile.dev</a> for the documentation.
</p>
</div>

View File

@@ -24,8 +24,14 @@ tasks:
env:
CGO_ENABLED: '0'
dl-deps:
desc: Downloads cli dependencies
mod:
desc: Downloads and tidy Go modules
cmds:
- go mod download
- go mod tidy
cli-deps:
desc: Downloads CLI dependencies
cmds:
- task: go-get
vars: {REPO: golang.org/x/lint/golint}
@@ -34,17 +40,6 @@ tasks:
- task: go-get
vars: {REPO: github.com/goreleaser/godownloader}
vendor:
desc: Sync vendor/ directory according to go.mod file
cmds:
- go mod vendor
update-deps:
desc: Updates dependencies
cmds:
- dep ensure
- dep ensure -update
clean:
desc: Cleans temp files and folders
cmds:
@@ -67,7 +62,7 @@ tasks:
cmds:
- goreleaser --snapshot --rm-dist
generate-install-script:
gen-install-script:
desc: Generate install script using https://github.com/goreleaser/godownloader
cmds:
- godownloader --repo go-task/task -o install-task.sh

View File

@@ -3,11 +3,36 @@ package args
import (
"strings"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/taskfile"
)
// Parse parses command line argument: tasks and vars of each task
func Parse(args ...string) ([]taskfile.Call, *taskfile.Vars) {
// ParseV3 parses command line argument: tasks and global variables
func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
var calls []taskfile.Call
var globals *taskfile.Vars
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, taskfile.Call{Task: arg})
continue
}
if globals == nil {
globals = &taskfile.Vars{}
}
name, value := splitVar(arg)
globals.Set(name, taskfile.Var{Static: value})
}
if len(calls) == 0 {
calls = append(calls, taskfile.Call{Task: "default"})
}
return calls, globals
}
// ParseV2 parses command line argument: tasks and vars of each task
func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
var calls []taskfile.Call
var globals *taskfile.Vars

View File

@@ -4,13 +4,104 @@ import (
"fmt"
"testing"
"github.com/go-task/task/v2/internal/args"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3/args"
"github.com/go-task/task/v3/taskfile"
)
func TestArgs(t *testing.T) {
func TestArgsV3(t *testing.T) {
tests := []struct {
Args []string
ExpectedCalls []taskfile.Call
ExpectedGlobals *taskfile.Vars
}{
{
Args: []string{"task-a", "task-b", "task-c"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
},
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO", "BAR", "BAZ"},
Mapping: map[string]taskfile.Var{
"FOO": taskfile.Var{Static: "bar"},
"BAR": taskfile.Var{Static: "baz"},
"BAZ": taskfile.Var{Static: "foo"},
},
},
},
{
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"CONTENT"},
Mapping: map[string]taskfile.Var{
"CONTENT": taskfile.Var{Static: "with some spaces"},
},
},
},
{
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO"},
Mapping: map[string]taskfile.Var{
"FOO": {Static: "bar"},
},
},
},
{
Args: nil,
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
},
{
Args: []string{},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
},
{
Args: []string{"FOO=bar", "BAR=baz"},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
},
ExpectedGlobals: &taskfile.Vars{
Keys: []string{"FOO", "BAR"},
Mapping: map[string]taskfile.Var{
"FOO": {Static: "bar"},
"BAR": {Static: "baz"},
},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
calls, globals := args.ParseV3(test.Args...)
assert.Equal(t, test.ExpectedCalls, calls)
assert.Equal(t, test.ExpectedGlobals, globals)
})
}
}
func TestArgsV2(t *testing.T) {
tests := []struct {
Args []string
ExpectedCalls []taskfile.Call
@@ -105,7 +196,7 @@ func TestArgs(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
calls, globals := args.Parse(test.Args...)
calls, globals := args.ParseV2(test.Args...)
assert.Equal(t, test.ExpectedCalls, calls)
assert.Equal(t, test.ExpectedGlobals, globals)
})

View File

@@ -9,11 +9,12 @@ import (
"path/filepath"
"syscall"
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/args"
"github.com/go-task/task/v2/internal/logger"
"github.com/spf13/pflag"
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/args"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile"
)
var (
@@ -141,13 +142,26 @@ func main() {
if err := e.Setup(); err != nil {
log.Fatal(err)
}
v, err := e.Taskfile.ParsedVersion()
if err != nil {
log.Fatal(err)
return
}
if list {
e.PrintTasksHelp()
return
}
calls, globals := args.Parse(pflag.Args()...)
var (
calls []taskfile.Call
globals *taskfile.Vars
)
if v >= 3.0 {
calls, globals = args.ParseV3(pflag.Args()...)
} else {
calls, globals = args.ParseV2(pflag.Args()...)
}
e.Taskfile.Vars.Merge(globals)
ctx := context.Background()

24
completion/fish/task.fish Normal file
View File

@@ -0,0 +1,24 @@
function __task_get_tasks --description "Prints all available tasks with their description"
task -l | sed '1d' | awk '{ $1=""; print $0 }' | tr ': ', '\t' | string trim
end
complete -c task -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was
specified.' -xa "(__task_get_tasks)"
complete -c task -s c -l color -d 'colored output (default true)'
complete -c task -s d -l dir -d 'sets directory of execution'
complete -c task -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
complete -c task -s f -l force -d 'forces execution even when the task is up-to-date'
complete -c task -s h -l help -d 'shows Task usage'
complete -c task -s i -l init -d 'creates a new Taskfile.yml in the current folder'
complete -c task -s l -l list -d 'lists tasks with description of current Taskfile'
complete -c task -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed"
complete -c task -s p -l parallel -d 'executes tasks provided on command line in parallel'
complete -c task -s s -l silent -d 'disables echoing'
complete -c task -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
complete -c task -l summary -d 'show summary about a task'
complete -c task -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
complete -c task -s v -l verbose -d 'enables verbose mode'
complete -c task -l version -d 'show Task version'
complete -c task -s w -l watch -d 'enables watch of the given task'

View File

@@ -5,7 +5,7 @@ function __list() {
local -a scripts
if [ -f Taskfile.yml ]; then
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/'))
scripts=($(task -l | sed '1d' | sed 's/^\* //' | awk '{ print $1 }' | sed 's/:$//' | sed 's/:/\\:/g'))
_describe 'script' scripts
fi
}

BIN
docs/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,5 +1,9 @@
# Task
<div align="center">
<img id="logo" src="/Logo.png" height="250px" width="250px" />
</div>
Task is a task runner / build tool that aims to be simpler and easier to use
than, for example, [GNU Make][make].
@@ -11,7 +15,7 @@ Once [installed](installation.md), you just need to describe your build tasks
using a simple [YAML][yaml] schema in a file called `Taskfile.yml`:
```yaml
version: '2'
version: '3'
tasks:
hello:
@@ -38,18 +42,6 @@ guide to check the full schema documentation and Task features.
if a given set of files haven't changed since last run (based either on its
timestamp or content).
## Sponsors
[![Sponsors](https://opencollective.com/task/sponsors.svg?width=890)](https://opencollective.com/task)
## Backers
[![Backers](https://opencollective.com/task/backers.svg?width=890)](https://opencollective.com/task)
## Contributors
[![Contributors](https://opencollective.com/task/contributors.svg?width=890)](https://github.com/go-task/task/graphs/contributors)
[make]: https://www.gnu.org/software/make/
[go]: https://golang.org/
[yaml]: http://yaml.org/

View File

@@ -10,3 +10,8 @@ tasks:
desc: Serves the documentation site locally
cmds:
- docsify serve .
ico:
desc: Generate favicon.ico from Logo.png
cmds:
- convert -background transparent "Logo.png" -define icon:auto-resize=16,24,32,48,64,72,96,128,256 "favicon.ico"

View File

@@ -4,5 +4,5 @@
- [Taskfile Versions](taskfile_versions.md)
- [Examples](examples.md)
- [Releasing Task](releasing_task.md)
- [Alternative Task Runners](alternative_task_runners.md)
- [Donate](donate.md)
- [![GitHub](https://icongram.jgog.in/simple/github.svg?color=808080&size=16)GitHub](https://github.com/go-task/task)

View File

@@ -1,17 +0,0 @@
# Alternative task runners
## YAML based
- [rliebz/tusk][tusk]
## Go based
- [magefile/mage][mage]
## Make similar
- [casey/just][just]
[tusk]: https://github.com/rliebz/tusk
[mage]: https://github.com/magefile/mage
[just]: https://github.com/casey/just

35
docs/donate.md Normal file
View File

@@ -0,0 +1,35 @@
# Donate
If you find this project useful, you can consider donating by using one of the
channels listed below.
This is just a way of saying "thank you", it won't give you any benefits like
higher priority on issues or something similar.
## Open Collective
Task is on [Open Collective](https://opencollective.com/task) and you have
these options to donate:
- [$2 per month](https://opencollective.com/task/contribute/backer-4034/checkout)
- [$5 per month](https://opencollective.com/task/contribute/supporter-8404/checkout)
- [$20 per month](https://opencollective.com/task/contribute/sponsor-4035/checkout)
- [Custom value - One-time donation option supported](https://opencollective.com/task/donate)
## Patreon
I'm also on [Patreon](https://www.patreon.com/andreynering) if
you prefer:
- [$5 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229277)
- [$10 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229276)
- [$15 per month](https://www.patreon.com/join/andreynering/checkout?rid=4229275)
You can choose a custom value on any of the links above.
Patreon does not support one-time donation. As a workaround you can fire a
subscription and cancel it once the donation was succeded.
## PayPal
- [Any value - One-time donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=GSVDU63RKG45A&currency_code=BRL&source=url)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -6,25 +6,21 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="A task runner / simpler Make alternative written in Go">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify-themeable/dist/css/theme-simple.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-themeable/dist/css/theme-simple.css">
<meta name="google-site-verification" content="VGAYkbdmuaciIDGkBe-eAg9yfZg0C6ostgonbGxxOa0" />
<script>
var SeedAndDewConfig = {};
(function() {
SeedAndDewConfig['adClass'] = "snd-ad";
/* * * DON'T EDIT BELOW THIS LINE * * */
SeedAndDewConfig['projectId'] = '16e0aed0-b265-48c9-9eae-0aad56147553';
SeedAndDewConfig['loadStartTime'] = performance.now();
SeedAndDewConfig['apiVersion'] = '2018-05-28'
SeedAndDewConfig['sessionId'] = Math.random().toString(36).substring(2, 15);
var snd = document.createElement('script');
snd.type = 'text/javascript';
snd.async = true;
snd.src = 'https://www.seedanddew.com/static/embed.min.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(snd);
})();
</script>
<style>
#logo {
transition: all 0.7s ease;
}
#logo:hover {
-webkit-transform: rotateZ(360deg);
-ms-transform: rotateZ(360deg);
transform: rotateZ(360deg);
}
.app-name-link img {
width: 125px;
}
</style>
</head>
<body>
<div id="app"></div>
@@ -32,18 +28,18 @@
window.$docsify = {
name: 'Task',
repo: 'go-task/task',
ga: 'UA-126286662-1',
themeColor: '#00add8',
logo: 'Logo.png',
themeColor: '#29beb0',
loadSidebar: true,
auto2top: true,
maxLevel: 3,
subMaxLevel: 3
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/ga.min.js"></script>
<script src="//unpkg.com/docsify-themeable"></script>
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-yaml.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-themeable/dist/js/docsify-themeable.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-tabs"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-yaml.min.js"></script>
</body>
</html>

View File

@@ -1,12 +1,12 @@
# Installation
## Binary
Task offers many installation methods. Check out the available methods below.
Or you can download the binary from the [releases][releases] page and add to
your $PATH. DEB and RPM packages are also available.
The `task_checksums.txt` file contains the sha256 checksum for each file.
## Package Managers
## Homebrew
<!-- tabs:start -->
#### **Homebrew**
If you're on macOS or Linux and have [Homebrew][homebrew] installed, getting
Task is as simple as running:
@@ -15,19 +15,17 @@ Task is as simple as running:
brew install go-task/tap/go-task
```
> This installation method is only currently supported on amd64 architectures.
#### **Snap**
## Snap
Task is available for [Snapcraft][snapcraft], but keep in mind that your
Task is available in [Snapcraft][snapcraft], but keep in mind that your
Linux distribution should allow classic confinement for Snaps to Task work
right:
```bash
sudo snap install task
sudo snap install task --classic
```
## Scoop
#### **Scoop**
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
to install Task like:
@@ -40,7 +38,7 @@ scoop install task
This installation method is community owned. After a new release of Task, it
may take some time until it's available on Scoop.
## Arch Linux
#### **AUR**
If you're on Arch Linux you can install Task from
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
@@ -53,45 +51,40 @@ yay -S taskfile-git
This installation method is community owned, but since it's `-git` version of
the package, it's always latest available version based on the Git repository.
## Go
<!-- tabs:end -->
Task now uses [Go Modules](https://github.com/golang/go/wiki/Modules), which
means you may have trouble compiling it on older Go versions or using
`$GOPATH`.
## Get The Binary
For CI environments we recommend using the [Install Script](#install-script)
instead, which is faster and more stable, since it'll just download the latest
released binary, instead of compiling the edge (master branch) version.
<!-- tabs:start -->
Installing in another directory:
#### **Binary**
```bash
git clone https://github.com/go-task/task
cd task
You can download the binary from the [releases page on GitHub][releases] and
add to your `$PATH`.
# compiling binary to $GOPATH/bin
go install -v ./cmd/task
DEB and RPM packages are also available.
# compiling it to another location
# use -o ./task.exe on Windows
go build -v -o ./task ./cmd/task
```
The `task_checksums.txt` file contains the SHA-256 checksum for each file.
Both methods requires having the [Go][go] environment properly setup locally.
## Install script
#### **Install Script**
We also have a [install script][installscript], which is very useful on
scenarios like CIs. Many thanks to [godownloader][godownloader] for allowing
scenarios like CIs. Many thanks to [GoDownloader][godownloader] for allowing
easily generating this script.
```bash
curl -sL https://taskfile.dev/install.sh | sh
# For Default Installion to ./bin with debug logging
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
# For Installation To /usr/local/bin for userwide access with debug logging
# May require sudo sh
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
```
> This method will download the binary on the local `./bin` directory by default.
## GitHub Actions
#### **GitHub Actions**
If you want to install Task in GitHub Actions you can try using
[this action](https://github.com/arduino/actions/tree/master/setup-taskfile)
@@ -104,8 +97,44 @@ by the Arduino team:
This installation method is community owned.
<!-- tabs:end -->
## Build From Source
<!-- tabs:start -->
#### **Go Modules**
First, make sure you have [Go][go] properly installed and setup.
You can easily install it globally by running:
```bash
go get -u github.com/go-task/task/v3/cmd/task
```
Or you can install into another directory:
```bash
git clone https://github.com/go-task/task
cd task
# Compiling binary to $GOPATH/bin
go install -v ./cmd/task
# Compiling it to another location.
# Use -o ./task.exe on Windows.
go build -v -o ./task ./cmd/task
```
> For CI environments we recommend using the [Install Script](#get-the-binary)
> instead, which is faster and more stable, since it'll just download the latest
> released binary, instead of compiling the edge (master branch) version.
<!-- tabs:end -->
[go]: https://golang.org/
[snapcraft]: https://snapcraft.io/
[snapcraft]: https://snapcraft.io/task
[homebrew]: https://brew.sh/
[installscript]: https://github.com/go-task/task/blob/master/install-task.sh
[releases]: https://github.com/go-task/task/releases

View File

@@ -56,7 +56,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
includes:
docker: ./docker/Taskfile.yml
output: prefixed
@@ -70,7 +70,7 @@ tasks:
# good
version: 2
version: '3'
includes:
docker: ./docker/Taskfile.yml
@@ -92,7 +92,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
tasks:
foo:
@@ -107,7 +107,7 @@ tasks:
# good
version: 2
version: '3'
tasks:
foo:
@@ -127,7 +127,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
vars:
binary_name: myapp
@@ -139,7 +139,7 @@ tasks:
# good
version: 2
version: '3'
vars:
BINARY_NAME: myapp
@@ -154,7 +154,7 @@ tasks:
```yaml
# bad
version: 2
version: '3'
tasks:
greet:
@@ -163,7 +163,7 @@ tasks:
# good
version: 2
version: '3'
tasks:
greet:
@@ -177,7 +177,7 @@ This convention is also used by most people for any Go templating.
```yaml
# bad
version: 2
version: '3'
tasks:
do_something_fancy:
@@ -186,7 +186,7 @@ tasks:
# good
version: 2
version: '3'
tasks:
do-something-fancy:
@@ -198,7 +198,7 @@ tasks:
```yaml
# good
version: 2
version: '3'
tasks:
docker:build:

View File

@@ -9,8 +9,8 @@ The Taskfile version follows the Task version. E.g. the change to Taskfile
version `2` means that Task `v2.0.0` should be release to support it.
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
`2.0.0` is accepted. If you choose to use `2.0` Task will not enable future
`2.1` features, but if you choose to use `2`, then any `2.x.x` features will be
available, but not `3.0.0+`.
## Version 1
@@ -35,7 +35,7 @@ The variable priority order was also different:
## Version 2.0
At version 2, we introduced the `version:` key, to allow us to envolve Task
At version 2, we introduced the `version:` key, to allow us to evolve Task
with new features without breaking existing Taskfiles. The new syntax is as
follows:
@@ -110,7 +110,7 @@ tasks:
prefix: server
```
From this version it's not also possible to ignore errors of a command or task
From this version it's also possible to ignore errors of a command or task
(check documentation [here][ignore_errors]):
```yaml
@@ -163,3 +163,63 @@ Please check the [documentation][includes]
[output]: usage.md#output-syntax
[ignore_errors]: usage.md#ignore-errors
[includes]: usage.md#including-other-taskfiles
## Version 3
These are some major changes done on `v3`:
- Task's output will now be colored
- Added support for `.env` like files
- Added `label:` setting to task so one can override how the task name
appear in the logs
- A global `method:` was added to allow setting the default method,
and Task's default changed to `checksum`
- Two magic variables were added when using `status:`: `CHECKSUM` and
`TIMESTAMP` which contains, respectively, the md5 checksum and greatest
modification timestamp of the files listed on `sources:`
- Also, the `TASK` variable is always available with the current task name
- CLI variables are always treated as global variables
- Added `dir:` option to `includes` to allow choosing on which directory an
included Taskfile will run:
```yaml
includes:
docs:
taskfile: ./docs
dir: ./docs
```
- Implemented short task syntax. All below syntaxes are equivalent:
```yaml
version: '3'
tasks:
print:
cmds:
- echo "Hello, World!"
```
```yaml
version: '3'
tasks:
print:
- echo "Hello, World!"
```
```yaml
version: '3'
tasks:
print: echo "Hello, World!"
```
- There was a major refactor on how variables are handled. They're now easier
to understand. The `expansions:` setting was removed as it became unncessary.
This is the order in which Task will process variables, each level can see
the variables set by the previous one and override those.
- Environment variables
- Global + CLI variables
- Call variables
- Task variables

View File

@@ -8,7 +8,7 @@ The example below allows compiling a Go app and uses [Minify][minify] to concat
and minify multiple CSS files into a single one.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -33,12 +33,14 @@ executable called must be available by the OS or in PATH.
If you omit a task name, "default" will be assumed.
## Environment
## Environment variables
### Task
You can use `env` to set custom environment variables for a specific task:
```yaml
version: '2'
version: '3'
tasks:
greet:
@@ -52,7 +54,7 @@ Additionally, you can set globally environment variables, that'll be available
to all tasks:
```yaml
version: '2'
version: '3'
env:
GREETING: Hey, there!
@@ -66,53 +68,37 @@ tasks:
> NOTE: `env` supports expansion and retrieving output from a shell command
> just like variables, as you can see on the [Variables](#variables) section.
## Operating System specific tasks
### .env files
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
based on the operating system.
You can also ask Task to include `.env` like files by using the `dotenv:`
setting:
Example:
Taskfile.yml:
```yaml
version: '2'
tasks:
build:
cmds:
- echo "default"
```
# .env
KEYNAME=VALUE
```
Taskfile_linux.yml:
```yaml
version: '2'
# Taskfile.yml
version: '3'
dotenv: ['.env']
tasks:
build:
greet:
cmds:
- echo "linux"
- echo "Using $KEYNAME"
```
Will print out `linux` and not `default`.
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`, `Taskvars_linux.yml`, or `Taskvars_darwin.yml`. See the
[variables section](#variables) below.
## Including other Taskfiles
> This feature is still experimental and may have bugs.
If you want to share tasks between different projects (Taskfiles), you can use
the importing mechanism to include other Taskfiles using the `includes` keyword:
```yaml
version: '2'
version: '3'
includes:
docs: ./documentation # will look for ./documentation/Taskfile.yml
@@ -124,6 +110,21 @@ namespace. So, you'd call `task docs:serve` to run the `serve` task from
`documentation/Taskfile.yml` or `task docker:build` to run the `build` task
from the `DockerTasks.yml` file.
### OS-specific Taskfiles
With `version: '2'`, task automatically includes any `Taskfile_{{OS}}.yml`
if it exists (for example: `Taskfile_windows.yml`, `Taskfile_linux.yml` or
`Taskfile_darwin.yml`). Since this behavior was a bit too implicit, it
was removed on version 3, but you still can have a similar behavior by
explicitly importing these files:
```yaml
version: '3'
includes:
build: ./Taskfile_{{OS}}.yml
```
### Directory of included Taskfile
By default, included Taskfile's tasks are ran in the current directory, even
@@ -153,7 +154,7 @@ located. But you can easily make the task run in another folder informing
`dir`:
```yaml
version: '2'
version: '3'
tasks:
serve:
@@ -167,11 +168,15 @@ If the directory doesn't exist, `task` creates it.
## Task dependencies
> Dependencies run in parallel, so dependencies of a task shouldn't depend one
> another. If you want to force tasks to run serially take a look at the
> [Calling Another Task](#calling-another-task) section below.
You may have tasks that depend on others. Just pointing them on `deps` will
make them run automatically before running the parent task:
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -190,7 +195,7 @@ 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:
```yaml
version: '2'
version: '3'
tasks:
assets:
@@ -215,7 +220,7 @@ 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):
```yaml
version: '2'
version: '3'
tasks:
default:
@@ -239,7 +244,7 @@ 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:
```yaml
version: '2'
version: '3'
tasks:
main-task:
@@ -261,19 +266,19 @@ Overriding variables in the called task is as simple as informing `vars`
attribute:
```yaml
version: '2'
version: '3'
tasks:
main-task:
greet:
vars:
RECIPIENT: '{{default "World" .RECIPIENT}}'
cmds:
- task: write-file
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
- task: write-file
vars: {FILE: "world.txt", CONTENT: "World!"}
- echo "Hello, {{.RECIPIENT}}!"
write-file:
greet-pessimistically:
cmds:
- echo "{{.CONTENT}}" > {{.FILE}}
- task: greet
vars: {RECIPIENT: "Cruel World"}
```
The above syntax is also supported in `deps`.
@@ -290,7 +295,7 @@ If a task generates something, you can inform Task the source and generated
files, so Task will prevent to run them if not necessary.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -315,18 +320,18 @@ tasks:
- public/style.css
```
`sources` and `generates` can be files or file patterns. When both are given,
Task will compare the modification date/time of the files to determine if it's
`sources` and `generates` can be files or file patterns. When given,
Task will compare the checksum of the source files to determine if it's
necessary to run the task. If not, it will just print a message like
`Task "js" is up to date`.
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).
If you prefer this check to be made by the modification timestamp of the files,
instead of its checksum (content), just set the `method` property to `timestamp`.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -341,13 +346,17 @@ tasks:
> TIP: method `none` skips any validation and always run the task.
> NOTE: for the `checksum` (default) method to work, it's only necessary to
> inform the source files, but if you want to use the `timestamp` method, you
> also need to inform the generated files with `generates`.
### Using programmatic checks to indicate a task is up to date.
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:
```yaml
version: '2'
version: '3'
tasks:
generate-files:
@@ -392,7 +401,7 @@ conditions to be _true_ you can use the `preconditions` stanza.
expansion and they SHOULD all return 0.
```yaml
version: '2'
version: '3'
tasks:
generate-files:
@@ -420,19 +429,20 @@ executing tasks that depend on it, a `precondition` will fail a task, along
with any other tasks that depend on it.
```yaml
version: '2'
version: '3'
tasks:
task_will_fail:
task-will-fail:
preconditions:
- sh: "exit 1"
task_will_also_fail:
task-will-also-fail:
deps:
- task_will_fail
- task-will-fail
task_will_still_fail:
task-will-still-fail:
cmds:
- task: task_will_fail
- task: task-will-fail
- echo "I will not run"
```
@@ -442,10 +452,9 @@ 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 in the task definition
- Variables given while calling a task from another.
- Variables given while calling a task from another
(See [Calling another task](#calling-another-task) above)
- Variables declared in the `vars:` option in the `Taskfile`
- Variables available in the `Taskvars.yml` file
- Global variables (those declared in the `vars:` option in the Taskfile)
- Environment variables
Example of sending parameters with environment variables:
@@ -454,27 +463,20 @@ Example of sending parameters with environment variables:
$ TASK_VARIABLE=a-value task do-something
```
> TIP: A special variable `.TASK` is always available containg the task name.
> TIP: A special variable `.TASK` is always available containing the task name.
Since some shells don't support above syntax to set environment variables
(Windows) tasks also accepts a similar style when not in the beginning of
the command. Variables given in this form are only visible to the task called
right before.
the command.
```bash
$ task write-file FILE=file.txt "CONTENT=Hello, World!" print "MESSAGE=All done!"
```
If you want to set global variables using this syntax, give it before any task:
```bash
$ task OUTPUT=file.txt generate-file
```
Example of locally declared vars:
```yaml
version: '2'
version: '3'
tasks:
print-var:
@@ -487,7 +489,7 @@ tasks:
Example of global vars in a `Taskfile.yml`:
```yaml
version: '2'
version: '3'
vars:
GREETING: Hello from Taskfile!
@@ -498,38 +500,6 @@ tasks:
- echo "{{.GREETING}}"
```
Example of `Taskvars.yml` file:
```yaml
PROJECT_NAME: My Project
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:
```yaml
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.
@@ -537,7 +507,7 @@ 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.
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -559,7 +529,7 @@ All functions by the Go's [slim-sprig lib](https://go-task.github.io/slim-sprig/
are available. The following example gets the current date in a given format:
```yaml
version: '2'
version: '3'
tasks:
print-date:
@@ -585,7 +555,7 @@ Task also adds the following functions:
Example:
```yaml
version: '2'
version: '3'
tasks:
print-os:
@@ -613,7 +583,7 @@ Running `task --list` (or `task -l`) lists all tasks with a description.
The following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
build:
@@ -644,11 +614,11 @@ would print the following output:
## Display summary of task
Running `task --summary task-name` will show a summary of a task
Running `task --summary task-name` will show a summary of a task.
The following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
release:
@@ -656,7 +626,7 @@ tasks:
summary: |
Release your project to github
It will build your project before starting the release it.
It will build your project before starting the release.
Please make sure that you have set GITHUB_TOKEN before starting.
cmds:
- your-release-tool
@@ -673,7 +643,7 @@ task: release
Release your project to github
It will build your project before starting the release it.
It will build your project before starting the release.
Please make sure that you have set GITHUB_TOKEN before starting.
dependencies:
@@ -687,13 +657,37 @@ If the task does not have a summary or a description, a warning is printed.
Please note: *showing the summary will not execute the command*.
## Overriding task name
Sometimes you may want to override the task name print on summary, up-to-date
messages to STDOUT, etc. In this case you can just set `label:`, which can also
be interpolated with variables:
```yaml
version: '3'
tasks:
default:
- task: print
vars:
MESSAGE: hello
- task: print
vars:
MESSAGE: world
print:
label: 'print-{{.MESSAGE}}'
cmds:
- echo "{{.MESSAGE}}"
```
## Silent mode
Silent mode disables echoing of commands before Task runs it.
For the following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -719,7 +713,7 @@ There are four ways to enable silent mode:
* At command level:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -731,7 +725,7 @@ tasks:
* At task level:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -743,7 +737,7 @@ tasks:
* Globally at Taskfile level:
```yaml
version: '2'
version: '3'
silent: true
@@ -758,7 +752,7 @@ tasks:
If you want to suppress STDOUT instead, just redirect a command to `/dev/null`:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -777,7 +771,7 @@ You have the option to ignore errors during command execution.
Given the following Taskfile:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -790,7 +784,7 @@ Task will abort the execution after running `exit 1` because the status code `1`
However it is possible to continue with execution using `ignore_error`:
```yaml
version: '2'
version: '3'
tasks:
echo:
@@ -821,7 +815,7 @@ options you can choose:
To choose another one, just set it to root in the Taskfile:
```yaml
version: '2'
version: '3'
output: 'group'
@@ -838,7 +832,7 @@ tasks:
with the `prefix:` attribute:
```yaml
version: '2'
version: '3'
output: prefixed
@@ -871,7 +865,7 @@ $ task default
## Short task syntax
Starting on Task v3, you can now write tasks with a shorter syntax if they
have the default settings (e.g. no custom `env:`, `vars:`, `silent:` , etc):
have the default settings (e.g. no custom `env:`, `vars:`, `desc:`, `silent:` , etc):
```yaml
version: '3'
@@ -879,16 +873,16 @@ version: '3'
tasks:
build: go build -v -o ./app{{exeExt}} .
build:
run:
- task: build
- ./app{{exeExt}} -h localhost -p 8080
```
## Watch tasks
If you give a `--watch` or `-w` argument, task will watch for file changes
With the flags `--watch` or `-w` task will watch for file changes
and run the task again. This requires the `sources` attribute to be given,
so task know which files to watch.
so task knows which files to watch.
[gotemplate]: https://golang.org/pkg/text/template/
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify

9
go.mod
View File

@@ -1,16 +1,17 @@
module github.com/go-task/task/v2
module github.com/go-task/task/v3
require (
github.com/fatih/color v1.7.0
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
github.com/joho/godotenv v1.3.0
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-zglob v0.0.1
github.com/radovskyb/watcher v1.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.5.1
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
mvdan.cc/sh/v3 v3.1.1
mvdan.cc/sh/v3 v3.2.1
)
go 1.13

36
go.sum
View File

@@ -1,5 +1,5 @@
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -8,30 +8,30 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb h1:/qbv1F67s6ehqX9mG23cJOeca3FWpOVKgtPfPUMAi0k=
github.com/go-task/slim-sprig v0.0.0-20200516131648-f9bac4e523eb/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/radovskyb/watcher v1.0.5 h1:wqt7gb+HjGacvFoLTKeT44C+XVPxu7bvHvKT1IvZ7rw=
github.com/radovskyb/watcher v1.0.5/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -40,27 +40,23 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867 h1:JoRuNIf+rpHl+VhScRQQvzbHed86tKkqwPMV34T8myw=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407 h1:5zh5atpUEdIc478E/ebrIaHLKcfVvG6dL/fGv7BcMoM=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/editorconfig v0.1.1-0.20200121172147-e40951bde157/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8=
mvdan.cc/sh/v3 v3.1.1 h1:niuYC5Ug0KzLuN6CNX3ru37v4MkVD5Wm9T4Mk2eJr9A=
mvdan.cc/sh/v3 v3.1.1/go.mod h1:F+Vm4ZxPJxDKExMLhvjuI50oPnedVXpfjNSrusiTOno=
mvdan.cc/sh/v3 v3.2.1 h1:uQBpiGM+rEuHse3Q+W7ajuJUeOtFVJUN/6GeX4/dUWE=
mvdan.cc/sh/v3 v3.2.1/go.mod h1:fPQmabBpREM/XQ9YXSU5ZFZ/Sm+PmKP9/vkFHgYKJEI=

View File

@@ -5,8 +5,8 @@ import (
"sort"
"text/tabwriter"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile"
)
// PrintTasksHelp prints help os tasks that have a description
@@ -21,7 +21,7 @@ func (e *Executor) PrintTasksHelp() {
// 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.Fprintf(w, "* %s: \t%s\n", task.Task, task.Desc)
fmt.Fprintf(w, "* %s: \t%s\n", task.Name(), task.Desc)
}
w.Flush()
}

View File

@@ -1,12 +1,13 @@
package compiler
import (
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/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)
HandleDynamicVar(v taskfile.Var, dir string) (string, error)
ResetCache()
}

View File

@@ -4,7 +4,7 @@ import (
"os"
"strings"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/taskfile"
)
// GetEnviron the all return all environment variables encapsulated on a

View File

@@ -7,11 +7,11 @@ import (
"strings"
"sync"
"github.com/go-task/task/v2/internal/compiler"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
var _ compiler.Compiler = &CompilerV2{}
@@ -37,8 +37,12 @@ type CompilerV2 struct {
// 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()}
vr := varResolver{
c: c,
vars: compiler.GetEnviron(),
}
vr.vars.Set("TASK", taskfile.Var{Static: t.Task})
for _, vars := range []*taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
for i := 0; i < c.Expansions; i++ {
vr.merge(vars)
@@ -63,7 +67,7 @@ func (vr *varResolver) merge(vars *taskfile.Vars) {
Static: tr.Replace(v.Static),
Sh: tr.Replace(v.Sh),
}
static, err := vr.c.HandleDynamicVar(v)
static, err := vr.c.HandleDynamicVar(v, "")
if err != nil {
vr.err = err
return err
@@ -74,7 +78,7 @@ func (vr *varResolver) merge(vars *taskfile.Vars) {
vr.err = tr.Err()
}
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
@@ -92,7 +96,6 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
@@ -109,3 +112,11 @@ func (c *CompilerV2) HandleDynamicVar(v taskfile.Var) (string, error) {
return result, nil
}
// ResetCache clear the dymanic variables cache
func (c *CompilerV2) ResetCache() {
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
c.dynamicCache = nil
}

View File

@@ -4,14 +4,15 @@ import (
"bytes"
"context"
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/go-task/task/v2/internal/compiler"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
var _ compiler.Compiler = &CompilerV3{}
@@ -31,22 +32,27 @@ func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfi
result := compiler.GetEnviron()
result.Set("TASK", taskfile.Var{Static: t.Task})
rangeFunc := func(k string, v taskfile.Var) error {
tr := templater.Templater{Vars: result, RemoveNoValue: true}
v = taskfile.Var{
Static: tr.Replace(v.Static),
Sh: tr.Replace(v.Sh),
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
return func(k string, v taskfile.Var) error {
tr := templater.Templater{Vars: result, RemoveNoValue: true}
v = taskfile.Var{
Static: tr.Replace(v.Static),
Sh: tr.Replace(v.Sh),
Dir: v.Dir,
}
if err := tr.Err(); err != nil {
return err
}
static, err := c.HandleDynamicVar(v, dir)
if err != nil {
return err
}
result.Set(k, taskfile.Var{Static: static})
return nil
}
if err := tr.Err(); err != nil {
return err
}
static, err := c.HandleDynamicVar(v)
if err != nil {
return err
}
result.Set(k, taskfile.Var{Static: static})
return nil
}
rangeFunc := getRangeFunc(c.Dir)
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
return nil, err
@@ -54,14 +60,27 @@ func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfi
if err := call.Vars.Range(rangeFunc); err != nil {
return nil, err
}
if err := t.Vars.Range(rangeFunc); err != nil {
// NOTE(@andreynering): We're manually joining these paths here because
// this is the raw task, not the compiled one.
tr := templater.Templater{Vars: result, RemoveNoValue: true}
dir := tr.Replace(t.Dir)
if err := tr.Err(); err != nil {
return nil, err
}
if !filepath.IsAbs(dir) {
dir = filepath.Join(c.Dir, dir)
}
taskRangeFunc := getRangeFunc(dir)
if err := t.Vars.Range(taskRangeFunc); err != nil {
return nil, err
}
return result, nil
}
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var) (string, error) {
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var, dir string) (string, error) {
if v.Static != "" || v.Sh == "" {
return v.Static, nil
}
@@ -76,10 +95,15 @@ func (c *CompilerV3) HandleDynamicVar(v taskfile.Var) (string, error) {
return result, nil
}
// NOTE(@andreynering): If a var have a specific dir, use this instead
if v.Dir != "" {
dir = v.Dir
}
var stdout bytes.Buffer
opts := &execext.RunCommandOptions{
Command: v.Sh,
Dir: c.Dir,
Dir: dir,
Stdout: &stdout,
Stderr: c.Logger.Stderr,
}
@@ -96,3 +120,11 @@ func (c *CompilerV3) HandleDynamicVar(v taskfile.Var) (string, error) {
return result, nil
}
// ResetCache clear the dymanic variables cache
func (c *CompilerV3) ResetCache() {
c.muDynamicCache.Lock()
defer c.muDynamicCache.Unlock()
c.dynamicCache = nil
}

View File

@@ -46,16 +46,10 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
}
r, err := interp.New(
interp.Params("-e"),
interp.Dir(opts.Dir),
interp.Env(expand.ListEnviron(environ...)),
interp.OpenHandler(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
if path == "/dev/null" {
return devNull{}, nil
}
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
}),
interp.OpenHandler(openHandler),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
)
if err != nil {
@@ -86,3 +80,10 @@ func Expand(s string) (string, error) {
}
return "", nil
}
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
if path == "/dev/null" {
return devNull{}, nil
}
return interp.DefaultOpenHandler()(ctx, path, flag, perm)
}

View File

@@ -6,9 +6,9 @@ import (
"io"
"testing"
"github.com/go-task/task/v2/internal/output"
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3/internal/output"
)
func TestInterleaved(t *testing.T) {

View File

@@ -14,7 +14,8 @@ import (
// Checksum validades if a task is up to date by calculating its source
// files checksum
type Checksum struct {
Dir string
BaseDir string
TaskDir string
Task string
Sources []string
Generates []string
@@ -32,7 +33,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
data, _ := ioutil.ReadFile(checksumFile)
oldMd5 := strings.TrimSpace(string(data))
sources, err := globs(c.Dir, c.Sources)
sources, err := globs(c.TaskDir, c.Sources)
if err != nil {
return false, err
}
@@ -43,7 +44,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
}
if !c.Dry {
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
_ = os.MkdirAll(filepath.Join(c.BaseDir, ".task", "checksum"), 0755)
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
return false, err
}
@@ -52,7 +53,7 @@ func (c *Checksum) IsUpToDate() (bool, error) {
if len(c.Generates) > 0 {
// For each specified 'generates' field, check whether the files actually exist
for _, g := range c.Generates {
generates, err := glob(c.Dir, g)
generates, err := glob(c.TaskDir, g)
if os.IsNotExist(err) {
return false, nil
}
@@ -95,6 +96,9 @@ func (c *Checksum) Value() (interface{}, error) {
// OnError implements the Checker interface
func (c *Checksum) OnError() error {
if len(c.Sources) == 0 {
return nil
}
return os.Remove(c.checksumFilePath())
}
@@ -104,7 +108,7 @@ func (*Checksum) Kind() string {
}
func (c *Checksum) checksumFilePath() string {
return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
return filepath.Join(c.BaseDir, ".task", "checksum", c.normalizeFilename(c.Task))
}
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")

View File

@@ -5,9 +5,9 @@ import (
"path/filepath"
"sort"
"github.com/go-task/task/v2/internal/execext"
"github.com/mattn/go-zglob"
"github.com/go-task/task/v3/internal/execext"
)
func globs(dir string, globs []string) ([]string, error) {

View File

@@ -3,8 +3,8 @@ package summary
import (
"strings"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile"
)
func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
@@ -56,7 +56,7 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
}
func printTaskName(l *logger.Logger, t *taskfile.Task) {
l.Outf(logger.Default, "task: %s", t.Task)
l.Outf(logger.Default, "task: %s", t.Name())
l.Outf(logger.Default, "")
}

View File

@@ -5,11 +5,11 @@ import (
"strings"
"testing"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/summary"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/summary"
"github.com/go-task/task/v3/taskfile"
)
func TestPrintsDependenciesIfPresent(t *testing.T) {

View File

@@ -1,40 +0,0 @@
package taskfile
import "errors"
var (
// ErrCantUnmarshalIncludedTaskfile is returned for invalid var YAML.
ErrCantUnmarshalIncludedTaskfile = errors.New("task: can't unmarshal included value")
)
// IncludedTaskfile represents information about included tasksfile
type IncludedTaskfile struct {
Taskfile string
Dir string
AdvancedImport bool
}
// IncludedTaskfiles represents information about included tasksfiles
type IncludedTaskfiles = map[string]IncludedTaskfile
// UnmarshalYAML implements yaml.Unmarshaler interface
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
it.Taskfile = str
return nil
}
var includedTaskfile struct {
Taskfile string
Dir string
}
if err := unmarshal(&includedTaskfile); err == nil {
it.Dir = includedTaskfile.Dir
it.Taskfile = includedTaskfile.Taskfile
it.AdvancedImport = true
return nil
}
return ErrCantUnmarshalIncludedTaskfile
}

View File

@@ -5,7 +5,7 @@ import (
"strings"
"text/template"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/taskfile"
)
// Templater is a help struct that allow us to call "replaceX" funcs multiple

View File

@@ -4,9 +4,9 @@ import (
"context"
"errors"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile"
)
var (

View File

@@ -4,10 +4,10 @@ import (
"context"
"fmt"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/status"
"github.com/go-task/task/v3/taskfile"
)
// Status returns an error if any the of given tasks is not up-to-date
@@ -22,7 +22,7 @@ func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
return err
}
if !isUpToDate {
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Task)
return fmt.Errorf(`task: Task "%s" is not up-to-date`, t.Name())
}
}
return nil
@@ -76,8 +76,9 @@ func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
return &status.Checksum{
Dir: t.Dir,
Task: t.Task,
BaseDir: e.Dir,
TaskDir: t.Dir,
Task: t.Name(),
Sources: t.Sources,
Generates: t.Generates,
Dry: e.Dry,

28
task.go
View File

@@ -9,15 +9,15 @@ import (
"sync"
"sync/atomic"
"github.com/go-task/task/v2/internal/compiler"
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
compilerv3 "github.com/go-task/task/v2/internal/compiler/v3"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/logger"
"github.com/go-task/task/v2/internal/output"
"github.com/go-task/task/v2/internal/summary"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/taskfile/read"
"github.com/go-task/task/v3/internal/compiler"
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/summary"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/read"
"golang.org/x/sync/errgroup"
)
@@ -176,7 +176,7 @@ func (e *Executor) Setup() error {
if v < 2.1 && e.Taskfile.Output != "" {
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
}
if v < 2.2 && len(e.Taskfile.Includes) > 0 {
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
}
if v >= 3.0 && e.Taskfile.Expansions > 2 {
@@ -229,10 +229,14 @@ func (e *Executor) Setup() error {
}
if v < 3 {
for _, taskfile := range e.Taskfile.Includes {
err := e.Taskfile.Includes.Range(func(_ string, taskfile taskfile.IncludedTaskfile) error {
if taskfile.AdvancedImport {
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
}
return nil
})
if err != nil {
return err
}
}
@@ -272,7 +276,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
if upToDate && preCondMet {
if !e.Silent {
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Task)
e.Logger.Errf(logger.Magenta, `task: Task "%s" is up to date`, t.Name())
}
return nil
}

View File

@@ -11,10 +11,10 @@ import (
"strings"
"testing"
"github.com/go-task/task/v2"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/taskfile"
)
// fileContentTest provides a basic reusable test-case for running a Taskfile
@@ -56,6 +56,16 @@ func (fct fileContentTest) Run(t *testing.T) {
}
}
func TestEmptyTask(t *testing.T) {
e := &task.Executor{
Dir: "testdata/empty_task",
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup(), "e.Setup()")
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
}
func TestEnv(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/env",
@@ -293,6 +303,8 @@ func TestPrecondition(t *testing.T) {
}
func TestGenerates(t *testing.T) {
const dir = "testdata/generates"
const (
srcTask = "sub/src.txt"
relTask = "rel.txt"
@@ -300,9 +312,6 @@ func TestGenerates(t *testing.T) {
fileWithSpaces = "my text file.txt"
)
// This test does not work with a relative dir.
dir, err := filepath.Abs("testdata/generates")
assert.NoError(t, err)
var srcFile = filepath.Join(dir, srcTask)
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
@@ -384,6 +393,90 @@ func TestStatusChecksum(t *testing.T) {
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
}
func TestLabelUpToDate(t *testing.T) {
const dir = "testdata/label_uptodate"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
func TestLabelSummary(t *testing.T) {
const dir = "testdata/label_summary"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
func TestLabelInStatus(t *testing.T) {
const dir = "testdata/label_status"
e := task.Executor{
Dir: dir,
}
assert.NoError(t, e.Setup())
err := e.Status(context.Background(), taskfile.Call{Task: "foo"})
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "foobar")
}
}
func TestLabelWithVariableExpansion(t *testing.T) {
const dir = "testdata/label_var"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobaz")
}
func TestLabelInSummary(t *testing.T) {
const dir = "testdata/label_summary"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "foo"}))
assert.Contains(t, buff.String(), "foobar")
}
func TestLabelInList(t *testing.T) {
const dir = "testdata/label_list"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
e.PrintTasksHelp()
assert.Contains(t, buff.String(), "foobar")
}
func TestStatusVariables(t *testing.T) {
const dir = "testdata/status_vars"
@@ -706,6 +799,21 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
_ = os.RemoveAll(toBeCreated)
}
func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dir/dynamic_var",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"subdirectory/from_root_taskfile.txt": "subdirectory\n",
"subdirectory/from_included_taskfile.txt": "subdirectory\n",
"subdirectory/from_included_taskfile_task.txt": "subdirectory\n",
"subdirectory/from_interpolated_dir.txt": "subdirectory\n",
},
}
tt.Run(t)
}
func TestDisplaysErrorOnUnsupportedVersion(t *testing.T) {
e := task.Executor{
Dir: "testdata/version/v1",
@@ -732,3 +840,61 @@ func TestShortTaskNotation(t *testing.T) {
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
}
func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dotenv/default",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
},
}
tt.Run(t)
}
func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
const dir = "testdata/dotenv/error_included_envs"
const entry = "Taskfile.yml"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: entry,
Summary: true,
Stdout: &buff,
Stderr: &buff,
}
err := e.Setup()
assert.Error(t, err)
assert.Contains(t, err.Error(), "move the dotenv")
}
func TestDotenvShouldAllowMissingEnv(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/dotenv/missing_env",
Target: "default",
TrimSpace: false,
Files: map[string]string{
"include.txt": "INCLUDE1='' INCLUDE2=''\n",
},
}
tt.Run(t)
}
func TestExitImmediately(t *testing.T) {
const dir = "testdata/exit_immediately"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
assert.NoError(t, e.Setup())
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
}

View File

@@ -0,0 +1,109 @@
package taskfile
import (
"errors"
"gopkg.in/yaml.v3"
)
var (
// ErrCantUnmarshalIncludedTaskfile is returned for invalid var YAML.
ErrCantUnmarshalIncludedTaskfile = errors.New("task: can't unmarshal included value")
)
// IncludedTaskfile represents information about included tasksfile
type IncludedTaskfile struct {
Taskfile string
Dir string
AdvancedImport bool
}
// IncludedTaskfiles represents information about included tasksfiles
type IncludedTaskfiles struct {
Keys []string
Mapping map[string]IncludedTaskfile
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (tfs *IncludedTaskfiles) UnmarshalYAML(node *yaml.Node) error {
if node.Kind != yaml.MappingNode {
return errors.New("task: includes is not a map")
}
// NOTE(@andreynering): on this style of custom unmarsheling,
// even number contains the keys, while odd numbers contains
// the values.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
var v IncludedTaskfile
if err := valueNode.Decode(&v); err != nil {
return err
}
tfs.Set(keyNode.Value, v)
}
return nil
}
// Len returns the length of the map
func (tfs *IncludedTaskfiles) Len() int {
if tfs == nil {
return 0
}
return len(tfs.Keys)
}
// Merge merges the given IncludedTaskfiles into the caller one
func (tfs *IncludedTaskfiles) Merge(other *IncludedTaskfiles) {
other.Range(func(key string, value IncludedTaskfile) error {
tfs.Set(key, value)
return nil
})
}
// Set sets a value to a given key
func (tfs *IncludedTaskfiles) Set(key string, includedTaskfile IncludedTaskfile) {
if tfs.Mapping == nil {
tfs.Mapping = make(map[string]IncludedTaskfile, 1)
}
if !stringSliceContains(tfs.Keys, key) {
tfs.Keys = append(tfs.Keys, key)
}
tfs.Mapping[key] = includedTaskfile
}
// Range allows you to loop into the included taskfiles in its right order
func (tfs *IncludedTaskfiles) Range(yield func(key string, includedTaskfile IncludedTaskfile) error) error {
if tfs == nil {
return nil
}
for _, k := range tfs.Keys {
if err := yield(k, tfs.Mapping[k]); err != nil {
return err
}
}
return nil
}
// UnmarshalYAML implements yaml.Unmarshaler interface
func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err == nil {
it.Taskfile = str
return nil
}
var includedTaskfile struct {
Taskfile string
Dir string
}
if err := unmarshal(&includedTaskfile); err == nil {
it.Dir = includedTaskfile.Dir
it.Taskfile = includedTaskfile.Taskfile
it.AdvancedImport = true
return nil
}
return ErrCantUnmarshalIncludedTaskfile
}

View File

@@ -22,11 +22,9 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
}
if t1.Includes == nil {
t1.Includes = make(IncludedTaskfiles)
}
for k, v := range t2.Includes {
t1.Includes[k] = v
t1.Includes = &IncludedTaskfiles{}
}
t1.Includes.Merge(t2.Includes)
if t1.Vars == nil {
t1.Vars = &Vars{}

View File

@@ -3,10 +3,10 @@ package taskfile_test
import (
"testing"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/taskfile"
)
func TestPreconditionParse(t *testing.T) {

View File

@@ -7,15 +7,18 @@ import (
"path/filepath"
"runtime"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
"github.com/joho/godotenv"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
var (
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
)
// Taskfile reads a Taskfile for a given directory
@@ -34,7 +37,28 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return nil, err
}
for namespace, includedTask := range t.Includes {
if v >= 3.0 {
for _, dotEnvPath := range t.Dotenv {
if !filepath.IsAbs(dotEnvPath) {
dotEnvPath = filepath.Join(dir, dotEnvPath)
}
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
continue
}
envs, err := godotenv.Read(dotEnvPath)
if err != nil {
return nil, err
}
for key, value := range envs {
if _, ok := t.Env.Mapping[key]; !ok {
t.Env.Set(key, taskfile.Var{Static: value})
}
}
}
}
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
if v >= 3.0 {
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
includedTask = taskfile.IncludedTaskfile{
@@ -43,7 +67,7 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
AdvancedImport: includedTask.AdvancedImport,
}
if err := tr.Err(); err != nil {
return nil, err
return err
}
}
@@ -55,20 +79,35 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
return err
}
if info.IsDir() {
path = filepath.Join(path, "Taskfile.yml")
}
includedTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
return err
}
if len(includedTaskfile.Includes) > 0 {
return nil, ErrIncludedTaskfilesCantHaveIncludes
if includedTaskfile.Includes.Len() > 0 {
return ErrIncludedTaskfilesCantHaveIncludes
}
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
return ErrIncludedTaskfilesCantHaveDotenvs
}
if includedTask.AdvancedImport {
for k, v := range includedTaskfile.Vars.Mapping {
o := v
o.Dir = filepath.Join(dir, includedTask.Dir)
includedTaskfile.Vars.Mapping[k] = o
}
for k, v := range includedTaskfile.Env.Mapping {
o := v
o.Dir = filepath.Join(dir, includedTask.Dir)
includedTaskfile.Env.Mapping[k] = o
}
for _, task := range includedTaskfile.Tasks {
if !filepath.IsAbs(task.Dir) {
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
@@ -77,8 +116,12 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
}
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
return nil, err
return err
}
return nil
})
if err != nil {
return nil, err
}
if v < 3.0 {
@@ -95,6 +138,10 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
}
for name, task := range t.Tasks {
if task == nil {
task = &taskfile.Task{}
t.Tasks[name] = task
}
task.Task = name
}

View File

@@ -6,9 +6,9 @@ import (
"path/filepath"
"runtime"
"github.com/go-task/task/v2/internal/taskfile"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/taskfile"
)
// Taskvars reads a Taskvars for a given directory

10
taskfile/slice.go Normal file
View File

@@ -0,0 +1,10 @@
package taskfile
func stringSliceContains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}

View File

@@ -12,6 +12,7 @@ type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Summary string
Sources []string
@@ -32,6 +33,13 @@ var (
ErrCantUnmarshalTask = errors.New("task: can't unmarshal task value")
)
func (t *Task) Name() string {
if t.Label != "" {
return t.Label
}
return t.Task
}
func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
var cmd Cmd
if err := unmarshal(&cmd); err == nil && cmd.Cmd != "" {
@@ -48,6 +56,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
var task struct {
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Summary string
Sources []string
@@ -65,6 +74,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&task); err == nil {
t.Cmds = task.Cmds
t.Deps = task.Deps
t.Label = task.Label
t.Desc = task.Desc
t.Summary = task.Summary
t.Sources = task.Sources

View File

@@ -11,11 +11,12 @@ type Taskfile struct {
Expansions int
Output string
Method string
Includes IncludedTaskfiles
Includes *IncludedTaskfiles
Vars *Vars
Env *Vars
Tasks Tasks
Silent bool
Dotenv []string
}
// UnmarshalYAML implements yaml.Unmarshaler interface
@@ -25,11 +26,12 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
Expansions int
Output string
Method string
Includes IncludedTaskfiles
Includes *IncludedTaskfiles
Vars *Vars
Env *Vars
Tasks Tasks
Silent bool
Dotenv []string
}
if err := unmarshal(&taskfile); err != nil {
return err
@@ -43,6 +45,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Env = taskfile.Env
tf.Tasks = taskfile.Tasks
tf.Silent = taskfile.Silent
tf.Dotenv = taskfile.Dotenv
if tf.Expansions <= 0 {
tf.Expansions = 2
}

View File

@@ -3,10 +3,10 @@ package taskfile_test
import (
"testing"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/taskfile"
)
func TestCmdParse(t *testing.T) {

View File

@@ -53,21 +53,12 @@ func (vs *Vars) Set(key string, value Var) {
if vs.Mapping == nil {
vs.Mapping = make(map[string]Var, 1)
}
if !strSliceContains(vs.Keys, key) {
if !stringSliceContains(vs.Keys, key) {
vs.Keys = append(vs.Keys, key)
}
vs.Mapping[key] = value
}
func strSliceContains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
// Range allows you to loop into the vars in its right order
func (vs *Vars) Range(yield func(key string, value Var) error) error {
if vs == nil {
@@ -107,6 +98,7 @@ type Var struct {
Static string
Live interface{}
Sh string
Dir string
}
// UnmarshalYAML implements yaml.Unmarshaler interface.

1
testdata/dir/dynamic_var/.gitignore vendored Normal file
View File

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

33
testdata/dir/dynamic_var/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
version: '3'
includes:
sub:
taskfile: subdirectory
dir: subdirectory
vars:
DIRECTORY: subdirectory
tasks:
default:
- task: from-root-taskfile
- task: sub:from-included-taskfile
- task: sub:from-included-taskfile-task
- task: from-interpolated-dir
from-root-taskfile:
cmds:
- echo '{{.TASK_DIR}}' > from_root_taskfile.txt
dir: subdirectory
vars:
TASK_DIR:
sh: basename $(pwd)
silent: true
from-interpolated-dir:
cmds:
- echo '{{.INTERPOLATED_DIR}}' > from_interpolated_dir.txt
dir: '{{.DIRECTORY}}'
vars:
INTERPOLATED_DIR:
sh: basename $(pwd)

View File

@@ -0,0 +1,19 @@
version: '3'
vars:
TASKFILE_DIR:
sh: basename $(pwd)
tasks:
from-included-taskfile:
cmds:
- echo '{{.TASKFILE_DIR}}' > from_included_taskfile.txt
silent: true
from-included-taskfile-task:
cmds:
- echo '{{.TASKFILE_TASK_DIR}}' > from_included_taskfile_task.txt
silent: true
vars:
TASKFILE_TASK_DIR:
sh: basename $(pwd)

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

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

8
testdata/dotenv/default/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: '3'
dotenv: ['../include1/.env', '../include1/envs/.env']
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include.txt

View File

@@ -0,0 +1,9 @@
version: '3'
includes:
include1: '../include1'
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include-errors2.txt

1
testdata/dotenv/include1/.env vendored Normal file
View File

@@ -0,0 +1 @@
INCLUDE1=from_include1

3
testdata/dotenv/include1/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
version: '3'
dotenv: ['.env']

1
testdata/dotenv/include1/envs/.env vendored Normal file
View File

@@ -0,0 +1 @@
INCLUDE2=from_include2

View File

@@ -0,0 +1,8 @@
version: '3'
dotenv: ['.env']
tasks:
default:
cmds:
- echo "INCLUDE1='$INCLUDE1' INCLUDE2='$INCLUDE2'" > include.txt

4
testdata/empty_task/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
version: '3'
tasks:
default:

View File

@@ -0,0 +1,6 @@
version: '3'
tasks:
default: |
this_should_fail
echo "This shoudn't be print"

View File

@@ -1,7 +1,8 @@
version: '3'
vars:
BUILD_DIR: $pwd
BUILD_DIR:
sh: pwd
tasks:
abs.txt:

0
testdata/generates/sub/.keep vendored Normal file
View File

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

@@ -0,0 +1,6 @@
version: '3'
tasks:
foo:
label: "foobar"
desc: "task description"

7
testdata/label_status/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
foo:
label: "foobar"
status:
- "false"

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

@@ -0,0 +1,8 @@
version: '3'
tasks:
foo:
label: "foobar"
desc: description
status:
- echo "I'm ok"

7
testdata/label_uptodate/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
foo:
label: "foobar"
status:
- echo "I'm ok"

10
testdata/label_var/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: '3'
vars:
BAR: baz
tasks:
foo:
label: "foo{{.BAR}}"
status:
- echo "I'm ok"

View File

@@ -4,10 +4,10 @@ import (
"path/filepath"
"strings"
"github.com/go-task/task/v2/internal/execext"
"github.com/go-task/task/v2/internal/status"
"github.com/go-task/task/v2/internal/taskfile"
"github.com/go-task/task/v2/internal/templater"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/status"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
// CompiledTask returns a copy of a task, but replacing variables in almost all
@@ -32,6 +32,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
new := taskfile.Task{
Task: origTask.Task,
Label: r.Replace(origTask.Label),
Desc: r.Replace(origTask.Desc),
Summary: r.Replace(origTask.Summary),
Sources: r.ReplaceSlice(origTask.Sources),
@@ -59,7 +60,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
new.Env.Merge(r.ReplaceVars(e.Taskfile.Env))
new.Env.Merge(r.ReplaceVars(origTask.Env))
err = new.Env.Range(func(k string, v taskfile.Var) error {
static, err := e.Compiler.HandleDynamicVar(v)
static, err := e.Compiler.HandleDynamicVar(v, new.Dir)
if err != nil {
return err
}

View File

@@ -1,27 +0,0 @@
language: go
go:
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 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
- 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,86 +0,0 @@
# 1.4.2 (2018-04-10)
## Changed
- #72: Updated the docs to point to vert for a console appliaction
- #71: Update the docs on pre-release comparator handling
## Fixed
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
# 1.4.1 (2018-04-02)
## Fixed
- Fixed #64: Fix pre-release precedence issue (thanks @uudashr)
# 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,20 +0,0 @@
The Masterminds
Copyright (C) 2014-2015, Matt Butcher and Matt Farina
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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

@@ -1,24 +0,0 @@
package semver
// Collection is a collection of Version instances and implements the sort
// interface. See the sort package for more details.
// https://golang.org/pkg/sort/
type Collection []*Version
// Len returns the length of a collection. The number of Version instances
// on the slice.
func (c Collection) Len() int {
return len(c)
}
// Less is needed for the sort interface to compare two Version objects on the
// slice. If checks if one is less than the other.
func (c Collection) Less(i, j int) bool {
return c[i].LessThan(c[j])
}
// Swap is needed for the sort interface to replace the Version objects
// at two different positions in the slice.
func (c Collection) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}

View File

@@ -1,426 +0,0 @@
package semver
import (
"errors"
"fmt"
"regexp"
"strings"
)
// Constraints is one or more constraint that a semantic version can be
// checked against.
type Constraints struct {
constraints [][]*constraint
}
// NewConstraint returns a Constraints instance that a Version instance can
// be checked against. If there is a parse error it will be returned.
func NewConstraint(c string) (*Constraints, error) {
// Rewrite - ranges into a comparison operation.
c = rewriteRange(c)
ors := strings.Split(c, "||")
or := make([][]*constraint, len(ors))
for k, v := range ors {
cs := strings.Split(v, ",")
result := make([]*constraint, len(cs))
for i, s := range cs {
pc, err := parseConstraint(s)
if err != nil {
return nil, err
}
result[i] = pc
}
or[k] = result
}
o := &Constraints{constraints: or}
return o, nil
}
// Check tests if a version satisfies the constraints.
func (cs Constraints) Check(v *Version) bool {
// loop over the ORs and check the inner ANDs
for _, o := range cs.constraints {
joy := true
for _, c := range o {
if !c.check(v) {
joy = false
break
}
}
if joy {
return true
}
}
return false
}
// Validate checks if a version satisfies a constraint. If not a slice of
// reasons for the failure are returned in addition to a bool.
func (cs Constraints) Validate(v *Version) (bool, []error) {
// loop over the ORs and check the inner ANDs
var e []error
for _, o := range cs.constraints {
joy := true
for _, c := range o {
if !c.check(v) {
em := fmt.Errorf(c.msg, v, c.orig)
e = append(e, em)
joy = false
}
}
if joy {
return true, []error{}
}
}
return false, e
}
var constraintOps map[string]cfunc
var constraintMsg map[string]string
var constraintRegex *regexp.Regexp
func init() {
constraintOps = map[string]cfunc{
"": constraintTildeOrEqual,
"=": constraintTildeOrEqual,
"!=": constraintNotEqual,
">": constraintGreaterThan,
"<": constraintLessThan,
">=": constraintGreaterThanEqual,
"=>": constraintGreaterThanEqual,
"<=": constraintLessThanEqual,
"=<": constraintLessThanEqual,
"~": constraintTilde,
"~>": constraintTilde,
"^": constraintCaret,
}
constraintMsg = map[string]string{
"": "%s is not equal to %s",
"=": "%s is not equal to %s",
"!=": "%s is equal to %s",
">": "%s is less than or equal to %s",
"<": "%s is greater than or equal to %s",
">=": "%s is less than %s",
"=>": "%s is less than %s",
"<=": "%s is greater than %s",
"=<": "%s is greater than %s",
"~": "%s does not have same major and minor version as %s",
"~>": "%s does not have same major and minor version as %s",
"^": "%s does not have same major version as %s",
}
ops := make([]string, 0, len(constraintOps))
for k := range constraintOps {
ops = append(ops, regexp.QuoteMeta(k))
}
constraintRegex = regexp.MustCompile(fmt.Sprintf(
`^\s*(%s)\s*(%s)\s*$`,
strings.Join(ops, "|"),
cvRegex))
constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
`\s*(%s)\s+-\s+(%s)\s*`,
cvRegex, cvRegex))
}
// An individual constraint
type constraint struct {
// The callback function for the restraint. It performs the logic for
// the constraint.
function cfunc
msg string
// The version used in the constraint check. For example, if a constraint
// is '<= 2.0.0' the con a version instance representing 2.0.0.
con *Version
// The original parsed version (e.g., 4.x from != 4.x)
orig string
// When an x is used as part of the version (e.g., 1.x)
minorDirty bool
dirty bool
patchDirty bool
}
// Check if a version meets the constraint
func (c *constraint) check(v *Version) bool {
return c.function(v, c)
}
type cfunc func(v *Version, c *constraint) bool
func parseConstraint(c string) (*constraint, error) {
m := constraintRegex.FindStringSubmatch(c)
if m == nil {
return nil, fmt.Errorf("improper constraint: %s", c)
}
ver := m[2]
orig := ver
minorDirty := false
patchDirty := false
dirty := false
if isX(m[3]) {
ver = "0.0.0"
dirty = true
} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
minorDirty = true
dirty = true
ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
} else if isX(strings.TrimPrefix(m[5], ".")) {
dirty = true
patchDirty = true
ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
}
con, err := NewVersion(ver)
if err != nil {
// The constraintRegex should catch any regex parsing errors. So,
// we should never get here.
return nil, errors.New("constraint Parser Error")
}
cs := &constraint{
function: constraintOps[m[1]],
msg: constraintMsg[m[1]],
con: con,
orig: orig,
minorDirty: minorDirty,
patchDirty: patchDirty,
dirty: dirty,
}
return cs, nil
}
// Constraint functions
func constraintNotEqual(v *Version, c *constraint) bool {
if c.dirty {
// 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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
if c.con.Major() != v.Major() {
return true
}
if c.con.Minor() != v.Minor() && !c.minorDirty {
return true
} else if c.minorDirty {
return false
}
return false
}
return !v.Equal(c.con)
}
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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
return v.Compare(c.con) == 1
}
func constraintLessThan(v *Version, c *constraint) bool {
// 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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
if !c.dirty {
return v.Compare(c.con) < 0
}
if v.Major() > c.con.Major() {
return false
} else if v.Minor() > c.con.Minor() && !c.minorDirty {
return false
}
return true
}
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
// more details.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
return v.Compare(c.con) >= 0
}
func constraintLessThanEqual(v *Version, c *constraint) bool {
// 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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
if !c.dirty {
return v.Compare(c.con) <= 0
}
if v.Major() > c.con.Major() {
return false
} else if v.Minor() > c.con.Minor() && !c.minorDirty {
return false
}
return true
}
// ~*, ~>* --> >= 0.0.0 (any)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
func constraintTilde(v *Version, c *constraint) bool {
// 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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
if v.LessThan(c.con) {
return false
}
// ~0.0.0 is a special case where all constraints are accepted. It's
// equivalent to >= 0.0.0.
if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
!c.minorDirty && !c.patchDirty {
return true
}
if v.Major() != c.con.Major() {
return false
}
if v.Minor() != c.con.Minor() && !c.minorDirty {
return false
}
return true
}
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
// it's a straight =
func constraintTildeOrEqual(v *Version, c *constraint) bool {
// 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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
if c.dirty {
c.msg = constraintMsg["~"]
return constraintTilde(v, c)
}
return v.Equal(c.con)
}
// ^* --> (any)
// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0
// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0
// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0
// ^1.2.3 --> >=1.2.3, <2.0.0
// ^1.2.0 --> >=1.2.0, <2.0.0
func constraintCaret(v *Version, c *constraint) bool {
// 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.
if v.Prerelease() != "" && c.con.Prerelease() == "" {
return false
}
if v.LessThan(c.con) {
return false
}
if v.Major() != c.con.Major() {
return false
}
return true
}
var constraintRangeRegex *regexp.Regexp
const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
func isX(x string) bool {
switch x {
case "x", "*", "X":
return true
default:
return false
}
}
func rewriteRange(i string) string {
m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
if m == nil {
return i
}
o := i
for _, v := range m {
t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
o = strings.Replace(o, v[0], t, 1)
}
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

@@ -1,115 +0,0 @@
/*
Package semver 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
Parsing Semantic Versions
To parse a semantic version use the `NewVersion` function. For example,
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 at https://godoc.org/github.com/Masterminds/semver.
Sorting Semantic Versions
A set of versions can be sorted using the `sort` package from the standard library.
For example,
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.
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
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`
*/
package semver

View File

@@ -1,421 +0,0 @@
package semver
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
// The compiled version of the regex created at init() is cached here so it
// only needs to be created once.
var versionRegex *regexp.Regexp
var validPrereleaseRegex *regexp.Regexp
var (
// ErrInvalidSemVer is returned a version is found to be invalid when
// being parsed.
ErrInvalidSemVer = errors.New("Invalid Semantic Version")
// ErrInvalidMetadata is returned when the metadata is an invalid format
ErrInvalidMetadata = errors.New("Invalid Metadata string")
// ErrInvalidPrerelease is returned when the pre-release is an invalid format
ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
)
// SemVerRegex is the regular expression used to parse a semantic version.
const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
// ValidPrerelease is the regular expression which validates
// both prerelease and metadata values.
const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`
// Version represents a single semantic version.
type Version struct {
major, minor, patch int64
pre string
metadata string
original string
}
func init() {
versionRegex = regexp.MustCompile("^" + SemVerRegex + "$")
validPrereleaseRegex = regexp.MustCompile(ValidPrerelease)
}
// NewVersion parses a given version and returns an instance of Version or
// an error if unable to parse the version.
func NewVersion(v string) (*Version, error) {
m := versionRegex.FindStringSubmatch(v)
if m == nil {
return nil, ErrInvalidSemVer
}
sv := &Version{
metadata: m[8],
pre: m[5],
original: v,
}
var temp int64
temp, err := strconv.ParseInt(m[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err)
}
sv.major = temp
if m[2] != "" {
temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64)
if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err)
}
sv.minor = temp
} else {
sv.minor = 0
}
if m[3] != "" {
temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64)
if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err)
}
sv.patch = temp
} else {
sv.patch = 0
}
return sv, nil
}
// MustParse parses a given version and panics on error.
func MustParse(v string) *Version {
sv, err := NewVersion(v)
if err != nil {
panic(err)
}
return sv
}
// String converts a Version object to a string.
// 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.
func (v *Version) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
if v.pre != "" {
fmt.Fprintf(&buf, "-%s", v.pre)
}
if v.metadata != "" {
fmt.Fprintf(&buf, "+%s", v.metadata)
}
return buf.String()
}
// Original returns the original value passed in to be parsed.
func (v *Version) Original() string {
return v.original
}
// Major returns the major version.
func (v *Version) Major() int64 {
return v.major
}
// Minor returns the minor version.
func (v *Version) Minor() int64 {
return v.minor
}
// Patch returns the patch version.
func (v *Version) Patch() int64 {
return v.patch
}
// Prerelease returns the pre-release version.
func (v *Version) Prerelease() string {
return v.pre
}
// Metadata returns the metadata on the version.
func (v *Version) Metadata() string {
return v.metadata
}
// originalVPrefix returns the original 'v' prefix if any.
func (v *Version) originalVPrefix() string {
// Note, only lowercase v is supported as a prefix by the parser.
if v.original != "" && v.original[:1] == "v" {
return v.original[:1]
}
return ""
}
// IncPatch produces the next patch version.
// If the current version does not have prerelease/metadata information,
// it unsets metadata and prerelease values, increments patch number.
// If the current version has any of prerelease or metadata information,
// it unsets both values and keeps curent patch value
func (v Version) IncPatch() Version {
vNext := v
// according to http://semver.org/#spec-item-9
// Pre-release versions have a lower precedence than the associated normal version.
// according to http://semver.org/#spec-item-10
// Build metadata SHOULD be ignored when determining version precedence.
if v.pre != "" {
vNext.metadata = ""
vNext.pre = ""
} else {
vNext.metadata = ""
vNext.pre = ""
vNext.patch = v.patch + 1
}
vNext.original = v.originalVPrefix() + "" + vNext.String()
return vNext
}
// IncMinor produces the next minor version.
// Sets patch to 0.
// Increments minor number.
// Unsets metadata.
// Unsets prerelease status.
func (v Version) IncMinor() Version {
vNext := v
vNext.metadata = ""
vNext.pre = ""
vNext.patch = 0
vNext.minor = v.minor + 1
vNext.original = v.originalVPrefix() + "" + vNext.String()
return vNext
}
// IncMajor produces the next major version.
// Sets patch to 0.
// Sets minor to 0.
// Increments major number.
// Unsets metadata.
// Unsets prerelease status.
func (v Version) IncMajor() Version {
vNext := v
vNext.metadata = ""
vNext.pre = ""
vNext.patch = 0
vNext.minor = 0
vNext.major = v.major + 1
vNext.original = v.originalVPrefix() + "" + vNext.String()
return vNext
}
// SetPrerelease defines the prerelease value.
// Value must not include the required 'hypen' prefix.
func (v Version) SetPrerelease(prerelease string) (Version, error) {
vNext := v
if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) {
return vNext, ErrInvalidPrerelease
}
vNext.pre = prerelease
vNext.original = v.originalVPrefix() + "" + vNext.String()
return vNext, nil
}
// SetMetadata defines metadata value.
// Value must not include the required 'plus' prefix.
func (v Version) SetMetadata(metadata string) (Version, error) {
vNext := v
if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) {
return vNext, ErrInvalidMetadata
}
vNext.metadata = metadata
vNext.original = v.originalVPrefix() + "" + vNext.String()
return vNext, nil
}
// LessThan tests if one version is less than another one.
func (v *Version) LessThan(o *Version) bool {
return v.Compare(o) < 0
}
// GreaterThan tests if one version is greater than another one.
func (v *Version) GreaterThan(o *Version) bool {
return v.Compare(o) > 0
}
// Equal tests if two versions are equal to each other.
// Note, versions can be equal with different metadata since metadata
// is not considered part of the comparable version.
func (v *Version) Equal(o *Version) bool {
return v.Compare(o) == 0
}
// Compare compares this version to another one. It returns -1, 0, or 1 if
// the version smaller, equal, or larger than the other version.
//
// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
// lower than the version without a prerelease.
func (v *Version) Compare(o *Version) int {
// Compare the major, minor, and patch version for differences. If a
// difference is found return the comparison.
if d := compareSegment(v.Major(), o.Major()); d != 0 {
return d
}
if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
return d
}
if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
return d
}
// At this point the major, minor, and patch versions are the same.
ps := v.pre
po := o.Prerelease()
if ps == "" && po == "" {
return 0
}
if ps == "" {
return 1
}
if po == "" {
return -1
}
return comparePrerelease(ps, po)
}
// UnmarshalJSON implements JSON.Unmarshaler interface.
func (v *Version) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
temp, err := NewVersion(s)
if err != nil {
return err
}
v.major = temp.major
v.minor = temp.minor
v.patch = temp.patch
v.pre = temp.pre
v.metadata = temp.metadata
v.original = temp.original
temp = nil
return nil
}
// MarshalJSON implements JSON.Marshaler interface.
func (v *Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func compareSegment(v, o int64) int {
if v < o {
return -1
}
if v > o {
return 1
}
return 0
}
func comparePrerelease(v, o string) int {
// split the prelease versions by their part. The separator, per the spec,
// is a .
sparts := strings.Split(v, ".")
oparts := strings.Split(o, ".")
// Find the longer length of the parts to know how many loop iterations to
// go through.
slen := len(sparts)
olen := len(oparts)
l := slen
if olen > slen {
l = olen
}
// Iterate over each part of the prereleases to compare the differences.
for i := 0; i < l; i++ {
// Since the lentgh of the parts can be different we need to create
// a placeholder. This is to avoid out of bounds issues.
stemp := ""
if i < slen {
stemp = sparts[i]
}
otemp := ""
if i < olen {
otemp = oparts[i]
}
d := comparePrePart(stemp, otemp)
if d != 0 {
return d
}
}
// Reaching here means two versions are of equal value but have different
// metadata (the part following a +). They are not identical in string form
// but the version comparison finds them to be equal.
return 0
}
func comparePrePart(s, o string) int {
// Fastpath if they are equal
if s == o {
return 0
}
// When s or o are empty we can use the other in an attempt to determine
// the response.
if s == "" {
if o != "" {
return -1
}
return 1
}
if o == "" {
if s != "" {
return 1
}
return -1
}
// When comparing strings "99" is greater than "103". To handle
// cases like this we need to detect numbers and compare them.
oi, n1 := strconv.ParseInt(o, 10, 64)
si, n2 := strconv.ParseInt(s, 10, 64)
// The case where both are strings compare the strings
if n1 != nil && n2 != nil {
if s > o {
return 1
}
return -1
} else if n1 != nil {
// o is a string and s is a number
return -1
} else if n2 != nil {
// s is a string and o is a number
return 1
}
// Both are numbers
if si > oi {
return 1
}
return -1
}

View File

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

View File

@@ -1,24 +0,0 @@
language: go
go:
- 1.9.x
- 1.10.x
- 1.11.x
- 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,153 +0,0 @@
# Changelog
## Release 2.15.0 (2018-04-02)
### Added
- #68 and #69: Add json helpers to docs (thanks @arunvelsriram)
- #66: Add ternary function (thanks @binoculars)
- #67: Allow keys function to take multiple dicts (thanks @binoculars)
- #89: Added sha1sum to crypto function (thanks @benkeil)
- #81: Allow customizing Root CA that used by genSignedCert (thanks @chenzhiwei)
- #92: Add travis testing for go 1.10
- #93: Adding appveyor config for windows testing
### Changed
- #90: Updating to more recent dependencies
- #73: replace satori/go.uuid with google/uuid (thanks @petterw)
### Fixed
- #76: Fixed documentation typos (thanks @Thiht)
- Fixed rounding issue on the `ago` function. Note, the removes support for Go 1.8 and older
## Release 2.14.1 (2017-12-01)
### Fixed
- #60: Fix typo in function name documentation (thanks @neil-ca-moore)
- #61: Removing line with {{ due to blocking github pages genertion
- #64: Update the list functions to handle int, string, and other slices for compatibility
## Release 2.14.0 (2017-10-06)
This new version of Sprig adds a set of functions for generating and working with SSL certificates.
- `genCA` generates an SSL Certificate Authority
- `genSelfSignedCert` generates an SSL self-signed certificate
- `genSignedCert` generates an SSL certificate and key based on a given CA
## Release 2.13.0 (2017-09-18)
This release adds new functions, including:
- `regexMatch`, `regexFindAll`, `regexFind`, `regexReplaceAll`, `regexReplaceAllLiteral`, and `regexSplit` to work with regular expressions
- `floor`, `ceil`, and `round` math functions
- `toDate` converts a string to a date
- `nindent` is just like `indent` but also prepends a new line
- `ago` returns the time from `time.Now`
### Added
- #40: Added basic regex functionality (thanks @alanquillin)
- #41: Added ceil floor and round functions (thanks @alanquillin)
- #48: Added toDate function (thanks @andreynering)
- #50: Added nindent function (thanks @binoculars)
- #46: Added ago function (thanks @slayer)
### Changed
- #51: Updated godocs to include new string functions (thanks @curtisallen)
- #49: Added ability to merge multiple dicts (thanks @binoculars)
## Release 2.12.0 (2017-05-17)
- `snakecase`, `camelcase`, and `shuffle` are three new string functions
- `fail` allows you to bail out of a template render when conditions are not met
## Release 2.11.0 (2017-05-02)
- Added `toJson` and `toPrettyJson`
- Added `merge`
- Refactored documentation
## Release 2.10.0 (2017-03-15)
- Added `semver` and `semverCompare` for Semantic Versions
- `list` replaces `tuple`
- Fixed issue with `join`
- Added `first`, `last`, `intial`, `rest`, `prepend`, `append`, `toString`, `toStrings`, `sortAlpha`, `reverse`, `coalesce`, `pluck`, `pick`, `compact`, `keys`, `omit`, `uniq`, `has`, `without`
## Release 2.9.0 (2017-02-23)
- Added `splitList` to split a list
- Added crypto functions of `genPrivateKey` and `derivePassword`
## Release 2.8.0 (2016-12-21)
- Added access to several path functions (`base`, `dir`, `clean`, `ext`, and `abs`)
- Added functions for _mutating_ dictionaries (`set`, `unset`, `hasKey`)
## Release 2.7.0 (2016-12-01)
- Added `sha256sum` to generate a hash of an input
- Added functions to convert a numeric or string to `int`, `int64`, `float64`
## Release 2.6.0 (2016-10-03)
- Added a `uuidv4` template function for generating UUIDs inside of a template.
## Release 2.5.0 (2016-08-19)
- New `trimSuffix`, `trimPrefix`, `hasSuffix`, and `hasPrefix` functions
- New aliases have been added for a few functions that didn't follow the naming conventions (`trimAll` and `abbrevBoth`)
- `trimall` and `abbrevboth` (notice the case) are deprecated and will be removed in 3.0.0
## Release 2.4.0 (2016-08-16)
- Adds two functions: `until` and `untilStep`
## Release 2.3.0 (2016-06-21)
- cat: Concatenate strings with whitespace separators.
- replace: Replace parts of a string: `replace " " "-" "Me First"` renders "Me-First"
- plural: Format plurals: `len "foo" | plural "one foo" "many foos"` renders "many foos"
- indent: Indent blocks of text in a way that is sensitive to "\n" characters.
## Release 2.2.0 (2016-04-21)
- Added a `genPrivateKey` function (Thanks @bacongobbler)
## Release 2.1.0 (2016-03-30)
- `default` now prints the default value when it does not receive a value down the pipeline. It is much safer now to do `{{.Foo | default "bar"}}`.
- Added accessors for "hermetic" functions. These return only functions that, when given the same input, produce the same output.
## Release 2.0.0 (2016-03-29)
Because we switched from `int` to `int64` as the return value for all integer math functions, the library's major version number has been incremented.
- `min` complements `max` (formerly `biggest`)
- `empty` indicates that a value is the empty value for its type
- `tuple` creates a tuple inside of a template: `{{$t := tuple "a", "b" "c"}}`
- `dict` creates a dictionary inside of a template `{{$d := dict "key1" "val1" "key2" "val2"}}`
- Date formatters have been added for HTML dates (as used in `date` input fields)
- Integer math functions can convert from a number of types, including `string` (via `strconv.ParseInt`).
## 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,20 +0,0 @@
Sprig
Copyright (C) 2013 Masterminds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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

@@ -1,26 +0,0 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\Masterminds\sprig
shallow_clone: true
environment:
GOPATH: C:\gopath
platform:
- x64
install:
- go get -u github.com/Masterminds/glide
- set PATH=%GOPATH%\bin;%PATH%
- go version
- go env
build_script:
- glide install
- go install ./...
test_script:
- go test -v
deploy: off

View File

@@ -1,435 +0,0 @@
package sprig
import (
"bytes"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"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"
"errors"
"fmt"
"math/big"
"net"
"time"
"github.com/google/uuid"
"golang.org/x/crypto/scrypt"
)
func sha256sum(input string) string {
hash := sha256.Sum256([]byte(input))
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.New())
}
var master_password_seed = "com.lyndir.masterpassword"
var password_type_templates = map[string][][]byte{
"maximum": {[]byte("anoxxxxxxxxxxxxxxxxx"), []byte("axxxxxxxxxxxxxxxxxno")},
"long": {[]byte("CvcvnoCvcvCvcv"), []byte("CvcvCvcvnoCvcv"), []byte("CvcvCvcvCvcvno"), []byte("CvccnoCvcvCvcv"), []byte("CvccCvcvnoCvcv"),
[]byte("CvccCvcvCvcvno"), []byte("CvcvnoCvccCvcv"), []byte("CvcvCvccnoCvcv"), []byte("CvcvCvccCvcvno"), []byte("CvcvnoCvcvCvcc"),
[]byte("CvcvCvcvnoCvcc"), []byte("CvcvCvcvCvccno"), []byte("CvccnoCvccCvcv"), []byte("CvccCvccnoCvcv"), []byte("CvccCvccCvcvno"),
[]byte("CvcvnoCvccCvcc"), []byte("CvcvCvccnoCvcc"), []byte("CvcvCvccCvccno"), []byte("CvccnoCvcvCvcc"), []byte("CvccCvcvnoCvcc"),
[]byte("CvccCvcvCvccno")},
"medium": {[]byte("CvcnoCvc"), []byte("CvcCvcno")},
"short": {[]byte("Cvcn")},
"basic": {[]byte("aaanaaan"), []byte("aannaaan"), []byte("aaannaaa")},
"pin": {[]byte("nnnn")},
}
var template_characters = map[byte]string{
'V': "AEIOU",
'C': "BCDFGHJKLMNPQRSTVWXYZ",
'v': "aeiou",
'c': "bcdfghjklmnpqrstvwxyz",
'A': "AEIOUBCDFGHJKLMNPQRSTVWXYZ",
'a': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz",
'n': "0123456789",
'o': "@&%?,=[]_:-+*$#!'^~;()/.",
'x': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()",
}
func derivePassword(counter uint32, password_type, password, user, site string) string {
var templates = password_type_templates[password_type]
if templates == nil {
return fmt.Sprintf("cannot find password template %s", password_type)
}
var buffer bytes.Buffer
buffer.WriteString(master_password_seed)
binary.Write(&buffer, binary.BigEndian, uint32(len(user)))
buffer.WriteString(user)
salt := buffer.Bytes()
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 2, 64)
if err != nil {
return fmt.Sprintf("failed to derive password: %s", err)
}
buffer.Truncate(len(master_password_seed))
binary.Write(&buffer, binary.BigEndian, uint32(len(site)))
buffer.WriteString(site)
binary.Write(&buffer, binary.BigEndian, counter)
var hmacv = hmac.New(sha256.New, key)
hmacv.Write(buffer.Bytes())
var seed = hmacv.Sum(nil)
var temp = templates[int(seed[0])%len(templates)]
buffer.Truncate(0)
for i, element := range temp {
pass_chars := template_characters[element]
pass_char := pass_chars[int(seed[i+1])%len(pass_chars)]
buffer.WriteByte(pass_char)
}
return buffer.String()
}
func generatePrivateKey(typ string) string {
var priv interface{}
var err error
switch typ {
case "", "rsa":
// good enough for government work
priv, err = rsa.GenerateKey(rand.Reader, 4096)
case "dsa":
key := new(dsa.PrivateKey)
// again, good enough for government work
if err = dsa.GenerateParameters(&key.Parameters, rand.Reader, dsa.L2048N256); err != nil {
return fmt.Sprintf("failed to generate dsa params: %s", err)
}
err = dsa.GenerateKey(key, rand.Reader)
priv = key
case "ecdsa":
// again, good enough for government work
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
default:
return "Unknown type " + typ
}
if err != nil {
return fmt.Sprintf("failed to generate private key: %s", err)
}
return string(pem.EncodeToMemory(pemBlockForKey(priv)))
}
type DSAKeyFormat struct {
Version int
P, Q, G, Y, X *big.Int
}
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *dsa.PrivateKey:
val := DSAKeyFormat{
P: k.P, Q: k.Q, G: k.G,
Y: k.Y, X: k.X,
}
bytes, _ := asn1.Marshal(val)
return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes}
case *ecdsa.PrivateKey:
b, _ := x509.MarshalECPrivateKey(k)
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
type certificate struct {
Cert string
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,
) (certificate, error) {
ca := certificate{}
template, err := getBaseCertTemplate(cn, nil, nil, daysValid)
if err != nil {
return ca, err
}
// Override KeyUsage and IsCA
template.KeyUsage = x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature |
x509.KeyUsageCertSign
template.IsCA = true
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return ca, fmt.Errorf("error generating rsa key: %s", err)
}
ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv)
if err != nil {
return ca, err
}
return ca, nil
}
func generateSelfSignedCertificate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
) (certificate, error) {
cert := certificate{}
template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
if err != nil {
return cert, err
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return cert, fmt.Errorf("error generating rsa key: %s", err)
}
cert.Cert, cert.Key, err = getCertAndKey(template, priv, template, priv)
if err != nil {
return cert, err
}
return cert, nil
}
func generateSignedCertificate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
ca certificate,
) (certificate, error) {
cert := certificate{}
decodedSignerCert, _ := pem.Decode([]byte(ca.Cert))
if decodedSignerCert == nil {
return cert, errors.New("unable to decode certificate")
}
signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes)
if err != nil {
return cert, fmt.Errorf(
"error parsing certificate: decodedSignerCert.Bytes: %s",
err,
)
}
decodedSignerKey, _ := pem.Decode([]byte(ca.Key))
if decodedSignerKey == nil {
return cert, errors.New("unable to decode key")
}
signerKey, err := x509.ParsePKCS1PrivateKey(decodedSignerKey.Bytes)
if err != nil {
return cert, fmt.Errorf(
"error parsing prive key: decodedSignerKey.Bytes: %s",
err,
)
}
template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
if err != nil {
return cert, err
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return cert, fmt.Errorf("error generating rsa key: %s", err)
}
cert.Cert, cert.Key, err = getCertAndKey(
template,
priv,
signerCert,
signerKey,
)
if err != nil {
return cert, err
}
return cert, nil
}
func getCertAndKey(
template *x509.Certificate,
signeeKey *rsa.PrivateKey,
parent *x509.Certificate,
signingKey *rsa.PrivateKey,
) (string, string, error) {
derBytes, err := x509.CreateCertificate(
rand.Reader,
template,
parent,
&signeeKey.PublicKey,
signingKey,
)
if err != nil {
return "", "", fmt.Errorf("error creating certificate: %s", err)
}
certBuffer := bytes.Buffer{}
if err := pem.Encode(
&certBuffer,
&pem.Block{Type: "CERTIFICATE", Bytes: derBytes},
); err != nil {
return "", "", fmt.Errorf("error pem-encoding certificate: %s", err)
}
keyBuffer := bytes.Buffer{}
if err := pem.Encode(
&keyBuffer,
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(signeeKey),
},
); err != nil {
return "", "", fmt.Errorf("error pem-encoding key: %s", err)
}
return string(certBuffer.Bytes()), string(keyBuffer.Bytes()), nil
}
func getBaseCertTemplate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
) (*x509.Certificate, error) {
ipAddresses, err := getNetIPs(ips)
if err != nil {
return nil, err
}
dnsNames, err := getAlternateDNSStrs(alternateDNS)
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: serialNumber,
Subject: pkix.Name{
CommonName: cn,
},
IPAddresses: ipAddresses,
DNSNames: dnsNames,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}, nil
}
func getNetIPs(ips []interface{}) ([]net.IP, error) {
if ips == nil {
return []net.IP{}, nil
}
var ipStr string
var ok bool
var netIP net.IP
netIPs := make([]net.IP, len(ips))
for i, ip := range ips {
ipStr, ok = ip.(string)
if !ok {
return nil, fmt.Errorf("error parsing ip: %v is not a string", ip)
}
netIP = net.ParseIP(ipStr)
if netIP == nil {
return nil, fmt.Errorf("error parsing ip: %s", ipStr)
}
netIPs[i] = netIP
}
return netIPs, nil
}
func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) {
if alternateDNS == nil {
return []string{}, nil
}
var dnsStr string
var ok bool
alternateDNSStrs := make([]string, len(alternateDNS))
for i, dns := range alternateDNS {
dnsStr, ok = dns.(string)
if !ok {
return nil, fmt.Errorf(
"error processing alternate dns name: %v is not a string",
dns,
)
}
alternateDNSStrs[i] = dnsStr
}
return alternateDNSStrs, nil
}

View File

@@ -1,76 +0,0 @@
package sprig
import (
"time"
)
// Given a format and a date, format the date string.
//
// Date can be a `time.Time` or an `int, int32, int64`.
// In the later case, it is treated as seconds since UNIX
// epoch.
func date(fmt string, date interface{}) string {
return dateInZone(fmt, date, "Local")
}
func htmlDate(date interface{}) string {
return dateInZone("2006-01-02", date, "Local")
}
func htmlDateInZone(date interface{}, zone string) string {
return dateInZone("2006-01-02", date, zone)
}
func dateInZone(fmt string, date interface{}, zone string) string {
var t time.Time
switch date := date.(type) {
default:
t = time.Now()
case time.Time:
t = date
case int64:
t = time.Unix(date, 0)
case int:
t = time.Unix(int64(date), 0)
case int32:
t = time.Unix(int64(date), 0)
}
loc, err := time.LoadLocation(zone)
if err != nil {
loc, _ = time.LoadLocation("UTC")
}
return t.In(loc).Format(fmt)
}
func dateModify(fmt string, date time.Time) time.Time {
d, err := time.ParseDuration(fmt)
if err != nil {
return date
}
return date.Add(d)
}
func dateAgo(date interface{}) string {
var t time.Time
switch date := date.(type) {
default:
t = time.Now()
case time.Time:
t = date
case int64:
t = time.Unix(date, 0)
case int:
t = time.Unix(int64(date), 0)
}
// Drop resolution to seconds
duration := time.Since(t).Round(time.Second)
return duration.String()
}
func toDate(fmt, str string) time.Time {
t, _ := time.ParseInLocation(fmt, str, time.Local)
return t
}

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