Add Unit Test for TemplateCharReplace

We keep finding issues in this function, add some tests.

Tested: The tests pass, this is mostly just moving the
code into a header. One test didn't work, but it didn't
work before either. It will be fixed later

Change-Id: I3eb60960104e861b0a0313580dc90bfb12051829
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/src/Utils.cpp b/src/Utils.cpp
index ac16d36..4662c2a 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -14,7 +14,17 @@
 // limitations under the License.
 */
 
-#include <Utils.hpp>
+#include "Utils.hpp"
+
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/find.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/lexical_cast.hpp>
 #include <filesystem>
 #include <fstream>
 #include <regex>
@@ -24,6 +34,8 @@
 #include <valijson/schema_parser.hpp>
 #include <valijson/validator.hpp>
 
+constexpr const char* templateChar = "$";
+
 namespace fs = std::filesystem;
 static bool powerStatusOn = false;
 static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
@@ -124,4 +136,222 @@
         "namespace='/xyz/openbmc_project/Chassis/Control/"
         "Power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
         eventHandler);
+}
+
+// finds the template character (currently set to $) and replaces the value with
+// the field found in a dbus object i.e. $ADDRESS would get populated with the
+// ADDRESS field from a object on dbus
+void templateCharReplace(
+    nlohmann::json::iterator& keyPair,
+    const boost::container::flat_map<std::string, BasicVariantType>&
+        foundDevice,
+    const size_t foundDeviceIdx)
+{
+    if (keyPair.value().type() == nlohmann::json::value_t::object ||
+        keyPair.value().type() == nlohmann::json::value_t::array)
+    {
+        for (auto nextLayer = keyPair.value().begin();
+             nextLayer != keyPair.value().end(); nextLayer++)
+        {
+            templateCharReplace(nextLayer, foundDevice, foundDeviceIdx);
+        }
+        return;
+    }
+
+    std::string* strPtr = keyPair.value().get_ptr<std::string*>();
+    if (strPtr == nullptr)
+    {
+        return;
+    }
+
+    boost::replace_all(*strPtr, std::string(templateChar) + "index",
+                       std::to_string(foundDeviceIdx));
+
+    for (auto& foundDevicePair : foundDevice)
+    {
+        std::string templateName = templateChar + foundDevicePair.first;
+        boost::iterator_range<std::string::const_iterator> find =
+            boost::ifind_first(*strPtr, templateName);
+        if (find)
+        {
+            size_t start = find.begin() - strPtr->begin();
+            // check for additional operations
+            if (!start && find.end() == strPtr->end())
+            {
+                std::visit([&](auto&& val) { keyPair.value() = val; },
+                           foundDevicePair.second);
+                return;
+            }
+            else if (find.end() == strPtr->end())
+            {
+                std::string val = std::visit(VariantToStringVisitor(),
+                                             foundDevicePair.second);
+                boost::replace_all(*strPtr,
+                                   templateChar + foundDevicePair.first, val);
+                return;
+            }
+
+            // save the prefix
+            std::string prefix = strPtr->substr(0, start);
+
+            // operate on the rest (+1 for trailing space)
+            std::string end = strPtr->substr(start + templateName.size() + 1);
+
+            std::vector<std::string> split;
+            boost::split(split, end, boost::is_any_of(" "));
+
+            // need at least 1 operation and number
+            if (split.size() < 2)
+            {
+                std::cerr << "Syntax error on template replacement of "
+                          << *strPtr << "\n";
+                for (const std::string& data : split)
+                {
+                    std::cerr << data << " ";
+                }
+                std::cerr << "\n";
+                continue;
+            }
+
+            // we assume that the replacement is a number, because we can
+            // only do math on numbers.. we might concatenate strings in the
+            // future, but thats later
+            int number =
+                std::visit(VariantToIntVisitor(), foundDevicePair.second);
+
+            bool isOperator = true;
+            TemplateOperation next = TemplateOperation::addition;
+
+            auto it = split.begin();
+
+            for (; it != split.end(); it++)
+            {
+                if (isOperator)
+                {
+                    if (*it == "+")
+                    {
+                        next = TemplateOperation::addition;
+                    }
+                    else if (*it == "-")
+                    {
+                        next = TemplateOperation::subtraction;
+                    }
+                    else if (*it == "*")
+                    {
+                        next = TemplateOperation::multiplication;
+                    }
+                    else if (*it == R"(%)")
+                    {
+                        next = TemplateOperation::modulo;
+                    }
+                    else if (*it == R"(/)")
+                    {
+                        next = TemplateOperation::division;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+                else
+                {
+                    int constant = 0;
+                    try
+                    {
+                        constant = std::stoi(*it);
+                    }
+                    catch (std::invalid_argument&)
+                    {
+                        std::cerr << "Parameter not supported for templates "
+                                  << *it << "\n";
+                        continue;
+                    }
+                    switch (next)
+                    {
+                        case TemplateOperation::addition:
+                        {
+                            number += constant;
+                            break;
+                        }
+                        case TemplateOperation::subtraction:
+                        {
+                            number -= constant;
+                            break;
+                        }
+                        case TemplateOperation::multiplication:
+                        {
+                            number *= constant;
+                            break;
+                        }
+                        case TemplateOperation::division:
+                        {
+                            number /= constant;
+                            break;
+                        }
+                        case TemplateOperation::modulo:
+                        {
+                            number = number % constant;
+                            break;
+                        }
+
+                        default:
+                            break;
+                    }
+                }
+                isOperator = !isOperator;
+            }
+            std::string result = prefix + std::to_string(number);
+
+            if (it != split.end())
+            {
+                for (; it != split.end(); it++)
+                {
+                    result += " " + *it;
+                }
+            }
+            keyPair.value() = result;
+
+            // We probably just invalidated the pointer above, so set it to null
+            strPtr = nullptr;
+            break;
+        }
+    }
+
+    strPtr = keyPair.value().get_ptr<std::string*>();
+    if (strPtr == nullptr)
+    {
+        return;
+    }
+
+    // convert hex numbers to ints
+    if (boost::starts_with(*strPtr, "0x"))
+    {
+        try
+        {
+            size_t pos = 0;
+            int64_t temp = std::stoul(*strPtr, &pos, 0);
+            if (pos == strPtr->size())
+            {
+                keyPair.value() = static_cast<uint64_t>(temp);
+            }
+        }
+        catch (std::invalid_argument&)
+        {
+        }
+        catch (std::out_of_range&)
+        {
+        }
+    }
+    // non-hex numbers
+    else
+    {
+        try
+        {
+            uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
+            keyPair.value() = temp;
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+    }
 }
\ No newline at end of file