Add property and property group support

Add support for defining groups of DBus properties.  Groups are
a list of property/interface/metadata tuples.  Metadata can be used to
give a property context when required.

Change-Id: I5fc27f4d815b53332a2ddea2d270fde8e355dcbc
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/src/example/example.yaml b/src/example/example.yaml
index 87c9790..3d60396 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -26,3 +26,21 @@
       path: /xyz/openbmc_project/testing/inst3
     - meta: path
       path: /xyz/openbmc_project/testing/inst4
+
+- name: example property group
+  description: >
+    'Like path groups, a property group is a named collection
+    of DBus property names and associated metadata.
+
+    Properties in a group must all have the same DBus type signature
+    and must be explicitly declared.'
+  class: group
+  group: property
+  type: uint32
+  members:
+    - interface: xyz.openbmc_project.Sensor.Value
+      meta: property
+      property: ValueA
+    - interface: xyz.openbmc_project.Sensor.Value
+      meta: property
+      property: ValueB
diff --git a/src/pdmgen.py b/src/pdmgen.py
index fcda960..61546eb 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -22,6 +22,7 @@
 from argparse import ArgumentParser
 from sdbusplus.renderer import Renderer
 from sdbusplus.namedelement import NamedElement
+import sdbusplus.property
 
 
 class InvalidConfigError(BaseException):
@@ -159,6 +160,54 @@
         super(Path, self).setup(objs)
 
 
+class Property(ConfigEntry):
+    '''Property/interface/metadata association.'''
+
+    def __init__(self, *a, **kw):
+        super(Property, self).__init__(**kw)
+
+    def factory(self, objs):
+        '''Create interface, property name and metadata elements.'''
+
+        args = {
+            'class': 'interface',
+            'interface': 'element',
+            'name': self.name['interface']
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        args = {
+            'class': 'propertyname',
+            'propertyname': 'element',
+            'name': self.name['property']
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        args = {
+            'class': 'meta',
+            'meta': 'element',
+            'name': self.name['meta']
+        }
+        add_unique(ConfigEntry(
+            configfile=self.configfile, **args), objs)
+
+        super(Property, self).factory(objs)
+
+    def setup(self, objs):
+        '''Resolve interface, property and metadata to indicies.'''
+
+        self.interface = get_index(
+            objs, 'interface', self.name['interface'])
+        self.prop = get_index(
+            objs, 'propertyname', self.name['property'])
+        self.meta = get_index(
+            objs, 'meta', self.name['meta'])
+
+        super(Property, self).setup(objs)
+
+
 class Group(ConfigEntry):
     '''Pop the members keyword for groups.'''
 
@@ -215,6 +264,36 @@
         super(GroupOfPaths, self).setup(objs)
 
 
+class GroupOfProperties(ImplicitGroup):
+    '''Property group config file directive.'''
+
+    def __init__(self, *a, **kw):
+        self.datatype = sdbusplus.property.Property(
+            name=kw.get('name'),
+            type=kw.pop('type')).cppTypeName
+
+        super(GroupOfProperties, self).__init__(**kw)
+
+    def setup(self, objs):
+        '''Resolve group members.'''
+
+        def map_member(x):
+            iface = get_index(
+                objs, 'interface', x['interface'])
+            prop = get_index(
+                objs, 'propertyname', x['property'])
+            meta = get_index(
+                objs, 'meta', x['meta'])
+
+            return (iface, prop, meta)
+
+        self.members = map(
+            map_member,
+            self.members)
+
+        super(GroupOfProperties, self).setup(objs)
+
+
 class Everything(Renderer):
     '''Parse/render entry point.'''
 
@@ -230,6 +309,12 @@
             'pathgroup': {
                 'path': GroupOfPaths,
             },
+            'propertygroup': {
+                'property': GroupOfProperties,
+            },
+            'property': {
+                'element': Property,
+            },
         }
 
         if cls not in class_map:
@@ -312,6 +397,10 @@
         self.paths = kw.pop('pathname', [])
         self.meta = kw.pop('meta', [])
         self.pathgroups = kw.pop('pathgroup', [])
+        self.interfaces = kw.pop('interface', [])
+        self.properties = kw.pop('property', [])
+        self.propertynames = kw.pop('propertyname', [])
+        self.propertygroups = kw.pop('propertygroup', [])
 
         super(Everything, self).__init__(**kw)
 
@@ -323,9 +412,13 @@
                     loader,
                     args.template,
                     meta=self.meta,
+                    properties=self.properties,
+                    propertynames=self.propertynames,
+                    interfaces=self.interfaces,
                     paths=self.paths,
                     pathmeta=self.pathmeta,
                     pathgroups=self.pathgroups,
+                    propertygroups=self.propertygroups,
                     indent=Indent()))
 
 if __name__ == '__main__':
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 856975f..7c6e527 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -45,6 +45,38 @@
         return paths;
     }
 };
+
+struct ConfigInterfaces
+{
+    using Interfaces = std::array<std::string, ${len(interfaces)}>;
+
+    static auto& get()
+    {
+        static const Interfaces interfaces =
+        {
+% for i in interfaces:
+            "${i.name}"s,
+% endfor
+        };
+        return interfaces;
+    }
+};
+
+struct ConfigProperties
+{
+    using Properties = std::array<std::string, ${len(propertynames)}>;
+
+    static auto& get()
+    {
+        static const Properties properties =
+        {
+% for p in propertynames:
+            "${p.name}"s,
+% endfor
+        };
+        return properties;
+    }
+};
 } // namespace monitoring
 } // namespace dbus
 } // namespace phosphor