xref: /openbmc/phosphor-dbus-monitor/src/pdmgen.py (revision 3c5318d843651dd18a6b8b0da67e3aaef810ca91)
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 inteface.'''
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
357
358class Group(ConfigEntry):
359    '''Pop the members keyword for groups.'''
360
361    def __init__(self, *a, **kw):
362        self.members = kw.pop('members')
363        super(Group, self).__init__(**kw)
364
365
366class ImplicitGroup(Group):
367    '''Provide a factory method for groups whose members are
368    not explicitly declared in the config files.'''
369
370    def __init__(self, *a, **kw):
371        super(ImplicitGroup, self).__init__(**kw)
372
373    def factory(self, objs):
374        '''Create group members.'''
375
376        factory = Everything.classmap(self.subclass, 'element')
377        for m in self.members:
378            args = {
379                'class': self.subclass,
380                self.subclass: 'element',
381                'name': m
382            }
383
384            obj = factory(configfile=self.configfile, **args)
385            add_unique(obj, objs)
386            obj.factory(objs)
387
388        super(ImplicitGroup, self).factory(objs)
389
390
391class GroupOfPaths(ImplicitGroup):
392    '''Path group config file directive.'''
393
394    def __init__(self, *a, **kw):
395        super(GroupOfPaths, self).__init__(**kw)
396
397    def setup(self, objs):
398        '''Resolve group members.'''
399
400        def map_member(x):
401            path = get_index(
402                objs, 'pathname', x['path'])
403            meta = get_index(
404                objs, 'meta', x['meta'])
405            return (path, meta)
406
407        self.members = map(
408            map_member,
409            self.members)
410
411        super(GroupOfPaths, self).setup(objs)
412
413
414class GroupOfProperties(ImplicitGroup):
415    '''Property group config file directive.'''
416
417    def __init__(self, *a, **kw):
418        self.type = kw.pop('type')
419        self.datatype = sdbusplus.property.Property(
420            name=kw.get('name'),
421            type=self.type).cppTypeName
422
423        super(GroupOfProperties, self).__init__(**kw)
424
425    def setup(self, objs):
426        '''Resolve group members.'''
427
428        def map_member(x):
429            iface = get_index(
430                objs, 'interface', x['interface'])
431            prop = get_index(
432                objs, 'propertyname', x['property'])
433            meta = get_index(
434                objs, 'meta', x['meta'])
435
436            return (iface, prop, meta)
437
438        self.members = map(
439            map_member,
440            self.members)
441
442        super(GroupOfProperties, self).setup(objs)
443
444
445class GroupOfInstances(ImplicitGroup):
446    '''A group of property instances.'''
447
448    def __init__(self, *a, **kw):
449        super(GroupOfInstances, self).__init__(**kw)
450
451    def setup(self, objs):
452        '''Resolve group members.'''
453
454        def map_member(x):
455            path = get_index(objs, 'pathname', x['path']['path'])
456            pathmeta = get_index(objs, 'meta', x['path']['meta'])
457            interface = get_index(
458                objs, 'interface', x['property']['interface'])
459            prop = get_index(objs, 'propertyname', x['property']['property'])
460            propmeta = get_index(objs, 'meta', x['property']['meta'])
461            instance = get_index(objs, 'instance', x)
462
463            return (path, pathmeta, interface, prop, propmeta, instance)
464
465        self.members = map(
466            map_member,
467            self.members)
468
469        super(GroupOfInstances, self).setup(objs)
470
471
472class HasPropertyIndex(ConfigEntry):
473    '''Handle config file directives that require an index to be
474    constructed.'''
475
476    def __init__(self, *a, **kw):
477        self.paths = kw.pop('paths')
478        self.properties = kw.pop('properties')
479        super(HasPropertyIndex, self).__init__(**kw)
480
481    def factory(self, objs):
482        '''Create a group of instances for this index.'''
483
484        members = []
485        path_group = get_index(
486            objs, 'pathgroup', self.paths, config=self.configfile)
487        property_group = get_index(
488            objs, 'propertygroup', self.properties, config=self.configfile)
489
490        for path in objs['pathgroup'][path_group].members:
491            for prop in objs['propertygroup'][property_group].members:
492                member = {
493                    'path': path,
494                    'property': prop,
495                }
496                members.append(member)
497
498        args = {
499            'members': members,
500            'class': 'instancegroup',
501            'instancegroup': 'instance',
502            'name': '{0} {1}'.format(self.paths, self.properties)
503        }
504
505        group = GroupOfInstances(configfile=self.configfile, **args)
506        add_unique(group, objs, config=self.configfile)
507        group.factory(objs)
508
509        super(HasPropertyIndex, self).factory(objs)
510
511    def setup(self, objs):
512        '''Resolve path, property, and instance groups.'''
513
514        self.instances = get_index(
515            objs,
516            'instancegroup',
517            '{0} {1}'.format(self.paths, self.properties),
518            config=self.configfile)
519        self.paths = get_index(
520            objs,
521            'pathgroup',
522            self.paths,
523            config=self.configfile)
524        self.properties = get_index(
525            objs,
526            'propertygroup',
527            self.properties,
528            config=self.configfile)
529        self.datatype = objs['propertygroup'][self.properties].datatype
530        self.type = objs['propertygroup'][self.properties].type
531
532        super(HasPropertyIndex, self).setup(objs)
533
534
535class PropertyWatch(HasPropertyIndex):
536    '''Handle the property watch config file directive.'''
537
538    def __init__(self, *a, **kw):
539        self.callback = kw.pop('callback', None)
540        super(PropertyWatch, self).__init__(**kw)
541
542    def setup(self, objs):
543        '''Resolve optional callback.'''
544
545        if self.callback:
546            self.callback = get_index(
547                objs,
548                'callback',
549                self.callback,
550                config=self.configfile)
551
552        super(PropertyWatch, self).setup(objs)
553
554
555class Callback(HasPropertyIndex):
556    '''Interface and common logic for callbacks.'''
557
558    def __init__(self, *a, **kw):
559        super(Callback, self).__init__(**kw)
560
561
562class ConditionCallback(ConfigEntry, Renderer):
563    '''Handle the journal callback config file directive.'''
564
565    def __init__(self, *a, **kw):
566        self.condition = kw.pop('condition')
567        self.instance = kw.pop('instance')
568        self.defer = kw.pop('defer', None)
569        super(ConditionCallback, self).__init__(**kw)
570
571    def factory(self, objs):
572        '''Create a graph instance for this callback.'''
573
574        args = {
575            'configfile': self.configfile,
576            'members': [self.instance],
577            'class': 'callbackgroup',
578            'callbackgroup': 'callback',
579            'name': [self.instance]
580        }
581
582        entry = CallbackGraphEntry(**args)
583        add_unique(entry, objs, config=self.configfile)
584
585        super(ConditionCallback, self).factory(objs)
586
587    def setup(self, objs):
588        '''Resolve condition and graph entry.'''
589
590        self.graph = get_index(
591            objs,
592            'callbackgroup',
593            [self.instance],
594            config=self.configfile)
595
596        self.condition = get_index(
597            objs,
598            'condition',
599            self.name,
600            config=self.configfile)
601
602        super(ConditionCallback, self).setup(objs)
603
604    def construct(self, loader, indent):
605        return self.render(
606            loader,
607            'conditional.mako.cpp',
608            c=self,
609            indent=indent)
610
611
612class Condition(HasPropertyIndex):
613    '''Interface and common logic for conditions.'''
614
615    def __init__(self, *a, **kw):
616        self.callback = kw.pop('callback')
617        self.defer = kw.pop('defer', None)
618        super(Condition, self).__init__(**kw)
619
620    def factory(self, objs):
621        '''Create a callback instance for this conditional.'''
622
623        args = {
624            'configfile': self.configfile,
625            'condition': self.name,
626            'class': 'callback',
627            'callback': 'conditional',
628            'instance': self.callback,
629            'name': self.name,
630            'defer': self.defer
631        }
632
633        callback = ConditionCallback(**args)
634        add_unique(callback, objs, config=self.configfile)
635        callback.factory(objs)
636
637        super(Condition, self).factory(objs)
638
639
640class CountCondition(Condition, Renderer):
641    '''Handle the count condition config file directive.'''
642
643    def __init__(self, *a, **kw):
644        self.countop = kw.pop('countop')
645        self.countbound = kw.pop('countbound')
646        self.op = kw.pop('op')
647        self.bound = kw.pop('bound')
648        self.oneshot = TrivialArgument(
649            type='boolean',
650            value=kw.pop('oneshot', False))
651        super(CountCondition, self).__init__(**kw)
652
653    def setup(self, objs):
654        '''Resolve type.'''
655
656        super(CountCondition, self).setup(objs)
657        self.bound = TrivialArgument(
658            type=self.type,
659            value=self.bound)
660
661    def construct(self, loader, indent):
662        return self.render(
663            loader,
664            'count.mako.cpp',
665            c=self,
666            indent=indent)
667
668
669class Journal(Callback, Renderer):
670    '''Handle the journal callback config file directive.'''
671
672    def __init__(self, *a, **kw):
673        self.severity = kw.pop('severity')
674        self.message = kw.pop('message')
675        super(Journal, self).__init__(**kw)
676
677    def construct(self, loader, indent):
678        return self.render(
679            loader,
680            'journal.mako.cpp',
681            c=self,
682            indent=indent)
683
684
685class Elog(Callback, Renderer):
686    '''Handle the elog callback config file directive.'''
687
688    def __init__(self, *a, **kw):
689        self.error = kw.pop('error')
690        self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
691        super(Elog, self).__init__(**kw)
692
693    def construct(self, loader, indent):
694        with open('errors.hpp', 'a') as fd:
695            fd.write(
696                self.render(
697                    loader,
698                    'errors.mako.hpp',
699                    c=self))
700        return self.render(
701            loader,
702            'elog.mako.cpp',
703            c=self,
704            indent=indent)
705
706class Event(Callback, Renderer):
707    '''Handle the event callback config file directive.'''
708
709    def __init__(self, *a, **kw):
710        self.eventName = kw.pop('eventName')
711        self.eventMessage = kw.pop('eventMessage')
712        super(Event, self).__init__(**kw)
713
714    def construct(self, loader, indent):
715        return self.render(
716            loader,
717            'event.mako.cpp',
718            c=self,
719            indent=indent)
720
721
722class ElogWithMetadata(Callback, Renderer):
723    '''Handle the elog_with_metadata callback config file directive.'''
724
725    def __init__(self, *a, **kw):
726        self.error = kw.pop('error')
727        self.metadata = kw.pop('metadata')
728        super(ElogWithMetadata, self).__init__(**kw)
729
730    def construct(self, loader, indent):
731        with open('errors.hpp', 'a') as fd:
732            fd.write(
733                self.render(
734                    loader,
735                    'errors.mako.hpp',
736                    c=self))
737        return self.render(
738            loader,
739            'elog_with_metadata.mako.cpp',
740            c=self,
741            indent=indent)
742
743
744class ResolveCallout(Callback, Renderer):
745    '''Handle the 'resolve callout' callback config file directive.'''
746
747    def __init__(self, *a, **kw):
748        self.callout = kw.pop('callout')
749        super(ResolveCallout, self).__init__(**kw)
750
751    def construct(self, loader, indent):
752        return self.render(
753            loader,
754            'resolve_errors.mako.cpp',
755            c=self,
756            indent=indent)
757
758
759class Method(ConfigEntry, Renderer):
760    '''Handle the method callback config file directive.'''
761
762    def __init__(self, *a, **kw):
763        self.service = kw.pop('service')
764        self.path = kw.pop('path')
765        self.interface = kw.pop('interface')
766        self.method = kw.pop('method')
767        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
768        super(Method, self).__init__(**kw)
769
770    def factory(self, objs):
771        args = {
772            'class': 'interface',
773            'interface': 'element',
774            'name': self.service
775        }
776        add_unique(ConfigEntry(
777            configfile=self.configfile, **args), objs)
778
779        args = {
780            'class': 'pathname',
781            'pathname': 'element',
782            'name': self.path
783        }
784        add_unique(ConfigEntry(
785            configfile=self.configfile, **args), objs)
786
787        args = {
788            'class': 'interface',
789            'interface': 'element',
790            'name': self.interface
791        }
792        add_unique(ConfigEntry(
793            configfile=self.configfile, **args), objs)
794
795        args = {
796            'class': 'propertyname',
797            'propertyname': 'element',
798            'name': self.method
799        }
800        add_unique(ConfigEntry(
801            configfile=self.configfile, **args), objs)
802
803        super(Method, self).factory(objs)
804
805    def setup(self, objs):
806        '''Resolve elements.'''
807
808        self.service = get_index(
809            objs,
810            'interface',
811            self.service)
812
813        self.path = get_index(
814            objs,
815            'pathname',
816            self.path)
817
818        self.interface = get_index(
819            objs,
820            'interface',
821            self.interface)
822
823        self.method = get_index(
824            objs,
825            'propertyname',
826            self.method)
827
828        super(Method, self).setup(objs)
829
830    def construct(self, loader, indent):
831        return self.render(
832            loader,
833            'method.mako.cpp',
834            c=self,
835            indent=indent)
836
837
838class CallbackGraphEntry(Group):
839    '''An entry in a traversal list for groups of callbacks.'''
840
841    def __init__(self, *a, **kw):
842        super(CallbackGraphEntry, self).__init__(**kw)
843
844    def setup(self, objs):
845        '''Resolve group members.'''
846
847        def map_member(x):
848            return get_index(
849                objs, 'callback', x, config=self.configfile)
850
851        self.members = map(
852            map_member,
853            self.members)
854
855        super(CallbackGraphEntry, self).setup(objs)
856
857
858class GroupOfCallbacks(ConfigEntry, Renderer):
859    '''Handle the callback group config file directive.'''
860
861    def __init__(self, *a, **kw):
862        self.members = kw.pop('members')
863        super(GroupOfCallbacks, self).__init__(**kw)
864
865    def factory(self, objs):
866        '''Create a graph instance for this group of callbacks.'''
867
868        args = {
869            'configfile': self.configfile,
870            'members': self.members,
871            'class': 'callbackgroup',
872            'callbackgroup': 'callback',
873            'name': self.members
874        }
875
876        entry = CallbackGraphEntry(**args)
877        add_unique(entry, objs, config=self.configfile)
878
879        super(GroupOfCallbacks, self).factory(objs)
880
881    def setup(self, objs):
882        '''Resolve graph entry.'''
883
884        self.graph = get_index(
885            objs, 'callbackgroup', self.members, config=self.configfile)
886
887        super(GroupOfCallbacks, self).setup(objs)
888
889    def construct(self, loader, indent):
890        return self.render(
891            loader,
892            'callbackgroup.mako.cpp',
893            c=self,
894            indent=indent)
895
896
897class Everything(Renderer):
898    '''Parse/render entry point.'''
899
900    @staticmethod
901    def classmap(cls, sub=None):
902        '''Map render item class and subclass entries to the appropriate
903        handler methods.'''
904
905        class_map = {
906            'path': {
907                'element': Path,
908            },
909            'pathgroup': {
910                'path': GroupOfPaths,
911            },
912            'propertygroup': {
913                'property': GroupOfProperties,
914            },
915            'property': {
916                'element': Property,
917            },
918            'watch': {
919                'property': PropertyWatch,
920            },
921            'instance': {
922                'element': Instance,
923            },
924            'callback': {
925                'journal': Journal,
926                'elog': Elog,
927                'elog_with_metadata': ElogWithMetadata,
928                'event': Event,
929                'group': GroupOfCallbacks,
930                'method': Method,
931                'resolve callout': ResolveCallout,
932            },
933            'condition': {
934                'count': CountCondition,
935            },
936        }
937
938        if cls not in class_map:
939            raise NotImplementedError('Unknown class: "{0}"'.format(cls))
940        if sub not in class_map[cls]:
941            raise NotImplementedError('Unknown {0} type: "{1}"'.format(
942                cls, sub))
943
944        return class_map[cls][sub]
945
946    @staticmethod
947    def load_one_yaml(path, fd, objs):
948        '''Parse a single YAML file.  Parsing occurs in three phases.
949        In the first phase a factory method associated with each
950        configuration file directive is invoked.  These factory
951        methods generate more factory methods.  In the second
952        phase the factory methods created in the first phase
953        are invoked.  In the last phase a callback is invoked on
954        each object created in phase two.  Typically the callback
955        resolves references to other configuration file directives.'''
956
957        factory_objs = {}
958        for x in yaml.safe_load(fd.read()) or {}:
959
960            # Create factory object for this config file directive.
961            cls = x['class']
962            sub = x.get(cls)
963            if cls == 'group':
964                cls = '{0}group'.format(sub)
965
966            factory = Everything.classmap(cls, sub)
967            obj = factory(configfile=path, **x)
968
969            # For a given class of directive, validate the file
970            # doesn't have any duplicate names (duplicates are
971            # ok across config files).
972            if exists(factory_objs, obj.cls, obj.name, config=path):
973                raise NotUniqueError(path, cls, obj.name)
974
975            factory_objs.setdefault(cls, []).append(obj)
976            objs.setdefault(cls, []).append(obj)
977
978        for cls, items in factory_objs.items():
979            for obj in items:
980                # Add objects for template consumption.
981                obj.factory(objs)
982
983    @staticmethod
984    def load(args):
985        '''Aggregate all the YAML in the input directory
986        into a single aggregate.'''
987
988        objs = {}
989        yaml_files = filter(
990            lambda x: x.endswith('.yaml'),
991            os.listdir(args.inputdir))
992
993        yaml_files.sort()
994
995        for x in yaml_files:
996            path = os.path.join(args.inputdir, x)
997            with open(path, 'r') as fd:
998                Everything.load_one_yaml(path, fd, objs)
999
1000        # Configuration file directives reference each other via
1001        # the name attribute; however, when rendered the reference
1002        # is just an array index.
1003        #
1004        # At this point all objects have been created but references
1005        # have not been resolved to array indices.  Instruct objects
1006        # to do that now.
1007        for cls, items in objs.items():
1008            for obj in items:
1009                obj.setup(objs)
1010
1011        return Everything(**objs)
1012
1013    def __init__(self, *a, **kw):
1014        self.pathmeta = kw.pop('path', [])
1015        self.paths = kw.pop('pathname', [])
1016        self.meta = kw.pop('meta', [])
1017        self.pathgroups = kw.pop('pathgroup', [])
1018        self.interfaces = kw.pop('interface', [])
1019        self.properties = kw.pop('property', [])
1020        self.propertynames = kw.pop('propertyname', [])
1021        self.propertygroups = kw.pop('propertygroup', [])
1022        self.instances = kw.pop('instance', [])
1023        self.instancegroups = kw.pop('instancegroup', [])
1024        self.watches = kw.pop('watch', [])
1025        self.callbacks = kw.pop('callback', [])
1026        self.callbackgroups = kw.pop('callbackgroup', [])
1027        self.conditions = kw.pop('condition', [])
1028
1029        super(Everything, self).__init__(**kw)
1030
1031    def generate_cpp(self, loader):
1032        '''Render the template with the provided data.'''
1033        # errors.hpp is used by generated.hpp to included any error.hpp files
1034        open('errors.hpp', 'w+')
1035
1036        with open(args.output, 'w') as fd:
1037            fd.write(
1038                self.render(
1039                    loader,
1040                    args.template,
1041                    meta=self.meta,
1042                    properties=self.properties,
1043                    propertynames=self.propertynames,
1044                    interfaces=self.interfaces,
1045                    paths=self.paths,
1046                    pathmeta=self.pathmeta,
1047                    pathgroups=self.pathgroups,
1048                    propertygroups=self.propertygroups,
1049                    instances=self.instances,
1050                    watches=self.watches,
1051                    instancegroups=self.instancegroups,
1052                    callbacks=self.callbacks,
1053                    callbackgroups=self.callbackgroups,
1054                    conditions=self.conditions,
1055                    indent=Indent()))
1056
1057if __name__ == '__main__':
1058    script_dir = os.path.dirname(os.path.realpath(__file__))
1059    valid_commands = {
1060        'generate-cpp': 'generate_cpp',
1061    }
1062
1063    parser = ArgumentParser(
1064        description='Phosphor DBus Monitor (PDM) YAML '
1065        'scanner and code generator.')
1066
1067    parser.add_argument(
1068        "-o", "--out", dest="output",
1069        default='generated.cpp',
1070        help="Generated output file name and path.")
1071    parser.add_argument(
1072        '-t', '--template', dest='template',
1073        default='generated.mako.hpp',
1074        help='The top level template to render.')
1075    parser.add_argument(
1076        '-p', '--template-path', dest='template_search',
1077        default=script_dir,
1078        help='The space delimited mako template search path.')
1079    parser.add_argument(
1080        '-d', '--dir', dest='inputdir',
1081        default=os.path.join(script_dir, 'example'),
1082        help='Location of files to process.')
1083    parser.add_argument(
1084        'command', metavar='COMMAND', type=str,
1085        choices=valid_commands.keys(),
1086        help='%s.' % " | ".join(valid_commands.keys()))
1087
1088    args = parser.parse_args()
1089
1090    if sys.version_info < (3, 0):
1091        lookup = mako.lookup.TemplateLookup(
1092            directories=args.template_search.split(),
1093            disable_unicode=True)
1094    else:
1095        lookup = mako.lookup.TemplateLookup(
1096            directories=args.template_search.split())
1097    try:
1098        function = getattr(
1099            Everything.load(args),
1100            valid_commands[args.command])
1101        function(lookup)
1102    except InvalidConfigError as e:
1103        sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1104        raise
1105