-
Notifications
You must be signed in to change notification settings - Fork 1
/
command.go
269 lines (236 loc) · 8.61 KB
/
command.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package osexec
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"github.com/yyle88/done"
"github.com/yyle88/erero"
"github.com/yyle88/eroticgo"
"github.com/yyle88/osexec/internal/utils"
"github.com/yyle88/printgo"
"github.com/yyle88/tern"
"github.com/yyle88/zaplog"
"go.uber.org/zap"
)
// CommandConfig represents the configuration for executing shell commands.
// CommandConfig 表示执行 shell 命令的配置。
type CommandConfig struct {
Envs []string // Optional environment variables. // 填写可选的环境变量。
Path string // Optional execution path. // 填写可选的执行路径。
ShellType string // Optional type of shell to use, e.g., bash, zsh. // 填写可选的 shell 类型,例如 bash,zsh。
ShellFlag string // Optional shell flag, e.g., "-c". // 填写可选的 Shell 参数,例如 "-c"。
DebugMode bool // enable debug mode. // 是否启用调试模式,即打印调试的日志。
MatchPipe func(line string) bool
MatchMore bool
}
// NewCommandConfig creates and returns a new CommandConfig instance.
// NewCommandConfig 创建并返回一个新的 CommandConfig 实例。
func NewCommandConfig() *CommandConfig {
return &CommandConfig{
DebugMode: debugModeOpen, // Initial value is consistent with the debugModeOpen variable. // 初始值与 debugModeOpen 变量保持一致。
}
}
// WithEnvs sets the environment variables for CommandConfig and returns the updated instance.
// WithEnvs 设置 CommandConfig 的环境变量并返回更新后的实例。
func (c *CommandConfig) WithEnvs(envs []string) *CommandConfig {
c.Envs = envs
return c
}
// WithPath sets the execution path for CommandConfig and returns the updated instance.
// WithPath 设置 CommandConfig 的执行路径并返回更新后的实例。
func (c *CommandConfig) WithPath(path string) *CommandConfig {
c.Path = path
return c
}
// WithShellType sets the shell type for CommandConfig and returns the updated instance.
// WithShellType 设置 CommandConfig 的 shell 类型并返回更新后的实例。
func (c *CommandConfig) WithShellType(shellType string) *CommandConfig {
c.ShellType = shellType
return c
}
// WithShellFlag sets the shell flag for CommandConfig and returns the updated instance.
// WithShellFlag 设置 CommandConfig 的 shell 参数并返回更新后的实例。
func (c *CommandConfig) WithShellFlag(shellFlag string) *CommandConfig {
c.ShellFlag = shellFlag
return c
}
// WithShell sets both the shell type and shell flag for CommandConfig and returns the updated instance.
// WithShell 同时设置 CommandConfig 的 shell 类型和 shell 参数,并返回更新后的实例。
func (c *CommandConfig) WithShell(shellType, shellFlag string) *CommandConfig {
c.ShellType = shellType
c.ShellFlag = shellFlag
return c
}
// WithBash sets the shell to bash with the "-c" flag and returns the updated instance.
// WithBash 设置 shell 为 bash 并附带 "-c" 参数,返回更新后的实例。
func (c *CommandConfig) WithBash() *CommandConfig {
return c.WithShell("bash", "-c")
}
// WithZsh sets the shell to zsh with the "-c" flag and returns the updated instance.
// WithZsh 设置 shell 为 zsh 并附带 "-c" 参数,返回更新后的实例。
func (c *CommandConfig) WithZsh() *CommandConfig {
return c.WithShell("zsh", "-c")
}
// WithSh sets the shell to sh with the "-c" flag and returns the updated instance.
// WithSh 设置 shell 为 sh 并附带 "-c" 参数,返回更新后的实例。
func (c *CommandConfig) WithSh() *CommandConfig {
return c.WithShell("sh", "-c")
}
// WithDebugMode sets the debug mode for CommandConfig and returns the updated instance.
// WithDebugMode 设置 CommandConfig 的调试模式并返回更新后的实例。
func (c *CommandConfig) WithDebugMode(debugMode bool) *CommandConfig {
c.DebugMode = debugMode
return c
}
func (c *CommandConfig) WithDebug() *CommandConfig {
return c.WithDebugMode(true)
}
func (c *CommandConfig) WithMatchPipe(matchPipe func(line string) bool) *CommandConfig {
c.MatchPipe = matchPipe
return c
}
func (c *CommandConfig) WithMatchMore(matchMore bool) *CommandConfig {
c.MatchMore = matchMore
return c
}
// Exec executes a shell command with the specified name and arguments, using the CommandConfig configuration.
// Exec 使用 CommandConfig 的配置执行带有指定名称和参数的 shell 命令。
func (c *CommandConfig) Exec(name string, args ...string) ([]byte, error) {
if err := c.validateConfig(name, args); err != nil {
return nil, erero.Ero(err)
}
command := c.prepareCommand(name, args)
return utils.WarpMessage(done.VAE(command.CombinedOutput()), c.DebugMode)
}
func (c *CommandConfig) validateConfig(name string, args []string) error {
if name == "" {
return erero.New("can-not-execute-with-empty-command-name")
}
if c.ShellFlag == "" && c.ShellType == "" {
if strings.Contains(name, " ") {
return erero.New("can-not-contains-space-in-command-name")
}
}
if c.ShellFlag != "" {
if c.ShellType == "" {
return erero.New("can-not-execute-with-wrong-shell-command")
}
}
if c.ShellType != "" {
if c.ShellFlag != "-c" {
return erero.New("can-not-execute-with-wrong-shell-options")
}
}
if c.DebugMode {
debugMessage := c.makeCommandMessage(name, args)
utils.ShowCommand(debugMessage)
zaplog.ZAPS.P1.LOG.Debug("EXEC:", zap.String("CMD", debugMessage))
}
return nil
}
func (c *CommandConfig) prepareCommand(name string, args []string) *exec.Cmd {
cmd := tern.BFF(c.ShellType != "",
func() *exec.Cmd {
return exec.Command(c.ShellType, c.ShellFlag, name+" "+strings.Join(args, " "))
},
func() *exec.Cmd {
return exec.Command(name, args...)
})
cmd.Dir = c.Path
cmd.Env = tern.BF(len(c.Envs) > 0, func() []string {
return append(os.Environ(), c.Envs...)
})
return cmd
}
// makeCommandMessage constructs a command-line string based on the CommandConfig and given command name and arguments.
// makeCommandMessage 根据 CommandConfig 和指定的命令名称及参数构造命令行字符串。
func (c *CommandConfig) makeCommandMessage(name string, args []string) string {
var pts = printgo.NewPTS()
if c.Path != "" {
pts.WriteString(fmt.Sprintf("cd %s && ", c.Path))
}
if len(c.Envs) > 0 {
pts.WriteString(fmt.Sprintf("%s ", strings.Join(c.Envs, " ")))
}
if c.ShellType != "" && c.ShellFlag != "" {
pts.WriteString(fmt.Sprintf("%s %s '%s'", c.ShellType, c.ShellFlag, escapeSingleQuotes(makeCommandMessage(name, args))))
} else {
pts.WriteString(fmt.Sprintf("%s %s", name, strings.Join(args, " ")))
}
return pts.String()
}
func (c *CommandConfig) StreamExec(name string, args ...string) ([]byte, error) {
return c.ExecInPipe(name, args...)
}
func (c *CommandConfig) ExecInPipe(name string, args ...string) ([]byte, error) {
if err := c.validateConfig(name, args); err != nil {
return nil, erero.Ero(err)
}
command := c.prepareCommand(name, args)
stdoutPipe, err := command.StdoutPipe()
if err != nil {
return nil, erero.Wro(err)
}
stderrPipe, err := command.StderrPipe()
if err != nil {
return nil, erero.Wro(err)
}
stdoutReader := bufio.NewReader(stdoutPipe)
stderrReader := bufio.NewReader(stderrPipe)
if err := command.Start(); err != nil {
return nil, erero.Wro(err)
}
wg := sync.WaitGroup{}
wg.Add(2)
var errMatch = false
var stderrBuffer = printgo.NewPTX()
go func() {
defer wg.Done()
errMatch = c.readPipe(stderrReader, stderrBuffer, "REASON", eroticgo.RED)
}()
var outMatch = false
var stdoutBuffer = printgo.NewPTX()
go func() {
defer wg.Done()
outMatch = c.readPipe(stdoutReader, stdoutBuffer, "OUTPUT", eroticgo.GREEN)
}()
wg.Wait()
if outMatch {
return utils.WarpMessage(done.VAE(stdoutBuffer.Bytes(), nil), c.DebugMode)
}
if errMatch { //比如 "go: upgraded github.com/xx/xx vxx => vxx" 这就不算错误,而是正确的
return utils.WarpMessage(done.VAE(stderrBuffer.Bytes(), nil), c.DebugMode)
}
if stderrBuffer.Len() > 0 {
return utils.WarpMessage(done.VAE(stdoutBuffer.Bytes(), erero.New(stderrBuffer.String())), c.DebugMode)
} else {
return utils.WarpMessage(done.VAE(stdoutBuffer.Bytes(), nil), c.DebugMode)
}
}
func (c *CommandConfig) readPipe(reader *bufio.Reader, ptx *printgo.PTX, debugMessage string, erotic eroticgo.COLOR) (matched bool) {
for {
streamLine, _, err := reader.ReadLine()
if c.DebugMode {
zaplog.SUG.Debugln(debugMessage, erotic.Sprint(string(streamLine)))
}
if (c.MatchMore || !matched) && c.MatchPipe != nil {
if c.MatchPipe(string(streamLine)) {
matched = true
}
}
if err != nil {
if err == io.EOF {
ptx.Write(streamLine)
return matched
}
panic(erero.Wro(err)) //panic: 读取结果出错很罕见
} else {
ptx.Write(streamLine)
ptx.Println()
}
}
}