Compare commits

..

24 Commits

Author SHA1 Message Date
Andrey Nering
5738436d55 v2.4.0 2019-02-21 21:28:10 -03:00
Andrey Nering
5e49b38c33 Mitigate execext.Expand problems on Windows
Closes #170

Co-authored-by: mikynov <micnov@gmail.com>
2019-02-21 21:22:40 -03:00
Andrey Nering
0c94adaff9 Update CHANGELOG.md 2019-02-21 21:06:46 -03:00
Andrey Nering
f8a6c5d06c Fix execext.Expand for file names with spaces
Fixes #176
2019-02-21 20:59:17 -03:00
Andrey Nering
21e66c7c02 Docs: Update theme color 2019-02-09 10:48:48 -02:00
Andrey Nering
902f0d3ac4 Don't persist new checksum on the disk if dry mode is enabled
Fixes #166
2019-02-09 10:44:35 -02:00
Andrey Nering
713ecd35f6 Pass context as an argument 2019-02-09 10:16:13 -02:00
Andrey Nering
27b35157cd Indentation fix 2019-02-09 10:15:38 -02:00
Andrey Nering
f8fb639870 Update documentation and changelog to mention the new --output flag
Ref #173
2019-02-09 10:01:41 -02:00
Andrey Nering
14f41ae619 Merge pull request #173 from kjdev/master
Add execute output style options
2019-02-07 19:51:49 -02:00
kj
a026d72924 Add execute output style options 2019-02-05 15:42:57 +09:00
Andrey Nering
2cb070f5b3 Merge pull request #172 from go-task/allow-calling-root-task-from-included
Allow calling a task of the root Taskfile from within an included Taskfile
2019-02-02 21:26:22 -02:00
Andrey Nering
1dec956e99 Allow calling a task of the root Taskfile from within an included Taskfile
Fixes #161
2019-02-02 21:22:08 -02:00
Tim Foerster
310394aa60 task: Fix merge behavior 2019-02-02 17:19:20 -02:00
Andrey Nering
468ff18243 Merge pull request #164 from saromanov/fix-error-message
taskfile: return defined error when taskfile.yml is not found
2019-01-22 22:16:45 -02:00
Sergey
44a63580f0 taskfile: missing task: prefix to the error message 2019-01-23 02:01:53 +05:00
Andrey Nering
4ac1fa43aa Merge pull request #165 from 0xflotus/patch-1
fixed docs
2019-01-21 23:00:01 -02:00
0xflotus
6f992a3cf7 fixed suppressed 2019-01-21 13:36:04 +01:00
0xflotus
fd4ce656d5 fixed Snapcraft 2019-01-21 13:33:19 +01:00
Sergey
9ed2dca427 taskfile: return defined error when taskfile.yml is not found 2019-01-21 14:56:14 +05:00
Andrey Nering
dfb804fe3f Update vendor/ 2019-01-19 19:25:49 -02:00
Andrey Nering
4f2a84b426 Upgrade some libs 2019-01-19 19:24:49 -02:00
Andrey Nering
14a127b6b3 Pin mattn/go-zglob version 2019-01-19 19:08:30 -02:00
Andrey Nering
06000533fb Pin mvdan/sh version 2019-01-19 19:07:11 -02:00
30 changed files with 392 additions and 119 deletions

View File

