Skip to content

Commit 2d6a54d

Browse files
committed
reorder flags so they can come after arguments.
1 parent c4cd0a5 commit 2d6a54d

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

command.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,10 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
744744
return cmd.Args(), cmd.flagSet.Parse(append([]string{"--"}, args.Tail()...))
745745
}
746746

747+
tracef("reordering flags so they appear before the arguments")
748+
749+
args = reorderArgs(cmd.Flags, args)
750+
747751
tracef("walking command lineage for persistent flags (cmd=%[1]q)", cmd.Name)
748752

749753
for pCmd := cmd.parent; pCmd != nil; pCmd = pCmd.parent {
@@ -1248,3 +1252,80 @@ func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
12481252
}
12491253
}
12501254
}
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

Comments
 (0)