| #!/usr/bin/env python |
| |
| '''Phosphor Inventory Manager 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 sys |
| import os |
| import argparse |
| import subprocess |
| import yaml |
| import mako.lookup |
| import sdbusplus.property |
| from sdbusplus.namedelement import NamedElement |
| from sdbusplus.renderer import Renderer |
| |
| |
| class Interface(list): |
| '''Provide various interface transformations.''' |
| |
| def __init__(self, iface): |
| super(Interface, self).__init__(iface.split('.')) |
| |
| def namespace(self): |
| '''Represent as an sdbusplus namespace.''' |
| return '::'.join(['sdbusplus'] + self[:-1] + ['server', self[-1]]) |
| |
| def header(self): |
| '''Represent as an sdbusplus server binding header.''' |
| return os.sep.join(self + ['server.hpp']) |
| |
| def __str__(self): |
| return '.'.join(self) |
| |
| |
| class Argument(sdbusplus.property.Property): |
| '''Bridge sdbusplus property typenames to syntatically correct c++.''' |
| |
| def __init__(self, **kw): |
| self.value = kw.pop('value') |
| super(Argument, self).__init__(**kw) |
| |
| def cppArg(self): |
| '''Transform string types to c++ string constants.''' |
| if self.typeName == 'string': |
| return '"%s"' % self.value |
| |
| return self.value |
| |
| |
| class MethodCall(NamedElement, Renderer): |
| '''Render syntatically correct c++ method calls.''' |
| |
| def __init__(self, **kw): |
| self.namespace = kw.pop('namespace', []) |
| self.pointer = kw.pop('pointer', False) |
| self.args = \ |
| [Argument(**x) for x in kw.pop('args', [])] |
| super(MethodCall, self).__init__(**kw) |
| |
| def bare_method(self): |
| '''Provide the method name and encompassing |
| namespace without any arguments.''' |
| return '::'.join(self.namespace + [self.name]) |
| |
| |
| class Filter(MethodCall): |
| '''Provide common attributes for any filter.''' |
| |
| def __init__(self, **kw): |
| kw['namespace'] = ['filters'] |
| super(Filter, self).__init__(**kw) |
| |
| |
| class Action(MethodCall): |
| '''Provide common attributes for any action.''' |
| |
| def __init__(self, **kw): |
| kw['namespace'] = ['actions'] |
| super(Action, self).__init__(**kw) |
| |
| |
| class DbusSignature(NamedElement, Renderer): |
| '''Represent a dbus signal match signature.''' |
| |
| def __init__(self, **kw): |
| self.sig = {x: y for x, y in kw.iteritems()} |
| kw.clear() |
| super(DbusSignature, self).__init__(**kw) |
| |
| |
| class DestroyObject(Action): |
| '''Render a destroyObject action.''' |
| |
| def __init__(self, **kw): |
| mapped = kw.pop('args') |
| kw['args'] = [ |
| {'value': mapped['path'], 'type':'string'}, |
| ] |
| super(DestroyObject, self).__init__(**kw) |
| |
| |
| class NoopAction(Action): |
| '''Render a noop action.''' |
| |
| def __init__(self, **kw): |
| kw['pointer'] = True |
| super(NoopAction, self).__init__(**kw) |
| |
| |
| class NoopFilter(Filter): |
| '''Render a noop filter.''' |
| |
| def __init__(self, **kw): |
| kw['pointer'] = True |
| super(NoopFilter, self).__init__(**kw) |
| |
| |
| class PropertyChanged(Filter): |
| '''Render a propertyChanged filter.''' |
| |
| def __init__(self, **kw): |
| mapped = kw.pop('args') |
| kw['args'] = [ |
| {'value': mapped['interface'], 'type':'string'}, |
| {'value': mapped['property'], 'type':'string'}, |
| mapped['value'] |
| ] |
| super(PropertyChanged, self).__init__(**kw) |
| |
| |
| class Event(NamedElement, Renderer): |
| '''Render an inventory manager event.''' |
| |
| action_map = { |
| 'noop': NoopAction, |
| 'destroyObject': DestroyObject, |
| } |
| |
| def __init__(self, **kw): |
| self.cls = kw.pop('type') |
| self.actions = \ |
| [self.action_map[x['name']](**x) |
| for x in kw.pop('actions', [{'name': 'noop'}])] |
| super(Event, self).__init__(**kw) |
| |
| |
| class MatchEvent(Event): |
| '''Associate one or more dbus signal match signatures with |
| a filter.''' |
| |
| filter_map = { |
| 'none': NoopFilter, |
| 'propertyChangedTo': PropertyChanged, |
| } |
| |
| def __init__(self, **kw): |
| self.signatures = \ |
| [DbusSignature(**x) for x in kw.pop('signatures', [])] |
| self.filters = \ |
| [self.filter_map[x['name']](**x) |
| for x in kw.pop('filters', [{'name': 'none'}])] |
| super(MatchEvent, self).__init__(**kw) |
| |
| |
| class Everything(Renderer): |
| '''Parse/render entry point.''' |
| |
| class_map = { |
| 'match': MatchEvent, |
| } |
| |
| @staticmethod |
| def load(args): |
| # Invoke sdbus++ to generate any extra interface bindings for |
| # extra interfaces that aren't defined externally. |
| yaml_files = [] |
| extra_ifaces_dir = os.path.join(args.inputdir, 'extra_interfaces.d') |
| if os.path.exists(extra_ifaces_dir): |
| for directory, _, files in os.walk(extra_ifaces_dir): |
| if not files: |
| continue |
| |
| yaml_files += map( |
| lambda f: os.path.relpath( |
| os.path.join(directory, f), |
| extra_ifaces_dir), |
| filter(lambda f: f.endswith('.interface.yaml'), files)) |
| |
| genfiles = { |
| 'server-cpp': lambda x: '%s.cpp' % ( |
| x.replace(os.sep, '.')), |
| 'server-header': lambda x: os.path.join( |
| os.path.join( |
| *x.split('.')), 'server.hpp') |
| } |
| |
| for i in yaml_files: |
| iface = i.replace('.interface.yaml', '').replace(os.sep, '.') |
| for process, f in genfiles.iteritems(): |
| |
| dest = os.path.join(args.outputdir, f(iface)) |
| parent = os.path.dirname(dest) |
| if parent and not os.path.exists(parent): |
| os.makedirs(parent) |
| |
| with open(dest, 'w') as fd: |
| subprocess.call([ |
| 'sdbus++', |
| '-r', |
| extra_ifaces_dir, |
| 'interface', |
| process, |
| iface], |
| stdout=fd) |
| |
| # Aggregate all the event YAML in the events.d directory |
| # into a single list of events. |
| |
| events_dir = os.path.join(args.inputdir, 'events.d') |
| yaml_files = filter( |
| lambda x: x.endswith('.yaml'), |
| os.listdir(events_dir)) |
| |
| events = [] |
| for x in yaml_files: |
| with open(os.path.join(events_dir, x), 'r') as fd: |
| for e in yaml.load(fd.read()).get('events', {}): |
| events.append(e) |
| |
| return Everything( |
| *events, |
| interfaces=Everything.get_interfaces(args)) |
| |
| @staticmethod |
| def get_interfaces(args): |
| '''Aggregate all the interface YAML in the interfaces.d |
| directory into a single list of interfaces.''' |
| |
| interfaces_dir = os.path.join(args.inputdir, 'interfaces.d') |
| yaml_files = filter( |
| lambda x: x.endswith('.yaml'), |
| os.listdir(interfaces_dir)) |
| |
| interfaces = [] |
| for x in yaml_files: |
| with open(os.path.join(interfaces_dir, x), 'r') as fd: |
| for i in yaml.load(fd.read()): |
| interfaces.append(i) |
| |
| return interfaces |
| |
| def __init__(self, *a, **kw): |
| self.interfaces = \ |
| [Interface(x) for x in kw.pop('interfaces', [])] |
| self.events = [ |
| self.class_map[x['type']](**x) for x in a] |
| super(Everything, self).__init__(**kw) |
| |
| def list_interfaces(self, *a): |
| print ' '.join([str(i) for i in self.interfaces]) |
| |
| def generate_cpp(self, loader): |
| '''Render the template with the provided events and interfaces.''' |
| with open(os.path.join( |
| args.outputdir, |
| 'generated.cpp'), 'w') as fd: |
| fd.write( |
| self.render( |
| loader, |
| 'generated.mako.cpp', |
| events=self.events, |
| interfaces=self.interfaces)) |
| |
| |
| if __name__ == '__main__': |
| script_dir = os.path.dirname(os.path.realpath(__file__)) |
| valid_commands = { |
| 'generate-cpp': 'generate_cpp', |
| 'list-interfaces': 'list_interfaces' |
| } |
| |
| parser = argparse.ArgumentParser( |
| description='Phosphor Inventory Manager (PIM) YAML ' |
| 'scanner and code generator.') |
| parser.add_argument( |
| '-o', '--output-dir', dest='outputdir', |
| default='.', help='Output directory.') |
| 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='Command to run.') |
| |
| args = parser.parse_args() |
| |
| if sys.version_info < (3, 0): |
| lookup = mako.lookup.TemplateLookup( |
| directories=[script_dir], |
| disable_unicode=True) |
| else: |
| lookup = mako.lookup.TemplateLookup( |
| directories=[script_dir]) |
| |
| function = getattr( |
| Everything.load(args), |
| valid_commands[args.command]) |
| function(lookup) |
| |
| |
| # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 |