@@ -172,8 +172,8 @@ type Invocation struct {
172
172
173
173
// WithOS returns the invocation as a main package, filling in the invocation's unset
174
174
// fields with OS defaults.
175
- func (i * Invocation ) WithOS () * Invocation {
176
- return i .with (func (i * Invocation ) {
175
+ func (inv * Invocation ) WithOS () * Invocation {
176
+ return inv .with (func (i * Invocation ) {
177
177
i .Stdout = os .Stdout
178
178
i .Stderr = os .Stderr
179
179
i .Stdin = os .Stdin
@@ -182,18 +182,18 @@ func (i *Invocation) WithOS() *Invocation {
182
182
})
183
183
}
184
184
185
- func (i * Invocation ) Context () context.Context {
186
- if i .ctx == nil {
185
+ func (inv * Invocation ) Context () context.Context {
186
+ if inv .ctx == nil {
187
187
return context .Background ()
188
188
}
189
- return i .ctx
189
+ return inv .ctx
190
190
}
191
191
192
- func (i * Invocation ) ParsedFlags () * pflag.FlagSet {
193
- if i .parsedFlags == nil {
192
+ func (inv * Invocation ) ParsedFlags () * pflag.FlagSet {
193
+ if inv .parsedFlags == nil {
194
194
panic ("flags not parsed, has Run() been called?" )
195
195
}
196
- return i .parsedFlags
196
+ return inv .parsedFlags
197
197
}
198
198
199
199
type runState struct {
@@ -218,39 +218,17 @@ func copyFlagSetWithout(fs *pflag.FlagSet, without string) *pflag.FlagSet {
218
218
// run recursively executes the command and its children.
219
219
// allArgs is wired through the stack so that global flags can be accepted
220
220
// anywhere in the command invocation.
221
- func (i * Invocation ) run (state * runState ) error {
222
- err := i .Command .Options .SetDefaults ()
223
- if err != nil {
224
- return xerrors .Errorf ("setting defaults: %w" , err )
225
- }
226
-
227
- // If we set the Default of an array but later see a flag for it, we
228
- // don't want to append, we want to replace. So, we need to keep the state
229
- // of defaulted array options.
230
- defaultedArrays := make (map [string ]int )
231
- for _ , opt := range i .Command .Options {
232
- sv , ok := opt .Value .(pflag.SliceValue )
233
- if ! ok {
234
- continue
235
- }
236
-
237
- if opt .Flag == "" {
238
- continue
239
- }
240
-
241
- defaultedArrays [opt .Flag ] = len (sv .GetSlice ())
242
- }
243
-
244
- err = i .Command .Options .ParseEnv (i .Environ )
221
+ func (inv * Invocation ) run (state * runState ) error {
222
+ err := inv .Command .Options .ParseEnv (inv .Environ )
245
223
if err != nil {
246
224
return xerrors .Errorf ("parsing env: %w" , err )
247
225
}
248
226
249
227
// Now the fun part, argument parsing!
250
228
251
229
children := make (map [string ]* Cmd )
252
- for _ , child := range i .Command .Children {
253
- child .Parent = i .Command
230
+ for _ , child := range inv .Command .Children {
231
+ child .Parent = inv .Command
254
232
for _ , name := range append (child .Aliases , child .Name ()) {
255
233
if _ , ok := children [name ]; ok {
256
234
return xerrors .Errorf ("duplicate command name: %s" , name )
@@ -259,57 +237,44 @@ func (i *Invocation) run(state *runState) error {
259
237
}
260
238
}
261
239
262
- if i .parsedFlags == nil {
263
- i .parsedFlags = pflag .NewFlagSet (i .Command .Name (), pflag .ContinueOnError )
240
+ if inv .parsedFlags == nil {
241
+ inv .parsedFlags = pflag .NewFlagSet (inv .Command .Name (), pflag .ContinueOnError )
264
242
// We handle Usage ourselves.
265
- i .parsedFlags .Usage = func () {}
243
+ inv .parsedFlags .Usage = func () {}
266
244
}
267
245
268
246
// If we find a duplicate flag, we want the deeper command's flag to override
269
247
// the shallow one. Unfortunately, pflag has no way to remove a flag, so we
270
248
// have to create a copy of the flagset without a value.
271
- i .Command .Options .FlagSet ().VisitAll (func (f * pflag.Flag ) {
272
- if i .parsedFlags .Lookup (f .Name ) != nil {
273
- i .parsedFlags = copyFlagSetWithout (i .parsedFlags , f .Name )
249
+ inv .Command .Options .FlagSet ().VisitAll (func (f * pflag.Flag ) {
250
+ if inv .parsedFlags .Lookup (f .Name ) != nil {
251
+ inv .parsedFlags = copyFlagSetWithout (inv .parsedFlags , f .Name )
274
252
}
275
- i .parsedFlags .AddFlag (f )
253
+ inv .parsedFlags .AddFlag (f )
276
254
})
277
255
278
256
var parsedArgs []string
279
257
280
- if ! i .Command .RawArgs {
258
+ if ! inv .Command .RawArgs {
281
259
// Flag parsing will fail on intermediate commands in the command tree,
282
260
// so we check the error after looking for a child command.
283
- state .flagParseErr = i .parsedFlags .Parse (state .allArgs )
284
- parsedArgs = i .parsedFlags .Args ()
285
-
286
- i .parsedFlags .VisitAll (func (f * pflag.Flag ) {
287
- i , ok := defaultedArrays [f .Name ]
288
- if ! ok {
289
- return
290
- }
291
-
292
- if ! f .Changed {
293
- return
294
- }
261
+ state .flagParseErr = inv .parsedFlags .Parse (state .allArgs )
262
+ parsedArgs = inv .parsedFlags .Args ()
263
+ }
295
264
296
- // If flag was changed, we need to remove the default values.
297
- sv , ok := f .Value .(pflag.SliceValue )
298
- if ! ok {
299
- panic ("defaulted array option is not a slice value" )
300
- }
301
- ss := sv .GetSlice ()
302
- if len (ss ) == 0 {
303
- // Slice likely zeroed by a flag.
304
- // E.g. "--fruit" may default to "apples,oranges" but the user
305
- // provided "--fruit=""".
306
- return
307
- }
308
- err := sv .Replace (ss [i :])
309
- if err != nil {
310
- panic (err )
311
- }
312
- })
265
+ // Set defaults for flags that weren't set by the user.
266
+ skipDefaults := make (map [int ]struct {}, len (inv .Command .Options ))
267
+ for i , opt := range inv .Command .Options {
268
+ if fl := inv .parsedFlags .Lookup (opt .Flag ); fl != nil && fl .Changed {
269
+ skipDefaults [i ] = struct {}{}
270
+ }
271
+ if opt .envChanged {
272
+ skipDefaults [i ] = struct {}{}
273
+ }
274
+ }
275
+ err = inv .Command .Options .SetDefaults (skipDefaults )
276
+ if err != nil {
277
+ return xerrors .Errorf ("setting defaults: %w" , err )
313
278
}
314
279
315
280
// Run child command if found (next child only)
@@ -318,64 +283,64 @@ func (i *Invocation) run(state *runState) error {
318
283
if len (parsedArgs ) > state .commandDepth {
319
284
nextArg := parsedArgs [state .commandDepth ]
320
285
if child , ok := children [nextArg ]; ok {
321
- child .Parent = i .Command
322
- i .Command = child
286
+ child .Parent = inv .Command
287
+ inv .Command = child
323
288
state .commandDepth ++
324
- return i .run (state )
289
+ return inv .run (state )
325
290
}
326
291
}
327
292
328
293
// Flag parse errors are irrelevant for raw args commands.
329
- if ! i .Command .RawArgs && state .flagParseErr != nil && ! errors .Is (state .flagParseErr , pflag .ErrHelp ) {
294
+ if ! inv .Command .RawArgs && state .flagParseErr != nil && ! errors .Is (state .flagParseErr , pflag .ErrHelp ) {
330
295
return xerrors .Errorf (
331
296
"parsing flags (%v) for %q: %w" ,
332
297
state .allArgs ,
333
- i .Command .FullName (), state .flagParseErr ,
298
+ inv .Command .FullName (), state .flagParseErr ,
334
299
)
335
300
}
336
301
337
- if i .Command .RawArgs {
302
+ if inv .Command .RawArgs {
338
303
// If we're at the root command, then the name is omitted
339
304
// from the arguments, so we can just use the entire slice.
340
305
if state .commandDepth == 0 {
341
- i .Args = state .allArgs
306
+ inv .Args = state .allArgs
342
307
} else {
343
- argPos , err := findArg (i .Command .Name (), state .allArgs , i .parsedFlags )
308
+ argPos , err := findArg (inv .Command .Name (), state .allArgs , inv .parsedFlags )
344
309
if err != nil {
345
310
panic (err )
346
311
}
347
- i .Args = state .allArgs [argPos + 1 :]
312
+ inv .Args = state .allArgs [argPos + 1 :]
348
313
}
349
314
} else {
350
315
// In non-raw-arg mode, we want to skip over flags.
351
- i .Args = parsedArgs [state .commandDepth :]
316
+ inv .Args = parsedArgs [state .commandDepth :]
352
317
}
353
318
354
- mw := i .Command .Middleware
319
+ mw := inv .Command .Middleware
355
320
if mw == nil {
356
321
mw = Chain ()
357
322
}
358
323
359
- ctx := i .ctx
324
+ ctx := inv .ctx
360
325
if ctx == nil {
361
326
ctx = context .Background ()
362
327
}
363
328
364
329
ctx , cancel := context .WithCancel (ctx )
365
330
defer cancel ()
366
- i = i .WithContext (ctx )
331
+ inv = inv .WithContext (ctx )
367
332
368
- if i .Command .Handler == nil || errors .Is (state .flagParseErr , pflag .ErrHelp ) {
369
- if i .Command .HelpHandler == nil {
370
- return xerrors .Errorf ("no handler or help for command %s" , i .Command .FullName ())
333
+ if inv .Command .Handler == nil || errors .Is (state .flagParseErr , pflag .ErrHelp ) {
334
+ if inv .Command .HelpHandler == nil {
335
+ return xerrors .Errorf ("no handler or help for command %s" , inv .Command .FullName ())
371
336
}
372
- return i .Command .HelpHandler (i )
337
+ return inv .Command .HelpHandler (inv )
373
338
}
374
339
375
- err = mw (i .Command .Handler )(i )
340
+ err = mw (inv .Command .Handler )(inv )
376
341
if err != nil {
377
342
return & RunCommandError {
378
- Cmd : i .Command ,
343
+ Cmd : inv .Command ,
379
344
Err : err ,
380
345
}
381
346
}
@@ -438,33 +403,33 @@ func findArg(want string, args []string, fs *pflag.FlagSet) (int, error) {
438
403
// If two command share a flag name, the first command wins.
439
404
//
440
405
//nolint:revive
441
- func (i * Invocation ) Run () (err error ) {
406
+ func (inv * Invocation ) Run () (err error ) {
442
407
defer func () {
443
408
// Pflag is panicky, so additional context is helpful in tests.
444
409
if flag .Lookup ("test.v" ) == nil {
445
410
return
446
411
}
447
412
if r := recover (); r != nil {
448
- err = xerrors .Errorf ("panic recovered for %s: %v" , i .Command .FullName (), r )
413
+ err = xerrors .Errorf ("panic recovered for %s: %v" , inv .Command .FullName (), r )
449
414
panic (err )
450
415
}
451
416
}()
452
- err = i .run (& runState {
453
- allArgs : i .Args ,
417
+ err = inv .run (& runState {
418
+ allArgs : inv .Args ,
454
419
})
455
420
return err
456
421
}
457
422
458
423
// WithContext returns a copy of the Invocation with the given context.
459
- func (i * Invocation ) WithContext (ctx context.Context ) * Invocation {
460
- return i .with (func (i * Invocation ) {
424
+ func (inv * Invocation ) WithContext (ctx context.Context ) * Invocation {
425
+ return inv .with (func (i * Invocation ) {
461
426
i .ctx = ctx
462
427
})
463
428
}
464
429
465
430
// with returns a copy of the Invocation with the given function applied.
466
- func (i * Invocation ) with (fn func (* Invocation )) * Invocation {
467
- i2 := * i
431
+ func (inv * Invocation ) with (fn func (* Invocation )) * Invocation {
432
+ i2 := * inv
468
433
fn (& i2 )
469
434
return & i2
470
435
}
0 commit comments