replace boost::ifind_first with std::ranges::search
Use a custom case-insensitive search to remove Boost dependency and
reduce compilation memory usage, keeping original string replacement
behavior.
Change-Id: I5778b208dfdb0082515e92f7bda335beb94c21cb
Signed-off-by: George Liu <liuxiwei@ieisystem.com>
diff --git a/src/entity_manager/utils.cpp b/src/entity_manager/utils.cpp
index 14b4f98..14f3c79 100644
--- a/src/entity_manager/utils.cpp
+++ b/src/entity_manager/utils.cpp
@@ -7,7 +7,6 @@
 
 #include <boost/algorithm/string/case_conv.hpp>
 #include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/find.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/algorithm/string/split.hpp>
 #include <phosphor-logging/lg2.hpp>
@@ -92,38 +91,31 @@
     // Walking through the string to find $<templateVar>
     while (true)
     {
-        boost::iterator_range<std::string::const_iterator> findStart =
-            boost::ifind_first(*strPtr, std::string_view(templateChar));
-
-        if (!findStart)
+        auto [firstIndex, lastIndex] = iFindFirst(*strPtr, templateChar);
+        if (firstIndex == std::string_view::npos)
         {
             break;
         }
 
-        boost::iterator_range<std::string::iterator> searchRange(
-            strPtr->begin() + (findStart.end() - strPtr->begin()),
-            strPtr->end());
-        boost::iterator_range<std::string::const_iterator> findSpace =
-            boost::ifind_first(searchRange, " ");
-
-        std::string::const_iterator templateVarEnd;
-
-        if (!findSpace)
+        size_t templateVarEndIndex = 0;
+        auto [firstSpaceIndex, _] = iFindFirst(strPtr->substr(lastIndex), " ");
+        if (firstSpaceIndex == std::string_view::npos)
         {
             // No space means the template var spans to the end of
             // of the keyPair value
-            templateVarEnd = strPtr->end();
+            templateVarEndIndex = strPtr->size();
         }
         else
         {
             // A space marks the end of a template var
-            templateVarEnd = findSpace.begin();
+            templateVarEndIndex = lastIndex + firstSpaceIndex;
         }
 
         lg2::error(
             "There's still template variable {VAR} un-replaced. Removing it from the string.\n",
-            "VAR", std::string(findStart.begin(), templateVarEnd));
-        strPtr->erase(findStart.begin(), templateVarEnd);
+            "VAR",
+            strPtr->substr(firstIndex, templateVarEndIndex - firstIndex));
+        strPtr->erase(firstIndex, templateVarEndIndex - firstIndex);
     }
 }
 
