Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Argument.append method to allow repeated argument use #99

Merged
merged 1 commit into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(key)` returns `std::optional<T>`, 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<std::vector<std::string>>({ "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<std::vector<std::string>>("--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!
Expand Down
12 changes: 9 additions & 3 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ class Argument {
template <size_t N, size_t... I>
explicit Argument(std::string_view(&&a)[N], std::index_sequence<I...>)
: 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) {
Expand Down Expand Up @@ -376,6 +376,11 @@ class Argument {
return *this;
}

auto &append() {
mIsRepeatable = true;
return *this;
}

template <char Shape, typename T>
auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
Expand Down Expand Up @@ -430,7 +435,7 @@ class Argument {
template <typename Iterator>
Iterator consume(Iterator start, Iterator end,
std::string_view usedName = {}) {
if (mIsUsed) {
if (!mIsRepeatable && mIsUsed) {
throw std::runtime_error("Duplicate argument");
}
mIsUsed = true;
Expand Down Expand Up @@ -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). "
Expand Down Expand Up @@ -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
};

Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions test/test_append.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <doctest.hpp>
#include <argparse/argparse.hpp>

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<std::vector<std::string>>("--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<std::vector<int>>("--factor") };
REQUIRE(result.at(0) == 2);
REQUIRE(result.at(1) == 5);
}

TEST_CASE("Default value with .append" * test_suite("append")) {
std::vector<std::string> expected { "./Src", "./Imgs" };

argparse::ArgumentParser program("test");
program.add_argument("--dir")
.default_value(expected)
.append();
program.parse_args({ "test" });
auto result { program.get<std::vector<std::string>>("--dir") };
REQUIRE(result == expected);
}