From 92adf2c8c2621d20a44cc1557b9187c95197a0ae Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 27 Jan 2025 19:14:18 -0800 Subject: [PATCH] Allow trailing spaces on strings for conversions (#1115) Allow trailing spaces on strings for conversions to floating point and integers. There was a potential confusing error that could occur if strings with trailing spaces were converted to integer or floating point values. This PR allows trailing spaces in the strings that would otherwise be convertible to the appropriate numerical type. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- include/CLI/TypeTools.hpp | 15 ++++++++++++++- tests/OptionTypeTest.cpp | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 5c268977..4b56f10c 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -968,6 +968,9 @@ bool integral_conversion(const std::string &input, T &output) noexcept { nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); return integral_conversion(nstring, output); } + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { val = nullptr; errno = 0; @@ -1016,13 +1019,16 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast(1); return true; } - // remove separators + // 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); } + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { val = nullptr; errno = 0; @@ -1147,6 +1153,13 @@ bool lexical_cast(const std::string &input, T &output) { if(val == (input.c_str() + input.size())) { return true; } + while(std::isspace(static_cast(*val))) { + ++val; + if(val == (input.c_str() + input.size())) { + return true; + } + } + // remove separators if(input.find_first_of("_'") != std::string::npos) { std::string nstring = input; diff --git a/tests/OptionTypeTest.cpp b/tests/OptionTypeTest.cpp index c3012755..21262649 100644 --- a/tests/OptionTypeTest.cpp +++ b/tests/OptionTypeTest.cpp @@ -227,9 +227,13 @@ TEST_CASE_METHOD(TApp, "atomic_int_option", "[optiontype]") { static const std::map testValuesDouble{ {"3.14159", 3.14159}, {"-3.14159", -3.14159}, + {"-3.14159\t", -3.14159}, + {"-3.14159 ", -3.14159}, {"+1.0", 1.0}, {"-0.01", -0.01}, {"5e22", 5e22}, + {" 5e22", 5e22}, + {" 5e22 ", 5e22}, {"-2E-2", -2e-2}, {"5e+22", 5e22}, {"1e06", 1e6}, @@ -268,6 +272,7 @@ static const std::map testValuesInt{ {"+99", 99}, {"99", 99}, {"-99", -99}, + {"-99 ", -99}, {"0xDEADBEEF", 0xDEADBEEF}, {"0xdeadbeef", 0xDEADBEEF}, {"0XDEADBEEF", 0xDEADBEEF}, @@ -280,6 +285,7 @@ static const std::map testValuesInt{ {"995862_262", 995862262}, {"995862262", 995862262}, {"-995862275", -995862275}, + {"\t-995862275\t", -995862275}, {"-995'862'275", -995862275}, {"0b11010110", 0xD6}, {"0b1101'0110", 0xD6}, @@ -323,6 +329,7 @@ TEST_CASE_METHOD(TApp, "intConversionsErange", "[optiontype]") { static const std::map testValuesUInt{ {"+99", 99}, {"99", 99}, + {" 99 ", 99}, {"0xDEADBEEF", 0xDEADBEEF}, {"0xdeadbeef", 0xDEADBEEF}, {"0XDEADBEEF", 0xDEADBEEF}, @@ -331,13 +338,16 @@ static const std::map testValuesUInt{ {"0xdead'beef", 0xDEADBEEF}, {"0o01234567", 001234567}, {"0o755", 0755}, + {"0o755\t", 0755}, {"0755", 0755}, {"995862_262", 995862262}, {"995862262", 995862262}, {"+995862275", +995862275}, + {"+995862275 \n\t", +995862275}, {"995'862'275", 995862275}, {"0b11010110", 0xD6}, {"0b1101'0110", 0xD6}, + {"0b1101'0110 ", 0xD6}, {"0B11010110", 0xD6}, {"0B1101'0110", 0xD6}, {"1_2_3_4_5", 12345},