blob: 226c5d8a3b041f08f6ff643c51131d1cec4dcb84 [file] [log] [blame]
#!/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
def cppTypeName(yaml_type):
''' Convert yaml types to cpp types.'''
return sdbusplus.property.Property(type=yaml_type).cppTypeName
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 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 Template(NamedElement):
'''Associate a template name with its namespace.'''
def __init__(self, **kw):
self.namespace = kw.pop('namespace')
super(Template, self).__init__(**kw)
def qualified(self):
return '::'.join(self.namespace + [self.name])
class Quote(object):
'''Decorate an argument by quoting it.'''
def __call__(self, arg):
return '"{0}"'.format(arg)
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.'''
literals = {
'string': 's',
'int64': 'll',
'uint64': 'ull'
}
def __init__(self, type):
self.type = type
def __call__(self, arg):
literal = self.literals.get(self.type)
if literal:
return '{0}{1}'.format(arg, literal)
return arg
class Argument(NamedElement, Renderer):
'''Define argument type inteface.'''
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) == 'string':
self.decorators.insert(0, Quote())
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 InitializerList(Argument):
'''Initializer list arguments.'''
def __init__(self, **kw):
self.values = kw.pop('values')
super(InitializerList, self).__init__(**kw)
def argument(self, loader, indent):
return self.render(
loader,
'argument.mako.cpp',
arg=self,
indent=indent)
class DbusSignature(Argument):
'''DBus signature arguments.'''
def __init__(self, **kw):
self.sig = {x: y for x, y in kw.iteritems()}
kw.clear()
super(DbusSignature, self).__init__(**kw)
def argument(self, loader, indent):
return self.render(
loader,
'signature.mako.cpp',
signature=self,
indent=indent)
class MethodCall(Argument):
'''Render syntatically correct c++ method calls.'''
def __init__(self, **kw):
self.namespace = kw.pop('namespace', [])
self.templates = kw.pop('templates', [])
self.args = kw.pop('args', [])
super(MethodCall, self).__init__(**kw)
def call(self, loader, indent):
return self.render(
loader,
'method.mako.cpp',
method=self,
indent=indent)
def argument(self, loader, indent):
return self.call(loader, indent)
class Vector(MethodCall):
'''Convenience type for vectors.'''
def __init__(self, **kw):
kw['name'] = 'vector'
kw['namespace'] = ['std']
kw['args'] = [InitializerList(values=kw.pop('args'))]
super(Vector, self).__init__(**kw)
class Wrapper(MethodCall):
'''Convenience type for functions that wrap other functions.'''
def __init__(self, **kw):
m = MethodCall(
name=kw.pop('name'),
namespace=kw.pop('namespace', []),
templates=kw.pop('templates', []),
args=kw.pop('args', []))
kw['name'] = kw.pop('wrapper_name')
kw['namespace'] = kw.pop('wrapper_namespace', [])
kw['args'] = [m]
super(Wrapper, self).__init__(**kw)
class Filter(Wrapper):
'''Convenience type for filters'''
def __init__(self, **kw):
kw['wrapper_name'] = 'make_filter'
kw['wrapper_namespace'] = []
kw['namespace'] = ['filters']
super(Filter, self).__init__(**kw)
class Action(Wrapper):
'''Convenience type for actions'''
def __init__(self, **kw):
kw['wrapper_name'] = 'make_action'
kw['wrapper_namespace'] = []
kw['namespace'] = ['actions']
super(Action, self).__init__(**kw)
class CreateObjects(Action):
'''Assemble a createObjects action.'''
def __init__(self, **kw):
objs = []
for path, interfaces in kw.pop('objs').iteritems():
key_o = TrivialArgument(
value=path,
type='string',
decorators=[Literal('string')])
value_i = []
for interface, properties, in interfaces.iteritems():
key_i = TrivialArgument(value=interface, type='string')
value_p = []
for prop, value in properties.iteritems():
key_p = TrivialArgument(value=prop, type='string')
value_v = TrivialArgument(
decorators=[Literal(value.get('type', None))],
**value)
value_p.append(InitializerList(values=[key_p, value_v]))
value_p = InitializerList(values=value_p)
value_i.append(InitializerList(values=[key_i, value_p]))
value_i = InitializerList(values=value_i)
objs.append(InitializerList(values=[key_o, value_i]))
kw['args'] = [InitializerList(values=objs)]
super(CreateObjects, self).__init__(**kw)
class DestroyObjects(Action):
'''Assemble a destroyObject action.'''
def __init__(self, **kw):
values = [{'value': x, 'type': 'string'} for x in kw.pop('paths')]
args = [InitializerList(
values=[TrivialArgument(**x) for x in values])]
kw['args'] = args
super(DestroyObjects, self).__init__(**kw)
class SetProperty(Action):
'''Assemble a setProperty action.'''
def __init__(self, **kw):
args = []
value = kw.pop('value')
prop = kw.pop('property')
iface = kw.pop('interface')
iface = Interface(iface)
namespace = iface.namespace().split('::')[:-1]
name = iface[-1]
t = Template(namespace=namespace, name=iface[-1])
member = '&%s' % '::'.join(
namespace + [name, NamedElement(name=prop).camelCase])
member_type = cppTypeName(value['type'])
member_cast = '{0} ({1}::*)({0})'.format(member_type, t.qualified())
args.append(TrivialArgument(value=kw.pop('path'), type='string'))
args.append(TrivialArgument(value=str(iface), type='string'))
args.append(TrivialArgument(
value=member, decorators=[Cast('static', member_cast)]))
args.append(TrivialArgument(**value))
kw['templates'] = [Template(name=name, namespace=namespace)]
kw['args'] = args
super(SetProperty, self).__init__(**kw)
class PropertyChanged(Filter):
'''Assemble a propertyChanged filter.'''
def __init__(self, **kw):
args = []
args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
args.append(TrivialArgument(value=kw.pop('property'), type='string'))
args.append(TrivialArgument(
decorators=[
Literal(kw['value'].get('type', None))], **kw.pop('value')))
kw['args'] = args
super(PropertyChanged, self).__init__(**kw)
class PropertyIs(Filter):
'''Assemble a propertyIs filter.'''
def __init__(self, **kw):
args = []
args.append(TrivialArgument(value=kw.pop('path'), type='string'))
args.append(TrivialArgument(value=kw.pop('interface'), type='string'))
args.append(TrivialArgument(value=kw.pop('property'), type='string'))
args.append(TrivialArgument(
decorators=[
Literal(kw['value'].get('type', None))], **kw.pop('value')))
service = kw.pop('service', None)
if service:
args.append(TrivialArgument(value=service, type='string'))
kw['args'] = args
super(PropertyIs, self).__init__(**kw)
class Event(MethodCall):
'''Assemble an inventory manager event.'''
action_map = {
'destroyObjects': DestroyObjects,
'createObjects': CreateObjects,
'setProperty': SetProperty,
}
filter_map = {
'propertyChangedTo': PropertyChanged,
'propertyIs': PropertyIs,
}
def __init__(self, **kw):
self.summary = kw.pop('name')
filters = [
self.filter_map[x['name']](**x) for x in kw.pop('filters', [])]
filters = Vector(
templates=[Template(name='Filter', namespace=[])],
args=filters)
event = MethodCall(
name='make_shared',
namespace=['std'],
templates=[Template(
name=kw.pop('event'),
namespace=kw.pop('event_namespace', []))],
args=kw.pop('event_args', []) + [filters])
events = Vector(
templates=[Template(name='EventBasePtr', namespace=[])],
args=[event])
action_type = Template(name='Action', namespace=[])
action_args = [
self.action_map[x['name']](**x) for x in kw.pop('actions', [])]
actions = Vector(
templates=[action_type],
args=action_args)
kw['name'] = 'make_tuple'
kw['namespace'] = ['std']
kw['args'] = [events, actions]
super(Event, self).__init__(**kw)
class MatchEvent(Event):
'''Associate one or more dbus signal match signatures with
a filter.'''
def __init__(self, **kw):
kw['event'] = 'DbusSignal'
kw['event_namespace'] = []
kw['event_args'] = [
DbusSignature(**x) for x in kw.pop('signatures', [])]
super(MatchEvent, self).__init__(**kw)
class StartupEvent(Event):
'''Assemble a startup event.'''
def __init__(self, **kw):
kw['event'] = 'StartupEvent'
kw['event_namespace'] = []
super(StartupEvent, self).__init__(**kw)
class Everything(Renderer):
'''Parse/render entry point.'''
class_map = {
'match': MatchEvent,
'startup': StartupEvent,
}
@staticmethod
def load(args):
# Aggregate all the event YAML in the events.d directory
# into a single list of events.
events = []
events_dir = os.path.join(args.inputdir, 'events.d')
if os.path.exists(events_dir):
yaml_files = filter(
lambda x: x.endswith('.yaml'),
os.listdir(events_dir))
for x in yaml_files:
with open(os.path.join(events_dir, x), 'r') as fd:
for e in yaml.safe_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 = []
interfaces_dir = os.path.join(args.inputdir, 'interfaces.d')
if os.path.exists(interfaces_dir):
yaml_files = filter(
lambda x: x.endswith('.yaml'),
os.listdir(interfaces_dir))
for x in yaml_files:
with open(os.path.join(interfaces_dir, x), 'r') as fd:
for i in yaml.safe_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 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,
indent=Indent()))
if __name__ == '__main__':
script_dir = os.path.dirname(os.path.realpath(__file__))
valid_commands = {
'generate-cpp': 'generate_cpp',
}
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='%s.' % " | ".join(valid_commands.keys()))
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