sdbus++: common: generate object_paths

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: Iacaa65fa1063b34269d55df3c581e5921dbbc300
diff --git a/tools/meson.build b/tools/meson.build
index d290b5f..bce5559 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -7,6 +7,7 @@
   'sdbusplus/main.py',
   'sdbusplus/method.py',
   'sdbusplus/namedelement.py',
+  'sdbusplus/path.py',
   'sdbusplus/property.py',
   'sdbusplus/renderer.py',
   'sdbusplus/signal.py',
diff --git a/tools/sdbusplus/interface.py b/tools/sdbusplus/interface.py
index 734a1fe..d6c2bac 100644
--- a/tools/sdbusplus/interface.py
+++ b/tools/sdbusplus/interface.py
@@ -5,6 +5,7 @@
 from .enum import Enum
 from .method import Method
 from .namedelement import NamedElement
+from .path import Path
 from .property import Property
 from .renderer import Renderer
 from .signal import Signal
@@ -28,6 +29,7 @@
         self.methods = [Method(**m) for m in kwargs.pop("methods", [])]
         self.signals = [Signal(**s) for s in kwargs.pop("signals", [])]
         self.enums = [Enum(**e) for e in kwargs.pop("enumerations", [])]
+        self.paths = [Path(**p) for p in kwargs.pop("paths", [])]
 
         super(Interface, self).__init__(**kwargs)
 
diff --git a/tools/sdbusplus/path.py b/tools/sdbusplus/path.py
new file mode 100644
index 0000000..c3a9e13
--- /dev/null
+++ b/tools/sdbusplus/path.py
@@ -0,0 +1,44 @@
+import re
+
+from .namedelement import NamedElement
+
+""" Class for parsing 'path' definition elements from an interface.
+"""
+
+
+class Path(NamedElement):
+    def __init__(self, segment=False, **kwargs):
+        if "namespace" in kwargs:
+            kwargs["name"] = "NamespacePath"
+            self.value = kwargs.pop("namespace")
+        elif "instance" in kwargs:
+            kwargs["name"] = "InstancePath"
+            self.value = kwargs.pop("instance")
+        else:
+            self.value = kwargs.pop("value", False)
+
+        # Validate path/segment format.
+        if len(self.value) == 0:
+            raise ValueError("Invalid empty path.")
+        if not segment and self.value[0] != "/":
+            raise ValueError(f"Paths must start with /: {self.value}")
+        if segment and self.value[0] == "/":
+            raise ValueError(f"Segments canot start with /: {self.value}")
+        segments = self.value.split("/")
+        if not segment:
+            segments = segments[1:]
+        for s in segments:
+            if len(s) == 0:
+                raise ValueError(
+                    f"Paths cannot have consecutive /: {self.value} {s}"
+                )
+            if re.search("[^a-zA-Z0-9_]", s):
+                raise ValueError(
+                    f"Path contains illegal characters: {self.value}"
+                )
+
+        self.segments = [
+            Path(segment=True, **s) for s in kwargs.pop("segments", [])
+        ]
+
+        super(Path, self).__init__(**kwargs)
diff --git a/tools/sdbusplus/templates/interface.common.hpp.mako b/tools/sdbusplus/templates/interface.common.hpp.mako
index d33fe12..6331245 100644
--- a/tools/sdbusplus/templates/interface.common.hpp.mako
+++ b/tools/sdbusplus/templates/interface.common.hpp.mako
@@ -25,6 +25,26 @@
     };
     % endfor
 
+    % for p in interface.paths:
+        % if p.description:
+    /** ${p.description.strip()} */
+        % endif
+        % if len(p.segments) == 0:
+    static constexpr auto ${p.snake_case} = "${p.value}";
+        % else:
+    struct ${p.snake_case}
+    {
+        static constexpr auto value = "${p.value}";
+            % for s in p.segments:
+                % if s.description:
+        /** ${s.description.strip()} */
+                % endif
+        static constexpr auto ${s.snake_case} = "${s.value}";
+            % endfor
+    };
+        % endif
+    % endfor
+
     % for e in interface.enums:
     /** @brief Convert a string to an appropriate enum value.
      *  @param[in] s - The string to convert in the form of