-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcmd.go
220 lines (199 loc) · 3.95 KB
/
cmd.go
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
212
213
214
215
216
217
218
219
220
package exec
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"os/exec"
"strings"
"time"
)
// Cmd ...
type Cmd struct {
// run at dir
Dir string
// target executable name
Name string
// arguments of cmd
Args []string
// exec started
Started bool
// exec canceled
Canceled bool
// exec timedout
TimedOut bool
// exec failed by error
Failed bool
// exec done
Done bool
// exec timeout
Timeout time.Duration
// exec duration
Duration time.Duration
// EventHandler pointer
EventHandler *EventHandler
// native exec.Cmd target
Cmd *exec.Cmd
// start time
startTime time.Time
// output pipe
outPipe io.ReadCloser
// output buf reader
outReader *bufio.Reader
// err buf
errBuf *bytes.Buffer
// result output bytes
out []byte
// result error bytes
err []byte
// latest ouput
msg []byte
}
// NewCmd new Cmd object
func NewCmd(dir string, name string, arg ...string) *Cmd {
cmd := exec.Command(name, arg...)
if dir == "" {
dir = "./"
}
cmd.Dir = dir
cmd.Env = os.Environ()
updateCmdByOS(cmd)
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
return &Cmd{
Dir: dir,
Name: name,
Args: arg,
Cmd: cmd,
EventHandler: DefaultEventHandler,
errBuf: &errBuf,
}
}
// SetEventHandler set event handler
func (c *Cmd) SetEventHandler(eh *EventHandler) *Cmd {
c.EventHandler = eh
return c
}
// SetEnv set Envs
func (c *Cmd) SetEnv(Env []string) *Cmd {
c.Cmd.Env = Env
return c
}
// AddEnv add Env
func (c *Cmd) AddEnv(name, value string) *Cmd {
c.Cmd.Env = append(c.Cmd.Env, name+"="+value)
return c
}
// SetTimeout set timeout
func (c *Cmd) SetTimeout(dur time.Duration) *Cmd {
c.Timeout = dur
return c
}
// GetCmd get cmd string
func (c *Cmd) GetCmd() string {
return c.Name + " " + strings.Join(c.Args, " ")
}
// Start start cmd
func (c *Cmd) Start() error {
outPipe, _ := c.Cmd.StdoutPipe()
c.outPipe = outPipe
err := c.Cmd.Start()
if err != nil {
return err
}
if c.Timeout != 0 {
go c.checkTimeout()
}
if c.EventHandler.CmdStarted != nil {
c.EventHandler.CmdStarted(c)
}
c.startTime = time.Now()
c.Started = true
c.outReader = bufio.NewReader(outPipe)
return nil
}
// do timeout checking
func (c *Cmd) checkTimeout() {
timer := time.NewTimer(c.Timeout)
<-timer.C
if !(c.Failed || c.Done) && !c.Canceled {
c.TimedOut = true
c.Cancel()
}
}
// Read read latest output from last Read()
func (c *Cmd) Read() bool {
if c.Canceled || c.Done {
return false
}
p := make([]byte, 65536)
n, err := c.outReader.Read(p)
if err != nil {
return false
}
c.out = append(c.out, p[:n]...)
c.msg = p[:n]
if c.EventHandler.CmdRead != nil {
c.EventHandler.CmdRead(c)
}
return true
}
// GetMsg get last output from cmd
func (c *Cmd) GetMsg() []byte {
return c.msg
}
// Cancel terminate the command process
func (c *Cmd) Cancel() {
if c.Canceled {
return
}
c.Canceled = true
c.Read()
killProcess(c.Cmd.Process)
if c.EventHandler.CmdCanceled != nil {
c.EventHandler.CmdCanceled(c)
}
}
// Wait wait for cmd until its done. It will return original error ( like exit(1) ) except error message. Use Error() to get error message.
func (c *Cmd) Wait() error {
for c.Read() {
}
err := c.Cmd.Wait()
if !c.Canceled {
if err != nil {
c.Failed = true
} else {
c.Done = true
}
}
c.Duration = time.Since(c.startTime)
c.err = c.errBuf.Bytes()
if c.Failed && c.EventHandler.CmdFailed != nil {
c.EventHandler.CmdFailed(c)
}
if c.Done && c.EventHandler.CmdDone != nil {
c.EventHandler.CmdDone(c)
}
return err
}
// Run run cmd. If error, it return output until error and error messages as error.
func (c *Cmd) Run() (string, error) {
err := c.Start()
if err != nil {
return "", err
}
err = c.Wait()
if err != nil {
return c.Output(), errors.New(c.Error())
}
return c.Output(), nil
}
// Output get outputs of cmd (without error) in string
func (c *Cmd) Output() string {
return string(c.out)
}
// Error get error output in string
func (c *Cmd) Error() string {
return string(c.err)
}