-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathexec.go
More file actions
211 lines (193 loc) · 5.2 KB
/
exec.go
File metadata and controls
211 lines (193 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// package exec is a universal wrapper around the os/exec package.
package exec
import (
"bytes"
"errors"
"io"
"os"
"os/exec"
"strings"
)
// System executes the command specified in command by calling /bin/sh -c command, and returns after the command has been completed. Stdin, Stdout, and Stderr are plumbed through to the child, but this behaviour can be modified by opts.
func System(command string, opts ...func(*Cmd) error) error {
args := strings.Fields(command)
args = append([]string{"-c"}, args...)
cmd := Command("/bin/sh", args...)
opts = append([]func(*Cmd) error{
Stdin(os.Stdin),
Stdout(os.Stdout),
Stderr(os.Stderr),
}, opts...)
return cmd.Run(opts...)
}
// LookPath searches for an executable binary named file in the directories
// named by the PATH environment variable. If file contains a slash, it is
// tried directly and the PATH is not consulted. The result may be an
// absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) { return exec.LookPath(file) }
// Command returns a Cmd to execute the named program with the given arguments.
func Command(name string, args ...string) *Cmd {
return &Cmd{
Cmd: exec.Command(name, args...),
initalised: true,
}
}
// Cmd represents a command to be run.
// Cmd must be created by calling Command.
// Cmd cannot be reused after calling its Run or Start methods.
type Cmd struct {
*exec.Cmd
initalised bool
waited bool
before, after func(*Cmd) error
}
// Run starts the specified command and waits for it to complete.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
// If the command fails to run or doesn't complete successfully, the
// error is of type *ExitError. Other error types may be
// returned for I/O problems.
func (c *Cmd) Run(opts ...func(*Cmd) error) error {
if err := c.Start(opts...); err != nil {
return err
}
return c.Wait()
}
// Start starts the specified command but does not wait for it to complete.
//
// The Wait method will return the exit code and release associated resources
// once the command exits.
func (c *Cmd) Start(opts ...func(*Cmd) error) error {
if !c.initalised {
return errors.New("exec: command not initalised")
}
if err := applyDefaultOptions(c); err != nil {
return err
}
if err := applyOptions(c, opts...); err != nil {
return err
}
if c.before != nil {
if err := c.before(c); err != nil {
return err
}
}
return c.Cmd.Start()
}
// Wait waits for the command to exit.
// It must have been started by Start.
func (c *Cmd) Wait() (err error) {
if c.waited {
return errors.New("exec: Wait was already called")
}
c.waited = true
defer func() {
if c.after == nil {
return
}
errAfter := c.after(c)
if err == nil {
err = errAfter
}
}()
return c.Cmd.Wait()
}
// Stdin specifies the process's standard input.
func Stdin(r io.Reader) func(*Cmd) error {
return func(c *Cmd) error {
if c.Stdin != nil {
return errors.New("exec: Stdin already set")
}
c.Stdin = r
return nil
}
}
// Stdout specifies the process's standard output.
func Stdout(w io.Writer) func(*Cmd) error {
return func(c *Cmd) error {
if c.Stdout != nil {
return errors.New("exec: Stdout already set")
}
c.Stdout = w
return nil
}
}
// Stderr specifies the process's standard error..
func Stderr(w io.Writer) func(*Cmd) error {
return func(c *Cmd) error {
if c.Stderr != nil {
return errors.New("exec: Stderr already set")
}
c.Stderr = w
return nil
}
}
// BeforeFunc runs fn just prior to executing the command. If an error
// is returned, the command will not be run.
func BeforeFunc(fn func(*Cmd) error) func(*Cmd) error {
return func(c *Cmd) error {
if c.before != nil {
return errors.New("exec: BeforeFunc already set")
}
c.before = fn
return nil
}
}
// AfterFunc runs fn just after to executing the command. If an error
// is returned, it will be returned providing the command exited cleanly.
func AfterFunc(fn func(*Cmd) error) func(*Cmd) error {
return func(c *Cmd) error {
if c.after != nil {
return errors.New("exec: AfterFunc already set")
}
c.after = fn
return nil
}
}
// Setenv applies (or overwrites) childs environment key.
func Setenv(key, val string) func(*Cmd) error {
return func(c *Cmd) error {
key += "="
for i := range c.Env {
if strings.HasPrefix(c.Env[i], key) {
c.Env[i] = key + val
return nil
}
}
c.Env = append(c.Env, key+val)
return nil
}
}
// Output runs the command and returns its standard output.
func (c *Cmd) Output(opts ...func(*Cmd) error) ([]byte, error) {
var b bytes.Buffer
opts = append([]func(*Cmd) error{Stdout(&b)}, opts...)
err := c.Run(opts...)
return b.Bytes(), err
}
// Dir specifies the working directory of the command.
// If Dir is empty, the command executes in the calling
// process's current directory.
func Dir(dir string) func(*Cmd) error {
return func(c *Cmd) error {
c.Dir = dir
return nil
}
}
func applyDefaultOptions(c *Cmd) error {
if c.Env == nil {
c.Env = os.Environ()
}
return nil
}
func applyOptions(c *Cmd, opts ...func(*Cmd) error) error {
for _, opt := range opts {
if err := opt(c); err != nil {
return err
}
}
return nil
}