From e6c6c9b31ce2b97de522e402ec631c736eebd504 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Sat, 30 Nov 2019 00:34:56 -0600 Subject: [PATCH] Get arguments in optional with .present() fixes: p-ranav/argparse#66 --- README.md | 12 +++++++++ include/argparse.hpp | 34 +++++++++++++++++++++--- test/test_parse_args.cpp | 57 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc29600..14d6654 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,18 @@ program.add_argument("-o", "--output") .help("specify the output file."); ``` +#### Accessing optional arguments without default values + +If you do not require an optional argument to present but has no good default value for it, you can combine testing and accessing the argument as following: + +```cpp +if (auto fn = program.present("-o")) { + do_something_with(*fn); +} +``` + +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`. + ### Negative Numbers Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes! diff --git a/include/argparse.hpp b/include/argparse.hpp index 32ab73a..fa10a06 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -702,6 +702,23 @@ private: throw std::logic_error("No value provided"); } + /* + * Get argument value given a type. + * @pre The object has no default value. + * @returns The stored value if any, std::nullopt otherwise. + */ + template auto present() const -> std::optional { + if (mDefaultValue.has_value()) + throw std::logic_error("Argument with default value always presents"); + + if (mValues.empty()) + return std::nullopt; + else if constexpr (details::is_container_v) + return any_cast_container(mValues); + else + return std::any_cast(mValues.front()); + } + template static auto any_cast_container(const std::vector &aOperand) -> T { using ValueType = typename T::value_type; @@ -811,14 +828,25 @@ public: parse_args(arguments); } - /* Getter enabled for all template types other than std::vector and std::list - * @throws std::logic_error in case of an invalid argument name - * @throws std::logic_error in case of incompatible types + /* Getter for options with default values. + * @throws std::logic_error if there is no such option + * @throws std::logic_error if the option has no value + * @throws std::bad_any_cast if the option is not of type T */ template T get(std::string_view aArgumentName) { return (*this)[aArgumentName].get(); } + /* Getter for options without default values. + * @pre The option has no default value. + * @throws std::logic_error if there is no such option + * @throws std::bad_any_cast if the option is not of type T + */ + template + auto present(std::string_view aArgumentName) -> std::optional { + return (*this)[aArgumentName].present(); + } + /* Indexing operator. Return a reference to an Argument object * Used in conjuction with Argument.operator== e.g., parser["foo"] == true * @throws std::logic_error in case of an invalid argument name diff --git a/test/test_parse_args.cpp b/test/test_parse_args.cpp index 40449f6..c0a658a 100644 --- a/test/test_parse_args.cpp +++ b/test/test_parse_args.cpp @@ -19,6 +19,32 @@ TEST_CASE("Parse a string argument with default value" * REQUIRE(program.get("--config") == "foo.yml"); } +TEST_CASE("Parse a string argument without default value" * + test_suite("parse_args")) { + argparse::ArgumentParser program("test"); + program.add_argument("--config"); + + WHEN("no value provided") { + program.parse_args({"test"}); + + THEN("the option is nullopt") { + auto opt = program.present("--config"); + REQUIRE_FALSE(opt); + REQUIRE(opt == std::nullopt); + } + } + + WHEN("a value is provided") { + program.parse_args({"test", "--config", ""}); + + THEN("the option has a value") { + auto opt = program.present("--config"); + REQUIRE(opt); + REQUIRE(opt->empty()); + } + } +} + TEST_CASE("Parse an int argument with value" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--count") @@ -103,6 +129,37 @@ TEST_CASE("Parse a vector of float arguments" * test_suite("parse_args")) { REQUIRE(vector[4] == 5.5f); } +TEST_CASE("Parse a vector of float without default value" * + test_suite("parse_args")) { + argparse::ArgumentParser program("test"); + program.add_argument("--vector").scan<'f', float>().nargs(3); + + WHEN("no value is provided") { + program.parse_args({"test"}); + + THEN("the option is nullopt") { + auto opt = program.present>("--vector"); + REQUIRE_FALSE(opt.has_value()); + REQUIRE(opt == std::nullopt); + } + } + + WHEN("a value is provided") { + program.parse_args({"test", "--vector", ".3", "1.3", "6"}); + + THEN("the option has a value") { + auto opt = program.present>("--vector"); + REQUIRE(opt.has_value()); + + auto &&vec = opt.value(); + REQUIRE(vec.size() == 3); + REQUIRE(vec[0] == .3f); + REQUIRE(vec[1] == 1.3f); + REQUIRE(vec[2] == 6.f); + } + } +} + TEST_CASE("Parse a vector of double arguments" * test_suite("parse_args")) { argparse::ArgumentParser program("test"); program.add_argument("--vector")