Skip to content

Commit 8529aba

Browse files
committed
support commands specified as exe/args
1 parent 97871a5 commit 8529aba

File tree

4 files changed

+54
-1
lines changed

4 files changed

+54
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/google/go-cmp v0.6.0
99
github.com/mattn/go-colorable v0.1.13
1010
github.com/mattn/go-runewidth v0.0.16
11+
github.com/mattn/go-shellwords v1.0.12
1112
github.com/robfig/cron/v3 v3.0.1
1213
github.com/yuin/goldmark v1.7.8
1314
golang.org/x/sync v0.9.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
1111
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
1212
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
1313
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
14+
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
15+
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
1416
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
1517
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
1618
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=

process.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os/exec"
88
"sync"
99

10+
"github.com/mattn/go-shellwords"
1011
"golang.org/x/sync/errgroup"
1112
"golang.org/x/sync/semaphore"
1213
"golang.org/x/sys/execabs"
@@ -109,18 +110,38 @@ func (proc *concurrentProcess) wait() {
109110
// newCommandRunner creates new external command runner for given executable. The executable path
110111
// is resolved in this function.
111112
func (proc *concurrentProcess) newCommandRunner(exe string, combineOutput bool) (*externalCommand, error) {
112-
p, err := execabs.LookPath(exe)
113+
var args []string
114+
p, args, err := findExe(exe)
113115
if err != nil {
114116
return nil, err
115117
}
116118
cmd := &externalCommand{
117119
proc: proc,
118120
exe: p,
121+
args: args,
119122
combineOutput: combineOutput,
120123
}
121124
return cmd, nil
122125
}
123126

127+
func findExe(exe string) (string, []string, error) {
128+
p, err := execabs.LookPath(exe)
129+
if err == nil {
130+
return p, nil, nil
131+
}
132+
// See if the command string contains args. As it is best effort, we do not
133+
// handle parse errors.
134+
if exeArgs, _ := shellwords.Parse(exe); len(exeArgs) > 0 {
135+
// We want to return the original error if this command isn't found so
136+
// do not overwrite it.
137+
if p, err2 := execabs.LookPath(exeArgs[0]); err2 == nil {
138+
return p, exeArgs[1:], nil
139+
}
140+
}
141+
142+
return "", nil, err
143+
}
144+
124145
// externalCommand is struct to run specific command concurrently with concurrentProcess bounding
125146
// number of processes at the same time. This type manages fatal errors while running the command
126147
// by using errgroup.Group. The wait() method must be called at the end for checking if some fatal
@@ -129,13 +150,20 @@ type externalCommand struct {
129150
proc *concurrentProcess
130151
eg errgroup.Group
131152
exe string
153+
args []string
132154
combineOutput bool
133155
}
134156

135157
// run runs the command with given arguments and stdin. The callback function is called after the
136158
// process runs. First argument is stdout and the second argument is an error while running the
137159
// process.
138160
func (cmd *externalCommand) run(args []string, stdin string, callback func([]byte, error) error) {
161+
if len(cmd.args) > 0 {
162+
var allArgs []string
163+
allArgs = append(allArgs, cmd.args...)
164+
allArgs = append(allArgs, args...)
165+
args = allArgs
166+
}
139167
exec := &cmdExecution{cmd.exe, args, stdin, cmd.combineOutput}
140168
cmd.proc.run(&cmd.eg, exec, callback)
141169
}

process_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ func TestProcessRunConcurrently(t *testing.T) {
6363
}
6464
}
6565

66+
func TestProcessRunWithArgs(t *testing.T) {
67+
var done atomic.Bool
68+
p := newConcurrentProcess(1)
69+
echo := testSkipIfNoCommand(t, p, "echo hello")
70+
echo.run(nil, "", func(b []byte, err error) error {
71+
if err != nil {
72+
t.Error(err)
73+
return err
74+
}
75+
if string(b) != "hello\n" {
76+
t.Errorf("unexpected output: %q", b)
77+
}
78+
done.Store(true)
79+
return nil
80+
})
81+
p.wait()
82+
83+
if !done.Load() {
84+
t.Error("callback did not run")
85+
}
86+
}
87+
6688
func TestProcessRunMultipleCommandsConcurrently(t *testing.T) {
6789
p := newConcurrentProcess(3)
6890

0 commit comments

Comments
 (0)