From 07cad7ac9e30e27972dc08141572920e0dca29ca Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Sun, 31 Mar 2019 10:27:33 -0400 Subject: [PATCH] Added support for compound arguments, e.g., ./main -aux foo bar --- src/argparse.hpp | 87 ++++++++++++++++++++++++---------- tests/test_implicit_values.hpp | 69 +++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 26 deletions(-) diff --git a/src/argparse.hpp b/src/argparse.hpp index 8c0e426..eed4f55 100644 --- a/src/argparse.hpp +++ b/src/argparse.hpp @@ -70,6 +70,7 @@ struct Argument { Argument& implicit_value(std::any aImplicitValue) { mImplicitValue = aImplicitValue; + mNumArgs = 0; return *this; } @@ -156,7 +157,7 @@ class ArgumentParser { add_argument_internal(tArgument, Fargs...); for (auto& mName : tArgument->mNames) { - if (starts_with(mName, "--") || starts_with(mName, "-")) + if (is_optional(mName)) tArgument->mIsOptional = true; } @@ -189,8 +190,8 @@ class ArgumentParser { // Check to see if implicit value should be used // Two cases to handle here: // (1) User has explicitly programmed nargs to be 0 - // (2) User has left nargs to be default, i.e., 1 and provided an implicit value - if (tCount == 0 || (tArgument->mImplicitValue.has_value() && tCount == 1)) { + // (2) User has provided an implicit value, which also sets nargs to 0 + if (tCount == 0) { // Use implicit value for this optional argument tArgument->mValues.push_back(tArgument->mImplicitValue); tArgument->mRawValues.push_back(""); @@ -213,32 +214,57 @@ class ArgumentParser { } } else { - // This is a positional argument. - // Parse and save into mPositionalArguments vector - auto tArgument = mPositionalArguments[mNextPositionalArgument]; - auto tCount = tArgument->mNumArgs - tArgument->mRawValues.size(); - while (tCount > 0) { - std::map>::iterator tIterator = mArgumentMap.find(argv[i]); - if (tIterator != mArgumentMap.end()) { - i = i - 1; - break; - } - if (i < argc) { - tArgument->mRawValues.push_back(argv[i]); - if (tArgument->mAction != nullptr) - tArgument->mValues.push_back(tArgument->mAction(argv[i])); - else { - if (tArgument->mDefaultValue.has_value()) - tArgument->mValues.push_back(tArgument->mDefaultValue); - else - tArgument->mValues.push_back(std::string(argv[i])); + if (is_optional(argv[i])) { + // This is possibly a compound optional argument + // Example: We have three optional arguments -a, -u and -x + // The user provides ./main -aux ... + // Here -aux is a compound optional argument + std::string tCompoundArgument = std::string(argv[i]); + for (size_t j = 1; j < tCompoundArgument.size(); j++) { + std::string tArgument(1, tCompoundArgument[j]); + size_t tNumArgs = 0; + std::map>::iterator tIterator = mArgumentMap.find("-" + tArgument); + if (tIterator != mArgumentMap.end()) { + auto tArgumentObject = tIterator->second; + tNumArgs = tArgumentObject->mNumArgs; } + std::vector tArgumentsForRecursiveParsing = { "", "-" + tArgument }; + while (tNumArgs > 0) { + i += 1; + tArgumentsForRecursiveParsing.push_back(argv[i]); + tNumArgs -= 1; + } + parse_args(tArgumentsForRecursiveParsing); } - tCount -= 1; - if (tCount > 0) i += 1; } - if (tCount == 0) - mNextPositionalArgument += 1; + else { + // This is a positional argument. + // Parse and save into mPositionalArguments vector + auto tArgument = mPositionalArguments[mNextPositionalArgument]; + auto tCount = tArgument->mNumArgs - tArgument->mRawValues.size(); + while (tCount > 0) { + std::map>::iterator tIterator = mArgumentMap.find(argv[i]); + if (tIterator != mArgumentMap.end()) { + i = i - 1; + break; + } + if (i < argc) { + tArgument->mRawValues.push_back(argv[i]); + if (tArgument->mAction != nullptr) + tArgument->mValues.push_back(tArgument->mAction(argv[i])); + else { + if (tArgument->mDefaultValue.has_value()) + tArgument->mValues.push_back(tArgument->mDefaultValue); + else + tArgument->mValues.push_back(std::string(argv[i])); + } + } + tCount -= 1; + if (tCount > 0) i += 1; + } + if (tCount == 0) + mNextPositionalArgument += 1; + } } } } @@ -279,6 +305,15 @@ class ArgumentParser { return *aArgument; } + bool is_optional(const std::string& aName) { + return (starts_with(aName, "--") || starts_with(aName, "-")); + } + + bool is_valid_argument(const std::string& aName) { + std::map>::iterator tIterator = mArgumentMap.find(aName); + return (tIterator != mArgumentMap.end()); + } + std::string mProgramName; std::vector> mPositionalArguments; size_t mNextPositionalArgument; diff --git a/tests/test_implicit_values.hpp b/tests/test_implicit_values.hpp index a83ad80..1c7469a 100644 --- a/tests/test_implicit_values.hpp +++ b/tests/test_implicit_values.hpp @@ -24,4 +24,73 @@ TEST_CASE("Parse toggle arguments with implicit value", "[parse_args]") { auto arguments = program.get_arguments(); REQUIRE(arguments.size() == 1); REQUIRE(program.get("--verbose") == true); +} + +TEST_CASE("Parse multiple toggle arguments with implicit values", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-u") + .default_value(false) + .implicit_value(true); + + program.add_argument("-x") + .default_value(false) + .implicit_value(true); + + program.parse_args({ "./test.exe", "-a", "-x" }); + auto arguments = program.get_arguments(); + REQUIRE(arguments.size() == 3); + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-u") == false); + REQUIRE(program.get("-x") == true); +} + +TEST_CASE("Parse compound toggle arguments with implicit values", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-u") + .default_value(false) + .implicit_value(true); + + program.add_argument("-x") + .default_value(false) + .implicit_value(true); + + program.parse_args({ "./test.exe", "-aux" }); + auto arguments = program.get_arguments(); + REQUIRE(arguments.size() == 3); + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-u") == true); + REQUIRE(program.get("-x") == true); +} + +TEST_CASE("Parse compound toggle arguments with implicit values and nargs", "[parse_args]") { + argparse::ArgumentParser program("test"); + program.add_argument("-a") + .default_value(false) + .implicit_value(true); + + program.add_argument("-b") + .default_value(false) + .implicit_value(true); + + program.add_argument("-c") + .nargs(2) + .action([](const std::string& value) { return std::stof(value); }); + + program.parse_args({ "./test.exe", "-abc", "3.14", "2.718" }); + auto arguments = program.get_arguments(); + REQUIRE(arguments.size() == 3); + REQUIRE(program.get("-a") == true); + REQUIRE(program.get("-b") == true); + auto c = program.get>("-c"); + REQUIRE(c.size() == 2); + REQUIRE(c[0] == 3.14f); + REQUIRE(c[1] == 2.718f); } \ No newline at end of file