xref: /openbmc/phosphor-dbus-monitor/src/pdmgen.py (revision 90bfaea4c69773f4f252d06fad1147a718e8b863)
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        super(CountCondition, self).__init__(**kw)
649
650    def setup(self, objs):
651        '''Resolve type.'''
652
653        super(CountCondition, self).setup(objs)
654        self.bound = TrivialArgument(
655            type=self.type,
656            value=self.bound)
657
658    def construct(self, loader, indent):
659        return self.render(
660            loader,
661            'count.mako.cpp',
662            c=self,
663            indent=indent)
664
665
666class Journal(Callback, Renderer):
667    '''Handle the journal callback config file directive.'''
668
669    def __init__(self, *a, **kw):
670        self.severity = kw.pop('severity')
671        self.message = kw.pop('message')
672        super(Journal, self).__init__(**kw)
673
674    def construct(self, loader, indent):
675        return self.render(
676            loader,
677            'journal.mako.cpp',
678            c=self,
679            indent=indent)
680
681
682class Elog(Callback, Renderer):
683    '''Handle the elog callback config file directive.'''
684
685    def __init__(self, *a, **kw):
686        self.error = kw.pop('error')
687        self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
688        super(Elog, self).__init__(**kw)
689
690    def construct(self, loader, indent):
691        with open('errors.hpp', 'a') as fd:
692            fd.write(
693                self.render(
694                    loader,
695                    'errors.mako.hpp',
696                    c=self))
697        return self.render(
698            loader,
699            'elog.mako.cpp',
700            c=self,
701            indent=indent)
702
703class Event(Callback, Renderer):
704    '''Handle the event callback config file directive.'''
705
706    def __init__(self, *a, **kw):
707        self.eventName = kw.pop('eventName')
708        self.eventMessage = kw.pop('eventMessage')
709        super(Event, self).__init__(**kw)
710
711    def construct(self, loader, indent):
712        return self.render(
713            loader,
714            'event.mako.cpp',
715            c=self,
716            indent=indent)
717
718class ResolveCallout(Callback, Renderer):
719    '''Handle the 'resolve callout' callback config file directive.'''
720
721    def __init__(self, *a, **kw):
722        self.callout = kw.pop('callout')
723        super(ResolveCallout, self).__init__(**kw)
724
725    def construct(self, loader, indent):
726        return self.render(
727            loader,
728            'resolve_errors.mako.cpp',
729            c=self,
730            indent=indent)
731
732
733class Method(ConfigEntry, Renderer):
734    '''Handle the method callback config file directive.'''
735
736    def __init__(self, *a, **kw):
737        self.service = kw.pop('service')
738        self.path = kw.pop('path')
739        self.interface = kw.pop('interface')
740        self.method = kw.pop('method')
741        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
742        super(Method, self).__init__(**kw)
743
744    def factory(self, objs):
745        args = {
746            'class': 'interface',
747            'interface': 'element',
748            'name': self.service
749        }
750        add_unique(ConfigEntry(
751            configfile=self.configfile, **args), objs)
752
753        args = {
754            'class': 'pathname',
755            'pathname': 'element',
756            'name': self.path
757        }
758        add_unique(ConfigEntry(
759            configfile=self.configfile, **args), objs)
760
761        args = {
762            'class': 'interface',
763            'interface': 'element',
764            'name': self.interface
765        }
766        add_unique(ConfigEntry(
767            configfile=self.configfile, **args), objs)
768
769        args = {
770            'class': 'propertyname',
771            'propertyname': 'element',
772            'name': self.method
773        }
774        add_unique(ConfigEntry(
775            configfile=self.configfile, **args), objs)
776
777        super(Method, self).factory(objs)
778
779    def setup(self, objs):
780        '''Resolve elements.'''
781
782        self.service = get_index(
783            objs,
784            'interface',
785            self.service)
786
787        self.path = get_index(
788            objs,
789            'pathname',
790            self.path)
791
792        self.interface = get_index(
793            objs,
794            'interface',
795            self.interface)
796
797        self.method = get_index(
798            objs,
799            'propertyname',
800            self.method)
801
802        super(Method, self).setup(objs)
803
804    def construct(self, loader, indent):
805        return self.render(
806            loader,
807            'method.mako.cpp',
808            c=self,
809            indent=indent)
810
811
812class CallbackGraphEntry(Group):
813    '''An entry in a traversal list for groups of callbacks.'''
814
815    def __init__(self, *a, **kw):
816        super(CallbackGraphEntry, self).__init__(**kw)
817
818    def setup(self, objs):
819        '''Resolve group members.'''
820
821        def map_member(x):
822            return get_index(
823                objs, 'callback', x, config=self.configfile)
824
825        self.members = map(
826            map_member,
827            self.members)
828
829        super(CallbackGraphEntry, self).setup(objs)
830
831
832class GroupOfCallbacks(ConfigEntry, Renderer):
833    '''Handle the callback group config file directive.'''
834
835    def __init__(self, *a, **kw):
836        self.members = kw.pop('members')
837        super(GroupOfCallbacks, self).__init__(**kw)
838
839    def factory(self, objs):
840        '''Create a graph instance for this group of callbacks.'''
841
842        args = {
843            'configfile': self.configfile,
844            'members': self.members,
845            'class': 'callbackgroup',
846            'callbackgroup': 'callback',
847            'name': self.members
848        }
849
850        entry = CallbackGraphEntry(**args)
851        add_unique(entry, objs, config=self.configfile)
852
853        super(GroupOfCallbacks, self).factory(objs)
854
855    def setup(self, objs):
856        '''Resolve graph entry.'''
857
858        self.graph = get_index(
859            objs, 'callbackgroup', self.members, config=self.configfile)
860
861        super(GroupOfCallbacks, self).setup(objs)
862
863    def construct(self, loader, indent):
864        return self.render(
865            loader,
866            'callbackgroup.mako.cpp',
867            c=self,
868            indent=indent)
869
870
871class Everything(Renderer):
872    '''Parse/render entry point.'''
873
874    @staticmethod
875    def classmap(cls, sub=None):
876        '''Map render item class and subclass entries to the appropriate
877        handler methods.'''
878
879        class_map = {
880            'path': {
881                'element': Path,
882            },
883            'pathgroup': {
884                'path': GroupOfPaths,
885            },
886            'propertygroup': {
887                'property': GroupOfProperties,
888            },
889            'property': {
890                'element': Property,
891            },
892            'watch': {
893                'property': PropertyWatch,
894            },
895            'instance': {
896                'element': Instance,
897            },
898            'callback': {
899                'journal': Journal,
900                'elog': Elog,
901                'event': Event,
902                'group': GroupOfCallbacks,
903                'method': Method,
904                'resolve callout': ResolveCallout,
905            },
906            'condition': {
907                'count': CountCondition,
908            },
909        }
910
911        if cls not in class_map:
912            raise NotImplementedError('Unknown class: "{0}"'.format(cls))
913        if sub not in class_map[cls]:
914            raise NotImplementedError('Unknown {0} type: "{1}"'.format(
915                cls, sub))
916
917        return class_map[cls][sub]
918
919    @staticmethod
920    def load_one_yaml(path, fd, objs):
921        '''Parse a single YAML file.  Parsing occurs in three phases.
922        In the first phase a factory method associated with each
923        configuration file directive is invoked.  These factory
924        methods generate more factory methods.  In the second
925        phase the factory methods created in the first phase
926        are invoked.  In the last phase a callback is invoked on
927        each object created in phase two.  Typically the callback
928        resolves references to other configuration file directives.'''
929
930        factory_objs = {}
931        for x in yaml.safe_load(fd.read()) or {}:
932
933            # Create factory object for this config file directive.
934            cls = x['class']
935            sub = x.get(cls)
936            if cls == 'group':
937                cls = '{0}group'.format(sub)
938
939            factory = Everything.classmap(cls, sub)
940            obj = factory(configfile=path, **x)
941
942            # For a given class of directive, validate the file
943            # doesn't have any duplicate names (duplicates are
944            # ok across config files).
945            if exists(factory_objs, obj.cls, obj.name, config=path):
946                raise NotUniqueError(path, cls, obj.name)
947
948            factory_objs.setdefault(cls, []).append(obj)
949            objs.setdefault(cls, []).append(obj)
950
951        for cls, items in factory_objs.items():
952            for obj in items:
953                # Add objects for template consumption.
954                obj.factory(objs)
955
956    @staticmethod
957    def load(args):
958        '''Aggregate all the YAML in the input directory
959        into a single aggregate.'''
960
961        objs = {}
962        yaml_files = filter(
963            lambda x: x.endswith('.yaml'),
964            os.listdir(args.inputdir))
965
966        yaml_files.sort()
967
968        for x in yaml_files:
969            path = os.path.join(args.inputdir, x)
970            with open(path, 'r') as fd:
971                Everything.load_one_yaml(path, fd, objs)
972
973        # Configuration file directives reference each other via
974        # the name attribute; however, when rendered the reference
975        # is just an array index.
976        #
977        # At this point all objects have been created but references
978        # have not been resolved to array indices.  Instruct objects
979        # to do that now.
980        for cls, items in objs.items():
981            for obj in items:
982                obj.setup(objs)
983
984        return Everything(**objs)
985
986    def __init__(self, *a, **kw):
987        self.pathmeta = kw.pop('path', [])
988        self.paths = kw.pop('pathname', [])
989        self.meta = kw.pop('meta', [])
990        self.pathgroups = kw.pop('pathgroup', [])
991        self.interfaces = kw.pop('interface', [])
992        self.properties = kw.pop('property', [])
993        self.propertynames = kw.pop('propertyname', [])
994        self.propertygroups = kw.pop('propertygroup', [])
995        self.instances = kw.pop('instance', [])
996        self.instancegroups = kw.pop('instancegroup', [])
997        self.watches = kw.pop('watch', [])
998        self.callbacks = kw.pop('callback', [])
999        self.callbackgroups = kw.pop('callbackgroup', [])
1000        self.conditions = kw.pop('condition', [])
1001
1002        super(Everything, self).__init__(**kw)
1003
1004    def generate_cpp(self, loader):
1005        '''Render the template with the provided data.'''
1006        # errors.hpp is used by generated.hpp to included any error.hpp files
1007        open('errors.hpp', 'w+')
1008
1009        with open(args.output, 'w') as fd:
1010            fd.write(
1011                self.render(
1012                    loader,
1013                    args.template,
1014                    meta=self.meta,
1015                    properties=self.properties,
1016                    propertynames=self.propertynames,
1017                    interfaces=self.interfaces,
1018                    paths=self.paths,
1019                    pathmeta=self.pathmeta,
1020                    pathgroups=self.pathgroups,
1021                    propertygroups=self.propertygroups,
1022                    instances=self.instances,
1023                    watches=self.watches,
1024                    instancegroups=self.instancegroups,
1025                    callbacks=self.callbacks,
1026                    callbackgroups=self.callbackgroups,
1027                    conditions=self.conditions,
1028                    indent=Indent()))
1029
1030if __name__ == '__main__':
1031    script_dir = os.path.dirname(os.path.realpath(__file__))
1032    valid_commands = {
1033        'generate-cpp': 'generate_cpp',
1034    }
1035
1036    parser = ArgumentParser(
1037        description='Phosphor DBus Monitor (PDM) YAML '
1038        'scanner and code generator.')
1039
1040    parser.add_argument(
1041        "-o", "--out", dest="output",
1042        default='generated.cpp',
1043        help="Generated output file name and path.")
1044    parser.add_argument(
1045        '-t', '--template', dest='template',
1046        default='generated.mako.hpp',
1047        help='The top level template to render.')
1048    parser.add_argument(
1049        '-p', '--template-path', dest='template_search',
1050        default=script_dir,
1051        help='The space delimited mako template search path.')
1052    parser.add_argument(
1053        '-d', '--dir', dest='inputdir',
1054        default=os.path.join(script_dir, 'example'),
1055        help='Location of files to process.')
1056    parser.add_argument(
1057        'command', metavar='COMMAND', type=str,
1058        choices=valid_commands.keys(),
1059        help='%s.' % " | ".join(valid_commands.keys()))
1060
1061    args = parser.parse_args()
1062
1063    if sys.version_info < (3, 0):
1064        lookup = mako.lookup.TemplateLookup(
1065            directories=args.template_search.split(),
1066            disable_unicode=True)
1067    else:
1068        lookup = mako.lookup.TemplateLookup(
1069            directories=args.template_search.split())
1070    try:
1071        function = getattr(
1072            Everything.load(args),
1073            valid_commands[args.command])
1074        function(lookup)
1075    except InvalidConfigError as e:
1076        sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1077        raise
1078