Rewrite pimgen in the image of sdbus++
Almost complete rewrite of pimgen and the mako template.
Adopt the sdbus++ application structure. The hope is that
this will encourage additional sdbusplus features deemed
general purpose enough for reuse with other applications.
Change-Id: I007ff9f5fc9a64f0465159bd1301475ada139d55
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/example/events.d/match1.yaml b/example/events.d/match1.yaml
index 00b401f..87279d2 100644
--- a/example/events.d/match1.yaml
+++ b/example/events.d/match1.yaml
@@ -6,11 +6,11 @@
description: >
Matches any PropertiesChanged signal.
type: match
- signature:
- type: signal
- interface: org.freedesktop.DBus.Properties
- member: PropertiesChanged
- action:
- type: noop
+ signatures:
+ - type: signal
+ interface: org.freedesktop.DBus.Properties
+ member: PropertiesChanged
+ actions:
+ - name: noop
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/example/events.d/match2.yaml b/example/events.d/match2.yaml
index 4bf2ddd..d31a694 100644
--- a/example/events.d/match2.yaml
+++ b/example/events.d/match2.yaml
@@ -7,18 +7,22 @@
Matches any PropertiesChanged signal emitted
by /xyz/openbmc_project/testing.
type: match
- signature:
- type: signal
- path: /xyz/openbmc_project/Inventory/example
- interface: org.freedesktop.DBus.Properties
- member: PropertiesChanged
- filter:
- type: propertyChangedTo
- args:
- - value: xyz.openbmc_project.Example.Iface1
- - value: ExampleProperty1
- - value: teststring
- action:
- type: destroyObject("Example")
+ signatures:
+ - type: signal
+ path: /xyz/openbmc_project/Inventory/example
+ interface: org.freedesktop.DBus.Properties
+ member: PropertiesChanged
+ filters:
+ - name: propertyChangedTo
+ args:
+ interface: xyz.openbmc_project.Example.Iface1
+ property: ExampleProperty1
+ value:
+ type: string
+ value: testString
+ actions:
+ - name: destroyObject
+ args:
+ path: Example
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/generated.mako.cpp b/generated.mako.cpp
index 8534674..a024d19 100644
--- a/generated.mako.cpp
+++ b/generated.mako.cpp
@@ -1,16 +1,10 @@
## This file is a template. The comment below is emitted
## into the rendered file; feel free to edit this file.
// This file was auto generated. Do not edit.
-<%
- def interface_type(interface):
- lst = interface.split('.')
- lst.insert(-1, 'server')
- return '::'.join(lst)
-%>
#include "manager.hpp"
#include "utils.hpp"
% for i in interfaces:
-#include <${'/'.join(i.split('.') + ['server.hpp'])}>
+#include <${i.header()}>
% endfor
namespace phosphor
@@ -23,10 +17,10 @@
const Manager::Makers Manager::_makers{
% for i in interfaces:
{
- "${i}",
+ "${str(i)}",
details::MakeInterface<
details::ServerObject<
- sdbusplus::${interface_type(i)}>>::make,
+ ${i.namespace()}>>::make,
},
% endfor
};
@@ -34,47 +28,71 @@
const Manager::Events Manager::_events{
% for e in events:
{
- % if e.get('description'):
- // ${e['description']}
+ % if e.description:
+ // ${e.description.strip()}
% endif
std::make_tuple(
- std::vector<details::EventBasePtr>({
- std::make_shared<details::DbusSignal>(
- % for i, s in enumerate(e['signature'].items()):
- % if i + 1 == len(e['signature']):
- ${'"{0}=\'{1}\'"'.format(*s)},
+ std::vector<details::EventBasePtr>(
+ {
+ % if e.cls == 'match':
+ std::make_shared<details::DbusSignal>(
+ % for i, s in enumerate(e.signatures[0].sig.items()):
+ % if i == 0:
+ ${'"{0}=\'{1}\',"'.format(*s)}
+ % elif i + 1 == len(e.signatures[0].sig):
+ ${'"{0}=\'{1}\'"'.format(*s)},
% else:
- ${'"{0}=\'{1}\',"'.format(*s)}
+ ${'"{0}=\'{1}\',"'.format(*s)}
% endif
% endfor
- % if e['filter'].get('args'):
- details::make_filter(filters::${e['filter']['type']}(
- % for i, a in enumerate(e['filter']['args']):
- % if i + 1 == len(e['filter']['args']):
- "${a['value']}")))}),
+ % if e.filters[0].pointer:
+ details::make_filter(${e.filters[0].bare_method()})),
+ % else:
+ % if e.filters[0].args:
+ details::make_filter(
+ ${e.filters[0].bare_method()}(
+ % for i, arg in enumerate(e.filters[0].args):
+ % if i + 1 != len(e.filters[0].args):
+ ${arg.cppArg()},
% else:
- "${a['value']}",
+ ${arg.cppArg()}))),
% endif
% endfor
% else:
- details::make_filter(filters::${e['filter']['type']}))}),
+ details::make_filter(
+ ${e.filters[0].bare_method()}()),
% endif
- % if e['action'].get('args'):
- std::vector<details::ActionBasePtr>({details::make_action(actions::${e['action']['type']}(
- % for i, a in enumerate(e['action']['args']):
- % if i + 1 == len(e['action']['args']):
- "${a['value']}"))})
- % else:
- "${a['value']}",
- % endif
- % endfor
- % else:
+ % endif
+ % endif
+ }
+ ),
std::vector<details::ActionBasePtr>(
- {details::make_action(actions::${e['action']['type']})})
+ {
+ % for action in e.actions:
+ % if action.pointer:
+ details::make_action(${action.bare_method()}),
+ % else:
+ % if action.args:
+ details::make_action(
+ ${action.bare_method()}(
+ % for i, arg in enumerate(action.args):
+ % if i + 1 != len(action.args):
+ ${arg.cppArg()},
+ % else:
+ ${arg.cppArg()})),
+ % endif
+ % endfor
+ % else:
+ details::make_action(
+ ${action.bare_method()}()),
% endif
- ),
+ % endif
+ % endfor
+ }
+ )
+ )
},
-% endfor
+%endfor
};
} // namespace manager
diff --git a/pimgen.py b/pimgen.py
index 072f7d6..e5771d6 100755
--- a/pimgen.py
+++ b/pimgen.py
@@ -1,122 +1,295 @@
#!/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 re
import argparse
-import yaml
import subprocess
-from mako.template import Template
-
-valid_c_name_pattern = re.compile('[\W_]+')
+import yaml
+import mako.lookup
+import sdbusplus.property
+from sdbusplus.namedelement import NamedElement
+from sdbusplus.renderer import Renderer
-def parse_event(e):
- e['name'] = valid_c_name_pattern.sub('_', e['name']).lower()
- if e.get('filter') is None:
- e.setdefault('filter', {}).setdefault('type', 'none')
- if e.get('action') is None:
- e.setdefault('action', {}).setdefault('type', 'noop')
- return e
+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)
-def get_interfaces(args):
- interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
- yaml_files = filter(
- lambda x: x.endswith('.yaml'),
- os.listdir(interfaces_dir))
+class Argument(sdbusplus.property.Property):
+ '''Bridge sdbusplus property typenames to syntatically correct c++.'''
- 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)
+ def __init__(self, **kw):
+ self.value = kw.pop('value')
+ super(Argument, self).__init__(**kw)
- return interfaces
+ def cppArg(self):
+ '''Transform string types to c++ string constants.'''
+ if self.typeName == 'string':
+ return '"%s"' % self.value
+
+ return self.value
-def list_interfaces(args):
- print ' '.join(get_interfaces(args))
+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])
-def generate_cpp(args):
- # 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))
+class Filter(MethodCall):
+ '''Provide common attributes for any filter.'''
- 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(parse_event(e))
+ def __init__(self, **kw):
+ kw['namespace'] = ['filters']
+ super(Filter, self).__init__(**kw)
- # Aggregate all the interface YAML in the interfaces.d
- # directory into a single list of interfaces.
- template = os.path.join(script_dir, 'generated.mako.cpp')
- t = Template(filename=template)
- interfaces = get_interfaces(args)
+class Action(MethodCall):
+ '''Provide common attributes for any action.'''
- # Render the template with the provided events and interfaces.
- template = os.path.join(script_dir, 'generated.mako.cpp')
- t = Template(filename=template)
- with open(os.path.join(args.outputdir, 'generated.cpp'), 'w') as fd:
- fd.write(
- t.render(
- interfaces=interfaces,
- events=events))
+ def __init__(self, **kw):
+ kw['namespace'] = ['actions']
+ super(Action, self).__init__(**kw)
- # 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))
+class DbusSignature(NamedElement, Renderer):
+ '''Represent a dbus signal match signature.'''
- 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')
+ 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,
}
- for i in yaml_files:
- iface = i.replace('.interface.yaml', '').replace(os.sep, '.')
- for process, f in genfiles.iteritems():
+ 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)
- 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)
+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'}
+ 'list-interfaces': 'list_interfaces'
+ }
parser = argparse.ArgumentParser(
description='Phosphor Inventory Manager (PIM) YAML '
@@ -134,8 +307,19 @@
help='Command to run.')
args = parser.parse_args()
- function = getattr(sys.modules[__name__], valid_commands[args.command])
- function(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