From 8dead89026466b3818e9c6b6b1d938600db39d8f Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 9 May 2024 09:46:55 +0200 Subject: [PATCH] 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. --- include/argparse/argparse.hpp | 25 +++++++++++++------ test/test_actions.cpp | 25 +++++++++++++++++++ test/test_scan.cpp | 47 +++++++++++++++++++++++++++++++++++ test/test_store_into.cpp | 35 ++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 7 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 921a4b41..4767b9e1 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -678,9 +678,9 @@ class Argument { std::is_void_v>, void_action, valued_action>; if constexpr (sizeof...(Args) == 0) { - m_action.emplace(std::forward(callable)); + m_actions.emplace_back(std::forward(callable)); } else { - m_action.emplace( + m_actions.emplace_back( [f = std::forward(callable), tup = std::make_tuple(std::forward(bound_args)...)]( std::string const &opt) mutable { @@ -980,7 +980,12 @@ class Argument { if (num_args_max == 0) { if (!dry_run) { 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; } return start; @@ -1020,7 +1025,12 @@ class Argument { Argument &self; }; 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; } return end; @@ -1570,9 +1580,10 @@ class Argument { std::optional> m_choices{std::nullopt}; using valued_action = std::function; using void_action = std::function; - std::variant m_action{ - std::in_place_type, - [](const std::string &value) { return value; }}; + std::vector> m_actions; + std::variant m_default_action{ + std::in_place_type, + [](const std::string &value) { return value; }}; std::vector m_values; NArgsRange m_num_args_range{1, 1}; // Bit field of bool values. Set default value in ctor. diff --git a/test/test_actions.cpp b/test/test_actions.cpp index aa8cd3de..01c399f8 100644 --- a/test/test_actions.cpp +++ b/test/test_actions.cpp @@ -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("-V")); + REQUIRE(count == 2); + } + } + } +} diff --git a/test/test_scan.cpp b/test/test_scan.cpp index c556cb90..36d00123 100644 --- a/test/test_scan.cpp +++ b/test/test_scan.cpp @@ -426,3 +426,50 @@ TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" * 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); + } + } + } + +} diff --git a/test/test_store_into.cpp b/test/test_store_into.cpp index 9717fab8..5fe575fc 100644 --- a/test/test_store_into.cpp +++ b/test/test_store_into.cpp @@ -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"); + } + } + } +} +