7070// / ~~~
7171// /
7272// / (see EFlagTreatment for more details). This will **disable** flag grouping globally, but allows the parser to
73- // / interpret flags and arguments that are not separated by spaces.
74- // / Note that this only makes sense for flags with arguments.
73+ // / interpret flags and arguments that are not separated by spaces. If prefix arg is used, the equal sign will *not*
74+ // / be considered as the key-value separator (`-Dfoo=bar` if `-D` is a prefix arg flag will be parsed as `"-D",
75+ // / "foo=bar"`). Note that this option only makes sense for flags with arguments.
7576// /
7677// / \author Giacomo Parolini <giacomo.parolini@cern.ch>
7778// / \date 2025-10-09
@@ -218,6 +219,10 @@ public:
218219 throw std::invalid_argument (" Flag `" + std::string (*aliases.begin ()) +
219220 " ` has option kFlagPrefixArg but it's a Switch, so the option makes no sense." );
220221
222+ std::vector<RExpectedFlag> flagsToBeAdded;
223+ flagsToBeAdded.reserve (aliases.size ());
224+
225+ const auto nCurrentFlags = fExpectedFlags .size ();
221226 int aliasIdx = -1 ;
222227 for (auto f : aliases) {
223228 auto prefixLen = f.find_first_not_of (' -' );
@@ -229,13 +234,17 @@ public:
229234
230235 auto flagName = f.substr (prefixLen);
231236
232- // Check that we're not introducing ambiguities with prefix flags. While we're at it, also check that none
233- // of the given aliases were already added.
237+ // Check that we're not introducing ambiguities with prefix flags.
238+ // While we're at it, also check that none of the given aliases were already added. Note that it is valid to
239+ // add the same flag name as both a long and short flag (like "-foo" and "--foo") but only if they are aliases
240+ // of the same flag.
234241 for (const auto &expFlag : fExpectedFlags ) {
235- // NOTE: we're checking against the full string, not just the flag name, to allow cases like:
236- // AddFlag({"-foo", "--foo"}).
237- if (expFlag.AsStr () == f)
238- throw std::invalid_argument (" Flag `" + expFlag.AsStr () + " ` was added multiple times." );
242+ if (expFlag.fName == flagName) {
243+ throw std::invalid_argument (
244+ " Flag `" + std::string (flagName) +
245+ " ` was added multiple times. Note that adding flags with the same name but different number of `-` "
246+ " can only be done as aliases of the same call to AddFlag()." );
247+ }
239248
240249 if (!(flagOpts & kFlagPrefixArg ) && !(expFlag.fOpts & kFlagPrefixArg ))
241250 continue ;
@@ -249,6 +258,15 @@ public:
249258 }
250259 }
251260
261+ // Check that the user didn't pass the very same flag multiple times in the same AddFlag invocation, as that's
262+ // likely a mistake. This is different from the check done in the previous loop as that one only concerns
263+ // flags that were already added before this AddFlag invocation.
264+ for (const auto &expFlag : flagsToBeAdded) {
265+ if (expFlag.AsStr () == f)
266+ throw std::invalid_argument (" The same flag `" + expFlag.AsStr () +
267+ " ` was passed multiple times to AddFlag()." );
268+ }
269+
252270 bool disallowsGrouping = (prefixLen == 1 && (f.size () > 2 || (flagOpts & kFlagPrefixArg )));
253271 if (disallowsGrouping) {
254272 if (fSettings .fFlagTreatment == EFlagTreatment::kDefault ) {
@@ -268,10 +286,12 @@ public:
268286 expected.fAlias = aliasIdx;
269287 expected.fShort = prefixLen == 1 ;
270288 expected.fOpts = flagOpts;
271- fExpectedFlags .push_back (expected);
289+ flagsToBeAdded .push_back (expected);
272290 if (aliasIdx < 0 )
273- aliasIdx = fExpectedFlags .size () - 1 ;
291+ aliasIdx = nCurrentFlags + flagsToBeAdded .size () - 1 ;
274292 }
293+
294+ fExpectedFlags .insert (fExpectedFlags .end (), flagsToBeAdded.begin (), flagsToBeAdded.end ());
275295 }
276296
277297 // / If `name` refers to a previously-defined switch (i.e. a boolean flag), returns how many times
@@ -431,7 +451,8 @@ public:
431451 continue ;
432452 }
433453
434- ++arg;
454+ ++arg; // eat first `-`.
455+
435456 // Parse long or short flag and its argument into `argStr` / `nxtArgStr`.
436457 // Note that `argStr` may contain multiple flags in case of grouped short flags (in which case nxtArgStr
437458 // refers only to the last one).
@@ -442,16 +463,31 @@ public:
442463 if (arg[0 ] == ' -' ) {
443464 // long flag
444465 ++arg;
445- const char *eq = strchr (arg, ' =' );
446- if (eq) {
447- argStr.push_back (std::string_view (arg, eq - arg));
448- nxtArgStr = std::string_view (eq + 1 );
449- nxtArgIsTentative = false ;
450- } else {
451- argStr.push_back (std::string_view (arg));
452- if (i < nArgs - 1 && args[i + 1 ][0 ] != ' -' ) {
453- nxtArgStr = args[i + 1 ];
454- ++i;
466+
467+ // if we have prefix flags we need to check them first, as in that case the `=` sign should not be treated
468+ // as the key-value separator.
469+ const auto argLen = strlen (arg);
470+ for (const auto &flag : fExpectedFlags ) {
471+ if ((flag.fOpts & kFlagPrefixArg ) && flag.fName .size () < argLen &&
472+ strncmp (arg, flag.fName .c_str (), flag.fName .size ()) == 0 ) {
473+ argStr.push_back (std::string_view (arg, flag.fName .size ()));
474+ nxtArgStr = std::string_view (arg + flag.fName .size (), argLen - flag.fName .size ());
475+ break ;
476+ }
477+ }
478+
479+ if (argStr.empty ()) {
480+ const char *eq = strchr (arg, ' =' );
481+ if (eq) {
482+ argStr.push_back (std::string_view (arg, eq - arg));
483+ nxtArgStr = std::string_view (eq + 1 );
484+ nxtArgIsTentative = false ;
485+ } else {
486+ argStr.push_back (std::string_view (arg));
487+ if (i < nArgs - 1 && args[i + 1 ][0 ] != ' -' ) {
488+ nxtArgStr = args[i + 1 ];
489+ ++i;
490+ }
455491 }
456492 }
457493 } else {
@@ -470,6 +506,8 @@ public:
470506 }
471507 }
472508
509+ assert (argStr.size () < 2 || fSettings .fFlagTreatment == EFlagTreatment::kGrouped );
510+
473511 for (auto j = 0u ; j < argStr.size (); ++j) {
474512 std::string_view argS = argStr[j];
475513
0 commit comments