@@ -1,5 +1,20 @@
# Changelog
## v2.4.0 - 2019-02-21
- Allow calling a task of the root Taskfile from an included Taskfile
by prefixing it with `:`
([#161](https://github.com/go-task/task/issues/161), [#172](https://github.com/go-task/task/issues/172)),
- Add flag to override the `output` option
([#173](https://github.com/go-task/task/pull/173));
- Fix bug where Task was persisting the new checksum on the disk when the Dry
Mode is enabled
([#166](https://github.com/go-task/task/issues/166));
- Fix file timestamp issue when the file name has spaces
([#176](https://github.com/go-task/task/issues/176));
- Mitigating path expanding issues on Windows
([#170](https://github.com/go-task/task/pull/170)).
## v2.3.0 - 2019-01-02
- On Windows, Task can now be installed using [Scoop](https://scoop.sh/)

View File

@@ -57,6 +57,7 @@ func main() {
silent bool
dry bool
dir string
output string
)
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
@@ -69,6 +70,7 @@ func main() {
pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing")
pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them")
pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution")
pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]")
pflag.Parse()
if versionFlag {
@@ -87,11 +89,6 @@ func main() {
return
}
ctx := context.Background()
if !watch {
ctx = getSignalContext()
}
e := task.Executor{
Force: force,
Watch: watch,
@@ -100,11 +97,11 @@ func main() {
Dir: dir,
Dry: dry,
Context: ctx,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
OutputStyle: output,
}
if err := e.Setup(); err != nil {
log.Fatal(err)
@@ -126,14 +123,19 @@ func main() {
log.Fatal(err)
}
ctx := context.Background()
if !watch {
ctx = getSignalContext()
}
if status {
if err = e.Status(calls...); err != nil {
if err = e.Status(ctx, calls...); err != nil {
log.Fatal(err)
}
return
}
if err := e.Run(calls...); err != nil {
if err := e.Run(ctx, calls...); err != nil {
log.Fatal(err)
}
}

View File

@@ -33,7 +33,7 @@
name: 'Task',
repo: 'go-task/task',
ga: 'UA-126286662-1',
themeColor: '#83d0f2',
themeColor: '#00add8',
loadSidebar: true,
auto2top: true,
maxLevel: 3,

View File

@@ -22,7 +22,7 @@ the binaries:
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
* Moving both `i386` and `amd64` new artifacts to the stable channel on
the [Snapscraft dashboard][snapcraftdashboard]
the [Snapcraft dashboard][snapcraftdashboard]
# Scoop

View File

@@ -258,6 +258,10 @@ tasks:
The above syntax is also supported in `deps`.
> NOTE: If you want to call a task declared in the root Taskfile from within an
> [included Taskfile](#including-other-taskfiles), add a leading `:` like this:
> `task: :task-name`.
## Prevent unnecessary work
If a task generates something, you can inform Task the source and generated
@@ -638,7 +642,7 @@ tasks:
- echo "Hello World"
```
`ignore_error` can also be set for a task, which mean errors will be supressed
`ignore_error` can also be set for a task, which mean errors will be suppressed
for all commands. But keep in mind this option won't propagate to other tasks
called either by `deps` or `cmds`!
@@ -704,6 +708,8 @@ $ task default
[print-baz] baz
```
> The `output` option can also be specified by the `--output` or `-o` flags.
## Watch tasks
If you give a `--watch` or `-w` argument, task will watch for file changes

9
go.mod
View File

@@ -9,16 +9,15 @@ require (
github.com/huandu/xstrings v1.1.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53
github.com/mattn/go-zglob v0.0.1
github.com/mitchellh/go-homedir v1.0.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/radovskyb/watcher v1.0.2
github.com/radovskyb/watcher v1.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.2.2
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 // indirect
gopkg.in/yaml.v2 v2.2.1
mvdan.cc/sh v2.6.3-0.20181216173157-8aeb0734cd0f+incompatible
mvdan.cc/sh v2.6.3+incompatible
)

18
go.sum
View File

@@ -4,6 +4,7 @@ github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
@@ -17,18 +18,19 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
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/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53 h1:tGfIHhDghvEnneeRhODvGYOt305TPwingKt6p90F4MU=
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
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/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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.2 h1:9L5TsZUbo1nKhQEQPtICVc+x9UZQ6VPdBepLHyGw/bQ=
github.com/radovskyb/watcher v1.0.2/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
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/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
@@ -41,5 +43,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
mvdan.cc/sh v2.6.3-0.20181216173157-8aeb0734cd0f+incompatible h1:jf0jjqiqwKXdH3JBKY+K3tFXGtUQZr/pFIO+cn0tQCc=
mvdan.cc/sh v2.6.3-0.20181216173157-8aeb0734cd0f+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
mvdan.cc/sh v2.6.3+incompatible h1:uXnnFNSBQbKUwwh2iBSkVjG+GbwoMuI+UmBVPnNiWhA=
mvdan.cc/sh v2.6.3+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=

View File

@@ -5,6 +5,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
"strings"
"mvdan.cc/sh/expand"
@@ -72,6 +73,8 @@ func IsExitError(err error) bool {
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
// if available.
func Expand(s string) (string, error) {
s = filepath.ToSlash(s)
s = strings.Replace(s, " ", `\ `, -1)
fields, err := shell.Fields(s, nil)
if err != nil {
return "", err

View File

@@ -17,6 +17,7 @@ type Checksum struct {
Dir string
Task string
Sources []string
Dry bool
}
// IsUpToDate implements the Checker interface
@@ -36,9 +37,11 @@ func (c *Checksum) IsUpToDate() (bool, error) {
return false, nil
}
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
return false, err
if !c.Dry {
_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
return false, err
}
}
return oldMd5 == newMd5, nil
}

View File

@@ -38,7 +38,7 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
if t1.Env == nil {
t1.Env = make(Vars)
}
for k, v := range t2.Vars {
for k, v := range t2.Env {
t1.Env[k] = v
}
@@ -66,5 +66,8 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
}
func taskNameWithNamespace(taskName string, namespaces ...string) string {
if strings.HasPrefix(taskName, ":") {
return strings.TrimPrefix(taskName, ":")
}
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
}

View File

@@ -12,14 +12,19 @@ import (
"gopkg.in/yaml.v2"
)
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
var ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main 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")
// ErrNoTaskfileFound is returned when Taskfile.yml is not found
ErrNoTaskfileFound = errors.New(`task: No Taskfile.yml found. Use "task --init" to create a new one`)
)
// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, "Taskfile.yml")
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf(`No Taskfile.yml found. Use "task --init" to create a new one`)
return nil, ErrNoTaskfileFound
}
t, err := readTaskfile(path)
if err != nil {

View File

@@ -10,13 +10,13 @@ import (
)
// Status returns an error if any the of given tasks is not up-to-date
func (e *Executor) Status(calls ...taskfile.Call) error {
func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error {
for _, call := range calls {
t, err := e.CompiledTask(call)
if err != nil {
return err
}
isUpToDate, err := isTaskUpToDate(e.Context, t)
isUpToDate, err := e.isTaskUpToDate(ctx, t)
if err != nil {
return err
}
@@ -27,12 +27,12 @@ func (e *Executor) Status(calls ...taskfile.Call) error {
return nil
}
func isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
if len(t.Status) > 0 {
return isTaskUpToDateStatus(ctx, t)
return e.isTaskUpToDateStatus(ctx, t)
}
checker, err := getStatusChecker(t)
checker, err := e.getStatusChecker(t)
if err != nil {
return false, err
}
@@ -40,15 +40,15 @@ func isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) {
return checker.IsUpToDate()
}
func statusOnError(t *taskfile.Task) error {
checker, err := getStatusChecker(t)
func (e *Executor) statusOnError(t *taskfile.Task) error {
checker, err := e.getStatusChecker(t)
if err != nil {
return err
}
return checker.OnError()
}
func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
switch t.Method {
case "", "timestamp":
return &status.Timestamp{
@@ -61,6 +61,7 @@ func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
Dir: t.Dir,
Task: t.Task,
Sources: t.Sources,
Dry: e.Dry,
}, nil
case "none":
return status.None{}, nil
@@ -69,7 +70,7 @@ func getStatusChecker(t *taskfile.Task) (status.Checker, error) {
}
}
func isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) {
for _, s := range t.Status {
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: s,

23
task.go
View File

@@ -37,15 +37,14 @@ type Executor struct {
Silent bool
Dry bool
Context context.Context
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Logger *logger.Logger
Compiler compiler.Compiler
Output output.Output
Logger *logger.Logger
Compiler compiler.Compiler
Output output.Output
OutputStyle string
taskvars taskfile.Vars
@@ -53,7 +52,7 @@ type Executor struct {
}
// Run runs Task
func (e *Executor) Run(calls ...taskfile.Call) error {
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
// check if given tasks exist
for _, c := range calls {
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
@@ -68,7 +67,7 @@ func (e *Executor) Run(calls ...taskfile.Call) error {
}
for _, c := range calls {
if err := e.RunTask(e.Context, c); err != nil {
if err := e.RunTask(ctx, c); err != nil {
return err
}
}
@@ -92,9 +91,6 @@ func (e *Executor) Setup() error {
return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
}
if e.Context == nil {
e.Context = context.Background()
}
if e.Stdin == nil {
e.Stdin = os.Stdin
}
@@ -134,6 +130,9 @@ func (e *Executor) Setup() error {
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
}
if e.OutputStyle != "" {
e.Taskfile.Output = e.OutputStyle
}
switch e.Taskfile.Output {
case "", "interleaved":
e.Output = output.Interleaved{}
@@ -182,7 +181,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
}
if !e.Force {
upToDate, err := isTaskUpToDate(ctx, t)
upToDate, err := e.isTaskUpToDate(ctx, t)
if err != nil {
return err
}
@@ -196,7 +195,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
for i := range t.Cmds {
if err := e.runCommand(ctx, t, call, i); err != nil {
if err2 := statusOnError(t); err2 != nil {
if err2 := e.statusOnError(t); err2 != nil {
e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
}

View File

@@ -2,6 +2,7 @@ package task_test
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
@@ -40,7 +41,7 @@ func (fct fileContentTest) Run(t *testing.T) {
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup(), "e.Setup()")
assert.NoError(t, e.Run(taskfile.Call{Task: fct.Target}), "e.Run(target)")
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
@@ -178,7 +179,7 @@ func TestVarsInvalidTmpl(t *testing.T) {
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup(), "e.Setup()")
assert.EqualError(t, e.Run(taskfile.Call{Task: target}), expectError, "e.Run(target)")
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
}
func TestParams(t *testing.T) {
@@ -230,7 +231,7 @@ func TestDeps(t *testing.T) {
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "default"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
for _, f := range files {
f = filepath.Join(dir, f)
@@ -258,14 +259,14 @@ func TestStatus(t *testing.T) {
Silent: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
if _, err := os.Stat(file); err != nil {
t.Errorf("File should exists: %v", err)
}
e.Silent = false
assert.NoError(t, e.Run(taskfile.Call{Task: "gen-foo"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
if buff.String() != `task: Task "gen-foo" is up to date`+"\n" {
t.Errorf("Wrong output message: %s", buff.String())
@@ -273,16 +274,19 @@ func TestStatus(t *testing.T) {
}
func TestGenerates(t *testing.T) {
var srcTask = "sub/src.txt"
var relTask = "rel.txt"
var absTask = "abs.txt"
const (
srcTask = "sub/src.txt"
relTask = "rel.txt"
absTask = "abs.txt"
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} {
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
path := filepath.Join(dir, task)
_ = os.Remove(path)
if _, err := os.Stat(path); err == nil {
@@ -298,13 +302,13 @@ func TestGenerates(t *testing.T) {
}
assert.NoError(t, e.Setup())
for _, theTask := range []string{relTask, absTask} {
for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
var destFile = filepath.Join(dir, theTask)
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
// Run task for the first time.
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
if _, err := os.Stat(srcFile); err != nil {
t.Errorf("File should exists: %v", err)
@@ -319,7 +323,7 @@ func TestGenerates(t *testing.T) {
buff.Reset()
// Re-run task to ensure it's now found to be up-to-date.
assert.NoError(t, e.Run(taskfile.Call{Task: theTask}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: theTask}))
if buff.String() != upToDate {
t.Errorf("Wrong output message: %s", buff.String())
}
@@ -350,14 +354,14 @@ func TestStatusChecksum(t *testing.T) {
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
for _, f := range files {
_, err := os.Stat(filepath.Join(dir, f))
assert.NoError(t, err)
}
buff.Reset()
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
}
@@ -388,7 +392,7 @@ func TestCyclicDep(t *testing.T) {
Stderr: ioutil.Discard,
}
assert.NoError(t, e.Setup())
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(taskfile.Call{Task: "task-1"}))
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run(context.Background(), taskfile.Call{Task: "task-1"}))
}
func TestTaskVersion(t *testing.T) {
@@ -424,10 +428,10 @@ func TestTaskIgnoreErrors(t *testing.T) {
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "task-should-pass"}))
assert.Error(t, e.Run(taskfile.Call{Task: "task-should-fail"}))
assert.NoError(t, e.Run(taskfile.Call{Task: "cmd-should-pass"}))
assert.Error(t, e.Run(taskfile.Call{Task: "cmd-should-fail"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-pass"}))
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "task-should-fail"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-pass"}))
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "cmd-should-fail"}))
}
func TestExpand(t *testing.T) {
@@ -445,7 +449,7 @@ func TestExpand(t *testing.T) {
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "pwd"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "pwd"}))
assert.Equal(t, home, strings.TrimSpace(buff.String()))
}
@@ -464,7 +468,7 @@ func TestDry(t *testing.T) {
Dry: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(taskfile.Call{Task: "build"}))
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
assert.Equal(t, "touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil {
@@ -472,6 +476,32 @@ func TestDry(t *testing.T) {
}
}
// TestDryChecksum tests if the checksum file is not being written to disk
// if the dry mode is enabled.
func TestDryChecksum(t *testing.T) {
const dir = "testdata/dry_checksum"
checksumFile := filepath.Join(dir, ".task/checksum/default")
_ = os.Remove(checksumFile)
e := task.Executor{
Dir: dir,
Stdout: ioutil.Discard,
Stderr: ioutil.Discard,
Dry: true,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
_, err := os.Stat(checksumFile)
assert.Error(t, err, "checksum file should not exist")
e.Dry = false
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
_, err = os.Stat(checksumFile)
assert.NoError(t, err, "checksum file should exist")
}
func TestIncludes(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes",
@@ -511,3 +541,15 @@ func TestIncludesDependencies(t *testing.T) {
}
tt.Run(t)
}
func TestIncludesCallingRoot(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes_call_root_task",
Target: "included:call-root",
TrimSpace: true,
Files: map[string]string{
"root_task.txt": "root task",
},
}
tt.Run(t)
}

9
testdata/dry_checksum/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
version: '2'
tasks:
default:
cmds:
- echo "Working..."
sources:
- source.txt
method: checksum

1
testdata/dry_checksum/source.txt vendored Normal file
View File

@@ -0,0 +1 @@
Something...

View File

@@ -29,3 +29,13 @@ sub/src.txt:
- echo "hello world" > sub/src.txt
status:
- test -f sub/src.txt
'my text file.txt':
desc: generate file with spaces in the name
deps: [sub/src.txt]
cmds:
- cat sub/src.txt > 'my text file.txt'
sources:
- sub/src.txt
generates:
- 'my text file.txt'

View File

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

View File

@@ -0,0 +1,9 @@
version: '2'
includes:
included: Taskfile2.yml
tasks:
root-task:
cmds:
- echo "root task" > root_task.txt

View File

@@ -0,0 +1,6 @@
version: '2'
tasks:
call-root:
cmds:
- task: :root-task

View File

@@ -15,7 +15,8 @@ Events contain the `os.FileInfo` of the file or directory that the event is base
[Watcher Command](#command)
# Update
Event.Path for Rename and Move events is now returned in the format of `fromPath -> toPath`
- Added new file filter hooks (Including a built in regexp filtering hook) [Dec 12, 2018]
- Event.Path for Rename and Move events is now returned in the format of `fromPath -> toPath`
#### Chmod event is not supported under windows.
@@ -68,6 +69,11 @@ func main() {
// Only notify rename and move events.
w.FilterOps(watcher.Rename, watcher.Move)
// Only files that match the regular expression during file listings
// will be watched.
r := regexp.MustCompile("^abc$")
w.AddFilterHook(watcher.RegexFilterHook(r, false))
go func() {
for {
select {
@@ -128,6 +134,8 @@ Usage of watcher:
command to run when an event occurs
-dotfiles
watch dot files (default true)
-ignore string
comma separated list of paths to ignore
-interval string
watcher poll interval (default "100ms")
-keepalive

12
vendor/github.com/radovskyb/watcher/ishidden.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// +build !windows
package watcher
import (
"path/filepath"
"strings"
)
func isHiddenFile(path string) (bool, error) {
return strings.HasPrefix(filepath.Base(path), "."), nil
}

View File

@@ -0,0 +1,21 @@
// +build windows
package watcher
import (
"syscall"
)
func isHiddenFile(path string) (bool, error) {
pointer, err := syscall.UTF16PtrFromString(path)
if err != nil {
return false, err
}
attributes, err := syscall.GetFileAttributes(pointer)
if err != nil {
return false, err
}
return attributes&syscall.FILE_ATTRIBUTE_HIDDEN != 0, nil
}

View File

@@ -4,6 +4,6 @@ package watcher
import "os"
func SameFile(fi1, fi2 os.FileInfo) bool {
func sameFile(fi1, fi2 os.FileInfo) bool {
return os.SameFile(fi1, fi2)
}

View File

@@ -4,7 +4,7 @@ package watcher
import "os"
func SameFile(fi1, fi2 os.FileInfo) bool {
func sameFile(fi1, fi2 os.FileInfo) bool {
return fi1.ModTime() == fi2.ModTime() &&
fi1.Size() == fi2.Size() &&
fi1.Mode() == fi2.Mode() &&

View File

@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
@@ -24,6 +25,10 @@ var (
// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
// being watched has been deleted.
ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")
// ErrSkip is less of an error, but more of a way for path hooks to skip a file or
// directory.
ErrSkip = errors.New("error: skipping file")
)
// An Op is a type that is used to describe what type
@@ -69,16 +74,43 @@ type Event struct {
// String returns a string depending on what type of event occurred and the
// file name associated with the event.
func (e Event) String() string {
if e.FileInfo != nil {
pathType := "FILE"
if e.IsDir() {
pathType = "DIRECTORY"
}
return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
if e.FileInfo == nil {
return "???"
}
return "???"
pathType := "FILE"
if e.IsDir() {
pathType = "DIRECTORY"
}
return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
}
// FilterFileHookFunc is a function that is called to filter files during listings.
// If a file is ok to be listed, nil is returned otherwise ErrSkip is returned.
type FilterFileHookFunc func(info os.FileInfo, fullPath string) error
// RegexFilterHook is a function that accepts or rejects a file
// for listing based on whether it's filename or full path matches
// a regular expression.
func RegexFilterHook(r *regexp.Regexp, useFullPath bool) FilterFileHookFunc {
return func(info os.FileInfo, fullPath string) error {
str := info.Name()
if useFullPath {
str = fullPath
}
// Match
if r.MatchString(str) {
return nil
}
// No match.
return ErrSkip
}
}
// Watcher describes a process that watches files for changes.
type Watcher struct {
Event chan Event
Error chan error
@@ -88,6 +120,7 @@ type Watcher struct {
// mu protects the following.
mu *sync.Mutex
ffh []FilterFileHookFunc
running bool
names map[string]bool // bool for recursive or not.
files map[string]os.FileInfo // map of files.
@@ -125,6 +158,13 @@ func (w *Watcher) SetMaxEvents(delta int) {
w.mu.Unlock()
}
// AddFilterHook
func (w *Watcher) AddFilterHook(f FilterFileHookFunc) {
w.mu.Lock()
w.ffh = append(w.ffh, f)
w.mu.Unlock()
}
// IgnoreHiddenFiles sets the watcher to ignore any file or directory
// that starts with a dot.
func (w *Watcher) IgnoreHiddenFiles(ignore bool) {
@@ -157,7 +197,13 @@ func (w *Watcher) Add(name string) (err error) {
// If name is on the ignored list or if hidden files are
// ignored and name is a hidden file or directory, simply return.
_, ignored := w.ignored[name]
if ignored || (w.ignoreHidden && strings.HasPrefix(name, ".")) {
isHidden, err := isHiddenFile(name)
if err != nil {
return err
}
if ignored || (w.ignoreHidden && isHidden) {
return nil
}
@@ -200,18 +246,36 @@ func (w *Watcher) list(name string) (map[string]os.FileInfo, error) {
// Add all of the files in the directory to the file list as long
// as they aren't on the ignored list or are hidden files if ignoreHidden
// is set to true.
outer:
for _, fInfo := range fInfoList {
path := filepath.Join(name, fInfo.Name())
_, ignored := w.ignored[path]
if ignored || (w.ignoreHidden && strings.HasPrefix(fInfo.Name(), ".")) {
isHidden, err := isHiddenFile(path)
if err != nil {
return nil, err
}
if ignored || (w.ignoreHidden && isHidden) {
continue
}
for _, f := range w.ffh {
err := f(fInfo, path)
if err == ErrSkip {
continue outer
}
if err != nil {
return nil, err
}
}
fileList[path] = fInfo
}
return fileList, nil
}
// Add adds either a single file or directory recursively to the file list.
// AddRecursive adds either a single file or directory recursively to the file list.
func (w *Watcher) AddRecursive(name string) (err error) {
w.mu.Lock()
defer w.mu.Unlock()
@@ -242,10 +306,27 @@ func (w *Watcher) listRecursive(name string) (map[string]os.FileInfo, error) {
if err != nil {
return err
}
for _, f := range w.ffh {
err := f(info, path)
if err == ErrSkip {
return nil
}
if err != nil {
return err
}
}
// If path is ignored and it's a directory, skip the directory. If it's
// ignored and it's a single file, skip the file.
_, ignored := w.ignored[path]
if ignored || (w.ignoreHidden && strings.HasPrefix(info.Name(), ".")) {
isHidden, err := isHiddenFile(path)
if err != nil {
return err
}
if ignored || (w.ignoreHidden && isHidden) {
if info.IsDir() {
return filepath.SkipDir
}
@@ -292,7 +373,7 @@ func (w *Watcher) Remove(name string) (err error) {
return nil
}
// Remove removes either a single file or a directory recursively from
// RemoveRecursive removes either a single file or a directory recursively from
// the file's list.
func (w *Watcher) RemoveRecursive(name string) (err error) {
w.mu.Lock()
@@ -346,11 +427,17 @@ func (w *Watcher) Ignore(paths ...string) (err error) {
return nil
}
// WatchedFiles returns a map of files added to a Watcher.
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
w.mu.Lock()
defer w.mu.Unlock()
return w.files
files := make(map[string]os.FileInfo)
for k, v := range w.files {
files[k] = v
}
return files
}
// fileInfo is an implementation of os.FileInfo that can be used
@@ -560,7 +647,7 @@ func (w *Watcher) pollEvents(files map[string]os.FileInfo, evt chan Event,
// Check for renames and moves.
for path1, info1 := range removes {
for path2, info2 := range creates {
if SameFile(info1, info2) {
if sameFile(info1, info2) {
e := Event{
Op: Move,
Path: fmt.Sprintf("%s -> %s", path1, path2),
@@ -606,6 +693,7 @@ func (w *Watcher) Wait() {
w.wg.Wait()
}
// Close stops a Watcher and unlocks its mutex, then sends a close signal.
func (w *Watcher) Close() {
w.mu.Lock()
if !w.running {

View File

@@ -1,22 +1,21 @@
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
MIT License
Please consider promoting this project if you find it useful.
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
Permission is hereby granted, free of charge, to any person
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:
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 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.
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

@@ -39,7 +39,7 @@ type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool
// for table driven tests.
type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
// ValuesAssertionFunc is a common function prototype when validating an error value. Can be useful
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
// for table driven tests.
type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
@@ -179,7 +179,11 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
return ""
}
if len(msgAndArgs) == 1 {
return msgAndArgs[0].(string)
msg := msgAndArgs[0]
if msgAsStr, ok := msg.(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", msg)
}
if len(msgAndArgs) > 1 {
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
@@ -415,6 +419,17 @@ func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
}
// containsKind checks if a specified kind in the slice of kinds.
func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool {
for i := 0; i < len(kinds); i++ {
if kind == kinds[i] {
return true
}
}
return false
}
// isNil checks if a specified object is nil or not, without Failing.
func isNil(object interface{}) bool {
if object == nil {
@@ -423,7 +438,14 @@ func isNil(object interface{}) bool {
value := reflect.ValueOf(object)
kind := value.Kind()
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
isNilableKind := containsKind(
[]reflect.Kind{
reflect.Chan, reflect.Func,
reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice},
kind)
if isNilableKind && value.IsNil() {
return true
}
@@ -1327,7 +1349,7 @@ func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
}
// diff returns a diff of both values as long as both are of the same type and
// are a struct, map, slice or array. Otherwise it returns an empty string.
// are a struct, map, slice, array or string. Otherwise it returns an empty string.
func diff(expected interface{}, actual interface{}) string {
if expected == nil || actual == nil {
return ""
@@ -1345,7 +1367,7 @@ func diff(expected interface{}, actual interface{}) string {
}
var e, a string
if ek != reflect.String {
if et != reflect.TypeOf("") {
e = spewConfig.Sdump(expected)
a = spewConfig.Sdump(actual)
} else {

8
vendor/modules.txt vendored
View File

@@ -12,18 +12,18 @@ github.com/google/uuid
github.com/huandu/xstrings
# github.com/imdario/mergo v0.3.6
github.com/imdario/mergo
# github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53
# github.com/mattn/go-zglob v0.0.1
github.com/mattn/go-zglob
github.com/mattn/go-zglob/fastwalk
# github.com/mitchellh/go-homedir v1.0.0
github.com/mitchellh/go-homedir
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
# github.com/radovskyb/watcher v1.0.2
# github.com/radovskyb/watcher v1.0.5
github.com/radovskyb/watcher
# github.com/spf13/pflag v1.0.3
github.com/spf13/pflag
# github.com/stretchr/testify v1.2.2
# github.com/stretchr/testify v1.3.0
github.com/stretchr/testify/assert
# golang.org/x/crypto v0.0.0-20180830192347-182538f80094
golang.org/x/crypto/ssh/terminal
@@ -38,7 +38,7 @@ golang.org/x/sys/unix
golang.org/x/sys/windows
# gopkg.in/yaml.v2 v2.2.1
gopkg.in/yaml.v2
# mvdan.cc/sh v2.6.3-0.20181216173157-8aeb0734cd0f+incompatible
# mvdan.cc/sh v2.6.3+incompatible
mvdan.cc/sh/expand
mvdan.cc/sh/interp
mvdan.cc/sh/shell

View File

@@ -709,7 +709,14 @@ func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, matches []string
}
infos, err := cfg.ReadDir(filepath.Join(base, dir))
if err != nil {
return nil, err
// Ignore the error, as this might be a file instead of a
// directory. v3 refactored globbing to only use one ReadDir
// call per directory instead of two, so it knows to skip this
// kind of path at the ReadDir call of its parent.
// Instead of backporting that complex rewrite into v2, just
// work around the edge case here. We might ignore other kinds
// of errors, but at least we don't fail on a correct glob.
return matches, nil
}
for _, info := range infos {
name := info.Name()