Add path and path group support

Add support for defining groups of object paths.  Groups are
a list of path/metadata tuples.  Metadata can be used to
give a path context when required.

Change-Id: I355ebf76b40f2ffc8d783e94e888b930cde8ee9c
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/src/example/example.yaml b/src/example/example.yaml
index 5a798e9..87c9790 100644
--- a/src/example/example.yaml
+++ b/src/example/example.yaml
@@ -1 +1,28 @@
 # Example PDM configuration file.
+
+- name: example path group
+  description: >
+    'A path group is a named collection of DBus object
+    paths and associated metadata.  These collections
+    serve only to be referenced by other configuration
+    directives.
+
+    The metadata element has different uses depending
+    on the referencing directive.
+
+    Within a single configuration file path group names
+    must be unique.  The same name can appear in multiple
+    configuration files; however, the referencing directive
+    will only search for the group in the same configuration
+    file.'
+  class: group
+  group: path
+  members:
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst1
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst2
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst3
+    - meta: path
+      path: /xyz/openbmc_project/testing/inst4
diff --git a/src/pdmgen.py b/src/pdmgen.py
index 711df52..fcda960 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -121,6 +121,44 @@
         pass
 
 
+class Path(ConfigEntry):
+    '''Path/metadata association.'''
+
+    def __init__(self, *a, **kw):
+        super(Path, self).__init__(**kw)
+
+    def factory(self, objs):
+        '''Create path and metadata elements.'''
+
+        args = {
+            'class': 'pathname',
+            'pathname': 'element',
+            'name': self.name['path']
+        }
+        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(Path, self).factory(objs)
+
+    def setup(self, objs):
+        '''Resolve path and metadata names to indicies.'''
+
+        self.path = get_index(
+            objs, 'pathname', self.name['path'])
+        self.meta = get_index(
+            objs, 'meta', self.name['meta'])
+
+        super(Path, self).setup(objs)
+
+
 class Group(ConfigEntry):
     '''Pop the members keyword for groups.'''
 
@@ -154,6 +192,29 @@
         super(ImplicitGroup, self).factory(objs)
 
 
+class GroupOfPaths(ImplicitGroup):
+    '''Path group config file directive.'''
+
+    def __init__(self, *a, **kw):
+        super(GroupOfPaths, self).__init__(**kw)
+
+    def setup(self, objs):
+        '''Resolve group members.'''
+
+        def map_member(x):
+            path = get_index(
+                objs, 'pathname', x['path'])
+            meta = get_index(
+                objs, 'meta', x['meta'])
+            return (path, meta)
+
+        self.members = map(
+            map_member,
+            self.members)
+
+        super(GroupOfPaths, self).setup(objs)
+
+
 class Everything(Renderer):
     '''Parse/render entry point.'''
 
@@ -163,6 +224,12 @@
         handler methods.'''
 
         class_map = {
+            'path': {
+                'element': Path,
+            },
+            'pathgroup': {
+                'path': GroupOfPaths,
+            },
         }
 
         if cls not in class_map:
@@ -241,6 +308,11 @@
         return Everything(**objs)
 
     def __init__(self, *a, **kw):
+        self.pathmeta = kw.pop('path', [])
+        self.paths = kw.pop('pathname', [])
+        self.meta = kw.pop('meta', [])
+        self.pathgroups = kw.pop('pathgroup', [])
+
         super(Everything, self).__init__(**kw)
 
     def generate_cpp(self, loader):
@@ -250,6 +322,10 @@
                 self.render(
                     loader,
                     args.template,
+                    meta=self.meta,
+                    paths=self.paths,
+                    pathmeta=self.pathmeta,
+                    pathgroups=self.pathgroups,
                     indent=Indent()))
 
 if __name__ == '__main__':
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index 2a9c2d1..856975f 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -14,6 +14,37 @@
 namespace monitoring
 {
 
+struct ConfigMeta
+{
+    using Meta = std::array<std::string, ${len(meta)}>;
+
+    static auto& get()
+    {
+        static const Meta meta =
+        {
+% for m in meta:
+            "${m.name}"s,
+% endfor
+        };
+        return meta;
+    }
+};
+
+struct ConfigPaths
+{
+    using Paths = std::array<std::string, ${len(paths)}>;
+
+    static auto& get()
+    {
+        static const Paths paths =
+        {
+% for p in paths:
+            "${p.name}"s,
+% endfor
+        };
+        return paths;
+    }
+};
 } // namespace monitoring
 } // namespace dbus
 } // namespace phosphor