158 const auto StartsWith = [](std::string_view string, std::string_view prefix) {
159 return string.size() >= prefix.size() &&
string.substr(0, prefix.size()) == prefix;
169 }
else if (flag.fName ==
name) {
191 stream <<
err <<
"\n";
209 std::string_view
help =
"", std::uint32_t flagOpts = 0)
211 const auto IsPrefixOf = [](std::string_view
a, std::string_view
b) {
212 return a.size() <
b.size() && std::equal(
a.begin(),
a.end(),
b.begin());
215 if (aliases.size() == 0)
216 throw std::invalid_argument(
"AddFlag must receive at least 1 name for the flag!");
219 throw std::invalid_argument(
"Flag `" + std::string(*aliases.begin()) +
220 "` has option kFlagPrefixArg but it's a Switch, so the option makes no sense.");
222 std::vector<RExpectedFlag> flagsToBeAdded;
223 flagsToBeAdded.reserve(aliases.size());
227 for (
auto f : aliases) {
228 auto prefixLen =
f.find_first_not_of(
'-');
229 if (prefixLen != 1 && prefixLen != 2)
230 throw std::invalid_argument(std::string(
"Invalid flag `") + std::string(
f) +
231 "`: flags must start with '-' or '--'");
232 if (
f.size() == prefixLen)
233 throw std::invalid_argument(
"Flag name cannot be empty");
235 auto flagName =
f.substr(prefixLen);
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().");
253 if (((expFlag.fOpts &
kFlagPrefixArg) && IsPrefixOf(expFlag.fName, flagName)) ||
254 ((flagOpts &
kFlagPrefixArg) && IsPrefixOf(flagName, expFlag.fName))) {
255 throw std::invalid_argument(
"Flags `" + expFlag.AsStr() +
"` and `" + std::string(
f) +
256 "` have a common prefix. This causes ambiguity because at least one of them "
257 "is marked with kFlagPrefixArg.");
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().");
270 bool disallowsGrouping = (prefixLen == 1 && (
f.size() > 2 || (flagOpts &
kFlagPrefixArg)));
271 if (disallowsGrouping) {
275 throw std::invalid_argument(
276 std::string(
"Flags starting with a single dash must be 1 character long when `FlagTreatment == "
277 "EFlagTreatment::kGrouped'! Cannot accept given flag `") +
278 std::string(
f) +
"`");
284 expected.
fName = flagName;
286 expected.
fAlias = aliasIdx;
287 expected.
fShort = prefixLen == 1;
288 expected.
fOpts = flagOpts;
289 flagsToBeAdded.push_back(expected);
291 aliasIdx = nCurrentFlags + flagsToBeAdded.size() - 1;
304 throw std::invalid_argument(std::string(
"Flag `") + std::string(
name) +
"` is not expected");
306 throw std::invalid_argument(std::string(
"Flag `") + std::string(
name) +
"` is not a switch");
308 std::string_view lookedUpName =
name;
309 if (exp->fAlias >= 0)
314 n +=
f.fName == lookedUpName;
334 throw std::invalid_argument(std::string(
"Flag `") + std::string(
name) +
"` is not expected");
336 throw std::invalid_argument(std::string(
"Flag `") + std::string(
name) +
"` is a switch, use GetSwitch()");
338 std::string_view lookedUpName =
name;
339 if (exp->fAlias >= 0)
342 std::vector<std::string_view>
values;
344 if (
f.fName == lookedUpName)
355 template <
typename T>
359 return values.empty() ? std::nullopt : std::make_optional(
values[0]);
366 template <
typename T>
369 static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>);
377#if __cplusplus >= 202002L && !defined(__APPLE__)
379 auto res = std::from_chars(val.data(), val.data() + val.size(), converted);
380 if (res.ptr == val.data() + val.size() && res.ec == std::errc{}) {
381 values.push_back(converted);
383 std::stringstream
err;
384 err <<
"Failed to parse flag `" <<
name <<
"` with value `" << val <<
"`";
385 if constexpr (std::is_integral_v<T>)
386 err <<
" as an integer.\n";
388 err <<
" as a floating point number.\n";
390 if (res.ec == std::errc::result_out_of_range)
391 throw std::out_of_range(
err.str());
393 throw std::invalid_argument(
err.str());
396 std::conditional_t<std::is_integral_v<T>,
long long,
long double> converted;
397 std::size_t unconvertedPos;
398 if constexpr (std::is_integral_v<T>) {
399 converted = std::stoll(std::string(val), &unconvertedPos);
401 converted = std::stold(std::string(val), &unconvertedPos);
404 const bool isOor = converted > std::numeric_limits<T>::max();
405 if (unconvertedPos != val.size() || isOor) {
406 std::stringstream
err;
407 err <<
"Failed to parse flag `" <<
name <<
"` with value `" << val <<
"`";
408 if constexpr (std::is_integral_v<T>)
409 err <<
" as an integer.\n";
411 err <<
" as a floating point number.\n";
414 throw std::out_of_range(
err.str());
416 throw std::invalid_argument(
err.str());
419 values.push_back(converted);
425 void Parse(
const char **args, std::size_t nArgs)
427 bool forcePositional =
false;
436 std::vector<std::string_view> argStr;
438 for (std::size_t i = 0; i < nArgs &&
fErrors.empty(); ++i) {
439 const char *arg = args[i];
440 const char *
const argOrig = arg;
442 if (strcmp(arg,
"--") == 0) {
443 forcePositional =
true;
447 bool isFlag = !forcePositional && arg[0] ==
'-';
450 fArgs.push_back(arg);
460 std::string_view nxtArgStr;
462 bool nxtArgIsTentative =
true;
469 const auto argLen = strlen(arg);
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());
479 if (argStr.empty()) {
480 const char *eq = strchr(arg,
'=');
482 argStr.push_back(std::string_view(arg, eq - arg));
483 nxtArgStr = std::string_view(eq + 1);
484 nxtArgIsTentative =
false;
486 argStr.push_back(std::string_view(arg));
487 if (i < nArgs - 1 && args[i + 1][0] !=
'-') {
488 nxtArgStr = args[i + 1];
496 auto argLen = strlen(arg);
498 argStr.push_back(std::string_view{arg, 1});
502 argStr.push_back(std::string_view(arg));
503 if (i < nArgs - 1 && args[i + 1][0] !=
'-') {
504 nxtArgStr = args[i + 1];
511 for (
auto j = 0u; j < argStr.size(); ++j) {
512 std::string_view argS = argStr[j];
516 fErrors.push_back(std::string(
"Unknown flag: ") + argOrig);
527 if ((exp->fOpts &
kFlagPrefixArg) && argS.size() > exp->fName.size()) {
528 if (nxtArgIsTentative) {
529 i -= !nxtArgStr.empty();
530 nxtArgStr = argS.substr(exp->fName.size());
531 nxtArgIsTentative =
false;
533 fErrors.push_back(std::string(
"Unknown flag: ") + argOrig);
537 assert(exp->fName.size() == argS.size());
540 std::string_view nxtArg = (j == argStr.size() - 1) ? nxtArgStr :
"";
543 flag.fHelp = exp->fHelp;
547 flag.fName = exp->fName;
554 std::find_if(
fFlags.begin(),
fFlags.end(), [&flag](
const auto &
f) { return f.fName == flag.fName; });
555 if (existingIt !=
fFlags.end()) {
556 std::string
err = std::string(
"Flag ") + exp->AsStr() +
" appeared more than once";
558 err +=
" with the value: " + existingIt->fValue;
566 if (!nxtArg.empty()) {
567 flag.fValue = nxtArg;
569 fErrors.push_back(
"Missing argument for flag " + exp->AsStr());
572 if (!nxtArg.empty()) {
573 if (nxtArgIsTentative)
576 fErrors.push_back(
"Flag " + exp->AsStr() +
" does not expect an argument");
std::vector< double > values
std::vector< RFlag > fFlags
@ kSimple
-abc will always be treated as the single flag "-abc"
@ kDefault
Will result to kGrouped if you don't define any short flag longer than 1 character,...
@ kGrouped
-abc will be treated as "-a -b -c".
const RExpectedFlag * GetExpectedFlag(std::string_view name) const
void AddFlag(std::initializer_list< std::string_view > aliases, EFlagType type=EFlagType::kSwitch, std::string_view help="", std::uint32_t flagOpts=0)
Defines a new flag (either a switch or a flag with argument).
const std::vector< std::string > & GetErrors() const
Returns all parsing errors.
int GetSwitch(std::string_view name) const
If name refers to a previously-defined switch (i.e.
const std::vector< std::string > & GetArgs() const
Retrieves all positional arguments.
std::vector< T > GetFlagValuesAs(std::string_view name) const
std::string_view GetFlagValue(std::string_view name) const
If name refers to a previously-defined non-switch flag, gets its value.
std::vector< RExpectedFlag > fExpectedFlags
RCmdLineOpts(RSettings settings={EFlagTreatment::kDefault})
const std::vector< RFlag > & GetFlags() const
Retrieves all parsed flags.
void Parse(const char **args, std::size_t nArgs)
std::vector< std::string > fArgs
std::vector< std::string_view > GetFlagValues(std::string_view name) const
If name refers to a previously-defined non-switch flag, gets its values.
bool ReportErrors(std::ostream &stream=std::cerr) const
Conveniency method to print any errors to stream.
std::optional< T > GetFlagValueAs(std::string_view name) const
@ kFlagPrefixArg
Flag argument can appear right after this flag without a space or equal sign in between.
@ kFlagAllowMultiple
Flag is allowed to appear multiple times (default: it's an error to see the same flag twice).
std::vector< std::string > fErrors
Small utility to parse cmdline options.
bool StartsWith(std::string_view string, std::string_view prefix)
std::string AsStr() const
EFlagTreatment fFlagTreatment
Affects how flags are parsed (.