Parse match rules

The 'pimgen.py' parser will parse one or more yaml files containing
inventory manager match rules and generate the required c++ header
file.

Change-Id: Id3b116450bd56487e266590dd339b93db9bc7d27
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/pimgen.py b/pimgen.py
new file mode 100755
index 0000000..f59e320
--- /dev/null
+++ b/pimgen.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import argparse
+import yaml
+
+valid_c_name_pattern = re.compile('[\W_]+')
+ignore_list = ['description']
+all_names = []
+
+
+def get_parser(x, fmt, lmbda=lambda x: x.capitalize()):
+    try:
+        return getattr(
+            sys.modules[__name__],
+            '%s' % (fmt.format(lmbda(x))))
+    except AttributeError:
+        raise NotImplementedError("Don't know how to parse '%s'" % x)
+
+
+class RenderList(list):
+    def __init__(self, renderers):
+        self.extend(renderers)
+
+    def __call__(self, fd):
+        for x in self:
+            x(fd)
+
+
+class ParseList(list):
+    def __init__(self, parsers):
+        self.extend(parsers)
+
+    def __call__(self):
+        return RenderList([x() for x in self])
+
+
+class MatchRender(object):
+    def __init__(self, name, signature):
+        self.name = valid_c_name_pattern.sub('_', name).lower()
+        self.signature = signature
+
+        if self.name in all_names:
+            raise RuntimeError('The name "%s" is not unique.' % name)
+        else:
+            all_names.append(self.name)
+
+    def __call__(self, fd):
+        sig = ['%s=\'%s\'' % (k, v) for k, v in self.signature.iteritems()]
+        sig = ['%s,' % x for x in sig[:-1]] + [sig[-1]]
+        sig = ['"%s"' % x for x in sig]
+        sig = ['%s\n' % x for x in sig[:-1]] + [sig[-1]]
+
+        fd.write('    {\n')
+        fd.write('        "%s",\n' % self.name)
+        fd.write('        {\n')
+        for s in sig:
+            fd.write('            %s' % s)
+        fd.write(',\n')
+        fd.write('        },\n')
+        fd.write('    },\n')
+
+
+class MatchEventParse(object):
+    def __init__(self, match):
+        self.name = match['name']
+        self.signature = match['signature']
+
+    def __call__(self):
+        return MatchRender(
+            self.name,
+            self.signature)
+
+
+class EventsParse(object):
+    def __init__(self, event):
+        self.delegate = None
+        cls = event['type']
+        if cls not in ignore_list:
+            fmt = '{0}EventParse'
+            self.delegate = get_parser(cls, fmt)(event)
+
+    def __call__(self):
+        if self.delegate:
+            return self.delegate()
+        return lambda x: None
+
+
+class DictParse(ParseList):
+    def __init__(self, data):
+        fmt = '{0}Parse'
+        parse = set(data.iterkeys()).difference(ignore_list)
+        ParseList.__init__(
+            self, [get_parser(x, fmt)(*data[x]) for x in parse])
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        description='Phosphor Inventory Manager (PIM) YAML '
+        'scanner and code generator.')
+    parser.add_argument(
+        '-o', '--output', dest='output',
+        default='generated.hpp', help='Output file name.')
+    parser.add_argument(
+        '-d', '--dir', dest='inputdir',
+        default='examples', help='Location of files to process.')
+
+    args = parser.parse_args()
+
+    yaml_files = filter(
+        lambda x: x.endswith('.yaml'),
+        os.listdir(args.inputdir))
+
+    def get_parsers(x):
+        with open(os.path.join(args.inputdir, x), 'r') as fd:
+            return DictParse(yaml.load(fd.read()))
+
+    head = """// This file was auto generated.  Do not edit.
+
+#pragma once
+
+const Manager::Events Manager::_events{
+"""
+
+    tail = """};
+"""
+
+    r = ParseList([get_parsers(x) for x in yaml_files])()
+    r.insert(0, lambda x: x.write(head))
+    r.append(lambda x: x.write(tail))
+
+    with open(args.output, 'w') as fd:
+        r(fd)
+
+# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4