@@ -183,17 +175,14 @@
     for (const auto& [propName, propValue] : interface)
     {
         std::string templateName = templateChar + propName;
-        boost::iterator_range<std::string::const_iterator> find =
-            boost::ifind_first(*strPtr, templateName);
-        if (!find)
+        auto [start, endIdx] = iFindFirst(*strPtr, templateName);
+        if (start == std::string::npos)
         {
             continue;
         }
 
-        size_t start = find.begin() - strPtr->begin();
-
         // check for additional operations
-        if ((start == 0U) && find.end() == strPtr->end())
+        if ((start == 0U) && endIdx == strPtr->size())
         {
             std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
             return ret;
@@ -243,7 +232,7 @@
 
         number = expression::evaluate(number, exprBegin, exprEnd);
 
-        std::string replaced(find.begin(), find.end());
+        std::string replaced(strPtr->begin() + start, strPtr->begin() + endIdx);
         while (exprBegin != exprEnd)
         {
             replaced.append(" ").append(*exprBegin++);
diff --git a/src/utils.cpp b/src/utils.cpp
index 4a35756..d5eb30b 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -4,7 +4,6 @@
 #include "utils.hpp"
 
 #include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/find.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/algorithm/string/split.hpp>
 #include <boost/container/flat_map.hpp>
@@ -12,9 +11,14 @@
 #include <phosphor-logging/lg2.hpp>
 #include <sdbusplus/bus/match.hpp>
 
+#include <algorithm>
+#include <cctype>
 #include <filesystem>
 #include <map>
+#include <ranges>
 #include <regex>
+#include <string_view>
+#include <utility>
 
 namespace fs = std::filesystem;
 
@@ -176,3 +180,34 @@
 {
     return std::visit(MatchProbeForwarder(probe), dbusValue);
 }
+
+inline char asciiToLower(char c)
+{
+    // Converts a character to lower case without relying on std::locale
+    if ('A' <= c && c <= 'Z')
+    {
+        c -= static_cast<char>('A' - 'a');
+    }
+    return c;
+}
+
+std::pair<FirstIndex, LastIndex> iFindFirst(std::string_view str,
+                                            std::string_view sub)
+{
+    if (sub.empty())
+    {
+        return {std::string_view::npos, std::string_view::npos};
+    }
+    auto result = std::ranges::search(str, sub, [](char a, char b) {
+        return asciiToLower(a) == asciiToLower(b);
+    });
+
+    if (!result.empty())
+    {
+        size_t start = static_cast<size_t>(
+            std::ranges::distance(str.begin(), result.begin()));
+        return {start, start + sub.size()};
+    }
+
+    return {std::string_view::npos, std::string_view::npos};
+}
diff --git a/src/utils.hpp b/src/utils.hpp
index 3a3a083..e51ed1e 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -18,6 +18,8 @@
 using DBusObject = boost::container::flat_map<std::string, DBusInterface>;
 using MapperGetSubTreeResponse =
     boost::container::flat_map<std::string, DBusObject>;
+using FirstIndex = size_t;
+using LastIndex = size_t;
 
 bool findFiles(const std::filesystem::path& dirPath,
                const std::string& matchString,
@@ -75,6 +77,9 @@
 /// \return true if the dbusValue matched the probe otherwise false.
 bool matchProbe(const nlohmann::json& probe, const DBusValueVariant& dbusValue);
 
+std::pair<FirstIndex, LastIndex> iFindFirst(std::string_view str,
+                                            std::string_view sub);
+
 template <typename T>
 std::from_chars_result fromCharsWrapper(const std::string_view& str, T& out,
                                         bool& fullMatch, int base = 10)
diff --git a/test/meson.build b/test/meson.build
index a22fd31..50f0901 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -54,3 +54,14 @@
         link_with: gpio_presence_lib,
     ),
 )
+
+test(
+    'test_utils',
+    executable(
+        'test_utils',
+        'test_utils.cpp',
+        '../src/utils.cpp',
+        include_directories: test_include_dir,
+        dependencies: [gtest, phosphor_logging_dep],
+    ),
+)
diff --git a/test/test_utils.cpp b/test/test_utils.cpp
new file mode 100644
index 0000000..76570df
--- /dev/null
+++ b/test/test_utils.cpp
@@ -0,0 +1,52 @@
+#include "utils.hpp"
+
+#include <gtest/gtest.h>
+
+TEST(IfindFirstTest, BasicMatch)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("Hello World", "World");
+    EXPECT_EQ(firstIndex, 6);
+    EXPECT_EQ(lastIndex, 11);
+}
+
+TEST(IfindFirstTest, CaseInsensitiveMatch)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("Hello World", "world");
+    EXPECT_EQ(firstIndex, 6);
+    EXPECT_EQ(lastIndex, 11);
+}
+
+TEST(IfindFirstTest, NoMatch)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("Hello World", "Planet");
+    EXPECT_EQ(firstIndex, std::string_view::npos);
+    EXPECT_EQ(lastIndex, std::string_view::npos);
+}
+
+TEST(IfindFirstTest, MatchAtStart)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("Hello World", "HeLLo");
+    EXPECT_EQ(firstIndex, 0);
+    EXPECT_EQ(lastIndex, 5);
+}
+
+TEST(IfindFirstTest, MatchAtEnd)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("Hello World", "LD");
+    EXPECT_EQ(firstIndex, 9);
+    EXPECT_EQ(lastIndex, 11);
+}
+
+TEST(IfindFirstTest, EmptySubstring)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("Hello", "");
+    EXPECT_EQ(firstIndex, std::string_view::npos);
+    EXPECT_EQ(lastIndex, std::string_view::npos);
+}
+
+TEST(IfindFirstTest, EmptyString)
+{
+    auto [firstIndex, lastIndex] = iFindFirst("", "Hello");
+    EXPECT_EQ(firstIndex, std::string_view::npos);
+    EXPECT_EQ(lastIndex, std::string_view::npos);
+}