@@ -744,6 +744,10 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
744
744
return cmd .Args (), cmd .flagSet .Parse (append ([]string {"--" }, args .Tail ()... ))
745
745
}
746
746
747
+ tracef ("reordering flags so they appear before the arguments" )
748
+
749
+ args = reorderArgs (cmd .Flags , args )
750
+
747
751
tracef ("walking command lineage for persistent flags (cmd=%[1]q)" , cmd .Name )
748
752
749
753
for pCmd := cmd .parent ; pCmd != nil ; pCmd = pCmd .parent {
@@ -1248,3 +1252,80 @@ func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
1248
1252
}
1249
1253
}
1250
1254
}
1255
+
1256
+ // reorderArgs moves all flags (via reorderedArgs) before the rest of
1257
+ // the arguments (remainingArgs) as this is what flag expects.
1258
+ func reorderArgs (commandFlags []Flag , args Args ) Args {
1259
+ var remainingArgs , reorderedArgs []string
1260
+
1261
+ tail := args .Tail ()
1262
+
1263
+ nextIndexMayContainValue := false
1264
+ for i , arg := range tail {
1265
+ // if we're expecting an option-value, check if this arg is a value, in
1266
+ // which case it should be re-ordered next to its associated flag
1267
+ if isFlag , _ := argIsFlag (commandFlags , arg ); nextIndexMayContainValue && ! isFlag {
1268
+ nextIndexMayContainValue = false
1269
+ reorderedArgs = append (reorderedArgs , arg )
1270
+ } else if arg == "--" {
1271
+ // don't reorder any args after the -- delimiter As described in the POSIX spec:
1272
+ // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
1273
+ // > Guideline 10:
1274
+ // > The first -- argument that is not an option-argument should be accepted
1275
+ // > as a delimiter indicating the end of options. Any following arguments
1276
+ // > should be treated as operands, even if they begin with the '-' character.
1277
+
1278
+ // make sure the "--" delimiter itself is at the start
1279
+ remainingArgs = append ([]string {"--" }, remainingArgs ... )
1280
+ remainingArgs = append (remainingArgs , tail [i + 1 :]... )
1281
+ break
1282
+ // checks if this is an arg that should be re-ordered
1283
+ } else if isFlag , isBooleanFlag := argIsFlag (commandFlags , arg ); isFlag {
1284
+ // we have determined that this is a flag that we should re-order
1285
+ reorderedArgs = append (reorderedArgs , arg )
1286
+
1287
+ // if this arg does not contain a "=", then the next index may contain the value for this flag
1288
+ nextIndexMayContainValue = ! strings .Contains (arg , "=" ) && ! isBooleanFlag
1289
+
1290
+ // simply append any remaining args
1291
+ } else {
1292
+ remainingArgs = append (remainingArgs , arg )
1293
+ }
1294
+ }
1295
+
1296
+ return & stringSliceArgs {append ([]string {args .First ()}, append (reorderedArgs , remainingArgs ... )... )}
1297
+ }
1298
+
1299
+ // argIsFlag checks if an arg is one of our command flags
1300
+ func argIsFlag (commandFlags []Flag , arg string ) (isFlag bool , isBooleanFlag bool ) {
1301
+ if arg == "-" || arg == "--" {
1302
+ // `-` is never a flag
1303
+ // `--` is an option-value when following a flag, and a delimiter indicating the end of options in other cases.
1304
+ return false , false
1305
+ }
1306
+ // flags always start with a -
1307
+ if ! strings .HasPrefix (arg , "-" ) {
1308
+ return false , false
1309
+ }
1310
+ // this line turns `--flag` into `flag`
1311
+ if strings .HasPrefix (arg , "--" ) {
1312
+ arg = strings .Replace (arg , "-" , "" , 2 )
1313
+ }
1314
+ // this line turns `-flag` into `flag`
1315
+ if strings .HasPrefix (arg , "-" ) {
1316
+ arg = strings .Replace (arg , "-" , "" , 1 )
1317
+ }
1318
+ // this line turns `flag=value` into `flag`
1319
+ arg = strings .Split (arg , "=" )[0 ]
1320
+ // look through all the flags, to see if the `arg` is one of our flags
1321
+ for _ , flag := range commandFlags {
1322
+ for _ , key := range flag .Names () {
1323
+ if key == arg {
1324
+ _ , isBooleanFlag = flag .(* BoolFlag )
1325
+ return true , isBooleanFlag
1326
+ }
1327
+ }
1328
+ }
1329
+ // return false if this arg was not one of our flags
1330
+ return false , false
1331
+ }
0 commit comments