-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexecutor.go
114 lines (89 loc) · 2.28 KB
/
executor.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
package vcsview
import (
"bufio"
"context"
"fmt"
"os/exec"
"strings"
"syscall"
)
// Function for read stdout of the command
type cmdReaderFunc func(s *bufio.Scanner)
// Function for debug messages
type DebugFunc func(m string)
// Command line executor
type Executor struct {
// Already created command line
cmd *exec.Cmd
// Reader of stdout
reader cmdReaderFunc
// Debug function
debugger DebugFunc
// Text representation of command
cmdTxt string
// Command context
ctx context.Context
// Function to stop context
cancel context.CancelFunc
}
// log message if set Debugger
func (e *Executor) log(msg string) {
if e.debugger != nil {
e.debugger(msg)
}
}
// log non-zero command line status
// need provide a command and exec.ExitError struct
func (e *Executor) logCmdNonZeroStatus(err error) {
if exitError, ok := err.(*exec.ExitError); ok {
if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
msg := fmt.Sprintf("Command %s finished with %d status code", e.cmdTxt, status)
e.log(msg)
return
}
}
msg := fmt.Sprintf("Command %s finished with non-zero status code", e.cmdTxt)
e.log(msg)
}
// Create a command stdout pipe and reader function using base reader
// Read will started after sch channel will filled data
// After read sch channel will filled data
func (e *Executor) read(sch chan interface{}) {
defer func() {
sch <- struct{}{}
}()
out, _ := e.cmd.StdoutPipe()
<-sch
e.reader(bufio.NewScanner(out))
e.cancel()
}
// Run command execution
// This method run async stdout reader and start the command
// To run command async start this method in goroutine
// If command cannot by started or if command fails - returns error
func (e *Executor) Run() error {
e.log(fmt.Sprintf("execute command: %s", e.cmdTxt))
sch := make(chan interface{})
go e.read(sch)
sch <- struct{}{}
if err := e.cmd.Start(); err != nil {
e.logCmdNonZeroStatus(err)
<- sch
return err
}
<-sch
if err := e.cmd.Wait(); err != nil {
e.logCmdNonZeroStatus(err)
return err
}
return nil
}
func NewExecutor(cmd *exec.Cmd, reader cmdReaderFunc, debugger DebugFunc) *Executor {
e := new(Executor)
e.cmd = cmd
e.reader = reader
e.debugger = debugger
e.cmdTxt = strings.Join(cmd.Args, " ")
e.ctx, e.cancel = context.WithCancel(context.Background())
return e
}