diff --git a/go.mod b/go.mod index 3686442a..7dc74392 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 github.com/go-task/template v0.1.0 github.com/joho/godotenv v1.5.1 - github.com/mattn/go-zglob v0.0.6 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/otiai10/copy v1.14.1 github.com/puzpuzpuz/xsync/v3 v3.5.1 diff --git a/go.sum b/go.sum index 52ac3bc3..a100547c 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A= -github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= diff --git a/internal/execext/exec.go b/internal/execext/exec.go index 1de964e7..811d539c 100644 --- a/internal/execext/exec.go +++ b/internal/execext/exec.go @@ -11,13 +11,15 @@ import ( "mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/interp" - "mvdan.cc/sh/v3/shell" "mvdan.cc/sh/v3/syntax" "github.com/go-task/task/v3/errors" ) -// RunCommandOptions is the options for the RunCommand func +// ErrNilOptions is returned when a nil options is given +var ErrNilOptions = errors.New("execext: nil options given") + +// RunCommandOptions is the options for the [RunCommand] func. type RunCommandOptions struct { Command string Dir string @@ -29,9 +31,6 @@ type RunCommandOptions struct { Stderr io.Writer } -// ErrNilOptions is returned when a nil options is given -var ErrNilOptions = errors.New("execext: nil options given") - // RunCommand runs a shell command func RunCommand(ctx context.Context, opts *RunCommandOptions) error { if opts == nil { @@ -91,22 +90,64 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error { return r.Run(ctx, p) } -// Expand is a helper to mvdan.cc/shell.Fields that returns the first field -// if available. -func Expand(s string) (string, error) { +func escape(s string) string { s = filepath.ToSlash(s) s = strings.ReplaceAll(s, " ", `\ `) s = strings.ReplaceAll(s, "&", `\&`) s = strings.ReplaceAll(s, "(", `\(`) s = strings.ReplaceAll(s, ")", `\)`) - fields, err := shell.Fields(s, nil) + return s +} + +// ExpandLiteral is a wrapper around [expand.Literal]. It will escape the input +// string, expand any shell symbols (such as '~') and resolve any environment +// variables. +func ExpandLiteral(s string) (string, error) { + if s == "" { + return "", nil + } + s = escape(s) + p := syntax.NewParser() + var words []*syntax.Word + err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool { + words = append(words, w) + return true + }) if err != nil { return "", err } - if len(fields) > 0 { - return fields[0], nil + if len(words) == 0 { + return "", nil } - return "", nil + cfg := &expand.Config{ + Env: expand.FuncEnviron(os.Getenv), + ReadDir2: os.ReadDir, + GlobStar: true, + } + return expand.Literal(cfg, words[0]) +} + +// ExpandFields is a wrapper around [expand.Fields]. It will escape the input +// string, expand any shell symbols (such as '~') and resolve any environment +// variables. It also expands brace expressions ({a.b}) and globs (*/**) and +// returns the results as a list of strings. +func ExpandFields(s string) ([]string, error) { + s = escape(s) + p := syntax.NewParser() + var words []*syntax.Word + err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool { + words = append(words, w) + return true + }) + if err != nil { + return nil, err + } + cfg := &expand.Config{ + Env: expand.FuncEnviron(os.Getenv), + ReadDir2: os.ReadDir, + GlobStar: true, + } + return expand.Fields(cfg, words...) } func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc { diff --git a/internal/fingerprint/glob.go b/internal/fingerprint/glob.go index d9e70135..bf12afdd 100644 --- a/internal/fingerprint/glob.go +++ b/internal/fingerprint/glob.go @@ -4,8 +4,6 @@ import ( "os" "sort" - "github.com/mattn/go-zglob" - "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/taskfile/ast" @@ -28,12 +26,7 @@ func Globs(dir string, globs []*ast.Glob) ([]string, error) { func glob(dir string, g string) ([]string, error) { g = filepathext.SmartJoin(dir, g) - g, err := execext.Expand(g) - if err != nil { - return nil, err - } - - fs, err := zglob.GlobFollowSymlinks(g) + fs, err := execext.ExpandFields(g) if err != nil { return nil, err } diff --git a/setup.go b/setup.go index f94bf54a..430d0fdf 100644 --- a/setup.go +++ b/setup.go @@ -120,7 +120,7 @@ func (e *Executor) setupTempDir() error { Fingerprint: filepathext.SmartJoin(e.Dir, ".task"), } } else if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") { - tempDir, err := execext.Expand(tempDir) + tempDir, err := execext.ExpandLiteral(tempDir) if err != nil { return err } @@ -141,7 +141,7 @@ func (e *Executor) setupTempDir() error { remoteDir := env.GetTaskEnv("REMOTE_DIR") if remoteDir != "" { if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") { - remoteTempDir, err := execext.Expand(remoteDir) + remoteTempDir, err := execext.ExpandLiteral(remoteDir) if err != nil { return err } diff --git a/taskfile/node_file.go b/taskfile/node_file.go index 99e2ec9c..e0dedcb5 100644 --- a/taskfile/node_file.go +++ b/taskfile/node_file.go @@ -84,7 +84,7 @@ func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) { return entrypoint, nil } - path, err := execext.Expand(entrypoint) + path, err := execext.ExpandLiteral(entrypoint) if err != nil { return "", err } @@ -100,7 +100,7 @@ func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) { } func (node *FileNode) ResolveDir(dir string) (string, error) { - path, err := execext.Expand(dir) + path, err := execext.ExpandLiteral(dir) if err != nil { return "", err } diff --git a/taskfile/node_git.go b/taskfile/node_git.go index 557986d5..72ac7e1a 100644 --- a/taskfile/node_git.go +++ b/taskfile/node_git.go @@ -106,7 +106,7 @@ func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) { } func (node *GitNode) ResolveDir(dir string) (string, error) { - path, err := execext.Expand(dir) + path, err := execext.ExpandLiteral(dir) if err != nil { return "", err } diff --git a/taskfile/node_http.go b/taskfile/node_http.go index 6e152972..6197f327 100644 --- a/taskfile/node_http.go +++ b/taskfile/node_http.go @@ -97,7 +97,7 @@ func (node *HTTPNode) ResolveEntrypoint(entrypoint string) (string, error) { } func (node *HTTPNode) ResolveDir(dir string) (string, error) { - path, err := execext.Expand(dir) + path, err := execext.ExpandLiteral(dir) if err != nil { return "", err } diff --git a/taskfile/node_stdin.go b/taskfile/node_stdin.go index 3855415d..20d33137 100644 --- a/taskfile/node_stdin.go +++ b/taskfile/node_stdin.go @@ -48,7 +48,7 @@ func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) { return entrypoint, nil } - path, err := execext.Expand(entrypoint) + path, err := execext.ExpandLiteral(entrypoint) if err != nil { return "", err } @@ -61,7 +61,7 @@ func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) { } func (node *StdinNode) ResolveDir(dir string) (string, error) { - path, err := execext.Expand(dir) + path, err := execext.ExpandLiteral(dir) if err != nil { return "", err } diff --git a/variables.go b/variables.go index ba108b02..261de59b 100644 --- a/variables.go +++ b/variables.go @@ -77,7 +77,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err Watch: origTask.Watch, Namespace: origTask.Namespace, } - new.Dir, err = execext.Expand(new.Dir) + new.Dir, err = execext.ExpandLiteral(new.Dir) if err != nil { return nil, err }