From c6c3be04d3533c8fb7b52ed04ad1ffe986da7a61 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Sat, 11 Sep 2021 05:22:33 +0900 Subject: [PATCH 01/22] 1st version of handling variable length nargs To handle variable length nargs, I replaced mNumArgs with mNumArgsRange. I defined SizeRange class for mNumArgsRange, which has simply min and max std::size_t member. To concentrate on this big change, I tentatively deleted remaining feature, which was originally implemented in the way that mNumArgs = -1 internally and maybe_args() -> Optional wrap method. Library users may make use of 4 types of interface to set mNumArgsRange. 1. nargs(std::size_t) 2. nargs(std::size_t, std::size_t) 3. nargs(SizeRange) 4. nargs(NArgsPattern) 1. is expected to behave same as original. This mthod sets min=max SizeRange to mNumArgsRange, which is actually, not a range, but an "exact" number. 2. sets min and max. 3. uses SizeRange class. This interface may be unnecessary. It is also an option to delete this method and make SizeRange class internal. 4. is provided to set common patterns. In Python, they are "?", "*" and "+". NArgsPattern is an enum class for type safety. std::string interface is also an option to mimic Python argparse. char interface would be ambiguous with 1. Changes on consume method is important. The parser tries to consume args until the count reaches mNumArgsRanges::max or it meets another optional like string. If consumed args count is under mNumArgsRanges::min, the parser fails. Now, when the required number of arguments are not provided, the parser will fail. So, we have to take care of get() method as well. get() failed when argument count is 0 and default value not provided. But now there are 0..1 or 0..* nargs are OK. So this behaviour has to be fixed. When T is container_v, it returns empty container. I implemented validate method so that it shows kind message. --- include/argparse/argparse.hpp | 172 ++++++++++++++++++++++++---------- 1 file changed, 125 insertions(+), 47 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index cb427cd..d855ee3 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -37,6 +37,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -312,6 +313,45 @@ template struct parse_number { } // namespace details +class SizeRange { + std::size_t mMin; + std::size_t mMax; + +public: + SizeRange(std::size_t aMin, std::size_t aMax) { + if (aMin > aMax) + throw std::logic_error("Range of number of arguments is invalid"); + mMin = aMin; + mMax = aMax; + } + + bool contains(std::size_t value) const { + return value >= mMin && value <= mMax; + } + + bool is_exact() const { + return mMin == mMax; + } + + bool is_right_bounded() const { + return mMax < std::numeric_limits::max(); + } + + std::size_t get_min() const { + return mMin; + } + + std::size_t get_max() const { + return mMax; + } +}; + +enum class NArgsPattern { + ZERO_OR_ONE, + ANY, + AT_LEAST_ONE +}; + class ArgumentParser; class Argument { @@ -353,7 +393,7 @@ public: Argument &implicit_value(std::any aImplicitValue) { mImplicitValue = std::move(aImplicitValue); - mNumArgs = 0; + mNumArgsRange = SizeRange{0, 0}; return *this; } @@ -420,15 +460,33 @@ public: return *this; } - Argument &nargs(int aNumArgs) { - if (aNumArgs < 0) - throw std::logic_error("Number of arguments must be non-negative"); - mNumArgs = aNumArgs; + Argument &nargs(std::size_t aNumArgs) { + mNumArgsRange = SizeRange{aNumArgs, aNumArgs}; return *this; } - Argument &remaining() { - mNumArgs = -1; + Argument &nargs(std::size_t aNumArgsMin, std::size_t aNumArgsMax) { + mNumArgsRange = SizeRange{aNumArgsMin, aNumArgsMax}; + return *this; + } + + Argument &nargs(SizeRange aNumArgsRange) { + mNumArgsRange = aNumArgsRange; + return *this; + } + + Argument &nargs(NArgsPattern aNargs) { + switch (aNargs) { + case NArgsPattern::ZERO_OR_ONE: + mNumArgsRange = SizeRange{0, 1}; + break; + case NArgsPattern::ANY: + mNumArgsRange = SizeRange{0, std::numeric_limits::max()}; + break; + case NArgsPattern::AT_LEAST_ONE: + mNumArgsRange = SizeRange{1, std::numeric_limits::max()}; + break; + } return *this; } @@ -440,16 +498,28 @@ public: } mIsUsed = true; mUsedName = usedName; - if (mNumArgs == 0) { + + const auto numArgsMax = mNumArgsRange.get_max(); + const auto numArgsMin = mNumArgsRange.get_min(); + if (numArgsMax == 0) { mValues.emplace_back(mImplicitValue); return start; - } else if (mNumArgs <= std::distance(start, end)) { - if (auto expected = maybe_nargs()) { - end = std::next(start, *expected); - if (std::any_of(start, end, Argument::is_optional)) { - throw std::runtime_error("optional argument in parameter sequence"); + } else if (static_cast(std::distance(start, end)) >= numArgsMin) { + + auto it = start; + for (std::size_t i = 0; it != end; ++it, ++i) { + if (Argument::is_optional(*it)) { + break; + } + if (i >= numArgsMax) { + break; } } + auto dist = static_cast(std::distance(start, it)); + if (dist < numArgsMin) { + throw std::runtime_error("Too few arguments"); + } + end = it; struct action_apply { void operator()(valued_action &f) { @@ -459,8 +529,7 @@ public: void operator()(void_action &f) { std::for_each(start, end, f); if (!self.mDefaultValue.has_value()) { - if (auto expected = self.maybe_nargs()) - self.mValues.resize(*expected); + self.mValues.resize(std::distance(start, end)); } } @@ -481,47 +550,53 @@ public: * @throws std::runtime_error if argument values are not valid */ void validate() const { - if (auto expected = maybe_nargs()) { - if (mIsOptional) { - if (mIsUsed && mValues.size() != *expected && !mIsRepeatable && - !mDefaultValue.has_value()) { - std::stringstream stream; - stream << mUsedName << ": expected " << *expected << " argument(s). " - << mValues.size() << " provided."; - throw std::runtime_error(stream.str()); + if (mIsOptional) { + if (mIsUsed && !mNumArgsRange.contains(mValues.size()) && !mIsRepeatable && + !mDefaultValue.has_value()) { + std::stringstream stream; + stream << mUsedName << ": expected "; + if (mNumArgsRange.is_exact()) { + stream << mNumArgsRange.get_min(); + } else if (mNumArgsRange.is_right_bounded()) { + stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); } else { - // TODO: check if an implicit value was programmed for this argument - if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { - std::stringstream stream; - stream << mNames[0] << ": required."; - throw std::runtime_error(stream.str()); - } - if (mIsUsed && mIsRequired && mValues.size() == 0) { - std::stringstream stream; - stream << mUsedName << ": no value provided."; - throw std::runtime_error(stream.str()); - } + stream << mNumArgsRange.get_min() << " or more"; } + stream << " argument(s). " + << mValues.size() << " provided."; + throw std::runtime_error(stream.str()); } else { - if (mValues.size() != expected && !mDefaultValue.has_value()) { + // TODO: check if an implicit value was programmed for this argument + if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { std::stringstream stream; - if (!mUsedName.empty()) - stream << mUsedName << ": "; - stream << *expected << " argument(s) expected. " << mValues.size() - << " provided."; + stream << mNames[0] << ": required."; + throw std::runtime_error(stream.str()); + } + if (mIsUsed && mIsRequired && mValues.size() == 0) { + std::stringstream stream; + stream << mUsedName << ": no value provided."; throw std::runtime_error(stream.str()); } } + } else { + if (!mNumArgsRange.contains(mValues.size()) && !mDefaultValue.has_value()) { + std::stringstream stream; + if (!mUsedName.empty()) + stream << mUsedName << ": "; + if (mNumArgsRange.is_exact()) { + stream << mNumArgsRange.get_min(); + } else if (mNumArgsRange.is_right_bounded()) { + stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); + } else { + stream << mNumArgsRange.get_min() << " or more"; + } + stream << " argument(s) expected. " << mValues.size() + << " provided."; + throw std::runtime_error(stream.str()); + } } } - auto maybe_nargs() const -> std::optional { - if (mNumArgs < 0) - return std::nullopt; - else - return static_cast(mNumArgs); - } - std::size_t get_arguments_length() const { return std::accumulate(std::begin(mNames), std::end(mNames), std::size_t(0), [](const auto &sum, const auto &s) { @@ -759,6 +834,9 @@ private: } if (mDefaultValue.has_value()) { return std::any_cast(mDefaultValue); + } else { + if constexpr (details::is_container_v) + return any_cast_container(mValues); } throw std::logic_error("No value provided for '" + mNames.back() + "'."); } @@ -803,7 +881,7 @@ private: std::in_place_type, [](const std::string &aValue) { return aValue; }}; std::vector mValues; - int mNumArgs = 1; + SizeRange mNumArgsRange {1, 1}; bool mIsOptional : true; bool mIsRequired : true; bool mIsRepeatable : true; From 3d559d3a23cf8567b1f300a1e8e70b8d87f90f0c Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Sun, 12 Sep 2021 03:36:05 +0900 Subject: [PATCH 02/22] Revive remaining method I reimplemented remaining() method for backward compatibility. It consumes "all" remaining args. So, I added mAcceptsOptionalLikeValue flag and handle it by using this flag. Currently, remaining() behavior is slightly different from the original when no args are provided and get>() method is called. Originally it raises an exception. But current implementation returns an empty container instead of exception. It is possible to implement complete backward compatibility by referencing mAcceptsOptionalLikeValue flag and raises an exception in get() method, but I did not do this. I think that is too much. --- include/argparse/argparse.hpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index d855ee3..4789d7f 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -490,6 +490,11 @@ public: return *this; } + Argument &remaining() { + mAcceptsOptionalLikeValue = true; + return nargs(NArgsPattern::ANY); + } + template Iterator consume(Iterator start, Iterator end, std::string_view usedName = {}) { @@ -501,25 +506,21 @@ public: const auto numArgsMax = mNumArgsRange.get_max(); const auto numArgsMin = mNumArgsRange.get_min(); + std::size_t dist = 0; if (numArgsMax == 0) { mValues.emplace_back(mImplicitValue); return start; - } else if (static_cast(std::distance(start, end)) >= numArgsMin) { - - auto it = start; - for (std::size_t i = 0; it != end; ++it, ++i) { - if (Argument::is_optional(*it)) { - break; - } - if (i >= numArgsMax) { - break; + } else if ((dist = static_cast(std::distance(start, end))) >= numArgsMin) { + if (numArgsMax < dist) { + end = std::next(start, numArgsMax); + } + if (!mAcceptsOptionalLikeValue) { + end = std::find_if(start, end, Argument::is_optional); + dist = static_cast(std::distance(start, end)); + if (dist < numArgsMin) { + throw std::runtime_error("Too few arguments"); } } - auto dist = static_cast(std::distance(start, it)); - if (dist < numArgsMin) { - throw std::runtime_error("Too few arguments"); - } - end = it; struct action_apply { void operator()(valued_action &f) { @@ -529,7 +530,8 @@ public: void operator()(void_action &f) { std::for_each(start, end, f); if (!self.mDefaultValue.has_value()) { - self.mValues.resize(std::distance(start, end)); + if (!self.mAcceptsOptionalLikeValue) + self.mValues.resize(std::distance(start, end)); } } @@ -882,6 +884,7 @@ private: [](const std::string &aValue) { return aValue; }}; std::vector mValues; SizeRange mNumArgsRange {1, 1}; + bool mAcceptsOptionalLikeValue = false; bool mIsOptional : true; bool mIsRequired : true; bool mIsRepeatable : true; From c99272b93dc9c891afc452083f1b2de1f4c97788 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Sat, 11 Sep 2021 07:31:00 +0900 Subject: [PATCH 03/22] Remove negative parameter test for now unsigned --- test/test_positional_arguments.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index 4e2489b..9eafba3 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -109,12 +109,6 @@ TEST_CASE("Parse remaining arguments deemed positional" * } } -TEST_CASE("Negative nargs is not allowed" * - test_suite("positional_arguments")) { - argparse::ArgumentParser program("test"); - REQUIRE_THROWS_AS(program.add_argument("output").nargs(-1), std::logic_error); -} - TEST_CASE("Square a number" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") From 10ddd393b68c8e97fae34f6a544df1c5900c587b Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 06:40:11 +0900 Subject: [PATCH 04/22] Fix tests for remaining --- test/test_optional_arguments.cpp | 6 +++--- test/test_positional_arguments.cpp | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index 4d50254..d73062a 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -88,10 +88,10 @@ TEST_CASE("Parse optional arguments of many values" * program.add_argument("-i").remaining().scan<'i', int>(); WHEN("provided no argument") { - THEN("the program accepts it but gets nothing") { + THEN("the program accepts it and gets empty container") { REQUIRE_NOTHROW(program.parse_args({"test"})); - REQUIRE_THROWS_AS(program.get>("-i"), - std::logic_error); + auto inputs = program.get>("-i"); + REQUIRE(inputs.size() == 0); } } diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index 9eafba3..d0d850f 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -69,10 +69,11 @@ TEST_CASE("Parse remaining arguments deemed positional" * program.add_argument("input").remaining(); WHEN("provided no argument") { - THEN("the program accepts it but gets nothing") { + THEN("the program accepts it and gets empty container") { REQUIRE_NOTHROW(program.parse_args({"test"})); - REQUIRE_THROWS_AS(program.get>("input"), - std::logic_error); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 0); } } From 2cfe115dfb9fe1707ca7974e30b9ffaf8671d416 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 06:41:17 +0900 Subject: [PATCH 05/22] Change test for remaining to test for * nargs --- test/test_actions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_actions.cpp b/test/test_actions.cpp index 6d624af..1aff18f 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -121,12 +121,12 @@ TEST_CASE("Users can bind arguments to actions" * test_suite("actions")) { } } -TEST_CASE("Users can use actions on remaining arguments" * +TEST_CASE("Users can use actions on nargs=ANY arguments" * test_suite("actions")) { argparse::ArgumentParser program("sum"); int result = 0; - program.add_argument("all").remaining().action( + program.add_argument("all").nargs(argparse::NArgsPattern::ANY).action( [](int &sum, std::string const &value) { sum += std::stoi(value); }, std::ref(result)); From aae2e9347a2c036a81ce6f1ad1bad72fd5904de8 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 06:42:01 +0900 Subject: [PATCH 06/22] Add another test for remaining --- test/test_actions.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/test_actions.cpp b/test/test_actions.cpp index 1aff18f..b315d16 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -133,3 +133,16 @@ TEST_CASE("Users can use actions on nargs=ANY arguments" * program.parse_args({"sum", "42", "100", "-3", "-20"}); REQUIRE(result == 119); } + +TEST_CASE("Users can use actions on remaining arguments" * + test_suite("actions")) { + argparse::ArgumentParser program("concat"); + + std::string result = ""; + program.add_argument("all").remaining().action( + [](std::string &sum, std::string const &value) { sum += value; }, + std::ref(result)); + + program.parse_args({"concat", "a", "-b", "-c", "--d"}); + REQUIRE(result == "a-b-c--d"); +} From bb115c04192d6f6028498aee43411c659629daab Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 06:42:29 +0900 Subject: [PATCH 07/22] Add test for variable nargs --- test/test_optional_arguments.cpp | 80 +++++++++++++++++++ test/test_positional_arguments.cpp | 124 +++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index d73062a..dd06d62 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -110,6 +110,86 @@ TEST_CASE("Parse optional arguments of many values" * } } +TEST_CASE("Parse 2 optional arguments of many values" * + test_suite("optional_arguments")) { + GIVEN("a program that accepts 2 optional arguments of many values") { + argparse::ArgumentParser program("test"); + program.add_argument("-i").nargs(argparse::NArgsPattern::ANY).scan<'i', int>(); + program.add_argument("-s").nargs(argparse::NArgsPattern::ANY); + + WHEN("provided no argument") { + THEN("the program accepts it and gets empty container") { + REQUIRE_NOTHROW(program.parse_args({"test"})); + auto i = program.get>("-i"); + REQUIRE(i.size() == 0); + + auto s = program.get>("-s"); + REQUIRE(s.size() == 0); + } + } + + WHEN("provided 2 options with many arguments") { + program.parse_args( + {"test", "-i", "-42", "8", "100", "300", "-s", "ok", "this", "works"}); + + THEN("the optional parameter consumes each arguments") { + auto i = program.get>("-i"); + REQUIRE(i.size() == 4); + REQUIRE(i[0] == -42); + REQUIRE(i[1] == 8); + REQUIRE(i[2] == 100); + REQUIRE(i[3] == 300); + + auto s = program.get>("-s"); + REQUIRE(s.size() == 3); + REQUIRE(s[0] == "ok"); + REQUIRE(s[1] == "this"); + REQUIRE(s[2] == "works"); + } + } + } +} + +TEST_CASE("Parse an optional argument of many values" + " and a positional argument of many values" * + test_suite("optional_arguments")) { + GIVEN("a program that accepts an optional argument of many values" + " and a positional argument of many values") { + argparse::ArgumentParser program("test"); + program.add_argument("-s").nargs(argparse::NArgsPattern::ANY); + program.add_argument("input").nargs(argparse::NArgsPattern::ANY); + + WHEN("provided no argument") { + program.parse_args({"test"}); + THEN("the program accepts it and gets empty containers") { + auto s = program.get>("-s"); + REQUIRE(s.size() == 0); + + auto input = program.get>("input"); + REQUIRE(input.size() == 0); + } + } + + WHEN("provided many arguments followed by an option with many arguments") { + program.parse_args( + {"test", "foo", "bar", "-s", "ok", "this", "works"}); + + THEN("the parameters consume each arguments") { + auto s = program.get>("-s"); + REQUIRE(s.size() == 3); + REQUIRE(s[0] == "ok"); + REQUIRE(s[1] == "this"); + REQUIRE(s[2] == "works"); + + auto input = program.get>("input"); + REQUIRE(input.size() == 2); + REQUIRE(input[0] == "foo"); + REQUIRE(input[1] == "bar"); + } + } + } +} + TEST_CASE("Parse arguments of different types" * test_suite("optional_arguments")) { using namespace std::literals; diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index d0d850f..5c68d6e 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -61,6 +61,130 @@ TEST_CASE("Parse positional arguments with optional arguments in the middle" * REQUIRE_THROWS(program.parse_args({ "test", "rocket.mesh", "thrust_profile.csv", "--num_iterations", "15", "output.mesh" })); } +TEST_CASE("Parse positional nargs=1..2 arguments" * + test_suite("positional_arguments")) { + GIVEN("a program that accepts an optional argument and nargs=1..2 positional arguments") { + argparse::ArgumentParser program("test"); + program.add_argument("-o"); + program.add_argument("input").nargs(1, 2); + + WHEN("provided no argument") { + THEN("the program does not accept it") { + REQUIRE_THROWS(program.parse_args({"test"})); + } + } + + WHEN("provided 1 argument") { + THEN("the program accepts it") { + REQUIRE_NOTHROW(program.parse_args({"test", "a.c"})); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 1); + REQUIRE(inputs[0] == "a.c"); + } + } + + WHEN("provided 2 arguments") { + THEN("the program accepts it") { + REQUIRE_NOTHROW(program.parse_args({"test", "a.c", "b.c"})); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 2); + REQUIRE(inputs[0] == "a.c"); + REQUIRE(inputs[1] == "b.c"); + } + } + + WHEN("provided 3 arguments") { + THEN("the program does not accept it") { + REQUIRE_THROWS(program.parse_args({"test", "a.c", "b.c", "main.c"})); + } + } + + WHEN("provided an optional followed by positional arguments") { + program.parse_args({"test", "-o", "a.out", "a.c", "b.c"}); + + THEN("the optional parameter consumes an argument") { + using namespace std::literals; + REQUIRE(program["-o"] == "a.out"s); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 2); + REQUIRE(inputs[0] == "a.c"); + REQUIRE(inputs[1] == "b.c"); + } + } + + WHEN("provided an optional preceded by positional arguments") { + program.parse_args({"test", "a.c", "b.c", "-o", "a.out"}); + + THEN("the optional parameter consumes an argument") { + using namespace std::literals; + REQUIRE(program["-o"] == "a.out"s); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 2); + REQUIRE(inputs[0] == "a.c"); + REQUIRE(inputs[1] == "b.c"); + } + } + + WHEN("provided an optional in between positional arguments") { + THEN("the program does not accept it") { + REQUIRE_THROWS(program.parse_args({"test", "a.c", "-o", "a.out", "b.c"})); + } + } + } +} + +TEST_CASE("Parse positional nargs=ANY arguments" * + test_suite("positional_arguments")) { + GIVEN("a program that accepts an optional argument and nargs=ANY positional arguments") { + argparse::ArgumentParser program("test"); + program.add_argument("-o"); + program.add_argument("input").nargs(argparse::NArgsPattern::ANY); + + WHEN("provided no argument") { + THEN("the program accepts it and gets empty container") { + REQUIRE_NOTHROW(program.parse_args({"test"})); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 0); + } + } + + WHEN("provided an optional followed by positional arguments") { + program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"}); + + THEN("the optional parameter consumes an argument") { + using namespace std::literals; + REQUIRE(program["-o"] == "a.out"s); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 3); + REQUIRE(inputs[0] == "a.c"); + REQUIRE(inputs[1] == "b.c"); + REQUIRE(inputs[2] == "main.c"); + } + } + + WHEN("provided an optional preceded by positional arguments") { + program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"}); + + THEN("the optional parameter consumes an argument") { + using namespace std::literals; + REQUIRE(program["-o"] == "a.out"s); + + auto inputs = program.get>("input"); + REQUIRE(inputs.size() == 3); + REQUIRE(inputs[0] == "a.c"); + REQUIRE(inputs[1] == "b.c"); + REQUIRE(inputs[2] == "main.c"); + } + } + } +} + TEST_CASE("Parse remaining arguments deemed positional" * test_suite("positional_arguments")) { GIVEN("a program that accepts an optional argument and remaining arguments") { From 8845260885380d314da7494478b5a2a3ec5d5da4 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 06:43:03 +0900 Subject: [PATCH 08/22] Add test for reversed order nargs --- test/test_positional_arguments.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index 5c68d6e..2cc6f94 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -234,6 +234,12 @@ TEST_CASE("Parse remaining arguments deemed positional" * } } +TEST_CASE("Reversed order nargs is not allowed" * + test_suite("positional_arguments")) { + argparse::ArgumentParser program("test"); + REQUIRE_THROWS_AS(program.add_argument("output").nargs(2, 1), std::logic_error); +} + TEST_CASE("Square a number" * test_suite("positional_arguments")) { argparse::ArgumentParser program; program.add_argument("--verbose", "-v") From bec93acaa774ba8d66502332e4c88a6c6838eb45 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 07:00:49 +0900 Subject: [PATCH 09/22] Avoid use ALL_CAPS for enumerators --- include/argparse/argparse.hpp | 14 +++++++------- test/test_actions.cpp | 2 +- test/test_optional_arguments.cpp | 8 ++++---- test/test_positional_arguments.cpp | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 4789d7f..e0a5c84 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -347,9 +347,9 @@ public: }; enum class NArgsPattern { - ZERO_OR_ONE, - ANY, - AT_LEAST_ONE + ZeroOrOne, + Any, + AtLeastOne }; class ArgumentParser; @@ -477,13 +477,13 @@ public: Argument &nargs(NArgsPattern aNargs) { switch (aNargs) { - case NArgsPattern::ZERO_OR_ONE: + case NArgsPattern::ZeroOrOne: mNumArgsRange = SizeRange{0, 1}; break; - case NArgsPattern::ANY: + case NArgsPattern::Any: mNumArgsRange = SizeRange{0, std::numeric_limits::max()}; break; - case NArgsPattern::AT_LEAST_ONE: + case NArgsPattern::AtLeastOne: mNumArgsRange = SizeRange{1, std::numeric_limits::max()}; break; } @@ -492,7 +492,7 @@ public: Argument &remaining() { mAcceptsOptionalLikeValue = true; - return nargs(NArgsPattern::ANY); + return nargs(NArgsPattern::Any); } template diff --git a/test/test_actions.cpp b/test/test_actions.cpp index b315d16..dbc9d5b 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -126,7 +126,7 @@ TEST_CASE("Users can use actions on nargs=ANY arguments" * argparse::ArgumentParser program("sum"); int result = 0; - program.add_argument("all").nargs(argparse::NArgsPattern::ANY).action( + program.add_argument("all").nargs(argparse::NArgsPattern::Any).action( [](int &sum, std::string const &value) { sum += std::stoi(value); }, std::ref(result)); diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index dd06d62..262cc6f 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -114,8 +114,8 @@ TEST_CASE("Parse 2 optional arguments of many values" * test_suite("optional_arguments")) { GIVEN("a program that accepts 2 optional arguments of many values") { argparse::ArgumentParser program("test"); - program.add_argument("-i").nargs(argparse::NArgsPattern::ANY).scan<'i', int>(); - program.add_argument("-s").nargs(argparse::NArgsPattern::ANY); + program.add_argument("-i").nargs(argparse::NArgsPattern::Any).scan<'i', int>(); + program.add_argument("-s").nargs(argparse::NArgsPattern::Any); WHEN("provided no argument") { THEN("the program accepts it and gets empty container") { @@ -156,8 +156,8 @@ TEST_CASE("Parse an optional argument of many values" GIVEN("a program that accepts an optional argument of many values" " and a positional argument of many values") { argparse::ArgumentParser program("test"); - program.add_argument("-s").nargs(argparse::NArgsPattern::ANY); - program.add_argument("input").nargs(argparse::NArgsPattern::ANY); + program.add_argument("-s").nargs(argparse::NArgsPattern::Any); + program.add_argument("input").nargs(argparse::NArgsPattern::Any); WHEN("provided no argument") { program.parse_args({"test"}); diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index 2cc6f94..6a7a6cb 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -142,7 +142,7 @@ TEST_CASE("Parse positional nargs=ANY arguments" * GIVEN("a program that accepts an optional argument and nargs=ANY positional arguments") { argparse::ArgumentParser program("test"); program.add_argument("-o"); - program.add_argument("input").nargs(argparse::NArgsPattern::ANY); + program.add_argument("input").nargs(argparse::NArgsPattern::Any); WHEN("provided no argument") { THEN("the program accepts it and gets empty container") { From 14abaa47d9b0660fd1729c9e0bc9b0903999ac7c Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Thu, 16 Sep 2021 07:04:56 +0900 Subject: [PATCH 10/22] Use member initializer list to SizeRange ctor --- include/argparse/argparse.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index e0a5c84..b4d6c2b 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -318,11 +318,9 @@ class SizeRange { std::size_t mMax; public: - SizeRange(std::size_t aMin, std::size_t aMax) { + SizeRange(std::size_t aMin, std::size_t aMax) : mMin(aMin), mMax(aMax) { if (aMin > aMax) throw std::logic_error("Range of number of arguments is invalid"); - mMin = aMin; - mMax = aMax; } bool contains(std::size_t value) const { From 6dfaa1c20c3771ea34891090447590aeffdfc7f8 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Tue, 21 Jun 2022 06:30:45 +0900 Subject: [PATCH 11/22] Restore a "remaining" test case for compat --- test/test_optional_arguments.cpp | 6 +++--- test/test_positional_arguments.cpp | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index 262cc6f..e0961a6 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -88,10 +88,10 @@ TEST_CASE("Parse optional arguments of many values" * program.add_argument("-i").remaining().scan<'i', int>(); WHEN("provided no argument") { - THEN("the program accepts it and gets empty container") { + THEN("the program accepts it bug gets nothing") { REQUIRE_NOTHROW(program.parse_args({"test"})); - auto inputs = program.get>("-i"); - REQUIRE(inputs.size() == 0); + REQUIRE_THROWS_AS(program.get>("-i"), + std::logic_error); } } diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index 6a7a6cb..c3c5b93 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -193,11 +193,10 @@ TEST_CASE("Parse remaining arguments deemed positional" * program.add_argument("input").remaining(); WHEN("provided no argument") { - THEN("the program accepts it and gets empty container") { + THEN("the program accepts it but gets nothing") { REQUIRE_NOTHROW(program.parse_args({"test"})); - - auto inputs = program.get>("input"); - REQUIRE(inputs.size() == 0); + REQUIRE_THROWS_AS(program.get>("input"), + std::logic_error); } } From 0195a5065c71cbd29c28f3f26f8a0ebb247c9ee9 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Tue, 21 Jun 2022 06:45:26 +0900 Subject: [PATCH 12/22] Complete "remainig" backward compatibility --- include/argparse/argparse.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index b4d6c2b..bb9474f 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -836,7 +836,8 @@ private: return std::any_cast(mDefaultValue); } else { if constexpr (details::is_container_v) - return any_cast_container(mValues); + if (!mAcceptsOptionalLikeValue) + return any_cast_container(mValues); } throw std::logic_error("No value provided for '" + mNames.back() + "'."); } From 12fcae66a761eb6570f376c81b9fd2bf8e0807f5 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Tue, 21 Jun 2022 06:48:13 +0900 Subject: [PATCH 13/22] Prefer pre-const to post-const --- test/test_actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_actions.cpp b/test/test_actions.cpp index dbc9d5b..c434cad 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -140,7 +140,7 @@ TEST_CASE("Users can use actions on remaining arguments" * std::string result = ""; program.add_argument("all").remaining().action( - [](std::string &sum, std::string const &value) { sum += value; }, + [](std::string &sum, const std::string &value) { sum += value; }, std::ref(result)); program.parse_args({"concat", "a", "-b", "-c", "--d"}); From 3459eec6473a9a10f19390447188903cefefd5ce Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Tue, 21 Jun 2022 07:14:09 +0900 Subject: [PATCH 14/22] Make throw_* funcs and make validate() clearer --- include/argparse/argparse.hpp | 64 ++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index bb9474f..9954245 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -553,46 +553,19 @@ public: if (mIsOptional) { if (mIsUsed && !mNumArgsRange.contains(mValues.size()) && !mIsRepeatable && !mDefaultValue.has_value()) { - std::stringstream stream; - stream << mUsedName << ": expected "; - if (mNumArgsRange.is_exact()) { - stream << mNumArgsRange.get_min(); - } else if (mNumArgsRange.is_right_bounded()) { - stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); - } else { - stream << mNumArgsRange.get_min() << " or more"; - } - stream << " argument(s). " - << mValues.size() << " provided."; - throw std::runtime_error(stream.str()); + throw_nargs_range_validation_error(); } else { // TODO: check if an implicit value was programmed for this argument if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) { - std::stringstream stream; - stream << mNames[0] << ": required."; - throw std::runtime_error(stream.str()); + throw_required_arg_not_used_error(); } if (mIsUsed && mIsRequired && mValues.size() == 0) { - std::stringstream stream; - stream << mUsedName << ": no value provided."; - throw std::runtime_error(stream.str()); + throw_required_arg_no_value_provided_error(); } } } else { if (!mNumArgsRange.contains(mValues.size()) && !mDefaultValue.has_value()) { - std::stringstream stream; - if (!mUsedName.empty()) - stream << mUsedName << ": "; - if (mNumArgsRange.is_exact()) { - stream << mNumArgsRange.get_min(); - } else if (mNumArgsRange.is_right_bounded()) { - stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); - } else { - stream << mNumArgsRange.get_min() << " or more"; - } - stream << " argument(s) expected. " << mValues.size() - << " provided."; - throw std::runtime_error(stream.str()); + throw_nargs_range_validation_error(); } } } @@ -646,6 +619,35 @@ public: } private: + + void throw_nargs_range_validation_error() const { + std::stringstream stream; + if (!mUsedName.empty()) + stream << mUsedName << ": "; + if (mNumArgsRange.is_exact()) { + stream << mNumArgsRange.get_min(); + } else if (mNumArgsRange.is_right_bounded()) { + stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max(); + } else { + stream << mNumArgsRange.get_min() << " or more"; + } + stream << " argument(s) expected. " + << mValues.size() << " provided."; + throw std::runtime_error(stream.str()); + } + + void throw_required_arg_not_used_error() const { + std::stringstream stream; + stream << mNames[0] << ": required."; + throw std::runtime_error(stream.str()); + } + + void throw_required_arg_no_value_provided_error() const { + std::stringstream stream; + stream << mUsedName << ": no value provided."; + throw std::runtime_error(stream.str()); + } + static constexpr int eof = std::char_traits::eof(); static auto lookahead(std::string_view s) -> int { From b869b5a209f6f2963988fbcd5a7ec62ddd60d573 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 07:07:59 +0900 Subject: [PATCH 15/22] NArgsPattern -> nargs_pattern (to snake case) --- include/argparse/argparse.hpp | 20 ++++++++++---------- test/test_actions.cpp | 2 +- test/test_optional_arguments.cpp | 8 ++++---- test/test_positional_arguments.cpp | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index f248fb6..5c21bcb 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -359,10 +359,10 @@ public: } }; -enum class NArgsPattern { - ZeroOrOne, - Any, - AtLeastOne +enum class nargs_pattern { + zero_or_one, + any, + at_least_one }; enum class default_arguments : unsigned int { @@ -505,15 +505,15 @@ public: return *this; } - Argument &nargs(NArgsPattern num_args_pattern) { - switch (num_args_pattern) { - case NArgsPattern::ZeroOrOne: + Argument &nargs(nargs_pattern pattern) { + switch (pattern) { + case nargs_pattern::zero_or_one: m_num_args_range = SizeRange{0, 1}; break; - case NArgsPattern::Any: + case nargs_pattern::any: m_num_args_range = SizeRange{0, std::numeric_limits::max()}; break; - case NArgsPattern::AtLeastOne: + case nargs_pattern::at_least_one: m_num_args_range = SizeRange{1, std::numeric_limits::max()}; break; } @@ -522,7 +522,7 @@ public: Argument &remaining() { m_accepts_optional_like_value = true; - return nargs(NArgsPattern::Any); + return nargs(nargs_pattern::any); } template diff --git a/test/test_actions.cpp b/test/test_actions.cpp index 95f8337..5b0ef76 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -126,7 +126,7 @@ TEST_CASE("Users can use actions on nargs=ANY arguments" * argparse::ArgumentParser program("sum"); int result = 0; - program.add_argument("all").nargs(argparse::NArgsPattern::Any).action( + program.add_argument("all").nargs(argparse::nargs_pattern::any).action( [](int &sum, std::string const &value) { sum += std::stoi(value); }, std::ref(result)); diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index e0961a6..f0bbf39 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -114,8 +114,8 @@ TEST_CASE("Parse 2 optional arguments of many values" * test_suite("optional_arguments")) { GIVEN("a program that accepts 2 optional arguments of many values") { argparse::ArgumentParser program("test"); - program.add_argument("-i").nargs(argparse::NArgsPattern::Any).scan<'i', int>(); - program.add_argument("-s").nargs(argparse::NArgsPattern::Any); + program.add_argument("-i").nargs(argparse::nargs_pattern::any).scan<'i', int>(); + program.add_argument("-s").nargs(argparse::nargs_pattern::any); WHEN("provided no argument") { THEN("the program accepts it and gets empty container") { @@ -156,8 +156,8 @@ TEST_CASE("Parse an optional argument of many values" GIVEN("a program that accepts an optional argument of many values" " and a positional argument of many values") { argparse::ArgumentParser program("test"); - program.add_argument("-s").nargs(argparse::NArgsPattern::Any); - program.add_argument("input").nargs(argparse::NArgsPattern::Any); + program.add_argument("-s").nargs(argparse::nargs_pattern::any); + program.add_argument("input").nargs(argparse::nargs_pattern::any); WHEN("provided no argument") { program.parse_args({"test"}); diff --git a/test/test_positional_arguments.cpp b/test/test_positional_arguments.cpp index c3c5b93..459f5dc 100644 --- a/test/test_positional_arguments.cpp +++ b/test/test_positional_arguments.cpp @@ -142,7 +142,7 @@ TEST_CASE("Parse positional nargs=ANY arguments" * GIVEN("a program that accepts an optional argument and nargs=ANY positional arguments") { argparse::ArgumentParser program("test"); program.add_argument("-o"); - program.add_argument("input").nargs(argparse::NArgsPattern::Any); + program.add_argument("input").nargs(argparse::nargs_pattern::any); WHEN("provided no argument") { THEN("the program accepts it and gets empty container") { From 5d6544a04e8bf47d739f7088c95e1f84cf149c16 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 07:11:52 +0900 Subject: [PATCH 16/22] Retrieve changes on 37a1f3b9e6ddb27ad70fb3b52c83266066949488 --- include/argparse/argparse.hpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 5c21bcb..6b77ad2 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -584,17 +584,12 @@ public: */ void validate() const { if (m_is_optional) { - if (m_is_used && !m_num_args_range.contains(m_values.size()) && !m_is_repeatable && - !m_default_value.has_value()) { - throw_nargs_range_validation_error(); - } else { - // TODO: check if an implicit value was programmed for this argument - if (!m_is_used && !m_default_value.has_value() && m_is_required) { - throw_required_arg_not_used_error(); - } - if (m_is_used && m_is_required && m_values.size() == 0) { - throw_required_arg_no_value_provided_error(); - } + // TODO: check if an implicit value was programmed for this argument + if (!m_is_used && !m_default_value.has_value() && m_is_required) { + throw_required_arg_not_used_error(); + } + if (m_is_used && m_is_required && m_values.size() == 0) { + throw_required_arg_no_value_provided_error(); } } else { if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) { From df6e7de86ac6378c8a90f47d942b5ef578f0ab83 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 07:15:50 +0900 Subject: [PATCH 17/22] Prefer empty() to size() == 0 --- include/argparse/argparse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 6b77ad2..c5ad384 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -588,7 +588,7 @@ public: if (!m_is_used && !m_default_value.has_value() && m_is_required) { throw_required_arg_not_used_error(); } - if (m_is_used && m_is_required && m_values.size() == 0) { + if (m_is_used && m_is_required && m_values.empty()) { throw_required_arg_no_value_provided_error(); } } else { From 7b5084c4548197e4cb07f851232e20367e1fc711 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 07:16:41 +0900 Subject: [PATCH 18/22] Fix a typo --- test/test_optional_arguments.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional_arguments.cpp b/test/test_optional_arguments.cpp index f0bbf39..5f90984 100644 --- a/test/test_optional_arguments.cpp +++ b/test/test_optional_arguments.cpp @@ -88,7 +88,7 @@ TEST_CASE("Parse optional arguments of many values" * program.add_argument("-i").remaining().scan<'i', int>(); WHEN("provided no argument") { - THEN("the program accepts it bug gets nothing") { + THEN("the program accepts it but gets nothing") { REQUIRE_NOTHROW(program.parse_args({"test"})); REQUIRE_THROWS_AS(program.get>("-i"), std::logic_error); From e44023f424c16a0136238624222b2e9a0b33b9b6 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 07:55:48 +0900 Subject: [PATCH 19/22] Explain variable-length arguments in README --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index a318b00..e63a814 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,29 @@ catch (const std::runtime_error& err) { auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2} ``` +You can also make a variable length list of arguments with the ```.nargs```. +Below are some examples. + +```cpp +program.add_argument("--input_files") + .nargs(1, 3); // This accepts 1 to 3 arguments. +``` + +Some useful patterns are defined like "?", "*", "+" of argparse in Python. + +```cpp +program.add_argument("--input_files") + .nargs(argparse::nargs_pattern::any); // "*" in Python. This accepts any number of arguments including 0. +``` +```cpp +program.add_argument("--input_files") + .nargs(argparse::nargs_pattern::at_least_one); // "+" in Python. This accepts one or more number of arguments. +``` +```cpp +program.add_argument("--input_files") + .nargs(argparse::nargs_pattern::zero_or_one); // "?" in Python. This accepts an argument optionally. +``` + ### Compound Arguments Compound arguments are optional arguments that are combined and provided as a single argument. Example: ```ps -aux``` From acff046fc58c3e9c7a3d07fdac669b29a2247cbd Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 09:24:51 +0900 Subject: [PATCH 20/22] Use optional instead of zero_or_one --- README.md | 2 +- include/argparse/argparse.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e63a814..c572264 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ program.add_argument("--input_files") ``` ```cpp program.add_argument("--input_files") - .nargs(argparse::nargs_pattern::zero_or_one); // "?" in Python. This accepts an argument optionally. + .nargs(argparse::nargs_pattern::optional); // "?" in Python. This accepts an argument optionally. ``` ### Compound Arguments diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index c5ad384..7e8f35a 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -360,7 +360,7 @@ public: }; enum class nargs_pattern { - zero_or_one, + optional, any, at_least_one }; @@ -507,7 +507,7 @@ public: Argument &nargs(nargs_pattern pattern) { switch (pattern) { - case nargs_pattern::zero_or_one: + case nargs_pattern::optional: m_num_args_range = SizeRange{0, 1}; break; case nargs_pattern::any: From 25d24c731bbef3b70bc48982d1b1b555df4c4289 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 09:26:43 +0900 Subject: [PATCH 21/22] SizeRange -> NArgsRange --- include/argparse/argparse.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 7e8f35a..b65b540 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -328,12 +328,12 @@ template struct parse_number { } // namespace details -class SizeRange { +class NArgsRange { std::size_t m_min; std::size_t m_max; public: - SizeRange(std::size_t minimum, std::size_t maximum) : m_min(minimum), m_max(maximum) { + NArgsRange(std::size_t minimum, std::size_t maximum) : m_min(minimum), m_max(maximum) { if (minimum > maximum) throw std::logic_error("Range of number of arguments is invalid"); } @@ -421,7 +421,7 @@ public: Argument &implicit_value(std::any value) { m_implicit_value = std::move(value); - m_num_args_range = SizeRange{0, 0}; + m_num_args_range = NArgsRange{0, 0}; return *this; } @@ -491,16 +491,16 @@ public: } Argument &nargs(std::size_t num_args) { - m_num_args_range = SizeRange{num_args, num_args}; + m_num_args_range = NArgsRange{num_args, num_args}; return *this; } Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { - m_num_args_range = SizeRange{num_args_min, num_args_max}; + m_num_args_range = NArgsRange{num_args_min, num_args_max}; return *this; } - Argument &nargs(SizeRange num_args_range) { + Argument &nargs(NArgsRange num_args_range) { m_num_args_range = num_args_range; return *this; } @@ -508,13 +508,13 @@ public: Argument &nargs(nargs_pattern pattern) { switch (pattern) { case nargs_pattern::optional: - m_num_args_range = SizeRange{0, 1}; + m_num_args_range = NArgsRange{0, 1}; break; case nargs_pattern::any: - m_num_args_range = SizeRange{0, std::numeric_limits::max()}; + m_num_args_range = NArgsRange{0, std::numeric_limits::max()}; break; case nargs_pattern::at_least_one: - m_num_args_range = SizeRange{1, std::numeric_limits::max()}; + m_num_args_range = NArgsRange{1, std::numeric_limits::max()}; break; } return *this; @@ -916,7 +916,7 @@ private: std::in_place_type, [](const std::string &value) { return value; }}; std::vector m_values; - SizeRange m_num_args_range {1, 1}; + NArgsRange m_num_args_range {1, 1}; bool m_accepts_optional_like_value = false; bool m_is_optional : true; bool m_is_required : true; From ed84d90d897ab4b91fa43f2e23d6a5b67e8bf241 Mon Sep 17 00:00:00 2001 From: Yoshihiro Hokazono Date: Wed, 22 Jun 2022 09:30:36 +0900 Subject: [PATCH 22/22] Move NArgsRange to private: because it is detail of implementation --- include/argparse/argparse.hpp | 67 ++++++++++++++++------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index b65b540..c9a256e 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -328,37 +328,6 @@ template struct parse_number { } // namespace details -class NArgsRange { - std::size_t m_min; - std::size_t m_max; - -public: - NArgsRange(std::size_t minimum, std::size_t maximum) : m_min(minimum), m_max(maximum) { - if (minimum > maximum) - throw std::logic_error("Range of number of arguments is invalid"); - } - - bool contains(std::size_t value) const { - return value >= m_min && value <= m_max; - } - - bool is_exact() const { - return m_min == m_max; - } - - bool is_right_bounded() const { - return m_max < std::numeric_limits::max(); - } - - std::size_t get_min() const { - return m_min; - } - - std::size_t get_max() const { - return m_max; - } -}; - enum class nargs_pattern { optional, any, @@ -500,11 +469,6 @@ public: return *this; } - Argument &nargs(NArgsRange num_args_range) { - m_num_args_range = num_args_range; - return *this; - } - Argument &nargs(nargs_pattern pattern) { switch (pattern) { case nargs_pattern::optional: @@ -650,6 +614,37 @@ public: private: + class NArgsRange { + std::size_t m_min; + std::size_t m_max; + + public: + NArgsRange(std::size_t minimum, std::size_t maximum) : m_min(minimum), m_max(maximum) { + if (minimum > maximum) + throw std::logic_error("Range of number of arguments is invalid"); + } + + bool contains(std::size_t value) const { + return value >= m_min && value <= m_max; + } + + bool is_exact() const { + return m_min == m_max; + } + + bool is_right_bounded() const { + return m_max < std::numeric_limits::max(); + } + + std::size_t get_min() const { + return m_min; + } + + std::size_t get_max() const { + return m_max; + } + }; + void throw_nargs_range_validation_error() const { std::stringstream stream; if (!m_used_name.empty())