Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show default value for arg in help message #92

Merged
merged 11 commits into from
Dec 15, 2020
98 changes: 75 additions & 23 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,71 @@ namespace argparse {

namespace details { // namespace for helper methods

template <typename... Ts> struct is_container_helper {};

template <typename T, typename _ = void>
template <typename T, typename = void>
struct is_container : std::false_type {};

template <> struct is_container<std::string> : std::false_type {};

template <typename T>
struct is_container<
T,
std::conditional_t<false,
is_container_helper<typename T::value_type,
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end()),
decltype(std::declval<T>().size())>,
void>> : std::true_type {};
struct is_container<T, std::void_t<typename T::value_type,
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end()),
decltype(std::declval<T>().size())>>
: std::true_type {};

template <typename T>
static constexpr bool is_container_v = is_container<T>::value;

template <typename T, typename = void>
struct is_streamable : std::false_type {};

template <typename T>
struct is_streamable<
T, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<T>())>>
: std::true_type {};

template <typename T>
static constexpr bool is_streamable_v = is_streamable<T>::value;

template <typename T>
static constexpr bool is_representable_v =
is_streamable_v<T> || is_container_v<T>;

constexpr size_t repr_max_container_size = 5;

template <typename T> std::string repr(T const &val) {
if constexpr (std::is_same_v<T, bool>) {
return val ? "true" : "false";
} else if constexpr (std::is_convertible_v<T, std::string_view>) {
return '"' + std::string{std::string_view{val}} + '"';
} else if constexpr (is_container_v<T>) {
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 if constexpr (is_streamable_v<T>) {
std::stringstream out;
out << val;
return out.str();
} else {
return "<not representable>";
}
}

namespace {

template <typename T> constexpr bool standard_signed_integer = false;
Expand All @@ -90,7 +135,7 @@ template <> constexpr bool standard_unsigned_integer<unsigned long int> = true;
template <>
constexpr bool standard_unsigned_integer<unsigned long long int> = true;

}
} // namespace

template <typename T>
constexpr bool standard_integer =
Expand Down Expand Up @@ -197,7 +242,7 @@ template <> constexpr auto generic_strtod<float> = strtof;
template <> constexpr auto generic_strtod<double> = strtod;
template <> constexpr auto generic_strtod<long double> = strtold;

}
} // namespace

template <class T> inline auto do_strtod(std::string const &s) -> T {
if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+')
Expand Down Expand Up @@ -294,8 +339,9 @@ class Argument {
return *this;
}

Argument &default_value(std::any aDefaultValue) {
mDefaultValue = std::move(aDefaultValue);
template <typename T> Argument &default_value(T &&aDefaultValue) {
mDefaultValueRepr = details::repr(aDefaultValue);
mDefaultValue = std::forward<T>(aDefaultValue);
return *this;
}

Expand Down Expand Up @@ -485,6 +531,11 @@ class Argument {
stream << nameStream.str() << "\t" << argument.mHelp;
if (argument.mIsRequired && !argument.mDefaultValue.has_value())
stream << "[Required]";
if (argument.mDefaultValue.has_value()) {
if (!argument.mHelp.empty())
stream << " ";
stream << "[default: " << argument.mDefaultValueRepr << "]";
}
stream << "\n";
return stream;
}
Expand Down Expand Up @@ -735,6 +786,7 @@ class Argument {
std::string_view mUsedName;
std::string mHelp;
std::any mDefaultValue;
std::string mDefaultValueRepr;
std::any mImplicitValue;
using valued_action = std::function<std::any(const std::string &)>;
using void_action = std::function<void(const std::string &)>;
Expand All @@ -750,11 +802,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);
Expand Down Expand Up @@ -850,7 +901,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 <typename T = std::string> T get(std::string_view aArgumentName) const {
template <typename T = std::string>
T get(std::string_view aArgumentName) const {
return (*this)[aArgumentName].get<T>();
}

Expand Down Expand Up @@ -889,7 +941,7 @@ class ArgumentParser {
}
stream << "\n\n";

if(!parser.mDescription.empty())
if (!parser.mDescription.empty())
stream << parser.mDescription << "\n\n";

if (!parser.mPositionalArguments.empty())
Expand All @@ -909,7 +961,7 @@ class ArgumentParser {
stream << mOptionalArgument;
}

if(!parser.mEpilog.empty())
if (!parser.mEpilog.empty())
stream << parser.mEpilog << "\n\n";
}

Expand Down Expand Up @@ -963,7 +1015,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);
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 15 additions & 5 deletions test/test_help.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
#include <doctest.hpp>
#include <argparse/argparse.hpp>
#include <doctest.hpp>

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<int>{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<float>{});
program.add_argument("-f").default_value(false);
}
std::ostringstream s;
s << program;
REQUIRE_FALSE(s.str().empty());
Expand Down
56 changes: 56 additions & 0 deletions test/test_repr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <argparse/argparse.hpp>
#include <doctest.hpp>
#include <set>

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<int>, std::list<int>, std::set<int>) {
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{}) == "<not representable>");
}