From 94ca8e25524304d4704aef82ef853ed6b0f84d6d Mon Sep 17 00:00:00 2001 From: Stephan van Veen Date: Sun, 12 May 2019 15:54:23 +0200 Subject: [PATCH 1/7] Unify container operations --- include/argparse.hpp | 63 +++++++++++-------------------- test/test_compound_arguments.hpp | 7 ++-- test/test_container_arguments.hpp | 15 ++++---- test/test_utility.hpp | 17 +++++++++ 4 files changed, 52 insertions(+), 50 deletions(-) create mode 100644 test/test_utility.hpp diff --git a/include/argparse.hpp b/include/argparse.hpp index 4910c9a..2cc9e69 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -57,17 +57,6 @@ 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 { @@ -150,33 +139,29 @@ public: template typename std::enable_if::value, bool>::type operator==(const T& aRhs) const { + using ValueType = typename T::value_type; 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(tLhs[i]); - if (tValueAtIndex != aRhs[i]) - return false; - } - return true; + return std::equal(std::begin(tLhs), std::begin(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { + return std::any_cast(lhs) == rhs; + }); } } // Template specialization for std::list<...> template typename std::enable_if::value, bool>::type - operator==(const T& aRhs) const { + operator==(const T& aRhs) const { + using ValueType = typename T::value_type; 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::begin(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { + return std::any_cast(lhs) == rhs; + }); } } @@ -214,13 +199,13 @@ public: template typename std::enable_if::value, T>::type get() const { + using ValueType = typename T::value_type; 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])); - } + std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), + std::back_inserter(tResult), std::any_cast); return tResult; } else @@ -229,16 +214,15 @@ public: else { if (!mRawValues.empty()) { for (const auto& mValue : mValues) { - tResult.emplace_back(std::any_cast(mValue)); + 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])); - } + auto tDefaultValues = std::any_cast>(mDefaultValue); + std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), + std::back_inserter(tResult), std::any_cast); return tResult; } else @@ -251,13 +235,13 @@ public: template typename std::enable_if::value, T>::type get() const { + using ValueType = typename T::value_type; 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))); - } + std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), + std::back_inserter(tResult), std::any_cast); return tResult; } else @@ -266,16 +250,15 @@ public: else { if (!mRawValues.empty()) { for (const auto& mValue : mValues) { - tResult.emplace_back(std::any_cast(mValue)); + 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))); - } + auto tDefaultValues = std::any_cast>(mDefaultValue); + std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), + std::back_inserter(tResult), std::any_cast); return tResult; } else 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 From ca68260ec4eccb34ffc02e51d61ced1f070ed052 Mon Sep 17 00:00:00 2001 From: Stephan van Veen Date: Mon, 13 May 2019 21:50:53 +0200 Subject: [PATCH 2/7] Enable equality operator for all iterable types --- include/argparse.hpp | 55 ++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index 2cc9e69..06bacd3 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -52,6 +52,31 @@ 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().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() @@ -129,38 +154,22 @@ public: // Entry point for template types other than std::vector and std::list 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 template - typename std::enable_if::value, bool>::type + std::enable_if_t , bool> operator==(const T& aRhs) const { using ValueType = typename T::value_type; - T tLhs = get(); + auto tLhs = get(); if (tLhs.size() != aRhs.size()) return false; else { - return std::equal(std::begin(tLhs), std::begin(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { - return std::any_cast(lhs) == rhs; - }); - } - } - - // Template specialization for std::list<...> - template - typename std::enable_if::value, bool>::type - operator==(const T& aRhs) const { - using ValueType = typename T::value_type; - T tLhs = get(); - if (tLhs.size() != aRhs.size()) - return false; - else { - return std::equal(std::begin(tLhs), std::begin(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { - return std::any_cast(lhs) == rhs; + return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs), [](const auto& lhs, const auto& rhs) { + return std::any_cast(lhs) == rhs; }); } } From 02b3ed187840a0c2ac59bb8b9320178cd287e781 Mon Sep 17 00:00:00 2001 From: Stephan van Veen Date: Mon, 13 May 2019 22:09:27 +0200 Subject: [PATCH 3/7] Cleanup Argument::get methods --- include/argparse.hpp | 105 +++++++++---------------------------------- 1 file changed, 21 insertions(+), 84 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index 06bacd3..d92ddba 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -182,98 +182,35 @@ public: // Getter for template types other than std::vector and std::list 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 + template + enable_if_container get() const { - using ValueType = typename T::value_type; - T tResult; - if (mValues.empty()) { - if (mDefaultValue.has_value()) { - T tDefaultValues = std::any_cast(mDefaultValue); - std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), - std::back_inserter(tResult), std::any_cast); - 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()) { - auto tDefaultValues = std::any_cast>(mDefaultValue); - std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), - std::back_inserter(tResult), std::any_cast); - return tResult; - } - else - return T(); - } - } - } - - // Getter for std::list. Here T = std::list<...> - template - typename std::enable_if::value, T>::type - get() const { - using ValueType = typename T::value_type; - T tResult; - if (mValues.empty()) { - if (mDefaultValue.has_value()) { - T tDefaultValues = std::any_cast(mDefaultValue); - std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), - std::back_inserter(tResult), std::any_cast); - 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()) { - auto tDefaultValues = std::any_cast>(mDefaultValue); - std::transform(std::begin(tDefaultValues), std::end(tDefaultValues), - std::back_inserter(tResult), std::any_cast); - 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; From f08a280f92afbd0dac7dfc213d3e5345538a1632 Mon Sep 17 00:00:00 2001 From: Stephan van Veen Date: Mon, 13 May 2019 22:29:09 +0200 Subject: [PATCH 4/7] Throw std::logic_error instead of returning empty value --- include/argparse.hpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index d92ddba..4313c8b 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -304,7 +304,7 @@ class ArgumentParser { 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 @@ -314,9 +314,7 @@ class ArgumentParser { 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 @@ -390,12 +388,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 */ From a6ceffdb63689af5ca9b61794738507acd7c7118 Mon Sep 17 00:00:00 2001 From: Stephan van Veen Date: Mon, 13 May 2019 22:29:35 +0200 Subject: [PATCH 5/7] Extend doxygen documentation --- include/argparse.hpp | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index 4313c8b..6e7ac31 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -152,14 +152,20 @@ 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 std::enable_if_t , bool> operator==(const T& aRhs) const { return get() == aRhs; } - // Template specialization for containers + /* + * Template specialization for containers + * @throws std::logic_error in case of incompatible types + */ template std::enable_if_t , bool> operator==(const T& aRhs) const { @@ -180,7 +186,10 @@ 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 enable_if_not_container get() const { @@ -193,7 +202,10 @@ public: throw std::logic_error("No value provided"); } - // Getter for container types + /* + * Getter for container types + * @throws std::logic_error in case of incompatible types + */ template enable_if_container get() const { @@ -297,7 +309,10 @@ 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); @@ -307,8 +322,10 @@ class ArgumentParser { 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()) { From 20a7d90abe07f6dccaafd6f5f65aad4bac68c1f8 Mon Sep 17 00:00:00 2001 From: Stephan van Veen Date: Mon, 13 May 2019 22:52:26 +0200 Subject: [PATCH 6/7] Don't accept std::string as container --- include/argparse.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/argparse.hpp b/include/argparse.hpp index 6e7ac31..644a7b8 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -58,6 +58,9 @@ struct is_container_helper {}; template struct is_container : std::false_type {}; +template<> +struct is_container : std::false_type {}; + template struct is_container Date: Mon, 13 May 2019 22:53:47 +0200 Subject: [PATCH 7/7] Remove is_specialization --- include/argparse.hpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/include/argparse.hpp b/include/argparse.hpp index 644a7b8..1b1e8df 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -45,12 +45,6 @@ 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 {};