-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathcommand_run.go
333 lines (290 loc) · 8.53 KB
/
command_run.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package cli
import (
"bufio"
"context"
"fmt"
"io"
"reflect"
"slices"
"unicode"
)
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
type state int
const (
stateSearchForToken state = -1
stateSearchForString state = 0
)
st := stateSearchForToken
linenum := 1
token := ""
args := []string{}
breader := bufio.NewReader(cmd.Reader)
outer:
for {
ch, _, err := breader.ReadRune()
if err == io.EOF {
switch st {
case stateSearchForToken:
if token != "--" {
args = append(args, token)
}
case stateSearchForString:
// make sure string is not empty
for _, t := range token {
if !unicode.IsSpace(t) {
args = append(args, token)
}
}
}
break outer
}
if err != nil {
return nil, err
}
switch st {
case stateSearchForToken:
if unicode.IsSpace(ch) || ch == '"' {
if ch == '\n' {
linenum++
}
if token != "" {
// end the processing here
if token == "--" {
break outer
}
args = append(args, token)
token = ""
}
if ch == '"' {
st = stateSearchForString
}
continue
}
token += string(ch)
case stateSearchForString:
if ch != '"' {
token += string(ch)
} else {
if token != "" {
args = append(args, token)
token = ""
}
/*else {
//TODO. Should we pass in empty strings ?
}*/
st = stateSearchForToken
}
}
}
tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)
return args, nil
}
// Run is the entry point to the command graph. The positional
// arguments are parsed according to the Flag and Command
// definitions and the matching Action functions are run.
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
cmd.setupDefaults(osArgs)
if v, ok := ctx.Value(commandContextKey).(*Command); ok {
tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
cmd.parent = v
}
if cmd.parent == nil {
if cmd.ReadArgsFromStdin {
if args, err := cmd.parseArgsFromStdin(); err != nil {
return err
} else {
osArgs = append(osArgs, args...)
}
}
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
}
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
ctx = context.WithValue(ctx, commandContextKey, cmd)
if cmd.parent == nil {
cmd.setupCommandGraph()
}
var rargs Args = &stringSliceArgs{v: osArgs}
for _, f := range cmd.allFlags() {
if err := f.PreParse(); err != nil {
return err
}
}
var args Args = &stringSliceArgs{rargs.Tail()}
var err error
if cmd.SkipFlagParsing {
tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
cmd.parsedArgs = args
} else {
cmd.parsedArgs, err = cmd.parseFlags(args)
}
tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
if checkCompletions(ctx, cmd) {
return nil
}
if err != nil {
tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
deferErr = err
cmd.isInError = true
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
err = cmd.handleExitCoder(ctx, err)
return err
}
fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
if cmd.Suggest {
if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
}
}
if !cmd.hideHelp() {
if cmd.parent == nil {
tracef("running ShowAppHelp")
if err := ShowAppHelp(cmd); err != nil {
tracef("SILENTLY IGNORING ERROR running ShowAppHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
}
} else {
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
}
}
}
return err
}
if cmd.checkHelp() {
return helpCommandAction(ctx, cmd)
} else {
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
}
if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
ShowVersion(cmd)
return nil
}
for _, flag := range cmd.allFlags() {
if err := flag.PostParse(); err != nil {
return err
}
}
if cmd.After != nil && !cmd.Root().shellCompletion {
defer func() {
if err := cmd.After(ctx, cmd); err != nil {
err = cmd.handleExitCoder(ctx, err)
if deferErr != nil {
deferErr = newMultiError(deferErr, err)
} else {
deferErr = err
}
}
}()
}
for _, grp := range cmd.MutuallyExclusiveFlags {
if err := grp.check(cmd); err != nil {
_ = ShowSubcommandHelp(cmd)
return err
}
}
var subCmd *Command
if cmd.parsedArgs.Present() {
tracef("checking positional args %[1]q (cmd=%[2]q)", cmd.parsedArgs, cmd.Name)
name := cmd.parsedArgs.First()
tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
if cmd.SuggestCommandFunc != nil && name != "--" {
name = cmd.SuggestCommandFunc(cmd.Commands, name)
}
subCmd = cmd.Command(name)
if subCmd == nil {
hasDefault := cmd.DefaultCommand != ""
isFlagName := slices.Contains(cmd.FlagNames(), name)
if hasDefault {
tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
}
if isFlagName || hasDefault {
argsWithDefault := cmd.argsWithDefaultCommand(args)
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
if !reflect.DeepEqual(args, argsWithDefault) {
subCmd = cmd.Command(argsWithDefault.First())
}
}
}
} else if cmd.parent == nil && cmd.DefaultCommand != "" {
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
subCmd = dc
}
}
// If a subcommand has been resolved, let it handle the remaining execution.
if subCmd != nil {
tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
return subCmd.Run(ctx, cmd.Args().Slice())
}
// This code path is the innermost command execution. Here we actually
// perform the command action.
//
// First, resolve the chain of nested commands up to the parent.
var cmdChain []*Command
for p := cmd; p != nil; p = p.parent {
cmdChain = append(cmdChain, p)
}
slices.Reverse(cmdChain)
// Run Before actions in order.
for _, cmd := range cmdChain {
if cmd.Before == nil {
continue
}
if bctx, err := cmd.Before(ctx, cmd); err != nil {
deferErr = cmd.handleExitCoder(ctx, err)
return deferErr
} else if bctx != nil {
ctx = bctx
}
}
// Run flag actions in order.
// These take a context, so this has to happen after Before actions.
for _, cmd := range cmdChain {
tracef("running flag actions (cmd=%[1]q)", cmd.Name)
if err := cmd.runFlagActions(ctx); err != nil {
deferErr = cmd.handleExitCoder(ctx, err)
return deferErr
}
}
if err := cmd.checkAllRequiredFlags(); err != nil {
cmd.isInError = true
_ = ShowSubcommandHelp(cmd)
return err
}
// Run the command action.
if len(cmd.Arguments) > 0 {
rargs := cmd.Args().Slice()
tracef("calling argparse with %[1]v", rargs)
for _, arg := range cmd.Arguments {
var err error
rargs, err = arg.Parse(rargs)
if err != nil {
tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
}
err = cmd.handleExitCoder(ctx, err)
return err
}
}
cmd.parsedArgs = &stringSliceArgs{v: rargs}
}
if err := cmd.Action(ctx, cmd); err != nil {
tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
deferErr = cmd.handleExitCoder(ctx, err)
}
tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
return deferErr
}