From 282f9ebf91b4bf7cccd291955526fdc46d5951f6 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 16:10:56 +0300 Subject: [PATCH 01/10] show default values in help --- include/argparse/argparse.hpp | 63 +++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index b8c9daf2..9843435e 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -73,6 +73,58 @@ struct is_container< template static constexpr bool is_container_v = is_container::value; +template +struct is_streamable : std::false_type {}; + +template +struct is_streamable< + T, + std::conditional_t() << std::declval()), + void>> : std::true_type {}; + +template +static constexpr bool is_streamable_v = is_streamable::value; + +template +static constexpr bool is_representable_v = + is_streamable_v || is_container_v; + + +constexpr size_t repr_max_container_size = 5; + +template +std::string repr(T const &val) { + if constexpr(std::is_convertible_v) { + return '"' + std::string{std::string_view{val}} + '"'; + } else if constexpr (is_streamable_v) { + std::stringstream out; + out << val; + return out.str(); + } else if constexpr(is_container_v) { + std::stringstream out; + out << "{"; + const auto size = val.size(); + if (size > 1) { + out << repr(*val.begin()); + std::for_each( + std::next(val.begin()), + std::next(val.begin(), std::min(size, repr_max_container_size)-1), + [&out](const auto &v) { out << " " << repr(v); }); + if (size <= repr_max_container_size) + out << " "; + else + out << "..."; + } + if (size > 0) + out << repr(*std::prev(val.end())); + out << "}"; + return out.str(); + } else { + return ""; + } +} + namespace { template constexpr bool standard_signed_integer = false; @@ -294,8 +346,10 @@ class Argument { return *this; } - Argument &default_value(std::any aDefaultValue) { - mDefaultValue = std::move(aDefaultValue); + template + Argument &default_value(T&& aDefaultValue) { + mDefaultValueRepr = details::repr(aDefaultValue); + mDefaultValue = std::forward(aDefaultValue); return *this; } @@ -485,6 +539,8 @@ class Argument { stream << nameStream.str() << "\t" << argument.mHelp; if (argument.mIsRequired && !argument.mDefaultValue.has_value()) stream << "[Required]"; + if (argument.mDefaultValue.has_value()) + stream << " [default: " << argument.mDefaultValueRepr << "]"; stream << "\n"; return stream; } @@ -735,6 +791,7 @@ class Argument { std::string_view mUsedName; std::string mHelp; std::any mDefaultValue; + std::string mDefaultValueRepr; std::any mImplicitValue; using valued_action = std::function; using void_action = std::function; @@ -963,7 +1020,7 @@ class ArgumentParser { std::cout << *this; std::exit(0); } - // the second optional argument is --version + // the second optional argument is --version else if (tArgument == std::next(mOptionalArguments.begin(), 1)) { std::cout << mVersion << "\n"; std::exit(0); From 6964cccd2f2b4298863a817141f68c6506816359 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 16:20:41 +0300 Subject: [PATCH 02/10] clang-format --- include/argparse/argparse.hpp | 42 ++++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 9843435e..3cb790e6 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -78,10 +78,9 @@ struct is_streamable : std::false_type {}; template struct is_streamable< - T, - std::conditional_t() << std::declval()), - void>> : std::true_type {}; + T, std::conditional_t< + false, decltype(std::declval() << std::declval()), + void>> : std::true_type {}; template static constexpr bool is_streamable_v = is_streamable::value; @@ -90,27 +89,25 @@ template static constexpr bool is_representable_v = is_streamable_v || is_container_v; - constexpr size_t repr_max_container_size = 5; -template -std::string repr(T const &val) { - if constexpr(std::is_convertible_v) { +template std::string repr(T const &val) { + if constexpr (std::is_convertible_v) { return '"' + std::string{std::string_view{val}} + '"'; } else if constexpr (is_streamable_v) { std::stringstream out; out << val; return out.str(); - } else if constexpr(is_container_v) { + } else if constexpr (is_container_v) { std::stringstream out; out << "{"; const auto size = val.size(); if (size > 1) { out << repr(*val.begin()); std::for_each( - std::next(val.begin()), - std::next(val.begin(), std::min(size, repr_max_container_size)-1), - [&out](const auto &v) { out << " " << repr(v); }); + std::next(val.begin()), + std::next(val.begin(), std::min(size, repr_max_container_size) - 1), + [&out](const auto &v) { out << " " << repr(v); }); if (size <= repr_max_container_size) out << " "; else @@ -142,7 +139,7 @@ template <> constexpr bool standard_unsigned_integer = true; template <> constexpr bool standard_unsigned_integer = true; -} +} // namespace template constexpr bool standard_integer = @@ -249,7 +246,7 @@ template <> constexpr auto generic_strtod = strtof; template <> constexpr auto generic_strtod = strtod; template <> constexpr auto generic_strtod = strtold; -} +} // namespace template inline auto do_strtod(std::string const &s) -> T { if (isspace(static_cast(s[0])) || s[0] == '+') @@ -346,8 +343,7 @@ class Argument { return *this; } - template - Argument &default_value(T&& aDefaultValue) { + template Argument &default_value(T &&aDefaultValue) { mDefaultValueRepr = details::repr(aDefaultValue); mDefaultValue = std::forward(aDefaultValue); return *this; @@ -807,11 +803,10 @@ class Argument { class ArgumentParser { public: - explicit ArgumentParser(std::string aProgramName = {}, std::string aVersion = "1.0") + explicit ArgumentParser(std::string aProgramName = {}, + std::string aVersion = "1.0") : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) { - add_argument("-h", "--help") - .help("shows help message and exits") - .nargs(0); + add_argument("-h", "--help").help("shows help message and exits").nargs(0); add_argument("-v", "--version") .help("prints version information and exits") .nargs(0); @@ -907,7 +902,8 @@ class ArgumentParser { * @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) const { + template + T get(std::string_view aArgumentName) const { return (*this)[aArgumentName].get(); } @@ -946,7 +942,7 @@ class ArgumentParser { } stream << "\n\n"; - if(!parser.mDescription.empty()) + if (!parser.mDescription.empty()) stream << parser.mDescription << "\n\n"; if (!parser.mPositionalArguments.empty()) @@ -966,7 +962,7 @@ class ArgumentParser { stream << mOptionalArgument; } - if(!parser.mEpilog.empty()) + if (!parser.mEpilog.empty()) stream << parser.mEpilog << "\n\n"; } From 5572cb0862d1b89aff1f938b88f0f8cec358c472 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 16:58:38 +0300 Subject: [PATCH 03/10] trying to fix strange AppleClang compiler error --- include/argparse/argparse.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 3cb790e6..29abf781 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -94,10 +94,6 @@ constexpr size_t repr_max_container_size = 5; template std::string repr(T const &val) { if constexpr (std::is_convertible_v) { return '"' + std::string{std::string_view{val}} + '"'; - } else if constexpr (is_streamable_v) { - std::stringstream out; - out << val; - return out.str(); } else if constexpr (is_container_v) { std::stringstream out; out << "{"; @@ -117,6 +113,10 @@ template std::string repr(T const &val) { out << repr(*std::prev(val.end())); out << "}"; return out.str(); + } else if constexpr (is_streamable_v) { + std::stringstream out; + out << val; + return out.str(); } else { return ""; } From 6b30a65ffddd44a3810903e5341b7baa5bc28902 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 16:59:45 +0300 Subject: [PATCH 04/10] special case for repr: bool value --- include/argparse/argparse.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 29abf781..376f34f4 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -92,7 +92,9 @@ static constexpr bool is_representable_v = constexpr size_t repr_max_container_size = 5; template std::string repr(T const &val) { - if constexpr (std::is_convertible_v) { + if constexpr (std::is_same_v) { + return val ? "true" : "false"; + } else if constexpr (std::is_convertible_v) { return '"' + std::string{std::string_view{val}} + '"'; } else if constexpr (is_container_v) { std::stringstream out; From 4de9f89b59fe818be6d12162b637f205c8e5e465 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 19:00:55 +0300 Subject: [PATCH 05/10] add tests for repr() and extend tests for help msg --- test/CMakeLists.txt | 1 + test/test_help.cpp | 20 ++++++++++++---- test/test_repr.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 test/test_repr.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 592fee1a..d3d23010 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,6 +35,7 @@ file(GLOB ARGPARSE_TEST_SOURCES test_parent_parsers.cpp test_parse_args.cpp test_positional_arguments.cpp + test_repr.cpp test_required_arguments.cpp test_scan.cpp test_value_semantics.cpp diff --git a/test/test_help.cpp b/test/test_help.cpp index 25b87af2..78438eae 100644 --- a/test/test_help.cpp +++ b/test/test_help.cpp @@ -1,15 +1,25 @@ -#include #include +#include using doctest::test_suite; TEST_CASE("Users can format help message" * test_suite("help")) { argparse::ArgumentParser program("test"); - program.add_argument("input") - .help("positional input"); - program.add_argument("-c") - .help("optional input"); + SUBCASE("Simple arguments") { + program.add_argument("input").help("positional input"); + program.add_argument("-c").help("optional input"); + } + SUBCASE("Default values") { + program.add_argument("-a").default_value(42); + program.add_argument("-b").default_value(4.4e-7); + program.add_argument("-c") + .default_value(std::vector{1, 2, 3, 4, 5}) + .nargs(5); + program.add_argument("-d").default_value("I am a string"); + program.add_argument("-e").default_value(std::optional{}); + program.add_argument("-f").default_value(false); + } std::ostringstream s; s << program; REQUIRE_FALSE(s.str().empty()); diff --git a/test/test_repr.cpp b/test/test_repr.cpp new file mode 100644 index 00000000..dc7d1c7e --- /dev/null +++ b/test/test_repr.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +using doctest::test_suite; + +TEST_CASE("Test bool representation" * test_suite("repr")) { + REQUIRE(argparse::details::repr(true) == "true"); + REQUIRE(argparse::details::repr(false) == "false"); +} + +TEST_CASE_TEMPLATE("Test built-in int types representation" * + test_suite("repr"), + T, char, short, int, long long, unsigned char, unsigned, + unsigned long long) { + std::stringstream ss; + T v = 42; + ss << v; + REQUIRE(argparse::details::repr(v) == ss.str()); +} + +TEST_CASE_TEMPLATE("Test built-in float types representation" * + test_suite("repr"), + T, float, double, long double) { + std::stringstream ss; + T v = 0.3333333333; + ss << v; + REQUIRE(argparse::details::repr(v) == ss.str()); +} + +TEST_CASE_TEMPLATE("Test container representation" * test_suite("repr"), T, + std::vector, std::list, std::set) { + T empty; + T one = {42}; + T small = {1, 2, 3}; + T big = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + REQUIRE(argparse::details::repr(empty) == "{}"); + REQUIRE(argparse::details::repr(one) == "{42}"); + REQUIRE(argparse::details::repr(small) == "{1 2 3}"); + REQUIRE(argparse::details::repr(big) == "{1 2 3 4...15}"); +} + +TEST_CASE_TEMPLATE("Test string representation" * test_suite("repr"), T, + char const *, std::string, std::string_view) { + T empty = ""; + T str = "A A A#"; + + REQUIRE(argparse::details::repr(empty) == "\"\""); + REQUIRE(argparse::details::repr(str) == "\"A A A#\""); +} + +TEST_CASE("Test unknown representation" * test_suite("repr")) { + struct TestClass {}; + REQUIRE(argparse::details::repr(TestClass{}) == ""); +} From b025166e4c9bfb8a119dd8f3aa08c2f4abe54ba0 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 19:09:08 +0300 Subject: [PATCH 06/10] prettier output in case of absence of arg help msg --- include/argparse/argparse.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 376f34f4..c108f2cb 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -537,8 +537,11 @@ class Argument { stream << nameStream.str() << "\t" << argument.mHelp; if (argument.mIsRequired && !argument.mDefaultValue.has_value()) stream << "[Required]"; - if (argument.mDefaultValue.has_value()) - stream << " [default: " << argument.mDefaultValueRepr << "]"; + if (argument.mDefaultValue.has_value()) { + if (!argument.mHelp.empty()) + stream << " "; + stream << "[default: " << argument.mDefaultValueRepr << "]"; + } stream << "\n"; return stream; } From 69c2cada353973999e7a0751d25d0352e018632b Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 20:01:26 +0300 Subject: [PATCH 07/10] simplified traits using std::void_t --- include/argparse/argparse.hpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index c108f2cb..7f285b25 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -53,34 +53,28 @@ namespace argparse { namespace details { // namespace for helper methods -template struct is_container_helper {}; - -template +template struct is_container : std::false_type {}; template <> struct is_container : std::false_type {}; template -struct is_container< - T, - std::conditional_t().begin()), - decltype(std::declval().end()), - decltype(std::declval().size())>, - void>> : std::true_type {}; +struct is_container().begin()), + decltype(std::declval().end()), + decltype(std::declval().size())>> + : std::true_type {}; template static constexpr bool is_container_v = is_container::value; -template +template struct is_streamable : std::false_type {}; template struct is_streamable< - T, std::conditional_t< - false, decltype(std::declval() << std::declval()), - void>> : std::true_type {}; + T, std::void_t() << std::declval())>> + : std::true_type {}; template static constexpr bool is_streamable_v = is_streamable::value; From bc6e948bca8c497e2c5eafc6b8b0028da86066ff Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 20:30:50 +0300 Subject: [PATCH 08/10] update MacOS image in Travis CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 620daeb6..533f9959 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - g++-8 env: CXX=g++-8 CC=gcc-8 - os: osx - osx_image: xcode10.2 + osx_image: xcode12.2 language: cpp compiler: clang - os: windows From 51dd98f49df073268977e695fa15b82ceabf91b1 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 21:59:52 +0300 Subject: [PATCH 09/10] fix is_streamable trait --- include/argparse/argparse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 7f285b25..44394883 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -73,7 +73,7 @@ struct is_streamable : std::false_type {}; template struct is_streamable< - T, std::void_t() << std::declval())>> + T, std::void_t() << std::declval())>> : std::true_type {}; template From 88f1614df6788bdb96cddec159114b7a6a2353e8 Mon Sep 17 00:00:00 2001 From: Mike Zozu Date: Tue, 15 Dec 2020 22:07:27 +0300 Subject: [PATCH 10/10] revert MacOS image change --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 533f9959..620daeb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - g++-8 env: CXX=g++-8 CC=gcc-8 - os: osx - osx_image: xcode12.2 + osx_image: xcode10.2 language: cpp compiler: clang - os: windows