sdbus++: include headers for enums inside complex types

Enumerations can be inside complex types such as 'array[enum[...]]', but
these enums were not previously included in the generated `#include`
directives.  Support enums buried inside a complex type for the header
file `#include` generation.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I40accda96ad355d1ffeb039a24f8007bf3f214cf
diff --git a/tools/sdbusplus/interface.py b/tools/sdbusplus/interface.py
index 274359f..69d08db 100644
--- a/tools/sdbusplus/interface.py
+++ b/tools/sdbusplus/interface.py
@@ -30,8 +30,11 @@
 
     def enum_includes(self, inc_list):
         includes = []
+        namespaces = []
         for e in inc_list:
-            es = e.enum_namespace(self.name).split("::")
+            namespaces.extend(e.enum_namespaces(self.name))
+        for e in sorted(set(namespaces)):
+            es = e.split("::")
             # Skip empty, non-enum values and self references like '::'
             if len(es) < 2:
                 continue
diff --git a/tools/sdbusplus/property.py b/tools/sdbusplus/property.py
index 2f0ce57..777025c 100644
--- a/tools/sdbusplus/property.py
+++ b/tools/sdbusplus/property.py
@@ -9,9 +9,8 @@
     NONLOCAL_ENUM_MAGIC = "<NONLOCAL_ENUM>"
 
     def __init__(self, **kwargs):
-        self.enum = False
         self.typeName = kwargs.pop('type', None)
-        self.cppTypeName = self.parse_cpp_type(self.typeName)
+        self.cppTypeName = self.parse_cpp_type()
         self.defaultValue = kwargs.pop('default', None)
         self.flags = kwargs.pop('flags', [])
         self.cpp_flags = self.or_cpp_flags(self.flags)
@@ -30,12 +29,17 @@
         super(Property, self).__init__(**kwargs)
 
     def is_enum(self):
-        return self.enum
+        if not self.typeName:
+            return false
+        return 'enum' == self.__type_tuple()[0]
 
     """ Return a conversion of the cppTypeName valid as a function parameter.
         Currently only 'enum' requires conversion.
     """
     def cppTypeParam(self, interface, full=False, server=True):
+        return self.__cppTypeParam(interface, self.cppTypeName, full, server)
+
+    def __cppTypeParam(self, interface, cppTypeName, full=False, server=True):
 
         ns_type = "server" if server else "client"
 
@@ -43,7 +47,7 @@
         iface.insert(-1, ns_type)
         iface = "::".join(iface)
 
-        r = self.cppTypeName
+        r = cppTypeName
 
         # Fix up local enum placeholders.
         if full:
@@ -56,30 +60,57 @@
 
         return r
 
-    def enum_namespace(self, interface):
-        if not self.is_enum():
-            return ""
-        ns = self.cppTypeParam(interface).split("::")[0:-1]
-        if len(ns) != 0:
-            return "::".join(ns) + "::"
-        return ""
+    """ Determine the C++ namespaces of an enumeration-type property.
+    """
+    def enum_namespaces(self, interface):
+        typeTuple = self.__type_tuple()
+        return self.__enum_namespaces(interface, typeTuple)
 
-    def enum_name(self, interface):
-        if not self.is_enum():
-            return ""
-        return self.cppTypeParam(interface).split("::")[-1]
+    def __enum_namespaces(self, interface, typeTuple):
+        # Enums can be processed directly.
+        if 'enum' == typeTuple[0]:
+            cppType = self.__cppTypeParam(interface,
+                                          self.__parse_cpp_type__(typeTuple))
+            ns = cppType.split("::")[0:-1]
+            if len(ns) != 0:
+                return ["::".join(ns) + "::"]
+            return []
 
-    def parse_cpp_type(self, typeName):
-        if not typeName:
+        # If the details part of the tuple has zero entries, no enums are
+        # present
+        if len(typeTuple[1]) == 0:
+            return []
+
+        # Otherwise, the tuple-type might have enums embedded in it.  Handle
+        # them recursively.
+        r = []
+        for t in typeTuple[1]:
+            r.extend(self.__enum_namespaces(interface, t))
+        return r
+
+    """ Convert the property type into a C++ type.
+    """
+    def parse_cpp_type(self):
+        if not self.typeName:
+            return None
+
+        typeTuple = self.__type_tuple()
+        return self.__parse_cpp_type__(typeTuple)
+
+    """ Convert the 'typeName' into a tuple of ('type', [ details ]) where
+        'details' is a recursive type-tuple.
+    """
+    def __type_tuple(self):
+        if not self.typeName:
             return None
 
         """ typeNames are _almost_ valid YAML.  Insert a , before each [
             and then wrap it in a [ ] and it becomes valid YAML.  (assuming
             the user gave us a valid typename)
         """
+        typeName = self.typeName
         typeArray = yaml.safe_load("[" + ",[".join(typeName.split("[")) + "]")
-        typeTuple = self.__preprocess_yaml_type_array(typeArray).pop(0)
-        return self.__parse_cpp_type__(typeTuple, top_type=True)
+        return self.__preprocess_yaml_type_array(typeArray).pop(0)
 
     """ Take a list of dbus types from YAML and convert it to a recursive data
         structure. Each entry of the original list gets converted into a tuple
@@ -113,7 +144,7 @@
             [ variant [ dict [ int32, int32 ], double ] ]
         This function then converts the type-list into a C++ type string.
     """
-    def __parse_cpp_type__(self, typeTuple, top_type=False):
+    def __parse_cpp_type__(self, typeTuple):
         propertyMap = {
             'byte': {'cppName': 'uint8_t', 'params': 0},
             'boolean': {'cppName': 'bool', 'params': 0},
@@ -164,8 +195,6 @@
 
         # Switch enum for proper type.
         if result == 'enum':
-            if top_type:
-                self.enum = True
             result = rest[0][0]
 
             # self. means local type.