From 5e7ce61ca7a67cd42490b2da6f2c588e11a8db1c Mon Sep 17 00:00:00 2001 From: Pranav Srinivas Kumar Date: Fri, 27 Oct 2023 12:59:04 -0500 Subject: [PATCH] Closes #278 --- include/argparse/argparse.hpp | 60 +++++++++++++++++++---------------- test/test_choices.cpp | 12 +++---- test/test_default_value.cpp | 19 ++++++----- test/test_scan.cpp | 33 +++++++++++++++++++ 4 files changed, 83 insertions(+), 41 deletions(-) diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 52e8aeb..8c9c410 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -233,8 +233,14 @@ template struct parse_number { template struct parse_number { auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) { - return do_from_chars(rest); + if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { + if (auto [ok, rest] = consume_hex_prefix(s); ok) { + return do_from_chars(rest); + } + } else { + // Allow passing hex numbers without prefix + // Shape 'x' already has to be specified + return do_from_chars(s); } throw std::invalid_argument{"pattern not found"}; } @@ -350,24 +356,22 @@ std::string join(StrIt first, StrIt last, const std::string &separator) { return value.str(); } -template -struct can_invoke_to_string { +template struct can_invoke_to_string { template - static auto test(int) -> decltype(std::to_string(std::declval()), std::true_type{}); - - template - static auto test(...) -> std::false_type; - + static auto test(int) + -> decltype(std::to_string(std::declval()), std::true_type{}); + + template static auto test(...) -> std::false_type; + static constexpr bool value = decltype(test(0))::value; }; -template -struct IsChoiceTypeSupported { - using CleanType = typename std::decay::type; - static const bool value = std::is_integral::value || - std::is_same::value || - std::is_same::value || - std::is_same::value; +template struct IsChoiceTypeSupported { + using CleanType = typename std::decay::type; + static const bool value = std::is_integral::value || + std::is_same::value || + std::is_same::value || + std::is_same::value; }; } // namespace details @@ -432,8 +436,7 @@ public: if constexpr (std::is_convertible_v) { m_default_value_str = std::string{std::string_view{value}}; - } - else if constexpr (details::can_invoke_to_string::value) { + } else if constexpr (details::can_invoke_to_string::value) { m_default_value_str = std::to_string(value); } @@ -554,18 +557,20 @@ public: return nargs(nargs_pattern::any); } - template - void add_choice(T&& choice) { - static_assert(details::IsChoiceTypeSupported::value, "Only string or integer type supported for choice"); - static_assert(std::is_convertible_v || details::can_invoke_to_string::value, "Choice is not convertible to string_type"); + template void add_choice(T &&choice) { + static_assert(details::IsChoiceTypeSupported::value, + "Only string or integer type supported for choice"); + static_assert(std::is_convertible_v || + details::can_invoke_to_string::value, + "Choice is not convertible to string_type"); if (!m_choices.has_value()) { m_choices = std::vector{}; } if constexpr (std::is_convertible_v) { - m_choices.value().push_back(std::string{std::string_view{std::forward(choice)}}); - } - else if constexpr (details::can_invoke_to_string::value) { + m_choices.value().push_back( + std::string{std::string_view{std::forward(choice)}}); + } else if constexpr (details::can_invoke_to_string::value) { m_choices.value().push_back(std::to_string(std::forward(choice))); } } @@ -578,7 +583,7 @@ public: } template - Argument &choices(T&& first, U&&...rest) { + Argument &choices(T &&first, U &&...rest) { add_choice(std::forward(first)); choices(std::forward(rest)...); return *this; @@ -1181,7 +1186,8 @@ private: std::string m_metavar; std::any m_default_value; std::string m_default_value_repr; - std::optional m_default_value_str; // used for checking default_value against choices + std::optional + m_default_value_str; // used for checking default_value against choices std::any m_implicit_value; std::optional> m_choices{std::nullopt}; using valued_action = std::function; diff --git a/test/test_choices.cpp b/test/test_choices.cpp index d8f4d54..678aba8 100644 --- a/test/test_choices.cpp +++ b/test/test_choices.cpp @@ -69,18 +69,18 @@ TEST_CASE("Parse multiple arguments one of which is not in the fixed number of " std::runtime_error); } -TEST_CASE( - "Parse multiple arguments that are in the fixed number of allowed INTEGER choices" * - test_suite("choices")) { +TEST_CASE("Parse multiple arguments that are in the fixed number of allowed " + "INTEGER choices" * + test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); program.parse_args({"test", "1", "2"}); } -TEST_CASE( - "Parse multiple arguments that are not in fixed number of allowed INTEGER choices" * - test_suite("choices")) { +TEST_CASE("Parse multiple arguments that are not in fixed number of allowed " + "INTEGER choices" * + test_suite("choices")) { argparse::ArgumentParser program("test"); program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); diff --git a/test/test_default_value.cpp b/test/test_default_value.cpp index 4805f5c..8ee8dfc 100644 --- a/test/test_default_value.cpp +++ b/test/test_default_value.cpp @@ -24,14 +24,16 @@ TEST_CASE("Use a 'string' default value" * test_suite("default_value")) { } } -TEST_CASE("Use a default value with flag arguments" * test_suite("default_value")) { +TEST_CASE("Use a default value with flag arguments" * + test_suite("default_value")) { - argparse::ArgumentParser program("test"); + argparse::ArgumentParser program("test"); program.add_argument("-inc_chr", "--include_chromes") - .help(std::string{"only process the anchor whose one of the end is contained on the specified " - "chromatin, used ',' to split."}) - .default_value("all"); + .help(std::string{"only process the anchor whose one of the end is " + "contained on the specified " + "chromatin, used ',' to split."}) + .default_value("all"); program.add_argument("-l").default_value(false).implicit_value(true); program.add_argument("-o").default_value(false).implicit_value(true); @@ -40,12 +42,12 @@ TEST_CASE("Use a default value with flag arguments" * test_suite("default_value" SUBCASE("Leading optional argument with default_value") { REQUIRE_NOTHROW(program.parse_args({"test", "-inc_chr", "-lo", "my.log"})); - REQUIRE(program.get("-inc_chr") == std::string{"all"}); + REQUIRE(program.get("-inc_chr") == std::string{"all"}); } SUBCASE("Trailing optional argument with default_value") { REQUIRE_NOTHROW(program.parse_args({"test", "-lo", "my.log", "-inc_chr"})); - REQUIRE(program.get("-inc_chr") == std::string{"all"}); + REQUIRE(program.get("-inc_chr") == std::string{"all"}); } } @@ -73,7 +75,8 @@ TEST_CASE("Position of the argument with default value") { } SUBCASE("Arg with default value replaces the value if given") { - REQUIRE_NOTHROW(program.parse_args({"test", "-g", "a_different_value", "-s", "./src"})); + REQUIRE_NOTHROW( + program.parse_args({"test", "-g", "a_different_value", "-s", "./src"})); REQUIRE(program.get("-g") == std::string("a_different_value")); REQUIRE(program.get("-s") == std::string("./src")); } diff --git a/test/test_scan.cpp b/test/test_scan.cpp index 4210d8f..902954f 100644 --- a/test/test_scan.cpp +++ b/test/test_scan.cpp @@ -112,6 +112,39 @@ TEST_CASE_TEMPLATE("Parse a hexadecimal integer argument" * test_suite("scan"), REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0XFFFFFFFFFFFFFFFF1"}), std::range_error); } + + SUBCASE("with hex digit without prefix") { + program.parse_args({"test", "-n", "1a"}); + REQUIRE(program.get("-n") == 0x1a); + } + + SUBCASE("minus sign without prefix produces an optional argument") { + REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-1"}), + std::invalid_argument); + } + + SUBCASE("plus sign without prefix is not allowed") { + REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+1a"}), + std::invalid_argument); + } + + SUBCASE("without prefix does not fit") { + REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "FFFFFFFFFFFFFFFF1"}), + std::range_error); + } +} + +TEST_CASE("Parse multiple hex numbers without prefix" * test_suite("scan")) { + argparse::ArgumentParser program("test"); + program.add_argument("-x", "--hex") + .help("bytes in hex separated by spaces") + .nargs(1, std::numeric_limits::max()) + .scan<'x', uint8_t>(); + + REQUIRE_NOTHROW( + program.parse_args({"test", "-x", "f2", "b2", "10", "80", "64"})); + const auto &input_bytes = program.get>("-x"); + REQUIRE((input_bytes == std::vector{0xf2, 0xb2, 0x10, 0x80, 0x64})); } TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"),