From 559016f26b37f102dfc72348c0c4530979006c2e Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 1 May 2024 14:07:14 -0700 Subject: [PATCH] dev: Add Strutil::eval_as_bool (#4250) Signed-off-by: Larry Gritz --- src/include/OpenImageIO/strutil.h | 13 +++++++++ src/libutil/strutil.cpp | 26 +++++++++++++++++ src/libutil/strutil_test.cpp | 48 +++++++++++++++++++++++++++++++ src/oiiotool/oiiotool.cpp | 4 +-- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/include/OpenImageIO/strutil.h b/src/include/OpenImageIO/strutil.h index fc67e96d14..d04f1d299c 100644 --- a/src/include/OpenImageIO/strutil.h +++ b/src/include/OpenImageIO/strutil.h @@ -1159,6 +1159,19 @@ OIIO_UTIL_API size_t edit_distance(string_view a, string_view b, EditDistMetric metric = EditDistMetric::Levenshtein); + +/// Evaluate a string as a boolean value using the following heuristic: +/// - If the string is a valid numeric value (represents an integer or +/// floating point value), return true if it's non-zero, false if it's +/// zero. +/// - If the string is one of "false", "no", or "off", or if it contains +/// only whitespace, return false. +/// - All other non-empty strings return true. +/// The comparisons are case-insensitive and ignore leading and trailing +/// whitespace. +OIIO_UTIL_API bool +eval_as_bool(string_view value); + } // namespace Strutil diff --git a/src/libutil/strutil.cpp b/src/libutil/strutil.cpp index 4c8ded417c..2435afd534 100644 --- a/src/libutil/strutil.cpp +++ b/src/libutil/strutil.cpp @@ -1872,4 +1872,30 @@ Strutil::edit_distance(string_view a, string_view b, EditDistMetric metric) return levenshtein_distance(a, b); } + + +/// Interpret a string as a boolean value using the following heuristic: +/// - If the string is a valid numeric value (represents an integer or +/// floating point value), return true if it's non-zero, false if it's +/// zero. +/// - If the string is one of "false", "no", or "off", or if it contains +/// only whitespace, return false. +/// - All other non-empty strings return true. +/// The comparisons are case-insensitive and ignore leading and trailing +/// whitespace. +bool +Strutil::eval_as_bool(string_view value) +{ + Strutil::trim_whitespace(value); + if (Strutil::string_is_int(value)) { + return Strutil::stoi(value) != 0; + } else if (Strutil::string_is_float(value)) { + return Strutil::stof(value) != 0.0f; + } else { + return !(value.empty() || Strutil::iequals(value, "false") + || Strutil::iequals(value, "no") + || Strutil::iequals(value, "off")); + } +} + OIIO_NAMESPACE_END diff --git a/src/libutil/strutil_test.cpp b/src/libutil/strutil_test.cpp index 5cdde17e0f..a057931ed7 100644 --- a/src/libutil/strutil_test.cpp +++ b/src/libutil/strutil_test.cpp @@ -1677,6 +1677,53 @@ test_base64_encode() +void +test_eval_as_bool() +{ + using namespace Strutil; + print("testing eval_as_bool()\n"); + + // Test cases for integer values + OIIO_CHECK_EQUAL(eval_as_bool("0"), false); + OIIO_CHECK_EQUAL(eval_as_bool("1"), true); + OIIO_CHECK_EQUAL(eval_as_bool("-1"), true); + OIIO_CHECK_EQUAL(eval_as_bool("10"), true); + OIIO_CHECK_EQUAL(eval_as_bool("-10"), true); + + // Test cases for floating-point values + OIIO_CHECK_EQUAL(eval_as_bool("0.0"), false); + OIIO_CHECK_EQUAL(eval_as_bool("1.0"), true); + OIIO_CHECK_EQUAL(eval_as_bool("-1.0"), true); + OIIO_CHECK_EQUAL(eval_as_bool("10.5"), true); + OIIO_CHECK_EQUAL(eval_as_bool("-10.5"), true); + + // Test cases for string values + OIIO_CHECK_EQUAL(eval_as_bool(""), false); + OIIO_CHECK_EQUAL(eval_as_bool("false"), false); + OIIO_CHECK_EQUAL(eval_as_bool("FALSE"), false); + OIIO_CHECK_EQUAL(eval_as_bool("no"), false); + OIIO_CHECK_EQUAL(eval_as_bool("NO"), false); + OIIO_CHECK_EQUAL(eval_as_bool("off"), false); + OIIO_CHECK_EQUAL(eval_as_bool("OFF"), false); + + OIIO_CHECK_EQUAL(eval_as_bool("true"), true); + OIIO_CHECK_EQUAL(eval_as_bool("TRUE"), true); + OIIO_CHECK_EQUAL(eval_as_bool("yes"), true); + OIIO_CHECK_EQUAL(eval_as_bool("YES"), true); + OIIO_CHECK_EQUAL(eval_as_bool("on"), true); + OIIO_CHECK_EQUAL(eval_as_bool("ON"), true); + OIIO_CHECK_EQUAL(eval_as_bool("OpenImageIO"), true); + + // Test whitespace, case insensitivity, other tricky cases + OIIO_CHECK_EQUAL(eval_as_bool(" "), false); + OIIO_CHECK_EQUAL(eval_as_bool("\t \n"), false); + OIIO_CHECK_EQUAL(eval_as_bool(" faLsE"), false); + OIIO_CHECK_EQUAL(eval_as_bool("\tOFf"), false); + OIIO_CHECK_EQUAL(eval_as_bool("off OpenImageIO"), true); +} + + + int main(int /*argc*/, char* /*argv*/[]) { @@ -1712,6 +1759,7 @@ main(int /*argc*/, char* /*argv*/[]) test_datetime(); test_edit_distance(); test_base64_encode(); + test_eval_as_bool(); Strutil::debug("debug message\n"); diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index d18d609c9c..58a745c447 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -1137,7 +1137,7 @@ control_if(Oiiotool& ot, cspan argv) if (ot.running()) { // string_view command = ot.express(argv[0]); string_view value = ot.express(argv[1]); - cond = eval_as_bool(value); + cond = Strutil::eval_as_bool(value); // Strutil::print("while: val='{}' cond={}\n", value, cond); } else { // If not running in the outer scope, don't even evaluate the @@ -1200,7 +1200,7 @@ control_while(Oiiotool& ot, cspan argv) if (ot.running()) { // string_view command = ot.express(argv[0]); string_view value = ot.express(argv[1]); - cond = eval_as_bool(value); + cond = Strutil::eval_as_bool(value); // Strutil::print("while: val='{}' cond={}\n", value, cond); } else { // If not running in the outer scope, don't even evaluate the