diff --git a/include/argparse.hpp b/include/argparse.hpp index addcc3b..f372dd3 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -45,29 +45,40 @@ SOFTWARE. namespace argparse { namespace { // anonymous namespace for helper methods - not visible outside this header file -// Some utility structs to check template specialization -template class Ref> -struct is_specialization : std::false_type {}; -template class Ref, typename... Args> -struct is_specialization, Ref> : std::true_type {}; +template +struct is_container_helper {}; + +template +struct is_container : std::false_type {}; + +template<> +struct is_container : std::false_type {}; + +template +struct is_container().begin()), + decltype(std::declval().end()), + decltype(std::declval().size()) + >, void>> : public std::true_type { +}; + +template +static constexpr bool is_container_v = is_container::value; + +template +using enable_if_container = std::enable_if_t, T>; + +template +using enable_if_not_container = std::enable_if_t, T>; // Check if string (haystack) starts with a substring (needle) bool starts_with(const std::string& haystack, const std::string& needle) { return needle.length() <= haystack.length() && std::equal(needle.begin(), needle.end(), haystack.begin()); } - -// Get value at index from std::list -template -T get_from_list(const std::list& aList, size_t aIndex) { - if (aList.size() > aIndex) { - auto tIterator = aList.begin(); - std::advance(tIterator, aIndex); - return *tIterator; - } - return T(); -} } class Argument { @@ -138,45 +149,31 @@ public: return !(*this == aRhs); } - // Entry point for template types other than std::vector and std::list + /* + * Entry point for template non-container types + * @throws std::logic_error in case of incompatible types + */ template - typename std::enable_if::value && - !is_specialization::value, bool>::type - operator==(const T& aRhs) const { + std::enable_if_t , bool> + operator==(const T& aRhs) const { return get() == aRhs; } - // Template specialization for std::vector<...> + /* + * Template specialization for containers + * @throws std::logic_error in case of incompatible types + */ template - typename std::enable_if::value, bool>::type + std::enable_if_t , bool> operator==(const T& aRhs) const { - T tLhs = get(); + using ValueType = typename T::value_type; + auto tLhs = get(); if (tLhs.size() != aRhs.size()) return false; else { - for (size_t i = 0; i < tLhs.size(); i++) { - auto tValueAtIndex = std::any_cast(tLhs[i]); - if (tValueAtIndex != aRhs[i]) - return false; - } - return true; - } - } - - // Template specialization for std::list<...> - template - typename std::enable_if::value, bool>::type - operator==(const T& aRhs) const { - T tLhs = get(); - if (tLhs.size() != aRhs.size()) - return false; - else { - for (size_t i = 0; i < tLhs.size(); i++) { - auto tValueAtIndex = std::any_cast(get_from_list(tLhs, i)); - if (tValueAtIndex != get_from_list(aRhs, i)) - return false; - } - return true; + return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { + return std::any_cast(lhs) == rhs; + }); } } @@ -186,102 +183,43 @@ public: return (starts_with(aName, "--") || starts_with(aName, "-")); } - // Getter for template types other than std::vector and std::list + /* + * Getter for template non-container types + * @throws std::logic_error in case of incompatible types + */ template - typename std::enable_if::value && - !is_specialization::value, T>::type + enable_if_not_container get() const { - if (mValues.empty()) { - if (mDefaultValue.has_value()) { - return std::any_cast(mDefaultValue); - } - else - return T(); + if (!mValues.empty()) { + return std::any_cast(mValues.front()); } - else { - if (!mRawValues.empty()) - return std::any_cast(mValues[0]); - else { - if (mDefaultValue.has_value()) - return std::any_cast(mDefaultValue); - else - return T(); - } + if (mDefaultValue.has_value()) { + return std::any_cast(mDefaultValue); } + throw std::logic_error("No value provided"); } - // Getter for std::vector. Here T = std::vector<...> - template - typename std::enable_if::value, T>::type + /* + * Getter for container types + * @throws std::logic_error in case of incompatible types + */ + template + enable_if_container get() const { - T tResult; - if (mValues.empty()) { - if (mDefaultValue.has_value()) { - T tDefaultValues = std::any_cast(mDefaultValue); - for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.emplace_back(std::any_cast(tDefaultValues[i])); - } - return tResult; - } - else - return T(); + using ValueType = typename CONTAINER::value_type; + CONTAINER tResult; + if (!mValues.empty()) { + std::transform(std::begin(mValues), std::end(mValues), + std::back_inserter(tResult), std::any_cast); + return tResult; } - else { - if (!mRawValues.empty()) { - for (const auto& mValue : mValues) { - tResult.emplace_back(std::any_cast(mValue)); - } - return tResult; - } - else { - if (mDefaultValue.has_value()) { - std::vector tDefaultValues = std::any_cast>(mDefaultValue); - for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.emplace_back(std::any_cast(tDefaultValues[i])); - } - return tResult; - } - else - return T(); - } - } - } - - // Getter for std::list. Here T = std::list<...> - template - typename std::enable_if::value, T>::type - get() const { - T tResult; - if (mValues.empty()) { - if (mDefaultValue.has_value()) { - T tDefaultValues = std::any_cast(mDefaultValue); - for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.emplace_back(std::any_cast(get_from_list(tDefaultValues, i))); - } - return tResult; - } - else - return T(); - } - else { - if (!mRawValues.empty()) { - for (const auto& mValue : mValues) { - tResult.emplace_back(std::any_cast(mValue)); - } - return tResult; - } - else { - if (mDefaultValue.has_value()) { - std::list tDefaultValues = std::any_cast>(mDefaultValue); - for (size_t i = 0; i < tDefaultValues.size(); i++) { - tResult.emplace_back(std::any_cast(get_from_list(tDefaultValues, i))); - } - return tResult; - } - else - return T(); - } + if (mDefaultValue.has_value()) { + const auto& tDefaultValues = std::any_cast(mDefaultValue); + std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), + std::back_inserter(tResult), std::any_cast); + return tResult; } + throw std::logic_error("No value provided"); } std::vector mNames; @@ -368,26 +306,29 @@ class ArgumentParser { parse_args_validate(); } - // Getter enabled for all template types other than std::vector and std::list + /* 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 + */ template T get(const std::string& aArgumentName) { auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return tIterator->second->get(); } - return T(); + throw std::logic_error("No such argument"); } - // Indexing operator. Return a reference to an Argument object - // Used in conjuction with Argument.operator== e.g., parser["foo"] == true + /* 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 + */ Argument& operator[](const std::string& aArgumentName) { auto tIterator = mArgumentMap.find(aArgumentName); if (tIterator != mArgumentMap.end()) { return *(tIterator->second); } - else { - throw std::runtime_error("Argument " + aArgumentName + " not found"); - } + throw std::logic_error("No such argument"); } // Printing the one and only help message @@ -461,12 +402,6 @@ class ArgumentParser { } private: - // If the argument was defined by the user and can be found in mArgumentMap, then it's valid - bool is_valid_argument(const std::string& aName) { - auto tIterator = mArgumentMap.find(aName); - return (tIterator != mArgumentMap.end()); - } - /* * @throws std::runtime_error in case of any invalid argument */ diff --git a/test/test_compound_arguments.hpp b/test/test_compound_arguments.hpp index 88bbb85..a096f02 100644 --- a/test/test_compound_arguments.hpp +++ b/test/test_compound_arguments.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include TEST_CASE("Parse compound toggle arguments with implicit values", "[compound_arguments]") { argparse::ArgumentParser program("test"); @@ -97,9 +98,9 @@ TEST_CASE("Parse compound toggle arguments with implicit values and nargs and ot REQUIRE(numbers[2] == 3); auto numbers_list = program.get>("numbers"); REQUIRE(numbers.size() == 3); - REQUIRE(argparse::get_from_list(numbers_list, 0) == 1); - REQUIRE(argparse::get_from_list(numbers_list, 1) == 2); - REQUIRE(argparse::get_from_list(numbers_list, 2) == 3); + REQUIRE(testutility::get_from_list(numbers_list, 0) == 1); + REQUIRE(testutility::get_from_list(numbers_list, 1) == 2); + REQUIRE(testutility::get_from_list(numbers_list, 2) == 3); } TEST_CASE("Parse out-of-order compound arguments", "[compound_arguments]") { diff --git a/test/test_container_arguments.hpp b/test/test_container_arguments.hpp index 7b12e16..8d3d754 100644 --- a/test/test_container_arguments.hpp +++ b/test/test_container_arguments.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include TEST_CASE("Parse vector of arguments", "[vector]") { argparse::ArgumentParser program("test"); @@ -24,8 +25,8 @@ TEST_CASE("Parse list of arguments", "[vector]") { auto inputs = program.get>("input"); REQUIRE(inputs.size() == 2); - REQUIRE(argparse::get_from_list(inputs, 0) == "rocket.mesh"); - REQUIRE(argparse::get_from_list(inputs, 1) == "thrust_profile.csv"); + REQUIRE(testutility::get_from_list(inputs, 0) == "rocket.mesh"); + REQUIRE(testutility::get_from_list(inputs, 1) == "thrust_profile.csv"); } TEST_CASE("Parse list of arguments with default values", "[vector]") { @@ -38,11 +39,11 @@ TEST_CASE("Parse list of arguments with default values", "[vector]") { auto inputs = program.get>("--input"); REQUIRE(inputs.size() == 5); - REQUIRE(argparse::get_from_list(inputs, 0) == 1); - REQUIRE(argparse::get_from_list(inputs, 1) == 2); - REQUIRE(argparse::get_from_list(inputs, 2) == 3); - REQUIRE(argparse::get_from_list(inputs, 3) == 4); - REQUIRE(argparse::get_from_list(inputs, 4) == 5); + REQUIRE(testutility::get_from_list(inputs, 0) == 1); + REQUIRE(testutility::get_from_list(inputs, 1) == 2); + REQUIRE(testutility::get_from_list(inputs, 2) == 3); + REQUIRE(testutility::get_from_list(inputs, 3) == 4); + REQUIRE(testutility::get_from_list(inputs, 4) == 5); REQUIRE(program["--input"] == std::list{1, 2, 3, 4, 5}); } diff --git a/test/test_utility.hpp b/test/test_utility.hpp new file mode 100644 index 0000000..fd061de --- /dev/null +++ b/test/test_utility.hpp @@ -0,0 +1,17 @@ +#ifndef ARGPARSE_TEST_UTILITY_HPP +#define ARGPARSE_TEST_UTILITY_HPP + +namespace testutility { +// Get value at index from std::list +template +T get_from_list(const std::list& aList, size_t aIndex) { + if (aList.size() > aIndex) { + auto tIterator = aList.begin(); + std::advance(tIterator, aIndex); + return *tIterator; + } + return T(); +} +} + +#endif //ARGPARSE_TEST_UTILITY_HPP