sysfs: Refactor LED property parsing

Added new test for parsing led description from sysfs.

Since there are some edge cases that can happen, to make sure the
parsing happens as expected in all cases.

The edge cases primarily come from the different led properties that can
be present or absent in devicetree. I have tested some combinations
thereof and would prefer the label to be generated by led sysfs instead
of manually providing the 3-component label.

However for that to work phosphor-led-sysfs must be able to extract the
labels components in all cases.

This modifies the behavior slightly but it will stay the same
for led names that have 1 or 3 components.

Change-Id: I8def089e4c8dc5d3a341cf6f6b1d6356f5aefe48
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/sysfs.cpp b/sysfs.cpp
index b1ea456..137218d 100644
--- a/sysfs.cpp
+++ b/sysfs.cpp
@@ -16,9 +16,14 @@
 
 #include "sysfs.hpp"
 
+#include "phosphor-logging/lg2.hpp"
+
+#include <boost/algorithm/string.hpp>
+
 #include <fstream>
-#include <iostream>
+#include <optional>
 #include <string>
+#include <vector>
 
 namespace fs = std::filesystem;
 
@@ -119,5 +124,83 @@
 {
     setSysfsAttr<unsigned long>(root / attrDelayOff, ms);
 }
+
+/* LED sysfs name can be any of
+ *
+ * - devicename:color:function
+ * - devicename::function
+ * - color:function (e.g. "red:fault")
+ * - label (e.g. "identify")
+ * - :function (e.g. ":boot")
+ * - color: (e.g. "green:")
+ *
+ * but no one prevents us from making all of this up and creating
+ * a label with colons inside, e.g. "mylabel:mynoncolorstring:lala".
+ *
+ * Reference: https://www.kernel.org/doc/html/latest/leds/leds-class.html
+ *
+ * Summary: It's bonkers (not my words, but describes it well)
+ */
+LedDescr SysfsLed::getLedDescr()
+{
+    std::string name = std::string(root).substr(strlen(devParent));
+    LedDescr ledDescr;
+
+    std::vector<std::optional<std::string>> words;
+    std::stringstream ss(name);
+    std::string item;
+
+    while (std::getline(ss, item, ':'))
+    {
+        if (item.empty())
+        {
+            words.emplace_back(std::nullopt);
+        }
+        else
+        {
+            words.emplace_back(item);
+        }
+    }
+
+    if (name.ends_with(":"))
+    {
+        words.emplace_back(std::nullopt);
+    }
+
+    if (name.empty())
+    {
+        lg2::warning("LED description '{DESC}' was empty", "DESC", name);
+        throw std::out_of_range("expected non-empty LED name");
+    }
+
+    if (words.size() != 3)
+    {
+        lg2::warning(
+            "LED description '{DESC}' not well formed, expected 3 parts but got {NPARTS}",
+            "DESC", name, "NPARTS", words.size());
+    }
+
+    switch (words.size())
+    {
+        default:
+        case 3:
+            ledDescr.function = words.at(2);
+            ledDescr.color = words.at(1);
+            ledDescr.devicename = words.at(0);
+            break;
+        case 2:
+            ledDescr.color = words.at(0);
+            ledDescr.function = words.at(1);
+            break;
+        case 1:
+            ledDescr.devicename = words.at(0);
+            break;
+        case 0:
+            throw std::out_of_range("expected non-empty LED name");
+    }
+
+    // if there is more than 3 parts we ignore the rest
+    return ledDescr;
+}
 } // namespace led
 } // namespace phosphor