Handle RTTI in a consistent way for locale inclusion for integral conversion.  
Resolve some missing code coverage lines.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Alexander Galanin <agalanin@nvidia.com>
This commit is contained in:
Philip Top
2025-05-26 10:35:22 -07:00
committed by GitHub
parent 3d9eb4e0a0
commit c4a6f31bd9
9 changed files with 85 additions and 44 deletions

View File

@@ -122,6 +122,32 @@
#endif
#endif
/** rtti enabled */
#ifndef CLI11_HAS_RTTI
#if defined(__GXX_RTTI) && __GXX_RTTI == 1
// gcc
#define CLI11_HAS_RTTI 1
#elif defined(_CPPRTTI) && _CPPRTTI == 1
// msvc
#define CLI11_HAS_RTTI 1
#elif defined(__NO_RTTI__) && __NO_RTTI__ == 1
// intel
#define CLI11_HAS_RTTI 0
#elif defined(__has_feature)
// clang and other newer compilers
#if __has_feature(cxx_rtti)
#define CLI11_HAS_RTTI 1
#else
#define CLI11_HAS_RTTI 0
#endif
#elif defined(__RTTI) || defined(__INTEL_RTTI__)
// more intel and some other compilers
#define CLI11_HAS_RTTI 1
#else
#define CLI11_HAS_RTTI 0
#endif
#endif
/** disable deprecations */
#if defined(__GNUC__) // GCC or clang
#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")

View File

@@ -194,6 +194,10 @@ inline std::string remove_underscore(std::string str) {
return str;
}
/// @brief get valid group separators _' + local separator if different
/// @return a string containing the group separators
CLI11_INLINE std::string get_group_separators();
/// Find and replace a substring with another substring
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to);

View File

@@ -961,20 +961,18 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll);
return (static_cast<std::int64_t>(output) == output_sll);
}
// remove locale-specific group separators
char group_separator = std::use_facet<std::numpunct<char>>(std::locale()).thousands_sep();
if(input.find_first_of(group_separator) != std::string::npos) {
// remove separators if present
auto group_separators = get_group_separators();
if(input.find_first_of(group_separators) != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), group_separator), nstring.end());
return integral_conversion(nstring, output);
}
// remove separators
if(input.find_first_of("_'") != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end());
nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end());
for(auto &separator : group_separators) {
if(input.find_first_of(separator) != std::string::npos) {
nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end());
}
}
return integral_conversion(nstring, output);
}
if(std::isspace(static_cast<unsigned char>(input.back()))) {
return integral_conversion(trim_copy(input), output);
}
@@ -1026,19 +1024,16 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
output = static_cast<T>(1);
return true;
}
// remove locale-specific group separators
char group_separator = std::use_facet<std::numpunct<char>>(std::locale()).thousands_sep();
if(input.find_first_of(group_separator) != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), group_separator), nstring.end());
return integral_conversion(nstring, output);
}
// remove separators and trailing spaces
if(input.find_first_of("_'") != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end());
nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end());
return integral_conversion(nstring, output);
// remove separators if present
auto group_separators = get_group_separators();
if(input.find_first_of(group_separators) != std::string::npos) {
for(auto &separator : group_separators) {
if(input.find_first_of(separator) != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end());
return integral_conversion(nstring, output);
}
}
}
if(std::isspace(static_cast<unsigned char>(input.back()))) {
return integral_conversion(trim_copy(input), output);
@@ -1174,19 +1169,16 @@ bool lexical_cast(const std::string &input, T &output) {
}
}
// remove locale-specific group separators
char group_separator = std::use_facet<std::numpunct<char>>(std::locale()).thousands_sep();
if(input.find_first_of(group_separator) != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), group_separator), nstring.end());
return lexical_cast(nstring, output);
}
// remove separators
if(input.find_first_of("_'") != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end());
nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end());
return lexical_cast(nstring, output);
// remove separators if present
auto group_separators = get_group_separators();
if(input.find_first_of(group_separators) != std::string::npos) {
for(auto &separator : group_separators) {
if(input.find_first_of(separator) != std::string::npos) {
std::string nstring = input;
nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end());
return lexical_cast(nstring, output);
}
}
}
return false;
}

View File

@@ -419,6 +419,8 @@ CLI11_INLINE bool App::remove_option(Option *opt) {
help_ptr_ = nullptr;
if(help_all_ptr_ == opt)
help_all_ptr_ = nullptr;
if(config_ptr_ == opt)
config_ptr_ = nullptr;
auto iterator =
std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
@@ -1790,9 +1792,8 @@ CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool ha
ConfigItem item;
item.name = posOpt->pname_;
item.inputs.push_back(positional);
if(!_add_flag_like_result(posOpt, item, item.inputs)) {
posOpt->add_result(positional);
}
// input is singular guaranteed to return true in that case
_add_flag_like_result(posOpt, item, item.inputs);
} else {
posOpt->add_result(positional);
}

View File

@@ -123,6 +123,15 @@ CLI11_INLINE bool valid_name_string(const std::string &str) {
return true;
}
CLI11_INLINE std::string get_group_separators() {
std::string separators{"_'"};
#if CLI11_HAS_RTTI != 0
char group_separator = std::use_facet<std::numpunct<char>>(std::locale()).thousands_sep();
separators.push_back(group_separator);
#endif
return separators;
}
CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) {
std::size_t start_pos = 0;

View File

@@ -315,7 +315,7 @@ TEST_CASE("app_roundtrip_custom") {
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
int index = GENERATE(range(1, 12));
int index = GENERATE(range(1, 13));
auto parseData = loadFailureFile("round_trip_custom", index);
std::size_t pstring_start{0};
pstring_start = fuzzdata.add_custom_options(app.get(), parseData);

View File

@@ -206,6 +206,13 @@ TEST_CASE_METHOD(TApp, "subcommandPrefixMultiple", "[subcom]") {
args = {"sub_"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
args = {"sub_long"};
// now turning prefix matching off on main app but left on in subcommands
app.allow_subcommand_prefix_matching(false);
run();
// as the subcommands can specifically match on prefix the first is matched even if there is ambiguity
CHECK(app.got_subcommand("sub_long_prefix"));
CHECK(1u == sub1->count());
}
TEST_CASE_METHOD(TApp, "RequiredAndSubcommands", "[subcom]") {

Binary file not shown.

View File

@@ -23,6 +23,7 @@ class CustomThousandsSeparator : public std::numpunct<char> {
std::string do_grouping() const override { return "\2"; } // Group digits in sets of 2
};
#if CLI11_HAS_RTTI != 0
// derived from https://github.com/CLIUtils/CLI11/pull/1160
TEST_CASE_METHOD(TApp, "locale", "[separators]") {
std::locale customLocale(std::locale::classic(), new CustomThousandsSeparator);
@@ -30,9 +31,9 @@ TEST_CASE_METHOD(TApp, "locale", "[separators]") {
// Ensure standard streams use the custom locale automatically
std::cout.imbue(std::locale());
std::int64_t foo;
std::uint64_t bar;
float qux;
std::int64_t foo{0};
std::uint64_t bar{0};
float qux{0};
app.add_option("FOO", foo, "Foo option")->default_val(1234567)->force_callback();
app.add_option("BAR", bar, "Bar option")->default_val(2345678)->force_callback();
@@ -43,3 +44,4 @@ TEST_CASE_METHOD(TApp, "locale", "[separators]") {
CHECK(bar == 2345678);
CHECK_THAT(qux, Catch::Matchers::WithinAbs(3456.78, 0.01));
}
#endif