-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathexecutor.go
136 lines (118 loc) · 3.39 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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package executor
import (
"bufio"
"context"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
// Options respresents options to start process
type Options struct {
Command string // Command to run
Args []string // Command arguments
Print bool // Print output to console?
Capture bool // Build buffer and capture output into Result.Output?
Wait bool // Wait for program to finish?
Timeout uint // Time in seconds allotted for the execution of the process before it get killed
Dir string // Working directory
NewConsole bool // Spawn new console window on Windows?
Hide bool // Try to hide process window on Windows?
OnChar func(c string, p *os.Process) // Callback for each character from process StdOut and StdErr
OnLine func(l string, p *os.Process) // Callback for each line from process StdOut and StdErr
}
// Result respresents process run result
type Result struct {
DoneOk bool // Process exited successfully?
StartOk bool // Process started successfully?
ExitCode int // Exit code
Output string // Output of StdOut and StdErr
}
// Start starts a process
func Start(opts Options) Result {
res := Result{
ExitCode: -1,
}
var outSb strings.Builder
var err error
// Create context for command (empty or with timeout)
ctx := context.Background()
var cancel context.CancelFunc
if opts.Timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, time.Duration(opts.Timeout)*time.Second)
defer cancel()
}
// Create command
cmd := exec.CommandContext(ctx, opts.Command, opts.Args...)
cmd.Dir = opts.Dir
// Fix "ERROR: Input redirection is not supported, exiting the process immediately" on Windows
cmd.Stdin = os.Stdin
if opts.NewConsole || opts.Hide {
setCmdAttr(cmd, opts.NewConsole, opts.Hide)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
} else { // Can capture output
stdoutReader, err := cmd.StdoutPipe()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return res
}
// Redirect StdErr to StdOut
cmd.Stderr = cmd.Stdout
stdoutScanner := bufio.NewScanner(stdoutReader)
stdoutScanner.Split(bufio.ScanRunes)
var lineSb strings.Builder
// Scan output
go func() {
for stdoutScanner.Scan() {
char := stdoutScanner.Text()
if opts.Print {
fmt.Print(char)
}
if opts.Capture {
outSb.WriteString(char)
}
// Char callback
if opts.OnChar != nil {
opts.OnChar(char, cmd.Process)
}
// Build the line
if opts.OnLine != nil {
if char != "\n" && char != "\r" {
lineSb.WriteString(char)
} else {
// Line callback
opts.OnLine(lineSb.String(), cmd.Process)
lineSb.Reset()
}
}
}
}()
}
// Start the command
err = cmd.Start()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return res
}
res.StartOk = true
// Wait for the command to finish execution
if opts.Wait {
err = cmd.Wait()
if err != nil {
fmt.Fprintf(os.Stderr, "\n%v\n", err)
if ctx.Err() != nil {
fmt.Fprintln(os.Stderr, ctx.Err())
}
return res
}
}
// Build and return Result
if cmd.ProcessState != nil {
res.DoneOk = cmd.ProcessState.Success()
res.ExitCode = cmd.ProcessState.ExitCode()
}
res.Output = outSb.String()
return res
}