| #!/usr/bin/env python3 |
| |
| '''Phosphor DBus Monitor YAML parser and code generator. |
| |
| The parser workflow is broken down as follows: |
| 1 - Import YAML files as native python type(s) instance(s). |
| 2 - Create an instance of the Everything class from the |
| native python type instance(s) with the Everything.load |
| method. |
| 3 - The Everything class constructor orchestrates conversion of the |
| native python type(s) instances(s) to render helper types. |
| Each render helper type constructor imports its attributes |
| from the native python type(s) instances(s). |
| 4 - Present the converted YAML to the command processing method |
| requested by the script user. |
| ''' |
| |
| import os |
| import sys |
| import yaml |
| import mako.lookup |
| from argparse import ArgumentParser |
| from sdbusplus.renderer import Renderer |
| from sdbusplus.namedelement import NamedElement |
| import sdbusplus.property |
| |
| |
| class InvalidConfigError(BaseException): |
| '''General purpose config file parsing error.''' |
| |
| def __init__(self, path, msg): |
| '''Display configuration file with the syntax |
| error and the error message.''' |
| |
| self.config = path |
| self.msg = msg |
| |
| |
| class NotUniqueError(InvalidConfigError): |
| '''Within a config file names must be unique. |
| Display the config file with the duplicate and |
| the duplicate itself.''' |
| |
| def __init__(self, path, cls, *names): |
| fmt = 'Duplicate {0}: "{1}"' |
| super(NotUniqueError, self).__init__( |
| path, fmt.format(cls, ' '.join(names))) |
| |
| |
| def get_index(objs, cls, name, config=None): |
| '''Items are usually rendered as C++ arrays and as |
| such are stored in python lists. Given an item name |
| its class, and an optional config file filter, find |
| the item index.''' |
| |
| for i, x in enumerate(objs.get(cls, [])): |
| if config and x.configfile != config: |
| continue |
| if x.name != name: |
| continue |
| |
| return i |
| raise InvalidConfigError(config, 'Could not find name: "{0}"'.format(name)) |
| |
| |
| def exists(objs, cls, name, config=None): |
| '''Check to see if an item already exists in a list given |
| the item name.''' |
| |
| try: |
| get_index(objs, cls, name, config) |
| except: |
| return False |
| |
| return True |
| |
| |
| def add_unique(obj, *a, **kw): |
| '''Add an item to one or more lists unless already present, |
| with an option to constrain the search to a specific config file.''' |
| |
| for container in a: |
| if not exists(container, obj.cls, obj.name, config=kw.get('config')): |
| container.setdefault(obj.cls, []).append(obj) |
| |
| |
| class Cast(object): |
| '''Decorate an argument by casting it.''' |
| |
| def __init__(self, cast, target): |
| '''cast is the cast type (static, const, etc...). |
| target is the cast target type.''' |
| self.cast = cast |
| self.target = target |
| |
| def __call__(self, arg): |
| return '{0}_cast<{1}>({2})'.format(self.cast, self.target, arg) |
| |
| |
| class Literal(object): |
| '''Decorate an argument with a literal operator.''' |
| |
| integer_types = [ |
| 'int16', |
| 'int32', |
| 'int64', |
| 'uint16', |
| 'uint32', |
| 'uint64' |
| ] |
| |
| def __init__(self, type): |
| self.type = type |
| |
| def __call__(self, arg): |
| if 'uint' in self.type: |
| arg = '{0}ull'.format(arg) |
| elif 'int' in self.type: |
| arg = '{0}ll'.format(arg) |
| |
| if self.type in self.integer_types: |
| return Cast('static', '{0}_t'.format(self.type))(arg) |
| elif self.type == 'byte': |
| return Cast('static', 'uint8_t'.format(self.type))(arg) |
| |
| if self.type == 'string': |
| return '{0}s'.format(arg) |
| |
| return arg |
| |
| |
| class FixBool(object): |
| '''Un-capitalize booleans.''' |
| |
| def __call__(self, arg): |
| return '{0}'.format(arg.lower()) |
| |
| |
| class Quote(object): |
| '''Decorate an argument by quoting it.''' |
| |
| def __call__(self, arg): |
| return '"{0}"'.format(arg) |
| |
| |
| class Argument(NamedElement, Renderer): |
| '''Define argument type interface.''' |
| |
| def __init__(self, **kw): |
| self.type = kw.pop('type', None) |
| super(Argument, self).__init__(**kw) |
| |
| def argument(self, loader, indent): |
| raise NotImplementedError |
| |
| |
| class TrivialArgument(Argument): |
| '''Non-array type arguments.''' |
| |
| def __init__(self, **kw): |
| self.value = kw.pop('value') |
| self.decorators = kw.pop('decorators', []) |
| if kw.get('type', None): |
| self.decorators.insert(0, Literal(kw['type'])) |
| if kw.get('type', None) == 'string': |
| self.decorators.insert(0, Quote()) |
| if kw.get('type', None) == 'boolean': |
| self.decorators.insert(0, FixBool()) |
| |
| super(TrivialArgument, self).__init__(**kw) |
| |
| def argument(self, loader, indent): |
| a = str(self.value) |
| for d in self.decorators: |
| a = d(a) |
| |
| return a |
| |
| |
| class Metadata(Argument): |
| '''Metadata type arguments.''' |
| |
| def __init__(self, **kw): |
| self.value = kw.pop('value') |
| self.decorators = kw.pop('decorators', []) |
| if kw.get('type', None) == 'string': |
| self.decorators.insert(0, Quote()) |
| |
| super(Metadata, self).__init__(**kw) |
| |
| def argument(self, loader, indent): |
| a = str(self.value) |
| for d in self.decorators: |
| a = d(a) |
| |
| return a |
| |
| |
| class OpArgument(Argument): |
| '''Operation type arguments.''' |
| |
| def __init__(self, **kw): |
| self.op = kw.pop('op') |
| self.bound = kw.pop('bound') |
| self.decorators = kw.pop('decorators', []) |
| if kw.get('type', None): |
| self.decorators.insert(0, Literal(kw['type'])) |
| if kw.get('type', None) == 'string': |
| self.decorators.insert(0, Quote()) |
| if kw.get('type', None) == 'boolean': |
| self.decorators.insert(0, FixBool()) |
| |
| super(OpArgument, self).__init__(**kw) |
| |
| def argument(self, loader, indent): |
| a = str(self.bound) |
| for d in self.decorators: |
| a = d(a) |
| |
| return a |
| |
| |
| class Indent(object): |
| '''Help templates be depth agnostic.''' |
| |
| def __init__(self, depth=0): |
| self.depth = depth |
| |
| def __add__(self, depth): |
| return Indent(self.depth + depth) |
| |
| def __call__(self, depth): |
| '''Render an indent at the current depth plus depth.''' |
| return 4*' '*(depth + self.depth) |
| |
| |
| class ConfigEntry(NamedElement): |
| '''Base interface for rendered items.''' |
| |
| def __init__(self, *a, **kw): |
| '''Pop the configfile/class/subclass keywords.''' |
| |
| self.configfile = kw.pop('configfile') |
| self.cls = kw.pop('class') |
| self.subclass = kw.pop(self.cls) |
| |
| # TODO: NamedElement requires 'name' to be a string, but in many cases |
| # this script treats 'name' as a dict. Save the property off and |
| # insert it after ConfigEntry does its own thing to avoid |
| # exceptions. This should be refactored throughout the whole |
| # script to not overload 'name' as a dict. |
| name_save = kw.pop('name') |
| super(ConfigEntry, self).__init__(**kw) |
| self.name = name_save |
| |
| def factory(self, objs): |
| ''' Optional factory interface for subclasses to add |
| additional items to be rendered.''' |
| |
| pass |
| |
| def setup(self, objs): |
| ''' Optional setup interface for subclasses, invoked |
| after all factory methods have been run.''' |
| |
| pass |
| |
| |
| class Path(ConfigEntry): |
| '''Path/metadata association.''' |
| |
| def __init__(self, *a, **kw): |
| super(Path, self).__init__(**kw) |
| |
| if self.name['meta'].upper() != self.name['meta']: |
| raise InvalidConfigError( |
| self.configfile, |
| 'Metadata tag "{0}" must be upper case.'.format( |
| self.name['meta'])) |
| |
| 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 indices.''' |
| |
| self.path = get_index( |
| objs, 'pathname', self.name['path']) |
| self.meta = get_index( |
| objs, 'meta', self.name['meta']) |
| |
| super(Path, self).setup(objs) |
| |
| |
| class Property(ConfigEntry): |
| '''Property/interface/metadata association.''' |
| |
| def __init__(self, *a, **kw): |
| super(Property, self).__init__(**kw) |
| |
| if self.name['meta'].upper() != self.name['meta']: |
| raise InvalidConfigError( |
| self.configfile, |
| 'Metadata tag "{0}" must be upper case.'.format( |
| self.name['meta'])) |
| |
| 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 indices.''' |
| |
| 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 Instance(ConfigEntry): |
| '''Property/Path association.''' |
| |
| def __init__(self, *a, **kw): |
| super(Instance, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve elements to indices.''' |
| |
| 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 PathInstance(ConfigEntry): |
| '''Path association.''' |
| |
| def __init__(self, *a, **kw): |
| super(PathInstance, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve elements to indices.''' |
| self.path = self.name['path']['path'] |
| self.pathmeta = self.name['path']['meta'] |
| super(PathInstance, self).setup(objs) |
| |
| class Group(ConfigEntry): |
| '''Pop the members keyword for groups.''' |
| |
| def __init__(self, *a, **kw): |
| self.members = kw.pop('members') |
| super(Group, self).__init__(**kw) |
| |
| |
| class ImplicitGroup(Group): |
| '''Provide a factory method for groups whose members are |
| not explicitly declared in the config files.''' |
| |
| def __init__(self, *a, **kw): |
| super(ImplicitGroup, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Create group members.''' |
| |
| factory = Everything.classmap(self.subclass, 'element') |
| for m in self.members: |
| args = { |
| 'class': self.subclass, |
| self.subclass: 'element', |
| 'name': m |
| } |
| |
| obj = factory(configfile=self.configfile, **args) |
| add_unique(obj, objs) |
| obj.factory(objs) |
| |
| 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 GroupOfProperties(ImplicitGroup): |
| '''Property group config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.type = kw.pop('type') |
| self.datatype = sdbusplus.property.Property( |
| name=kw.get('name'), |
| type=self.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 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 GroupOfPathInstances(ImplicitGroup): |
| '''A group of path instances.''' |
| |
| def __init__(self, *a, **kw): |
| super(GroupOfPathInstances, 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']) |
| pathinstance = get_index(objs, 'pathinstance', x) |
| return (path, pathmeta, pathinstance) |
| |
| self.members = map( |
| map_member, |
| self.members) |
| |
| super(GroupOfPathInstances, 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 |
| self.type = objs['propertygroup'][self.properties].type |
| |
| super(HasPropertyIndex, self).setup(objs) |
| |
| class HasPathIndex(ConfigEntry): |
| '''Handle config file directives that require an index to be |
| constructed.''' |
| |
| def __init__(self, *a, **kw): |
| self.paths = kw.pop('paths') |
| super(HasPathIndex, 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) |
| |
| for path in objs['pathgroup'][path_group].members: |
| member = { |
| 'path': path, |
| } |
| members.append(member) |
| |
| args = { |
| 'members': members, |
| 'class': 'pathinstancegroup', |
| 'pathinstancegroup': 'pathinstance', |
| 'name': '{0}'.format(self.paths) |
| } |
| |
| group = GroupOfPathInstances(configfile=self.configfile, **args) |
| add_unique(group, objs, config=self.configfile) |
| group.factory(objs) |
| |
| super(HasPathIndex, self).factory(objs) |
| |
| def setup(self, objs): |
| '''Resolve path and instance groups.''' |
| |
| self.pathinstances = get_index( |
| objs, |
| 'pathinstancegroup', |
| '{0}'.format(self.paths), |
| config=self.configfile) |
| self.paths = get_index( |
| objs, |
| 'pathgroup', |
| self.paths, |
| config=self.configfile) |
| super(HasPathIndex, self).setup(objs) |
| |
| class GroupOfFilters(ConfigEntry): |
| '''Handle config file directives that require an index for filters.''' |
| |
| def __init__(self, *a, **kw): |
| # Pop filters data for adding to the available filters array |
| self.type = kw.pop('type') |
| self.datatype = kw.pop('datatype', None) |
| self.filters = kw.pop('filters', None) |
| |
| super(GroupOfFilters, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Modify filters to add the property value type and |
| make them of operation argument type.''' |
| if self.filters: |
| # 'type' used within OpArgument to generate filter |
| # argument values so add to each filter |
| for f in self.filters: |
| f['type'] = self.type |
| self.filters = [OpArgument(**x) for x in self.filters] |
| |
| super(GroupOfFilters, self).factory(objs) |
| |
| class PropertyWatch(HasPropertyIndex): |
| '''Handle the property watch config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| # Pop optional filters for the properties being watched |
| self.filters = kw.pop('filters', None) |
| self.callback = kw.pop('callback', None) |
| super(PropertyWatch, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Create any filters for this property watch.''' |
| |
| if self.filters: |
| # Get the datatype(i.e. "int64_t") of the properties in this watch |
| # (Made available after all `super` classes init'd) |
| datatype = objs['propertygroup'][get_index( |
| objs, |
| 'propertygroup', |
| self.properties, |
| config=self.configfile)].datatype |
| # Get the type(i.e. "int64") of the properties in this watch |
| # (Made available after all `super` classes init'd) |
| type = objs['propertygroup'][get_index( |
| objs, |
| 'propertygroup', |
| self.properties, |
| config=self.configfile)].type |
| # Construct the data needed to make the filters for |
| # this watch available. |
| # *Note: 'class', 'subclass', 'name' are required for |
| # storing the filter data(i.e. 'type', 'datatype', & 'filters') |
| args = { |
| 'type': type, |
| 'datatype': datatype, |
| 'filters': self.filters, |
| 'class': 'filtersgroup', |
| 'filtersgroup': 'filters', |
| 'name': self.name, |
| } |
| # Init GroupOfFilters class with this watch's filters' arguments |
| group = GroupOfFilters(configfile=self.configfile, **args) |
| # Store this group of filters so it can be indexed later |
| add_unique(group, objs, config=self.configfile) |
| group.factory(objs) |
| |
| super(PropertyWatch, self).factory(objs) |
| |
| def setup(self, objs): |
| '''Resolve optional filters and callback.''' |
| |
| if self.filters: |
| # Watch has filters, provide array index to access them |
| self.filters = get_index( |
| objs, |
| 'filtersgroup', |
| self.name, |
| config=self.configfile) |
| |
| if self.callback: |
| self.callback = get_index( |
| objs, |
| 'callback', |
| self.callback, |
| config=self.configfile) |
| |
| super(PropertyWatch, self).setup(objs) |
| |
| class PathWatch(HasPathIndex): |
| '''Handle the path watch config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.pathcallback = kw.pop('pathcallback', None) |
| super(PathWatch, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve optional callback.''' |
| if self.pathcallback: |
| self.pathcallback = get_index( |
| objs, |
| 'pathcallback', |
| self.pathcallback, |
| config=self.configfile) |
| super(PathWatch, self).setup(objs) |
| |
| class Callback(HasPropertyIndex): |
| '''Interface and common logic for callbacks.''' |
| |
| def __init__(self, *a, **kw): |
| super(Callback, self).__init__(**kw) |
| |
| class PathCallback(HasPathIndex): |
| '''Interface and common logic for callbacks.''' |
| |
| def __init__(self, *a, **kw): |
| super(PathCallback, self).__init__(**kw) |
| |
| class ConditionCallback(ConfigEntry, Renderer): |
| '''Handle the journal callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.condition = kw.pop('condition') |
| self.instance = kw.pop('instance') |
| self.defer = kw.pop('defer', None) |
| super(ConditionCallback, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Create a graph instance for this callback.''' |
| |
| args = { |
| 'configfile': self.configfile, |
| 'members': [self.instance], |
| 'class': 'callbackgroup', |
| 'callbackgroup': 'callback', |
| 'name': [self.instance] |
| } |
| |
| entry = CallbackGraphEntry(**args) |
| add_unique(entry, objs, config=self.configfile) |
| |
| super(ConditionCallback, self).factory(objs) |
| |
| def setup(self, objs): |
| '''Resolve condition and graph entry.''' |
| |
| self.graph = get_index( |
| objs, |
| 'callbackgroup', |
| [self.instance], |
| config=self.configfile) |
| |
| self.condition = get_index( |
| objs, |
| 'condition', |
| self.name, |
| config=self.configfile) |
| |
| super(ConditionCallback, self).setup(objs) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'conditional.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class Condition(HasPropertyIndex): |
| '''Interface and common logic for conditions.''' |
| |
| def __init__(self, *a, **kw): |
| self.callback = kw.pop('callback') |
| self.defer = kw.pop('defer', None) |
| super(Condition, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Create a callback instance for this conditional.''' |
| |
| args = { |
| 'configfile': self.configfile, |
| 'condition': self.name, |
| 'class': 'callback', |
| 'callback': 'conditional', |
| 'instance': self.callback, |
| 'name': self.name, |
| 'defer': self.defer |
| } |
| |
| callback = ConditionCallback(**args) |
| add_unique(callback, objs, config=self.configfile) |
| callback.factory(objs) |
| |
| super(Condition, self).factory(objs) |
| |
| |
| class CountCondition(Condition, Renderer): |
| '''Handle the count condition config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.countop = kw.pop('countop') |
| self.countbound = kw.pop('countbound') |
| self.op = kw.pop('op') |
| self.bound = kw.pop('bound') |
| self.oneshot = TrivialArgument( |
| type='boolean', |
| value=kw.pop('oneshot', False)) |
| super(CountCondition, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve type.''' |
| |
| super(CountCondition, self).setup(objs) |
| self.bound = TrivialArgument( |
| type=self.type, |
| value=self.bound) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'count.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class MedianCondition(Condition, Renderer): |
| '''Handle the median condition config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.op = kw.pop('op') |
| self.bound = kw.pop('bound') |
| self.oneshot = TrivialArgument( |
| type='boolean', |
| value=kw.pop('oneshot', False)) |
| super(MedianCondition, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve type.''' |
| |
| super(MedianCondition, self).setup(objs) |
| self.bound = TrivialArgument( |
| type=self.type, |
| value=self.bound) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'median.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class Journal(Callback, Renderer): |
| '''Handle the journal callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.severity = kw.pop('severity') |
| self.message = kw.pop('message') |
| super(Journal, self).__init__(**kw) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'journal.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class Elog(Callback, Renderer): |
| '''Handle the elog callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.error = kw.pop('error') |
| self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})] |
| super(Elog, self).__init__(**kw) |
| |
| def construct(self, loader, indent): |
| with open('errors.hpp', 'a') as fd: |
| fd.write( |
| self.render( |
| loader, |
| 'errors.mako.hpp', |
| c=self)) |
| return self.render( |
| loader, |
| 'elog.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| class Event(Callback, Renderer): |
| '''Handle the event callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.eventName = kw.pop('eventName') |
| self.eventMessage = kw.pop('eventMessage') |
| super(Event, self).__init__(**kw) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'event.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| class EventPath(PathCallback, Renderer): |
| '''Handle the event path callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.eventType = kw.pop('eventType') |
| super(EventPath, self).__init__(**kw) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'eventpath.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| class ElogWithMetadata(Callback, Renderer): |
| '''Handle the elog_with_metadata callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.error = kw.pop('error') |
| self.metadata = kw.pop('metadata') |
| super(ElogWithMetadata, self).__init__(**kw) |
| |
| def construct(self, loader, indent): |
| with open('errors.hpp', 'a') as fd: |
| fd.write( |
| self.render( |
| loader, |
| 'errors.mako.hpp', |
| c=self)) |
| return self.render( |
| loader, |
| 'elog_with_metadata.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class ResolveCallout(Callback, Renderer): |
| '''Handle the 'resolve callout' callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.callout = kw.pop('callout') |
| super(ResolveCallout, self).__init__(**kw) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'resolve_errors.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class Method(ConfigEntry, Renderer): |
| '''Handle the method callback config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.service = kw.pop('service') |
| self.path = kw.pop('path') |
| self.interface = kw.pop('interface') |
| self.method = kw.pop('method') |
| self.args = [TrivialArgument(**x) for x in kw.pop('args', {})] |
| super(Method, self).__init__(**kw) |
| |
| def factory(self, objs): |
| args = { |
| 'class': 'interface', |
| 'interface': 'element', |
| 'name': self.service |
| } |
| add_unique(ConfigEntry( |
| configfile=self.configfile, **args), objs) |
| |
| args = { |
| 'class': 'pathname', |
| 'pathname': 'element', |
| 'name': self.path |
| } |
| add_unique(ConfigEntry( |
| configfile=self.configfile, **args), objs) |
| |
| args = { |
| 'class': 'interface', |
| 'interface': 'element', |
| 'name': self.interface |
| } |
| add_unique(ConfigEntry( |
| configfile=self.configfile, **args), objs) |
| |
| args = { |
| 'class': 'propertyname', |
| 'propertyname': 'element', |
| 'name': self.method |
| } |
| add_unique(ConfigEntry( |
| configfile=self.configfile, **args), objs) |
| |
| super(Method, self).factory(objs) |
| |
| def setup(self, objs): |
| '''Resolve elements.''' |
| |
| self.service = get_index( |
| objs, |
| 'interface', |
| self.service) |
| |
| self.path = get_index( |
| objs, |
| 'pathname', |
| self.path) |
| |
| self.interface = get_index( |
| objs, |
| 'interface', |
| self.interface) |
| |
| self.method = get_index( |
| objs, |
| 'propertyname', |
| self.method) |
| |
| super(Method, self).setup(objs) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'method.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| |
| class CallbackGraphEntry(Group): |
| '''An entry in a traversal list for groups of callbacks.''' |
| |
| def __init__(self, *a, **kw): |
| super(CallbackGraphEntry, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve group members.''' |
| |
| def map_member(x): |
| return get_index( |
| objs, 'callback', x, config=self.configfile) |
| |
| self.members = map( |
| map_member, |
| self.members) |
| |
| super(CallbackGraphEntry, self).setup(objs) |
| |
| class PathCallbackGraphEntry(Group): |
| '''An entry in a traversal list for groups of callbacks.''' |
| |
| def __init__(self, *a, **kw): |
| super(PathCallbackGraphEntry, self).__init__(**kw) |
| |
| def setup(self, objs): |
| '''Resolve group members.''' |
| |
| def map_member(x): |
| return get_index( |
| objs, 'pathcallback', x, config=self.configfile) |
| |
| self.members = map( |
| map_member, |
| self.members) |
| |
| super(PathCallbackGraphEntry, self).setup(objs) |
| |
| class GroupOfCallbacks(ConfigEntry, Renderer): |
| '''Handle the callback group config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.members = kw.pop('members') |
| super(GroupOfCallbacks, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Create a graph instance for this group of callbacks.''' |
| |
| args = { |
| 'configfile': self.configfile, |
| 'members': self.members, |
| 'class': 'callbackgroup', |
| 'callbackgroup': 'callback', |
| 'name': self.members |
| } |
| |
| entry = CallbackGraphEntry(**args) |
| add_unique(entry, objs, config=self.configfile) |
| |
| super(GroupOfCallbacks, self).factory(objs) |
| |
| def setup(self, objs): |
| '''Resolve graph entry.''' |
| |
| self.graph = get_index( |
| objs, 'callbackgroup', self.members, config=self.configfile) |
| |
| super(GroupOfCallbacks, self).setup(objs) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'callbackgroup.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| class GroupOfPathCallbacks(ConfigEntry, Renderer): |
| '''Handle the callback group config file directive.''' |
| |
| def __init__(self, *a, **kw): |
| self.members = kw.pop('members') |
| super(GroupOfPathCallbacks, self).__init__(**kw) |
| |
| def factory(self, objs): |
| '''Create a graph instance for this group of callbacks.''' |
| |
| args = { |
| 'configfile': self.configfile, |
| 'members': self.members, |
| 'class': 'pathcallbackgroup', |
| 'pathcallbackgroup': 'pathcallback', |
| 'name': self.members |
| } |
| |
| entry = PathCallbackGraphEntry(**args) |
| add_unique(entry, objs, config=self.configfile) |
| super(GroupOfPathCallbacks, self).factory(objs) |
| |
| def setup(self, objs): |
| '''Resolve graph entry.''' |
| |
| self.graph = get_index( |
| objs, 'callbackpathgroup', self.members, config=self.configfile) |
| |
| super(GroupOfPathCallbacks, self).setup(objs) |
| |
| def construct(self, loader, indent): |
| return self.render( |
| loader, |
| 'callbackpathgroup.mako.cpp', |
| c=self, |
| indent=indent) |
| |
| class Everything(Renderer): |
| '''Parse/render entry point.''' |
| |
| @staticmethod |
| def classmap(cls, sub=None): |
| '''Map render item class and subclass entries to the appropriate |
| handler methods.''' |
| class_map = { |
| 'path': { |
| 'element': Path, |
| }, |
| 'pathgroup': { |
| 'path': GroupOfPaths, |
| }, |
| 'propertygroup': { |
| 'property': GroupOfProperties, |
| }, |
| 'property': { |
| 'element': Property, |
| }, |
| 'watch': { |
| 'property': PropertyWatch, |
| }, |
| 'pathwatch': { |
| 'path': PathWatch, |
| }, |
| 'instance': { |
| 'element': Instance, |
| }, |
| 'pathinstance': { |
| 'element': PathInstance, |
| }, |
| 'callback': { |
| 'journal': Journal, |
| 'elog': Elog, |
| 'elog_with_metadata': ElogWithMetadata, |
| 'event': Event, |
| 'group': GroupOfCallbacks, |
| 'method': Method, |
| 'resolve callout': ResolveCallout, |
| }, |
| 'pathcallback': { |
| 'eventpath': EventPath, |
| 'grouppath': GroupOfPathCallbacks, |
| }, |
| 'condition': { |
| 'count': CountCondition, |
| 'median': MedianCondition, |
| }, |
| } |
| |
| if cls not in class_map: |
| raise NotImplementedError('Unknown class: "{0}"'.format(cls)) |
| if sub not in class_map[cls]: |
| raise NotImplementedError('Unknown {0} type: "{1}"'.format( |
| cls, sub)) |
| |
| return class_map[cls][sub] |
| |
| @staticmethod |
| def load_one_yaml(path, fd, objs): |
| '''Parse a single YAML file. Parsing occurs in three phases. |
| In the first phase a factory method associated with each |
| configuration file directive is invoked. These factory |
| methods generate more factory methods. In the second |
| phase the factory methods created in the first phase |
| are invoked. In the last phase a callback is invoked on |
| each object created in phase two. Typically the callback |
| resolves references to other configuration file directives.''' |
| |
| factory_objs = {} |
| for x in yaml.safe_load(fd.read()) or {}: |
| |
| # Create factory object for this config file directive. |
| cls = x['class'] |
| sub = x.get(cls) |
| if cls == 'group': |
| cls = '{0}group'.format(sub) |
| |
| factory = Everything.classmap(cls, sub) |
| obj = factory(configfile=path, **x) |
| |
| # For a given class of directive, validate the file |
| # doesn't have any duplicate names (duplicates are |
| # ok across config files). |
| if exists(factory_objs, obj.cls, obj.name, config=path): |
| raise NotUniqueError(path, cls, obj.name) |
| |
| factory_objs.setdefault(cls, []).append(obj) |
| objs.setdefault(cls, []).append(obj) |
| |
| for cls, items in factory_objs.items(): |
| for obj in items: |
| # Add objects for template consumption. |
| obj.factory(objs) |
| |
| @staticmethod |
| def load(args): |
| '''Aggregate all the YAML in the input directory |
| into a single aggregate.''' |
| |
| objs = {} |
| yaml_files = filter( |
| lambda x: x.endswith('.yaml'), |
| os.listdir(args.inputdir)) |
| |
| for x in sorted(yaml_files): |
| path = os.path.join(args.inputdir, x) |
| with open(path, 'r') as fd: |
| Everything.load_one_yaml(path, fd, objs) |
| |
| # Configuration file directives reference each other via |
| # the name attribute; however, when rendered the reference |
| # is just an array index. |
| # |
| # At this point all objects have been created but references |
| # have not been resolved to array indices. Instruct objects |
| # to do that now. |
| for cls, items in objs.items(): |
| for obj in items: |
| obj.setup(objs) |
| |
| 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', []) |
| self.interfaces = kw.pop('interface', []) |
| self.properties = kw.pop('property', []) |
| self.propertynames = kw.pop('propertyname', []) |
| self.propertygroups = kw.pop('propertygroup', []) |
| self.instances = kw.pop('instance', []) |
| self.pathinstances = kw.pop('pathinstance', []) |
| self.instancegroups = kw.pop('instancegroup', []) |
| self.pathinstancegroups = kw.pop('pathinstancegroup', []) |
| self.watches = kw.pop('watch', []) |
| self.pathwatches = kw.pop('pathwatch', []) |
| self.callbacks = kw.pop('callback', []) |
| self.pathcallbacks = kw.pop('pathcallback', []) |
| self.callbackgroups = kw.pop('callbackgroup', []) |
| self.pathcallbackgroups = kw.pop('pathcallbackgroup', []) |
| self.conditions = kw.pop('condition', []) |
| self.filters = kw.pop('filtersgroup', []) |
| |
| super(Everything, self).__init__(**kw) |
| |
| def generate_cpp(self, loader): |
| '''Render the template with the provided data.''' |
| # errors.hpp is used by generated.hpp to included any error.hpp files |
| open('errors.hpp', 'w+') |
| |
| with open(args.output, 'w') as fd: |
| fd.write( |
| self.render( |
| 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, |
| instances=self.instances, |
| pathinstances=self.pathinstances, |
| watches=self.watches, |
| pathwatches=self.pathwatches, |
| instancegroups=self.instancegroups, |
| pathinstancegroups=self.pathinstancegroups, |
| callbacks=self.callbacks, |
| pathcallbacks=self.pathcallbacks, |
| callbackgroups=self.callbackgroups, |
| pathcallbackgroups=self.pathcallbackgroups, |
| conditions=self.conditions, |
| filters=self.filters, |
| indent=Indent())) |
| |
| if __name__ == '__main__': |
| script_dir = os.path.dirname(os.path.realpath(__file__)) |
| valid_commands = { |
| 'generate-cpp': 'generate_cpp', |
| } |
| |
| parser = ArgumentParser( |
| description='Phosphor DBus Monitor (PDM) YAML ' |
| 'scanner and code generator.') |
| |
| parser.add_argument( |
| "-o", "--out", dest="output", |
| default='generated.cpp', |
| help="Generated output file name and path.") |
| parser.add_argument( |
| '-t', '--template', dest='template', |
| default='generated.mako.hpp', |
| help='The top level template to render.') |
| parser.add_argument( |
| '-p', '--template-path', dest='template_search', |
| default=script_dir, |
| help='The space delimited mako template search path.') |
| parser.add_argument( |
| '-d', '--dir', dest='inputdir', |
| default=os.path.join(script_dir, 'example'), |
| help='Location of files to process.') |
| parser.add_argument( |
| 'command', metavar='COMMAND', type=str, |
| choices=valid_commands.keys(), |
| help='%s.' % " | ".join(valid_commands.keys())) |
| |
| args = parser.parse_args() |
| |
| if sys.version_info < (3, 0): |
| lookup = mako.lookup.TemplateLookup( |
| directories=args.template_search.split(), |
| disable_unicode=True) |
| else: |
| lookup = mako.lookup.TemplateLookup( |
| directories=args.template_search.split()) |
| try: |
| function = getattr( |
| Everything.load(args), |
| valid_commands[args.command]) |
| function(lookup) |
| except InvalidConfigError as e: |
| sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg)) |
| raise |