Add property watches

Property watches cache DBus property values given an externally
supplied index of property names and paths, in an externally
supplied storage location.

Change-Id: I155081da88c3ab0e4f6a13b012fc9719203b1888
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/src/pdmgen.py b/src/pdmgen.py
index 61546eb..070c118 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -208,6 +208,29 @@
         super(Property, self).setup(objs)
 
 
+class Instance(ConfigEntry):
+    '''Property/Path association.'''
+
+    def __init__(self, *a, **kw):
+        super(Instance, self).__init__(**kw)
+
+    def setup(self, objs):
+        '''Resolve elements to indicies.'''
+
+        self.interface = get_index(
+            objs, 'interface', self.name['property']['interface'])
+        self.prop = get_index(
+            objs, 'propertyname', self.name['property']['property'])
+        self.propmeta = get_index(
+            objs, 'meta', self.name['property']['meta'])
+        self.path = get_index(
+            objs, 'pathname', self.name['path']['path'])
+        self.pathmeta = get_index(
+            objs, 'meta', self.name['path']['meta'])
+
+        super(Instance, self).setup(objs)
+
+
 class Group(ConfigEntry):
     '''Pop the members keyword for groups.'''
 
@@ -294,6 +317,102 @@
         super(GroupOfProperties, self).setup(objs)
 
 
+class GroupOfInstances(ImplicitGroup):
+    '''A group of property instances.'''
+
+    def __init__(self, *a, **kw):
+        super(GroupOfInstances, self).__init__(**kw)
+
+    def setup(self, objs):
+        '''Resolve group members.'''
+
+        def map_member(x):
+            path = get_index(objs, 'pathname', x['path']['path'])
+            pathmeta = get_index(objs, 'meta', x['path']['meta'])
+            interface = get_index(
+                objs, 'interface', x['property']['interface'])
+            prop = get_index(objs, 'propertyname', x['property']['property'])
+            propmeta = get_index(objs, 'meta', x['property']['meta'])
+            instance = get_index(objs, 'instance', x)
+
+            return (path, pathmeta, interface, prop, propmeta, instance)
+
+        self.members = map(
+            map_member,
+            self.members)
+
+        super(GroupOfInstances, self).setup(objs)
+
+
+class HasPropertyIndex(ConfigEntry):
+    '''Handle config file directives that require an index to be
+    constructed.'''
+
+    def __init__(self, *a, **kw):
+        self.paths = kw.pop('paths')
+        self.properties = kw.pop('properties')
+        super(HasPropertyIndex, self).__init__(**kw)
+
+    def factory(self, objs):
+        '''Create a group of instances for this index.'''
+
+        members = []
+        path_group = get_index(
+            objs, 'pathgroup', self.paths, config=self.configfile)
+        property_group = get_index(
+            objs, 'propertygroup', self.properties, config=self.configfile)
+
+        for path in objs['pathgroup'][path_group].members:
+            for prop in objs['propertygroup'][property_group].members:
+                member = {
+                    'path': path,
+                    'property': prop,
+                }
+                members.append(member)
+
+        args = {
+            'members': members,
+            'class': 'instancegroup',
+            'instancegroup': 'instance',
+            'name': '{0} {1}'.format(self.paths, self.properties)
+        }
+
+        group = GroupOfInstances(configfile=self.configfile, **args)
+        add_unique(group, objs, config=self.configfile)
+        group.factory(objs)
+
+        super(HasPropertyIndex, self).factory(objs)
+
+    def setup(self, objs):
+        '''Resolve path, property, and instance groups.'''
+
+        self.instances = get_index(
+            objs,
+            'instancegroup',
+            '{0} {1}'.format(self.paths, self.properties),
+            config=self.configfile)
+        self.paths = get_index(
+            objs,
+            'pathgroup',
+            self.paths,
+            config=self.configfile)
+        self.properties = get_index(
+            objs,
+            'propertygroup',
+            self.properties,
+            config=self.configfile)
+        self.datatype = objs['propertygroup'][self.properties].datatype
+
+        super(HasPropertyIndex, self).setup(objs)
+
+
+class PropertyWatch(HasPropertyIndex):
+    '''Handle the property watch config file directive.'''
+
+    def __init__(self, *a, **kw):
+        super(PropertyWatch, self).__init__(**kw)
+
+
 class Everything(Renderer):
     '''Parse/render entry point.'''
 
@@ -315,6 +434,12 @@
             'property': {
                 'element': Property,
             },
+            'watch': {
+                'property': PropertyWatch,
+            },
+            'instance': {
+                'element': Instance,
+            },
         }
 
         if cls not in class_map:
@@ -401,6 +526,9 @@
         self.properties = kw.pop('property', [])
         self.propertynames = kw.pop('propertyname', [])
         self.propertygroups = kw.pop('propertygroup', [])
+        self.instances = kw.pop('instance', [])
+        self.instancegroups = kw.pop('instancegroup', [])
+        self.watches = kw.pop('watch', [])
 
         super(Everything, self).__init__(**kw)
 
@@ -419,6 +547,9 @@
                     pathmeta=self.pathmeta,
                     pathgroups=self.pathgroups,
                     propertygroups=self.propertygroups,
+                    instances=self.instances,
+                    watches=self.watches,
+                    instancegroups=self.instancegroups,
                     indent=Indent()))
 
 if __name__ == '__main__':