diff --git a/README.md b/README.md index c85db739..67061aa4 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,30 @@ if (auto fn = program.present("-o")) { Similar to `get`, the `present` method also accepts a template argument. But rather than returning `T`, `parser.present(key)` returns `std::optional`, so that when the user does not provide a value to this parameter, the return value compares equal to `std::nullopt`. +#### Joining values of repeated optional arguments + +You may want to allow an optional argument to be repeated and gather all values in one place. + +```cpp +program.add_argument("--color") + .default_value>({ "orange" }) + .append() + .help("specify the cat's fur color"); + +try { + program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue +} +catch (const std::runtime_error& err) { + std::cout << err.what() << std::endl; + std::cout << program; + exit(0); +} + +auto colors = program.get>("--color"); // {"red", "green", "blue"} +``` + +Notice that ```.default_value``` is given an explicit template parameter to match the type you want to ```.get```. + ### Negative Numbers Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes! diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 38c7d922..ba137926 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -322,7 +322,7 @@ class Argument { template explicit Argument(std::string_view(&&a)[N], std::index_sequence) : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false), - mIsUsed(false) { + mIsRepeatable(false), mIsUsed(false) { ((void)mNames.emplace_back(a[I]), ...); std::sort( mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) { @@ -376,6 +376,11 @@ class Argument { return *this; } + auto &append() { + mIsRepeatable = true; + return *this; + } + template auto scan() -> std::enable_if_t, Argument &> { static_assert(!(std::is_const_v || std::is_volatile_v), @@ -430,7 +435,7 @@ class Argument { template Iterator consume(Iterator start, Iterator end, std::string_view usedName = {}) { - if (mIsUsed) { + if (!mIsRepeatable && mIsUsed) { throw std::runtime_error("Duplicate argument"); } mIsUsed = true; @@ -477,7 +482,7 @@ class Argument { void validate() const { if (auto expected = maybe_nargs()) { if (mIsOptional) { - if (mIsUsed && mValues.size() != *expected && + if (mIsUsed && mValues.size() != *expected && !mIsRepeatable && !mDefaultValue.has_value()) { std::stringstream stream; stream << mUsedName << ": expected " << *expected << " argument(s). " @@ -800,6 +805,7 @@ class Argument { int mNumArgs = 1; bool mIsOptional : true; bool mIsRequired : true; + bool mIsRepeatable : true; bool mIsUsed : true; // True if the optional argument is used by user }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d3d23010..f090973c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ endif() file(GLOB ARGPARSE_TEST_SOURCES main.cpp test_actions.cpp + test_append.cpp test_compound_arguments.cpp test_container_arguments.cpp test_help.cpp diff --git a/test/test_append.cpp b/test/test_append.cpp new file mode 100644 index 00000000..bd868ef9 --- /dev/null +++ b/test/test_append.cpp @@ -0,0 +1,46 @@ +#include +#include + +using doctest::test_suite; + +TEST_CASE("Simplest .append" * test_suite("append")) { + argparse::ArgumentParser program("test"); + program.add_argument("--dir") + .append(); + program.parse_args({ "test", "--dir", "./Docs" }); + std::string result { program.get("--dir") }; + REQUIRE(result == "./Docs"); +} + +TEST_CASE("Two parameter .append" * test_suite("append")) { + argparse::ArgumentParser program("test"); + program.add_argument("--dir") + .append(); + program.parse_args({ "test", "--dir", "./Docs", "--dir", "./Src" }); + auto result { program.get>("--dir") }; + REQUIRE(result.at(0) == "./Docs"); + REQUIRE(result.at(1) == "./Src"); +} + +TEST_CASE("Two int .append" * test_suite("append")) { + argparse::ArgumentParser program("test"); + program.add_argument("--factor") + .append() + .action([](auto s) { return stoi(s); }); + program.parse_args({ "test", "--factor", "2", "--factor", "5" }); + auto result { program.get>("--factor") }; + REQUIRE(result.at(0) == 2); + REQUIRE(result.at(1) == 5); +} + +TEST_CASE("Default value with .append" * test_suite("append")) { + std::vector expected { "./Src", "./Imgs" }; + + argparse::ArgumentParser program("test"); + program.add_argument("--dir") + .default_value(expected) + .append(); + program.parse_args({ "test" }); + auto result { program.get>("--dir") }; + REQUIRE(result == expected); +}