FEATURE: multiple actions

Also fixes the incompatibility between store_into and scan and action:
when the three methods above were called, being all based on the
(unique) action, the last one would overwrite the previous ones.

This issue was making the parser strictly dependant on the order
of the scan/store_into/action calls making them mutually exclusive.
This commit is contained in:
Alessandro Pasotti 2024-05-09 09:46:55 +02:00
parent eba16b3773
commit 8dead89026
4 changed files with 125 additions and 7 deletions

View File

@ -678,9 +678,9 @@ public:
std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>, std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>,
void_action, valued_action>; void_action, valued_action>;
if constexpr (sizeof...(Args) == 0) { if constexpr (sizeof...(Args) == 0) {
m_action.emplace<action_type>(std::forward<F>(callable)); m_actions.emplace_back<action_type>(std::forward<F>(callable));
} else { } else {
m_action.emplace<action_type>( m_actions.emplace_back<action_type>(
[f = std::forward<F>(callable), [f = std::forward<F>(callable),
tup = std::make_tuple(std::forward<Args>(bound_args)...)]( tup = std::make_tuple(std::forward<Args>(bound_args)...)](
std::string const &opt) mutable { std::string const &opt) mutable {
@ -980,7 +980,12 @@ public:
if (num_args_max == 0) { if (num_args_max == 0) {
if (!dry_run) { if (!dry_run) {
m_values.emplace_back(m_implicit_value); m_values.emplace_back(m_implicit_value);
std::visit([](const auto &f) { f({}); }, m_action); for(auto &action: m_actions) {
std::visit([&](const auto &f) { f({}); }, action);
}
if(m_actions.empty()){
std::visit([&](const auto &f) { f({}); }, m_default_action);
}
m_is_used = true; m_is_used = true;
} }
return start; return start;
@ -1020,7 +1025,12 @@ public:
Argument &self; Argument &self;
}; };
if (!dry_run) { if (!dry_run) {
std::visit(ActionApply{start, end, *this}, m_action); for(auto &action: m_actions) {
std::visit(ActionApply{start, end, *this}, action);
}
if(m_actions.empty()){
std::visit(ActionApply{start, end, *this}, m_default_action);
}
m_is_used = true; m_is_used = true;
} }
return end; return end;
@ -1570,9 +1580,10 @@ private:
std::optional<std::vector<std::string>> m_choices{std::nullopt}; std::optional<std::vector<std::string>> m_choices{std::nullopt};
using valued_action = std::function<std::any(const std::string &)>; using valued_action = std::function<std::any(const std::string &)>;
using void_action = std::function<void(const std::string &)>; using void_action = std::function<void(const std::string &)>;
std::variant<valued_action, void_action> m_action{ std::vector<std::variant<valued_action, void_action>> m_actions;
std::in_place_type<valued_action>, std::variant<valued_action, void_action> m_default_action{
[](const std::string &value) { return value; }}; std::in_place_type<valued_action>,
[](const std::string &value) { return value; }};
std::vector<std::any> m_values; std::vector<std::any> m_values;
NArgsRange m_num_args_range{1, 1}; NArgsRange m_num_args_range{1, 1};
// Bit field of bool values. Set default value in ctor. // Bit field of bool values. Set default value in ctor.

View File

@ -175,3 +175,28 @@ TEST_CASE("Users can run actions on parameterless optional arguments" *
} }
} }
} }
TEST_CASE("Users can add multiple actions and they are all run" *
test_suite("actions")) {
argparse::ArgumentParser program("test");
GIVEN("a flag argument with two counting actions") {
int count = 0;
program.add_argument("-V", "--verbose")
.action([&](const auto &) { ++count; })
.action([&](const auto &) { ++count; })
.append()
.default_value(false)
.implicit_value(true)
.nargs(0);
WHEN("the flag is parsed") {
program.parse_args({"test", "-V"});
THEN("the count increments twice") {
REQUIRE(program.get<bool>("-V"));
REQUIRE(count == 2);
}
}
}
}

View File

@ -426,3 +426,50 @@ TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" *
std::invalid_argument); std::invalid_argument);
} }
} }
TEST_CASE("Test that scan also works with a custom action" *
test_suite("scan")) {
GIVEN("an argument with scan followed by a custom action") {
argparse::ArgumentParser program("test");
int res;
program.add_argument("--int").scan<'i', int>().action([&](const auto &s) {res = std::stoi(s);});
WHEN("the argument is parsed") {
SUBCASE("with a valid value") {
program.parse_args({"./test.exe", "--int", "3"});
THEN("the value is stored") {
REQUIRE(res == 3);
}
}
SUBCASE("with an invalid value") {
REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}),
std::invalid_argument);
}
}
}
GIVEN("an argument with a custom action followed by scan") {
argparse::ArgumentParser program("test");
int res;
program.add_argument("--int").action([&](const auto &s) {res = std::stoi(s);}).scan<'i', int>();
WHEN("the argument is parsed") {
SUBCASE("with a valid value") {
program.parse_args({"./test.exe", "--int", "3"});
THEN("the value is stored") {
REQUIRE(res == 3);
}
}
SUBCASE("with an invalid value") {
REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}),
std::invalid_argument);
}
}
}
}

View File

@ -286,3 +286,38 @@ TEST_CASE("Test store_into(set of string), default value, multi valued, specifie
} }
} }
TEST_CASE("Test store_into(int) still works with a custom action" *
test_suite("store_into")) {
GIVEN("an argument with store_into followed by a custom action ") {
argparse::ArgumentParser program("test");
int res;
std::string string_res;
program.add_argument("--int").store_into(res).action([&](const auto &s) {string_res.append(s);});
WHEN("the argument is parsed") {
program.parse_args({"./test.exe", "--int", "3"});
THEN("the value is stored and the action was executed") {
REQUIRE(res == 3);
REQUIRE(string_res == "3");
}
}
}
GIVEN("an argument with a custom action followed by store_into")
{
argparse::ArgumentParser program("test");
int res;
std::string string_res;
program.add_argument("--int").action([&](const auto &s) {string_res.append(s);}).store_into(res);
WHEN("the argument is parsed") {
program.parse_args({"./test.exe", "--int", "3"});
THEN("the value is stored and the action was executed") {
REQUIRE(res == 3);
REQUIRE(string_res == "3");
}
}
}
}