From 8784cc8ddf43a1f5fca1225189f49c815b898aa4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 12 Mar 2024 12:45:09 +0100 Subject: [PATCH] Add Argument::store_into() functions It is possible to bind arguments to a variable storing their value, as an alternative to explicitly calling ``program.get(arg_name)`` or ``program[arg_name]`` This is currently implementeted for variables of type ``bool`` (this also implicitly calls ``flag()``), ``int``, ``double``, ``std::string`` and ``std::vector``. If the argument is not specified in the command line, the default value (if set) is set into the variable. ```cpp bool flagvar = false; program.add_argument("--flagvar").store_into(flagvar); int intvar = 0; program.add_argument("--intvar").store_into(intvar); double doublevar = 0; program.add_argument("--doublevar").store_into(doublevar); std::string strvar; program.add_argument("--strvar").store_into(strvar); std::vector strvar_repeated; program.add_argument("--strvar-repeated").append().store_into(strvar_repeated); std::vector strvar_multi_valued; program.add_argument("--strvar-multi-valued").nargs(2).store_into(strvar_multi_valued); ``` --- README.md | 31 +++++++ include/argparse/argparse.hpp | 55 +++++++++++- test/CMakeLists.txt | 1 + test/test_store_into.cpp | 161 ++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 test/test_store_into.cpp diff --git a/README.md b/README.md index 2fbb746a..46e716c0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ * [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments) * [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value) * [Mutually Exclusive Group](#mutually-exclusive-group) + * [Storing values into variables](#store-into) * [Negative Numbers](#negative-numbers) * [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments) * [Printing Help](#printing-help) @@ -315,6 +316,36 @@ foo@bar:/home/dev/$ ./main One of the arguments '--first VAR' or '--second VAR' is required ``` +### Storing values into variables + +It is possible to bind arguments to a variable storing their value, as an +alternative to explicitly calling ``program.get(arg_name)`` or ``program[arg_name]`` + +This is currently implementeted for variables of type ``bool`` (this also +implicitly calls ``flag()``), ``int``, ``double``, ``std::string`` and +``std::vector``. If the argument is not specified in the command +line, the default value (if set) is set into the variable. + +```cpp +bool flagvar = false; +program.add_argument("--flagvar").store_into(flagvar); + +int intvar = 0; +program.add_argument("--intvar").store_into(intvar); + +double doublevar = 0; +program.add_argument("--doublevar").store_into(doublevar); + +std::string strvar; +program.add_argument("--strvar").store_into(strvar); + +std::vector strvar_repeated; +program.add_argument("--strvar-repeated").append().store_into(strvar_repeated); + +std::vector strvar_multi_valued; +program.add_argument("--strvar-multi-valued").nargs(2).store_into(strvar_multi_valued); +``` + ### 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 c93c876d..13aac435 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -689,6 +689,57 @@ class Argument { return *this; } + auto &store_into(bool &var) { + flag(); + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto & /*unused*/) { var = true; }); + return *this; + } + + auto &store_into(int &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto &s) { + var = details::parse_number()(s); + }); + return *this; + } + + auto &store_into(double &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const auto &s) { + var = details::parse_number()(s); + }); + return *this; + } + + auto &store_into(std::string &var) { + if (m_default_value.has_value()) { + var = std::any_cast(m_default_value); + } + action([&var](const std::string &s) { var = s; }); + return *this; + } + + auto &store_into(std::vector &var) { + if (m_default_value.has_value()) { + var = std::any_cast>(m_default_value); + } + action([this, &var](const std::string &s) { + if (!m_is_used) { + var.clear(); + } + m_is_used = true; + var.push_back(s); + }); + return *this; + } + auto &append() { m_is_repeatable = true; return *this; @@ -852,7 +903,6 @@ class Argument { if (!m_is_repeatable && m_is_used) { throw std::runtime_error("Duplicate argument"); } - m_is_used = true; m_used_name = used_name; if (m_choices.has_value()) { @@ -875,6 +925,7 @@ class Argument { if (num_args_max == 0) { m_values.emplace_back(m_implicit_value); std::visit([](const auto &f) { f({}); }, m_action); + m_is_used = true; return start; } if ((dist = static_cast(std::distance(start, end))) >= @@ -912,9 +963,11 @@ class Argument { Argument &self; }; std::visit(ActionApply{start, end, *this}, m_action); + m_is_used = true; return end; } if (m_default_value.has_value()) { + m_is_used = true; return start; } throw std::runtime_error("Too few arguments for '" + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 587eb824..f72340cf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -49,6 +49,7 @@ file(GLOB ARGPARSE_TEST_SOURCES test_repr.cpp test_required_arguments.cpp test_scan.cpp + test_store_into.cpp test_stringstream.cpp test_version.cpp test_subparsers.cpp diff --git a/test/test_store_into.cpp b/test/test_store_into.cpp new file mode 100644 index 00000000..4723b842 --- /dev/null +++ b/test/test_store_into.cpp @@ -0,0 +1,161 @@ +#ifdef WITH_MODULE +import argparse; +#else +#include +#endif +#include + +using doctest::test_suite; + +TEST_CASE("Test store_into(bool), flag not specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + bool flag; + program.add_argument("--flag").store_into(flag); + + program.parse_args({"./test.exe"}); + REQUIRE(flag == false); +} + +TEST_CASE("Test store_into(bool), flag specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + bool flag; + program.add_argument("--flag").store_into(flag); + + program.parse_args({"./test.exe", "--flag"}); + REQUIRE(flag == true); +} + +TEST_CASE("Test store_into(int), no default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + int res = -1; + program.add_argument("--int-opt").store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == -1); +} + +TEST_CASE("Test store_into(int), default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + int res = -1; + program.add_argument("--int-opt").default_value(3).store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == 3); +} + +TEST_CASE("Test store_into(int), default value, specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + int res = -1; + program.add_argument("--int-opt").default_value(3).store_into(res); + + program.parse_args({"./test.exe", "--int-opt", "5"}); + REQUIRE(res == 5); +} + +TEST_CASE("Test store_into(double), no default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + double res = -1; + program.add_argument("--double-opt").store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == -1); +} + +TEST_CASE("Test store_into(double), default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + double res = -1; + program.add_argument("--double-opt").default_value(3.5).store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == 3.5); +} + +TEST_CASE("Test store_into(double), default value, specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + double res = -1; + program.add_argument("--double-opt").default_value(3.5).store_into(res); + + program.parse_args({"./test.exe", "--double-opt", "5.5"}); + REQUIRE(res == 5.5); +} + +TEST_CASE("Test store_into(string), no default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::string res = "init"; + program.add_argument("--str-opt").store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == "init"); +} + +TEST_CASE("Test store_into(string), default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::string res; + program.add_argument("--str-opt").default_value("default").store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == "default"); +} + +TEST_CASE("Test store_into(string), default value, specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::string res; + program.add_argument("--str-opt").default_value("default").store_into(res); + + program.parse_args({"./test.exe", "--str-opt", "foo"}); + REQUIRE(res == "foo"); +} + +TEST_CASE("Test store_into(vector of string), no default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::vector res; + program.add_argument("--strvector-opt").append().store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == std::vector{}); +} + +TEST_CASE("Test store_into(vector of string), default value, non specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::vector res; + program.add_argument("--strvector-opt").append().default_value( + std::vector{"a", "b"}).store_into(res); + + program.parse_args({"./test.exe"}); + REQUIRE(res == std::vector{"a", "b"}); +} + +TEST_CASE("Test store_into(vector of string), default value, specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::vector res; + program.add_argument("--strvector-opt").append().default_value( + std::vector{"a", "b"}).store_into(res); + + program.parse_args({"./test.exe", "--strvector-opt", "foo", "--strvector-opt", "bar"}); + REQUIRE(res == std::vector{"foo", "bar"}); +} + +TEST_CASE("Test store_into(vector of string), default value, multi valued, specified" * + test_suite("store_into")) { + argparse::ArgumentParser program("test"); + std::vector res; + program.add_argument("--strvector-opt").nargs(2).default_value( + std::vector{"a", "b"}).store_into(res); + + program.parse_args({"./test.exe", "--strvector-opt", "foo", "bar"}); + REQUIRE(res == std::vector{"foo", "bar"}); +}