xref: /openbmc/phosphor-dbus-monitor/src/pdmgen.py (revision 80c70610a0ea96cf609387638f9f3f9bee0c581d)
1#!/usr/bin/env python
2
3'''Phosphor DBus Monitor YAML parser and code generator.
4
5The parser workflow is broken down as follows:
6  1 - Import YAML files as native python type(s) instance(s).
7  2 - Create an instance of the Everything class from the
8        native python type instance(s) with the Everything.load
9        method.
10  3 - The Everything class constructor orchestrates conversion of the
11        native python type(s) instances(s) to render helper types.
12        Each render helper type constructor imports its attributes
13        from the native python type(s) instances(s).
14  4 - Present the converted YAML to the command processing method
15        requested by the script user.
16'''
17
18import os
19import sys
20import yaml
21import mako.lookup
22from argparse import ArgumentParser
23from sdbusplus.renderer import Renderer
24from sdbusplus.namedelement import NamedElement
25import sdbusplus.property
26
27
28class InvalidConfigError(BaseException):
29    '''General purpose config file parsing error.'''
30
31    def __init__(self, path, msg):
32        '''Display configuration file with the syntax
33        error and the error message.'''
34
35        self.config = path
36        self.msg = msg
37
38
39class NotUniqueError(InvalidConfigError):
40    '''Within a config file names must be unique.
41    Display the config file with the duplicate and
42    the duplicate itself.'''
43
44    def __init__(self, path, cls, *names):
45        fmt = 'Duplicate {0}: "{1}"'
46        super(NotUniqueError, self).__init__(
47            path, fmt.format(cls, ' '.join(names)))
48
49
50def get_index(objs, cls, name, config=None):
51    '''Items are usually rendered as C++ arrays and as
52    such are stored in python lists.  Given an item name
53    its class, and an optional config file filter, find
54    the item index.'''
55
56    for i, x in enumerate(objs.get(cls, [])):
57        if config and x.configfile != config:
58            continue
59        if x.name != name:
60            continue
61
62        return i
63    raise InvalidConfigError(config, 'Could not find name: "{0}"'.format(name))
64
65
66def exists(objs, cls, name, config=None):
67    '''Check to see if an item already exists in a list given
68    the item name.'''
69
70    try:
71        get_index(objs, cls, name, config)
72    except:
73        return False
74
75    return True
76
77
78def add_unique(obj, *a, **kw):
79    '''Add an item to one or more lists unless already present,
80    with an option to constrain the search to a specific config file.'''
81
82    for container in a:
83        if not exists(container, obj.cls, obj.name, config=kw.get('config')):
84            container.setdefault(obj.cls, []).append(obj)
85
86
87class Cast(object):
88    '''Decorate an argument by casting it.'''
89
90    def __init__(self, cast, target):
91        '''cast is the cast type (static, const, etc...).
92           target is the cast target type.'''
93        self.cast = cast
94        self.target = target
95
96    def __call__(self, arg):
97        return '{0}_cast<{1}>({2})'.format(self.cast, self.target, arg)
98
99
100class Literal(object):
101    '''Decorate an argument with a literal operator.'''
102
103    integer_types = [
104        'int16',
105        'int32',
106        'int64',
107        'uint16',
108        'uint32',
109        'uint64'
110    ]
111
112    def __init__(self, type):
113        self.type = type
114
115    def __call__(self, arg):
116        if 'uint' in self.type:
117            arg = '{0}ull'.format(arg)
118        elif 'int' in self.type:
119            arg = '{0}ll'.format(arg)
120
121        if self.type in self.integer_types:
122            return Cast('static', '{0}_t'.format(self.type))(arg)
123        elif self.type == 'byte':
124            return Cast('static', 'uint8_t'.format(self.type))(arg)
125
126        if self.type == 'string':
127            return '{0}s'.format(arg)
128
129        return arg
130
131
132class FixBool(object):
133    '''Un-capitalize booleans.'''
134
135    def __call__(self, arg):
136        return '{0}'.format(arg.lower())
137
138
139class Quote(object):
140    '''Decorate an argument by quoting it.'''
141
142    def __call__(self, arg):
143        return '"{0}"'.format(arg)
144
145
146class Argument(NamedElement, Renderer):
147    '''Define argument type interface.'''
148
149    def __init__(self, **kw):
150        self.type = kw.pop('type', None)
151        super(Argument, self).__init__(**kw)
152
153    def argument(self, loader, indent):
154        raise NotImplementedError
155
156
157class TrivialArgument(Argument):
158    '''Non-array type arguments.'''
159
160    def __init__(self, **kw):
161        self.value = kw.pop('value')
162        self.decorators = kw.pop('decorators', [])
163        if kw.get('type', None):
164            self.decorators.insert(0, Literal(kw['type']))
165        if kw.get('type', None) == 'string':
166            self.decorators.insert(0, Quote())
167        if kw.get('type', None) == 'boolean':
168            self.decorators.insert(0, FixBool())
169
170        super(TrivialArgument, self).__init__(**kw)
171
172    def argument(self, loader, indent):
173        a = str(self.value)
174        for d in self.decorators:
175            a = d(a)
176
177        return a
178
179
180class Metadata(Argument):
181    '''Metadata type arguments.'''
182
183    def __init__(self, **kw):
184        self.value = kw.pop('value')
185        self.decorators = kw.pop('decorators', [])
186        if kw.get('type', None) == 'string':
187            self.decorators.insert(0, Quote())
188
189        super(Metadata, self).__init__(**kw)
190
191    def argument(self, loader, indent):
192        a = str(self.value)
193        for d in self.decorators:
194            a = d(a)
195
196        return a
197
198
199class Indent(object):
200    '''Help templates be depth agnostic.'''
201
202    def __init__(self, depth=0):
203        self.depth = depth
204
205    def __add__(self, depth):
206        return Indent(self.depth + depth)
207
208    def __call__(self, depth):
209        '''Render an indent at the current depth plus depth.'''
210        return 4*' '*(depth + self.depth)
211
212
213class ConfigEntry(NamedElement):
214    '''Base interface for rendered items.'''
215
216    def __init__(self, *a, **kw):
217        '''Pop the configfile/class/subclass keywords.'''
218
219        self.configfile = kw.pop('configfile')
220        self.cls = kw.pop('class')
221        self.subclass = kw.pop(self.cls)
222        super(ConfigEntry, self).__init__(**kw)
223
224    def factory(self, objs):
225        ''' Optional factory interface for subclasses to add
226        additional items to be rendered.'''
227
228        pass
229
230    def setup(self, objs):
231        ''' Optional setup interface for subclasses, invoked
232        after all factory methods have been run.'''
233
234        pass
235
236
237class Path(ConfigEntry):
238    '''Path/metadata association.'''
239
240    def __init__(self, *a, **kw):
241        super(Path, self).__init__(**kw)
242
243        if self.name['meta'].upper() != self.name['meta']:
244            raise InvalidConfigError(
245                self.configfile,
246                'Metadata tag "{0}" must be upper case.'.format(
247                    self.name['meta']))
248
249    def factory(self, objs):
250        '''Create path and metadata elements.'''
251
252        args = {
253            'class': 'pathname',
254            'pathname': 'element',
255            'name': self.name['path']
256        }
257        add_unique(ConfigEntry(
258            configfile=self.configfile, **args), objs)
259
260        args = {
261            'class': 'meta',
262            'meta': 'element',
263            'name': self.name['meta']
264        }
265        add_unique(ConfigEntry(
266            configfile=self.configfile, **args), objs)
267
268        super(Path, self).factory(objs)
269
270    def setup(self, objs):
271        '''Resolve path and metadata names to indices.'''
272
273        self.path = get_index(
274            objs, 'pathname', self.name['path'])
275        self.meta = get_index(
276            objs, 'meta', self.name['meta'])
277
278        super(Path, self).setup(objs)
279
280
281class Property(ConfigEntry):
282    '''Property/interface/metadata association.'''
283
284    def __init__(self, *a, **kw):
285        super(Property, self).__init__(**kw)
286
287        if self.name['meta'].upper() != self.name['meta']:
288            raise InvalidConfigError(
289                self.configfile,
290                'Metadata tag "{0}" must be upper case.'.format(
291                    self.name['meta']))
292
293    def factory(self, objs):
294        '''Create interface, property name and metadata elements.'''
295
296        args = {
297            'class': 'interface',
298            'interface': 'element',
299            'name': self.name['interface']
300        }
301        add_unique(ConfigEntry(
302            configfile=self.configfile, **args), objs)
303
304        args = {
305            'class': 'propertyname',
306            'propertyname': 'element',
307            'name': self.name['property']
308        }
309        add_unique(ConfigEntry(
310            configfile=self.configfile, **args), objs)
311
312        args = {
313            'class': 'meta',
314            'meta': 'element',
315            'name': self.name['meta']
316        }
317        add_unique(ConfigEntry(
318            configfile=self.configfile, **args), objs)
319
320        super(Property, self).factory(objs)
321
322    def setup(self, objs):
323        '''Resolve interface, property and metadata to indices.'''
324
325        self.interface = get_index(
326            objs, 'interface', self.name['interface'])
327        self.prop = get_index(
328            objs, 'propertyname', self.name['property'])
329        self.meta = get_index(
330            objs, 'meta', self.name['meta'])
331
332        super(Property, self).setup(objs)
333
334
335class Instance(ConfigEntry):
336    '''Property/Path association.'''
337
338    def __init__(self, *a, **kw):
339        super(Instance, self).__init__(**kw)
340
341    def setup(self, objs):
342        '''Resolve elements to indices.'''
343
344        self.interface = get_index(
345            objs, 'interface', self.name['property']['interface'])
346        self.prop = get_index(
347            objs, 'propertyname', self.name['property']['property'])
348        self.propmeta = get_index(
349            objs, 'meta', self.name['property']['meta'])
350        self.path = get_index(
351            objs, 'pathname', self.name['path']['path'])
352        self.pathmeta = get_index(
353            objs, 'meta', self.name['path']['meta'])
354
355        super(Instance, self).setup(objs)
356
357class PathInstance(ConfigEntry):
358    '''Path association.'''
359
360    def __init__(self, *a, **kw):
361        super(PathInstance, self).__init__(**kw)
362
363    def setup(self, objs):
364        '''Resolve elements to indices.'''
365        self.path = self.name['path']['path']
366        self.pathmeta = self.name['path']['meta']
367        super(PathInstance, self).setup(objs)
368
369class Group(ConfigEntry):
370    '''Pop the members keyword for groups.'''
371
372    def __init__(self, *a, **kw):
373        self.members = kw.pop('members')
374        super(Group, self).__init__(**kw)
375
376
377class ImplicitGroup(Group):
378    '''Provide a factory method for groups whose members are
379    not explicitly declared in the config files.'''
380
381    def __init__(self, *a, **kw):
382        super(ImplicitGroup, self).__init__(**kw)
383
384    def factory(self, objs):
385        '''Create group members.'''
386
387        factory = Everything.classmap(self.subclass, 'element')
388        for m in self.members:
389            args = {
390                'class': self.subclass,
391                self.subclass: 'element',
392                'name': m
393            }
394
395            obj = factory(configfile=self.configfile, **args)
396            add_unique(obj, objs)
397            obj.factory(objs)
398
399        super(ImplicitGroup, self).factory(objs)
400
401
402class GroupOfPaths(ImplicitGroup):
403    '''Path group config file directive.'''
404
405    def __init__(self, *a, **kw):
406        super(GroupOfPaths, self).__init__(**kw)
407
408    def setup(self, objs):
409        '''Resolve group members.'''
410
411        def map_member(x):
412            path = get_index(
413                objs, 'pathname', x['path'])
414            meta = get_index(
415                objs, 'meta', x['meta'])
416            return (path, meta)
417
418        self.members = map(
419            map_member,
420            self.members)
421
422        super(GroupOfPaths, self).setup(objs)
423
424
425class GroupOfProperties(ImplicitGroup):
426    '''Property group config file directive.'''
427
428    def __init__(self, *a, **kw):
429        self.type = kw.pop('type')
430        self.datatype = sdbusplus.property.Property(
431            name=kw.get('name'),
432            type=self.type).cppTypeName
433
434        super(GroupOfProperties, self).__init__(**kw)
435
436    def setup(self, objs):
437        '''Resolve group members.'''
438
439        def map_member(x):
440            iface = get_index(
441                objs, 'interface', x['interface'])
442            prop = get_index(
443                objs, 'propertyname', x['property'])
444            meta = get_index(
445                objs, 'meta', x['meta'])
446
447            return (iface, prop, meta)
448
449        self.members = map(
450            map_member,
451            self.members)
452
453        super(GroupOfProperties, self).setup(objs)
454
455
456class GroupOfInstances(ImplicitGroup):
457    '''A group of property instances.'''
458
459    def __init__(self, *a, **kw):
460        super(GroupOfInstances, self).__init__(**kw)
461
462    def setup(self, objs):
463        '''Resolve group members.'''
464
465        def map_member(x):
466            path = get_index(objs, 'pathname', x['path']['path'])
467            pathmeta = get_index(objs, 'meta', x['path']['meta'])
468            interface = get_index(
469                objs, 'interface', x['property']['interface'])
470            prop = get_index(objs, 'propertyname', x['property']['property'])
471            propmeta = get_index(objs, 'meta', x['property']['meta'])
472            instance = get_index(objs, 'instance', x)
473
474            return (path, pathmeta, interface, prop, propmeta, instance)
475
476        self.members = map(
477            map_member,
478            self.members)
479
480        super(GroupOfInstances, self).setup(objs)
481
482class GroupOfPathInstances(ImplicitGroup):
483    '''A group of path instances.'''
484
485    def __init__(self, *a, **kw):
486        super(GroupOfPathInstances, self).__init__(**kw)
487
488    def setup(self, objs):
489        '''Resolve group members.'''
490
491        def map_member(x):
492            path = get_index(objs, 'pathname', x['path']['path'])
493            pathmeta = get_index(objs, 'meta', x['path']['meta'])
494            pathinstance = get_index(objs, 'pathinstance', x)
495            return (path, pathmeta, pathinstance)
496
497        self.members = map(
498            map_member,
499            self.members)
500
501        super(GroupOfPathInstances, self).setup(objs)
502
503
504class HasPropertyIndex(ConfigEntry):
505    '''Handle config file directives that require an index to be
506    constructed.'''
507
508    def __init__(self, *a, **kw):
509        self.paths = kw.pop('paths')
510        self.properties = kw.pop('properties')
511        super(HasPropertyIndex, self).__init__(**kw)
512
513    def factory(self, objs):
514        '''Create a group of instances for this index.'''
515
516        members = []
517        path_group = get_index(
518            objs, 'pathgroup', self.paths, config=self.configfile)
519        property_group = get_index(
520            objs, 'propertygroup', self.properties, config=self.configfile)
521
522        for path in objs['pathgroup'][path_group].members:
523            for prop in objs['propertygroup'][property_group].members:
524                member = {
525                    'path': path,
526                    'property': prop,
527                }
528                members.append(member)
529
530        args = {
531            'members': members,
532            'class': 'instancegroup',
533            'instancegroup': 'instance',
534            'name': '{0} {1}'.format(self.paths, self.properties)
535        }
536
537        group = GroupOfInstances(configfile=self.configfile, **args)
538        add_unique(group, objs, config=self.configfile)
539        group.factory(objs)
540
541        super(HasPropertyIndex, self).factory(objs)
542
543    def setup(self, objs):
544        '''Resolve path, property, and instance groups.'''
545
546        self.instances = get_index(
547            objs,
548            'instancegroup',
549            '{0} {1}'.format(self.paths, self.properties),
550            config=self.configfile)
551        self.paths = get_index(
552            objs,
553            'pathgroup',
554            self.paths,
555            config=self.configfile)
556        self.properties = get_index(
557            objs,
558            'propertygroup',
559            self.properties,
560            config=self.configfile)
561        self.datatype = objs['propertygroup'][self.properties].datatype
562        self.type = objs['propertygroup'][self.properties].type
563
564        super(HasPropertyIndex, self).setup(objs)
565
566class HasPathIndex(ConfigEntry):
567    '''Handle config file directives that require an index to be
568    constructed.'''
569
570    def __init__(self, *a, **kw):
571        self.paths = kw.pop('paths')
572        super(HasPathIndex, self).__init__(**kw)
573
574    def factory(self, objs):
575        '''Create a group of instances for this index.'''
576
577        members = []
578        path_group = get_index(
579            objs, 'pathgroup', self.paths, config=self.configfile)
580
581        for path in objs['pathgroup'][path_group].members:
582            member = {
583                'path': path,
584            }
585            members.append(member)
586
587        args = {
588            'members': members,
589            'class': 'pathinstancegroup',
590            'pathinstancegroup': 'pathinstance',
591            'name': '{0}'.format(self.paths)
592        }
593
594        group = GroupOfPathInstances(configfile=self.configfile, **args)
595        add_unique(group, objs, config=self.configfile)
596        group.factory(objs)
597
598        super(HasPathIndex, self).factory(objs)
599
600    def setup(self, objs):
601        '''Resolve path and instance groups.'''
602
603        self.pathinstances = get_index(
604            objs,
605            'pathinstancegroup',
606            '{0}'.format(self.paths),
607            config=self.configfile)
608        self.paths = get_index(
609            objs,
610            'pathgroup',
611            self.paths,
612            config=self.configfile)
613        super(HasPathIndex, self).setup(objs)
614
615class PropertyWatch(HasPropertyIndex):
616    '''Handle the property watch config file directive.'''
617
618    def __init__(self, *a, **kw):
619        self.callback = kw.pop('callback', None)
620        super(PropertyWatch, self).__init__(**kw)
621
622    def setup(self, objs):
623        '''Resolve optional callback.'''
624
625        if self.callback:
626            self.callback = get_index(
627                objs,
628                'callback',
629                self.callback,
630                config=self.configfile)
631
632        super(PropertyWatch, self).setup(objs)
633
634class PathWatch(HasPathIndex):
635    '''Handle the path watch config file directive.'''
636
637    def __init__(self, *a, **kw):
638        self.pathcallback = kw.pop('pathcallback', None)
639        super(PathWatch, self).__init__(**kw)
640
641    def setup(self, objs):
642        '''Resolve optional callback.'''
643        if self.pathcallback:
644            self.pathcallback = get_index(
645                objs,
646                'pathcallback',
647                self.pathcallback,
648                config=self.configfile)
649        super(PathWatch, self).setup(objs)
650
651
652class Callback(HasPropertyIndex):
653    '''Interface and common logic for callbacks.'''
654
655    def __init__(self, *a, **kw):
656        super(Callback, self).__init__(**kw)
657
658class PathCallback(HasPathIndex):
659    '''Interface and common logic for callbacks.'''
660
661    def __init__(self, *a, **kw):
662        super(PathCallback, self).__init__(**kw)
663
664class ConditionCallback(ConfigEntry, Renderer):
665    '''Handle the journal callback config file directive.'''
666
667    def __init__(self, *a, **kw):
668        self.condition = kw.pop('condition')
669        self.instance = kw.pop('instance')
670        self.defer = kw.pop('defer', None)
671        super(ConditionCallback, self).__init__(**kw)
672
673    def factory(self, objs):
674        '''Create a graph instance for this callback.'''
675
676        args = {
677            'configfile': self.configfile,
678            'members': [self.instance],
679            'class': 'callbackgroup',
680            'callbackgroup': 'callback',
681            'name': [self.instance]
682        }
683
684        entry = CallbackGraphEntry(**args)
685        add_unique(entry, objs, config=self.configfile)
686
687        super(ConditionCallback, self).factory(objs)
688
689    def setup(self, objs):
690        '''Resolve condition and graph entry.'''
691
692        self.graph = get_index(
693            objs,
694            'callbackgroup',
695            [self.instance],
696            config=self.configfile)
697
698        self.condition = get_index(
699            objs,
700            'condition',
701            self.name,
702            config=self.configfile)
703
704        super(ConditionCallback, self).setup(objs)
705
706    def construct(self, loader, indent):
707        return self.render(
708            loader,
709            'conditional.mako.cpp',
710            c=self,
711            indent=indent)
712
713
714class Condition(HasPropertyIndex):
715    '''Interface and common logic for conditions.'''
716
717    def __init__(self, *a, **kw):
718        self.callback = kw.pop('callback')
719        self.defer = kw.pop('defer', None)
720        super(Condition, self).__init__(**kw)
721
722    def factory(self, objs):
723        '''Create a callback instance for this conditional.'''
724
725        args = {
726            'configfile': self.configfile,
727            'condition': self.name,
728            'class': 'callback',
729            'callback': 'conditional',
730            'instance': self.callback,
731            'name': self.name,
732            'defer': self.defer
733        }
734
735        callback = ConditionCallback(**args)
736        add_unique(callback, objs, config=self.configfile)
737        callback.factory(objs)
738
739        super(Condition, self).factory(objs)
740
741
742class CountCondition(Condition, Renderer):
743    '''Handle the count condition config file directive.'''
744
745    def __init__(self, *a, **kw):
746        self.countop = kw.pop('countop')
747        self.countbound = kw.pop('countbound')
748        self.op = kw.pop('op')
749        self.bound = kw.pop('bound')
750        self.oneshot = TrivialArgument(
751            type='boolean',
752            value=kw.pop('oneshot', False))
753        super(CountCondition, self).__init__(**kw)
754
755    def setup(self, objs):
756        '''Resolve type.'''
757
758        super(CountCondition, self).setup(objs)
759        self.bound = TrivialArgument(
760            type=self.type,
761            value=self.bound)
762
763    def construct(self, loader, indent):
764        return self.render(
765            loader,
766            'count.mako.cpp',
767            c=self,
768            indent=indent)
769
770
771class Journal(Callback, Renderer):
772    '''Handle the journal callback config file directive.'''
773
774    def __init__(self, *a, **kw):
775        self.severity = kw.pop('severity')
776        self.message = kw.pop('message')
777        super(Journal, self).__init__(**kw)
778
779    def construct(self, loader, indent):
780        return self.render(
781            loader,
782            'journal.mako.cpp',
783            c=self,
784            indent=indent)
785
786
787class Elog(Callback, Renderer):
788    '''Handle the elog callback config file directive.'''
789
790    def __init__(self, *a, **kw):
791        self.error = kw.pop('error')
792        self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
793        super(Elog, self).__init__(**kw)
794
795    def construct(self, loader, indent):
796        with open('errors.hpp', 'a') as fd:
797            fd.write(
798                self.render(
799                    loader,
800                    'errors.mako.hpp',
801                    c=self))
802        return self.render(
803            loader,
804            'elog.mako.cpp',
805            c=self,
806            indent=indent)
807
808class Event(Callback, Renderer):
809    '''Handle the event callback config file directive.'''
810
811    def __init__(self, *a, **kw):
812        self.eventName = kw.pop('eventName')
813        self.eventMessage = kw.pop('eventMessage')
814        super(Event, self).__init__(**kw)
815
816    def construct(self, loader, indent):
817        return self.render(
818            loader,
819            'event.mako.cpp',
820            c=self,
821            indent=indent)
822
823class EventPath(PathCallback, Renderer):
824    '''Handle the event path callback config file directive.'''
825
826    def __init__(self, *a, **kw):
827        self.eventType = kw.pop('eventType')
828        super(EventPath, self).__init__(**kw)
829
830    def construct(self, loader, indent):
831        return self.render(
832            loader,
833            'eventpath.mako.cpp',
834            c=self,
835            indent=indent)
836
837class ElogWithMetadata(Callback, Renderer):
838    '''Handle the elog_with_metadata callback config file directive.'''
839
840    def __init__(self, *a, **kw):
841        self.error = kw.pop('error')
842        self.metadata = kw.pop('metadata')
843        super(ElogWithMetadata, self).__init__(**kw)
844
845    def construct(self, loader, indent):
846        with open('errors.hpp', 'a') as fd:
847            fd.write(
848                self.render(
849                    loader,
850                    'errors.mako.hpp',
851                    c=self))
852        return self.render(
853            loader,
854            'elog_with_metadata.mako.cpp',
855            c=self,
856            indent=indent)
857
858
859class ResolveCallout(Callback, Renderer):
860    '''Handle the 'resolve callout' callback config file directive.'''
861
862    def __init__(self, *a, **kw):
863        self.callout = kw.pop('callout')
864        super(ResolveCallout, self).__init__(**kw)
865
866    def construct(self, loader, indent):
867        return self.render(
868            loader,
869            'resolve_errors.mako.cpp',
870            c=self,
871            indent=indent)
872
873
874class Method(ConfigEntry, Renderer):
875    '''Handle the method callback config file directive.'''
876
877    def __init__(self, *a, **kw):
878        self.service = kw.pop('service')
879        self.path = kw.pop('path')
880        self.interface = kw.pop('interface')
881        self.method = kw.pop('method')
882        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
883        super(Method, self).__init__(**kw)
884
885    def factory(self, objs):
886        args = {
887            'class': 'interface',
888            'interface': 'element',
889            'name': self.service
890        }
891        add_unique(ConfigEntry(
892            configfile=self.configfile, **args), objs)
893
894        args = {
895            'class': 'pathname',
896            'pathname': 'element',
897            'name': self.path
898        }
899        add_unique(ConfigEntry(
900            configfile=self.configfile, **args), objs)
901
902        args = {
903            'class': 'interface',
904            'interface': 'element',
905            'name': self.interface
906        }
907        add_unique(ConfigEntry(
908            configfile=self.configfile, **args), objs)
909
910        args = {
911            'class': 'propertyname',
912            'propertyname': 'element',
913            'name': self.method
914        }
915        add_unique(ConfigEntry(
916            configfile=self.configfile, **args), objs)
917
918        super(Method, self).factory(objs)
919
920    def setup(self, objs):
921        '''Resolve elements.'''
922
923        self.service = get_index(
924            objs,
925            'interface',
926            self.service)
927
928        self.path = get_index(
929            objs,
930            'pathname',
931            self.path)
932
933        self.interface = get_index(
934            objs,
935            'interface',
936            self.interface)
937
938        self.method = get_index(
939            objs,
940            'propertyname',
941            self.method)
942
943        super(Method, self).setup(objs)
944
945    def construct(self, loader, indent):
946        return self.render(
947            loader,
948            'method.mako.cpp',
949            c=self,
950            indent=indent)
951
952
953class CallbackGraphEntry(Group):
954    '''An entry in a traversal list for groups of callbacks.'''
955
956    def __init__(self, *a, **kw):
957        super(CallbackGraphEntry, self).__init__(**kw)
958
959    def setup(self, objs):
960        '''Resolve group members.'''
961
962        def map_member(x):
963            return get_index(
964                objs, 'callback', x, config=self.configfile)
965
966        self.members = map(
967            map_member,
968            self.members)
969
970        super(CallbackGraphEntry, self).setup(objs)
971
972class PathCallbackGraphEntry(Group):
973    '''An entry in a traversal list for groups of callbacks.'''
974
975    def __init__(self, *a, **kw):
976        super(PathCallbackGraphEntry, self).__init__(**kw)
977
978    def setup(self, objs):
979        '''Resolve group members.'''
980
981        def map_member(x):
982            return get_index(
983                objs, 'pathcallback', x, config=self.configfile)
984
985        self.members = map(
986            map_member,
987            self.members)
988
989        super(PathCallbackGraphEntry, self).setup(objs)
990
991class GroupOfCallbacks(ConfigEntry, Renderer):
992    '''Handle the callback group config file directive.'''
993
994    def __init__(self, *a, **kw):
995        self.members = kw.pop('members')
996        super(GroupOfCallbacks, self).__init__(**kw)
997
998    def factory(self, objs):
999        '''Create a graph instance for this group of callbacks.'''
1000
1001        args = {
1002            'configfile': self.configfile,
1003            'members': self.members,
1004            'class': 'callbackgroup',
1005            'callbackgroup': 'callback',
1006            'name': self.members
1007        }
1008
1009        entry = CallbackGraphEntry(**args)
1010        add_unique(entry, objs, config=self.configfile)
1011
1012        super(GroupOfCallbacks, self).factory(objs)
1013
1014    def setup(self, objs):
1015        '''Resolve graph entry.'''
1016
1017        self.graph = get_index(
1018            objs, 'callbackgroup', self.members, config=self.configfile)
1019
1020        super(GroupOfCallbacks, self).setup(objs)
1021
1022    def construct(self, loader, indent):
1023        return self.render(
1024            loader,
1025            'callbackgroup.mako.cpp',
1026            c=self,
1027            indent=indent)
1028
1029class GroupOfPathCallbacks(ConfigEntry, Renderer):
1030    '''Handle the callback group config file directive.'''
1031
1032    def __init__(self, *a, **kw):
1033        self.members = kw.pop('members')
1034        super(GroupOfPathCallbacks, self).__init__(**kw)
1035
1036    def factory(self, objs):
1037        '''Create a graph instance for this group of callbacks.'''
1038
1039        args = {
1040            'configfile': self.configfile,
1041            'members': self.members,
1042            'class': 'pathcallbackgroup',
1043            'pathcallbackgroup': 'pathcallback',
1044            'name': self.members
1045        }
1046
1047        entry = PathCallbackGraphEntry(**args)
1048        add_unique(entry, objs, config=self.configfile)
1049        super(GroupOfPathCallbacks, self).factory(objs)
1050
1051    def setup(self, objs):
1052        '''Resolve graph entry.'''
1053
1054        self.graph = get_index(
1055            objs, 'callbackpathgroup', self.members, config=self.configfile)
1056
1057        super(GroupOfPathCallbacks, self).setup(objs)
1058
1059    def construct(self, loader, indent):
1060        return self.render(
1061            loader,
1062            'callbackpathgroup.mako.cpp',
1063            c=self,
1064            indent=indent)
1065
1066class Everything(Renderer):
1067    '''Parse/render entry point.'''
1068
1069    @staticmethod
1070    def classmap(cls, sub=None):
1071        '''Map render item class and subclass entries to the appropriate
1072        handler methods.'''
1073        class_map = {
1074            'path': {
1075                'element': Path,
1076            },
1077            'pathgroup': {
1078                'path': GroupOfPaths,
1079            },
1080            'propertygroup': {
1081                'property': GroupOfProperties,
1082            },
1083            'property': {
1084                'element': Property,
1085            },
1086            'watch': {
1087                'property': PropertyWatch,
1088            },
1089            'pathwatch': {
1090                'path': PathWatch,
1091            },
1092            'instance': {
1093                'element': Instance,
1094            },
1095            'pathinstance': {
1096                'element': PathInstance,
1097            },
1098            'callback': {
1099                'journal': Journal,
1100                'elog': Elog,
1101                'elog_with_metadata': ElogWithMetadata,
1102                'event': Event,
1103                'group': GroupOfCallbacks,
1104                'method': Method,
1105                'resolve callout': ResolveCallout,
1106            },
1107            'pathcallback': {
1108                'eventpath': EventPath,
1109                'grouppath': GroupOfPathCallbacks,
1110            },
1111            'condition': {
1112                'count': CountCondition,
1113            },
1114        }
1115
1116        if cls not in class_map:
1117            raise NotImplementedError('Unknown class: "{0}"'.format(cls))
1118        if sub not in class_map[cls]:
1119            raise NotImplementedError('Unknown {0} type: "{1}"'.format(
1120                cls, sub))
1121
1122        return class_map[cls][sub]
1123
1124    @staticmethod
1125    def load_one_yaml(path, fd, objs):
1126        '''Parse a single YAML file.  Parsing occurs in three phases.
1127        In the first phase a factory method associated with each
1128        configuration file directive is invoked.  These factory
1129        methods generate more factory methods.  In the second
1130        phase the factory methods created in the first phase
1131        are invoked.  In the last phase a callback is invoked on
1132        each object created in phase two.  Typically the callback
1133        resolves references to other configuration file directives.'''
1134
1135        factory_objs = {}
1136        for x in yaml.safe_load(fd.read()) or {}:
1137
1138            # Create factory object for this config file directive.
1139            cls = x['class']
1140            sub = x.get(cls)
1141            if cls == 'group':
1142                cls = '{0}group'.format(sub)
1143
1144            factory = Everything.classmap(cls, sub)
1145            obj = factory(configfile=path, **x)
1146
1147            # For a given class of directive, validate the file
1148            # doesn't have any duplicate names (duplicates are
1149            # ok across config files).
1150            if exists(factory_objs, obj.cls, obj.name, config=path):
1151                raise NotUniqueError(path, cls, obj.name)
1152
1153            factory_objs.setdefault(cls, []).append(obj)
1154            objs.setdefault(cls, []).append(obj)
1155
1156        for cls, items in factory_objs.items():
1157            for obj in items:
1158                # Add objects for template consumption.
1159                obj.factory(objs)
1160
1161    @staticmethod
1162    def load(args):
1163        '''Aggregate all the YAML in the input directory
1164        into a single aggregate.'''
1165
1166        objs = {}
1167        yaml_files = filter(
1168            lambda x: x.endswith('.yaml'),
1169            os.listdir(args.inputdir))
1170
1171        yaml_files.sort()
1172
1173        for x in yaml_files:
1174            path = os.path.join(args.inputdir, x)
1175            with open(path, 'r') as fd:
1176                Everything.load_one_yaml(path, fd, objs)
1177
1178        # Configuration file directives reference each other via
1179        # the name attribute; however, when rendered the reference
1180        # is just an array index.
1181        #
1182        # At this point all objects have been created but references
1183        # have not been resolved to array indices.  Instruct objects
1184        # to do that now.
1185        for cls, items in objs.items():
1186            for obj in items:
1187                obj.setup(objs)
1188
1189        return Everything(**objs)
1190
1191    def __init__(self, *a, **kw):
1192        self.pathmeta = kw.pop('path', [])
1193        self.paths = kw.pop('pathname', [])
1194        self.meta = kw.pop('meta', [])
1195        self.pathgroups = kw.pop('pathgroup', [])
1196        self.interfaces = kw.pop('interface', [])
1197        self.properties = kw.pop('property', [])
1198        self.propertynames = kw.pop('propertyname', [])
1199        self.propertygroups = kw.pop('propertygroup', [])
1200        self.instances = kw.pop('instance', [])
1201        self.pathinstances = kw.pop('pathinstance', [])
1202        self.instancegroups = kw.pop('instancegroup', [])
1203        self.pathinstancegroups = kw.pop('pathinstancegroup', [])
1204        self.watches = kw.pop('watch', [])
1205        self.pathwatches = kw.pop('pathwatch', [])
1206        self.callbacks = kw.pop('callback', [])
1207        self.pathcallbacks = kw.pop('pathcallback', [])
1208        self.callbackgroups = kw.pop('callbackgroup', [])
1209        self.pathcallbackgroups = kw.pop('pathcallbackgroup', [])
1210        self.conditions = kw.pop('condition', [])
1211
1212        super(Everything, self).__init__(**kw)
1213
1214    def generate_cpp(self, loader):
1215        '''Render the template with the provided data.'''
1216        # errors.hpp is used by generated.hpp to included any error.hpp files
1217        open('errors.hpp', 'w+')
1218
1219        with open(args.output, 'w') as fd:
1220            fd.write(
1221                self.render(
1222                    loader,
1223                    args.template,
1224                    meta=self.meta,
1225                    properties=self.properties,
1226                    propertynames=self.propertynames,
1227                    interfaces=self.interfaces,
1228                    paths=self.paths,
1229                    pathmeta=self.pathmeta,
1230                    pathgroups=self.pathgroups,
1231                    propertygroups=self.propertygroups,
1232                    instances=self.instances,
1233                    pathinstances=self.pathinstances,
1234                    watches=self.watches,
1235                    pathwatches=self.pathwatches,
1236                    instancegroups=self.instancegroups,
1237                    pathinstancegroups=self.pathinstancegroups,
1238                    callbacks=self.callbacks,
1239                    pathcallbacks=self.pathcallbacks,
1240                    callbackgroups=self.callbackgroups,
1241                    pathcallbackgroups=self.pathcallbackgroups,
1242                    conditions=self.conditions,
1243                    indent=Indent()))
1244
1245if __name__ == '__main__':
1246    script_dir = os.path.dirname(os.path.realpath(__file__))
1247    valid_commands = {
1248        'generate-cpp': 'generate_cpp',
1249    }
1250
1251    parser = ArgumentParser(
1252        description='Phosphor DBus Monitor (PDM) YAML '
1253        'scanner and code generator.')
1254
1255    parser.add_argument(
1256        "-o", "--out", dest="output",
1257        default='generated.cpp',
1258        help="Generated output file name and path.")
1259    parser.add_argument(
1260        '-t', '--template', dest='template',
1261        default='generated.mako.hpp',
1262        help='The top level template to render.')
1263    parser.add_argument(
1264        '-p', '--template-path', dest='template_search',
1265        default=script_dir,
1266        help='The space delimited mako template search path.')
1267    parser.add_argument(
1268        '-d', '--dir', dest='inputdir',
1269        default=os.path.join(script_dir, 'example'),
1270        help='Location of files to process.')
1271    parser.add_argument(
1272        'command', metavar='COMMAND', type=str,
1273        choices=valid_commands.keys(),
1274        help='%s.' % " | ".join(valid_commands.keys()))
1275
1276    args = parser.parse_args()
1277
1278    if sys.version_info < (3, 0):
1279        lookup = mako.lookup.TemplateLookup(
1280            directories=args.template_search.split(),
1281            disable_unicode=True)
1282    else:
1283        lookup = mako.lookup.TemplateLookup(
1284            directories=args.template_search.split())
1285    try:
1286        function = getattr(
1287            Everything.load(args),
1288            valid_commands[args.command])
1289        function(lookup)
1290    except InvalidConfigError as e:
1291        sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1292        raise
1293