@@ -11,13 +11,15 @@ import (
1111
1212 "mvdan.cc/sh/v3/expand"
1313 "mvdan.cc/sh/v3/interp"
14- "mvdan.cc/sh/v3/shell"
1514 "mvdan.cc/sh/v3/syntax"
1615
1716 "github.com/go-task/task/v3/errors"
1817)
1918
20- // RunCommandOptions is the options for the RunCommand func
19+ // ErrNilOptions is returned when a nil options is given
20+ var ErrNilOptions = errors .New ("execext: nil options given" )
21+
22+ // RunCommandOptions is the options for the [RunCommand] func.
2123type RunCommandOptions struct {
2224 Command string
2325 Dir string
@@ -29,9 +31,6 @@ type RunCommandOptions struct {
2931 Stderr io.Writer
3032}
3133
32- // ErrNilOptions is returned when a nil options is given
33- var ErrNilOptions = errors .New ("execext: nil options given" )
34-
3534// RunCommand runs a shell command
3635func RunCommand (ctx context.Context , opts * RunCommandOptions ) error {
3736 if opts == nil {
@@ -91,22 +90,64 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
9190 return r .Run (ctx , p )
9291}
9392
94- // Expand is a helper to mvdan.cc/shell.Fields that returns the first field
95- // if available.
96- func Expand (s string ) (string , error ) {
93+ func escape (s string ) string {
9794 s = filepath .ToSlash (s )
9895 s = strings .ReplaceAll (s , " " , `\ ` )
9996 s = strings .ReplaceAll (s , "&" , `\&` )
10097 s = strings .ReplaceAll (s , "(" , `\(` )
10198 s = strings .ReplaceAll (s , ")" , `\)` )
102- fields , err := shell .Fields (s , nil )
99+ return s
100+ }
101+
102+ // ExpandLiteral is a wrapper around [expand.Literal]. It will escape the input
103+ // string, expand any shell symbols (such as '~') and resolve any environment
104+ // variables.
105+ func ExpandLiteral (s string ) (string , error ) {
106+ if s == "" {
107+ return "" , nil
108+ }
109+ s = escape (s )
110+ p := syntax .NewParser ()
111+ var words []* syntax.Word
112+ err := p .Words (strings .NewReader (s ), func (w * syntax.Word ) bool {
113+ words = append (words , w )
114+ return true
115+ })
103116 if err != nil {
104117 return "" , err
105118 }
106- if len (fields ) > 0 {
107- return fields [0 ], nil
119+ if len (words ) == 0 {
120+ return "" , nil
121+ }
122+ cfg := & expand.Config {
123+ Env : expand .FuncEnviron (os .Getenv ),
124+ ReadDir2 : os .ReadDir ,
125+ GlobStar : true ,
126+ }
127+ return expand .Literal (cfg , words [0 ])
128+ }
129+
130+ // ExpandFields is a wrapper around [expand.Fields]. It will escape the input
131+ // string, expand any shell symbols (such as '~') and resolve any environment
132+ // variables. It also expands brace expressions ({a.b}) and globs (*/**) and
133+ // returns the results as a list of strings.
134+ func ExpandFields (s string ) ([]string , error ) {
135+ s = escape (s )
136+ p := syntax .NewParser ()
137+ var words []* syntax.Word
138+ err := p .Words (strings .NewReader (s ), func (w * syntax.Word ) bool {
139+ words = append (words , w )
140+ return true
141+ })
142+ if err != nil {
143+ return nil , err
144+ }
145+ cfg := & expand.Config {
146+ Env : expand .FuncEnviron (os .Getenv ),
147+ ReadDir2 : os .ReadDir ,
148+ GlobStar : true ,
108149 }
109- return "" , nil
150+ return expand . Fields ( cfg , words ... )
110151}
111152
112153func execHandler (next interp.ExecHandlerFunc ) interp.ExecHandlerFunc {
0 commit comments