xref: /openbmc/phosphor-dbus-monitor/src/pdmgen.py (revision efdd03c2041f995d56b77f0dc6ad5a47fe0ac199)
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 MedianCondition(Condition, Renderer):
772    '''Handle the median condition config file directive.'''
773
774    def __init__(self, *a, **kw):
775        self.op = kw.pop('op')
776        self.bound = kw.pop('bound')
777        self.oneshot = TrivialArgument(
778            type='boolean',
779            value=kw.pop('oneshot', False))
780        super(MedianCondition, self).__init__(**kw)
781
782    def setup(self, objs):
783        '''Resolve type.'''
784
785        super(MedianCondition, self).setup(objs)
786        self.bound = TrivialArgument(
787            type=self.type,
788            value=self.bound)
789
790    def construct(self, loader, indent):
791        return self.render(
792            loader,
793            'median.mako.cpp',
794            c=self,
795            indent=indent)
796
797
798class Journal(Callback, Renderer):
799    '''Handle the journal callback config file directive.'''
800
801    def __init__(self, *a, **kw):
802        self.severity = kw.pop('severity')
803        self.message = kw.pop('message')
804        super(Journal, self).__init__(**kw)
805
806    def construct(self, loader, indent):
807        return self.render(
808            loader,
809            'journal.mako.cpp',
810            c=self,
811            indent=indent)
812
813
814class Elog(Callback, Renderer):
815    '''Handle the elog callback config file directive.'''
816
817    def __init__(self, *a, **kw):
818        self.error = kw.pop('error')
819        self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
820        super(Elog, self).__init__(**kw)
821
822    def construct(self, loader, indent):
823        with open('errors.hpp', 'a') as fd:
824            fd.write(
825                self.render(
826                    loader,
827                    'errors.mako.hpp',
828                    c=self))
829        return self.render(
830            loader,
831            'elog.mako.cpp',
832            c=self,
833            indent=indent)
834
835class Event(Callback, Renderer):
836    '''Handle the event callback config file directive.'''
837
838    def __init__(self, *a, **kw):
839        self.eventName = kw.pop('eventName')
840        self.eventMessage = kw.pop('eventMessage')
841        super(Event, self).__init__(**kw)
842
843    def construct(self, loader, indent):
844        return self.render(
845            loader,
846            'event.mako.cpp',
847            c=self,
848            indent=indent)
849
850class EventPath(PathCallback, Renderer):
851    '''Handle the event path callback config file directive.'''
852
853    def __init__(self, *a, **kw):
854        self.eventType = kw.pop('eventType')
855        super(EventPath, self).__init__(**kw)
856
857    def construct(self, loader, indent):
858        return self.render(
859            loader,
860            'eventpath.mako.cpp',
861            c=self,
862            indent=indent)
863
864class ElogWithMetadata(Callback, Renderer):
865    '''Handle the elog_with_metadata callback config file directive.'''
866
867    def __init__(self, *a, **kw):
868        self.error = kw.pop('error')
869        self.metadata = kw.pop('metadata')
870        super(ElogWithMetadata, self).__init__(**kw)
871
872    def construct(self, loader, indent):
873        with open('errors.hpp', 'a') as fd:
874            fd.write(
875                self.render(
876                    loader,
877                    'errors.mako.hpp',
878                    c=self))
879        return self.render(
880            loader,
881            'elog_with_metadata.mako.cpp',
882            c=self,
883            indent=indent)
884
885
886class ResolveCallout(Callback, Renderer):
887    '''Handle the 'resolve callout' callback config file directive.'''
888
889    def __init__(self, *a, **kw):
890        self.callout = kw.pop('callout')
891        super(ResolveCallout, self).__init__(**kw)
892
893    def construct(self, loader, indent):
894        return self.render(
895            loader,
896            'resolve_errors.mako.cpp',
897            c=self,
898            indent=indent)
899
900
901class Method(ConfigEntry, Renderer):
902    '''Handle the method callback config file directive.'''
903
904    def __init__(self, *a, **kw):
905        self.service = kw.pop('service')
906        self.path = kw.pop('path')
907        self.interface = kw.pop('interface')
908        self.method = kw.pop('method')
909        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
910        super(Method, self).__init__(**kw)
911
912    def factory(self, objs):
913        args = {
914            'class': 'interface',
915            'interface': 'element',
916            'name': self.service
917        }
918        add_unique(ConfigEntry(
919            configfile=self.configfile, **args), objs)
920
921        args = {
922            'class': 'pathname',
923            'pathname': 'element',
924            'name': self.path
925        }
926        add_unique(ConfigEntry(
927            configfile=self.configfile, **args), objs)
928
929        args = {
930            'class': 'interface',
931            'interface': 'element',
932            'name': self.interface
933        }
934        add_unique(ConfigEntry(
935            configfile=self.configfile, **args), objs)
936
937        args = {
938            'class': 'propertyname',
939            'propertyname': 'element',
940            'name': self.method
941        }
942        add_unique(ConfigEntry(
943            configfile=self.configfile, **args), objs)
944
945        super(Method, self).factory(objs)
946
947    def setup(self, objs):
948        '''Resolve elements.'''
949
950        self.service = get_index(
951            objs,
952            'interface',
953            self.service)
954
955        self.path = get_index(
956            objs,
957            'pathname',
958            self.path)
959
960        self.interface = get_index(
961            objs,
962            'interface',
963            self.interface)
964
965        self.method = get_index(
966            objs,
967            'propertyname',
968            self.method)
969
970        super(Method, self).setup(objs)
971
972    def construct(self, loader, indent):
973        return self.render(
974            loader,
975            'method.mako.cpp',
976            c=self,
977            indent=indent)
978
979
980class CallbackGraphEntry(Group):
981    '''An entry in a traversal list for groups of callbacks.'''
982
983    def __init__(self, *a, **kw):
984        super(CallbackGraphEntry, self).__init__(**kw)
985
986    def setup(self, objs):
987        '''Resolve group members.'''
988
989        def map_member(x):
990            return get_index(
991                objs, 'callback', x, config=self.configfile)
992
993        self.members = map(
994            map_member,
995            self.members)
996
997        super(CallbackGraphEntry, self).setup(objs)
998
999class PathCallbackGraphEntry(Group):
1000    '''An entry in a traversal list for groups of callbacks.'''
1001
1002    def __init__(self, *a, **kw):
1003        super(PathCallbackGraphEntry, self).__init__(**kw)
1004
1005    def setup(self, objs):
1006        '''Resolve group members.'''
1007
1008        def map_member(x):
1009            return get_index(
1010                objs, 'pathcallback', x, config=self.configfile)
1011
1012        self.members = map(
1013            map_member,
1014            self.members)
1015
1016        super(PathCallbackGraphEntry, self).setup(objs)
1017
1018class GroupOfCallbacks(ConfigEntry, Renderer):
1019    '''Handle the callback group config file directive.'''
1020
1021    def __init__(self, *a, **kw):
1022        self.members = kw.pop('members')
1023        super(GroupOfCallbacks, self).__init__(**kw)
1024
1025    def factory(self, objs):
1026        '''Create a graph instance for this group of callbacks.'''
1027
1028        args = {
1029            'configfile': self.configfile,
1030            'members': self.members,
1031            'class': 'callbackgroup',
1032            'callbackgroup': 'callback',
1033            'name': self.members
1034        }
1035
1036        entry = CallbackGraphEntry(**args)
1037        add_unique(entry, objs, config=self.configfile)
1038
1039        super(GroupOfCallbacks, self).factory(objs)
1040
1041    def setup(self, objs):
1042        '''Resolve graph entry.'''
1043
1044        self.graph = get_index(
1045            objs, 'callbackgroup', self.members, config=self.configfile)
1046
1047        super(GroupOfCallbacks, self).setup(objs)
1048
1049    def construct(self, loader, indent):
1050        return self.render(
1051            loader,
1052            'callbackgroup.mako.cpp',
1053            c=self,
1054            indent=indent)
1055
1056class GroupOfPathCallbacks(ConfigEntry, Renderer):
1057    '''Handle the callback group config file directive.'''
1058
1059    def __init__(self, *a, **kw):
1060        self.members = kw.pop('members')
1061        super(GroupOfPathCallbacks, self).__init__(**kw)
1062
1063    def factory(self, objs):
1064        '''Create a graph instance for this group of callbacks.'''
1065
1066        args = {
1067            'configfile': self.configfile,
1068            'members': self.members,
1069            'class': 'pathcallbackgroup',
1070            'pathcallbackgroup': 'pathcallback',
1071            'name': self.members
1072        }
1073
1074        entry = PathCallbackGraphEntry(**args)
1075        add_unique(entry, objs, config=self.configfile)
1076        super(GroupOfPathCallbacks, self).factory(objs)
1077
1078    def setup(self, objs):
1079        '''Resolve graph entry.'''
1080
1081        self.graph = get_index(
1082            objs, 'callbackpathgroup', self.members, config=self.configfile)
1083
1084        super(GroupOfPathCallbacks, self).setup(objs)
1085
1086    def construct(self, loader, indent):
1087        return self.render(
1088            loader,
1089            'callbackpathgroup.mako.cpp',
1090            c=self,
1091            indent=indent)
1092
1093class Everything(Renderer):
1094    '''Parse/render entry point.'''
1095
1096    @staticmethod
1097    def classmap(cls, sub=None):
1098        '''Map render item class and subclass entries to the appropriate
1099        handler methods.'''
1100        class_map = {
1101            'path': {
1102                'element': Path,
1103            },
1104            'pathgroup': {
1105                'path': GroupOfPaths,
1106            },
1107            'propertygroup': {
1108                'property': GroupOfProperties,
1109            },
1110            'property': {
1111                'element': Property,
1112            },
1113            'watch': {
1114                'property': PropertyWatch,
1115            },
1116            'pathwatch': {
1117                'path': PathWatch,
1118            },
1119            'instance': {
1120                'element': Instance,
1121            },
1122            'pathinstance': {
1123                'element': PathInstance,
1124            },
1125            'callback': {
1126                'journal': Journal,
1127                'elog': Elog,
1128                'elog_with_metadata': ElogWithMetadata,
1129                'event': Event,
1130                'group': GroupOfCallbacks,
1131                'method': Method,
1132                'resolve callout': ResolveCallout,
1133            },
1134            'pathcallback': {
1135                'eventpath': EventPath,
1136                'grouppath': GroupOfPathCallbacks,
1137            },
1138            'condition': {
1139                'count': CountCondition,
1140                'median': MedianCondition,
1141            },
1142        }
1143
1144        if cls not in class_map:
1145            raise NotImplementedError('Unknown class: "{0}"'.format(cls))
1146        if sub not in class_map[cls]:
1147            raise NotImplementedError('Unknown {0} type: "{1}"'.format(
1148                cls, sub))
1149
1150        return class_map[cls][sub]
1151
1152    @staticmethod
1153    def load_one_yaml(path, fd, objs):
1154        '''Parse a single YAML file.  Parsing occurs in three phases.
1155        In the first phase a factory method associated with each
1156        configuration file directive is invoked.  These factory
1157        methods generate more factory methods.  In the second
1158        phase the factory methods created in the first phase
1159        are invoked.  In the last phase a callback is invoked on
1160        each object created in phase two.  Typically the callback
1161        resolves references to other configuration file directives.'''
1162
1163        factory_objs = {}
1164        for x in yaml.safe_load(fd.read()) or {}:
1165
1166            # Create factory object for this config file directive.
1167            cls = x['class']
1168            sub = x.get(cls)
1169            if cls == 'group':
1170                cls = '{0}group'.format(sub)
1171
1172            factory = Everything.classmap(cls, sub)
1173            obj = factory(configfile=path, **x)
1174
1175            # For a given class of directive, validate the file
1176            # doesn't have any duplicate names (duplicates are
1177            # ok across config files).
1178            if exists(factory_objs, obj.cls, obj.name, config=path):
1179                raise NotUniqueError(path, cls, obj.name)
1180
1181            factory_objs.setdefault(cls, []).append(obj)
1182            objs.setdefault(cls, []).append(obj)
1183
1184        for cls, items in factory_objs.items():
1185            for obj in items:
1186                # Add objects for template consumption.
1187                obj.factory(objs)
1188
1189    @staticmethod
1190    def load(args):
1191        '''Aggregate all the YAML in the input directory
1192        into a single aggregate.'''
1193
1194        objs = {}
1195        yaml_files = filter(
1196            lambda x: x.endswith('.yaml'),
1197            os.listdir(args.inputdir))
1198
1199        yaml_files.sort()
1200
1201        for x in yaml_files:
1202            path = os.path.join(args.inputdir, x)
1203            with open(path, 'r') as fd:
1204                Everything.load_one_yaml(path, fd, objs)
1205
1206        # Configuration file directives reference each other via
1207        # the name attribute; however, when rendered the reference
1208        # is just an array index.
1209        #
1210        # At this point all objects have been created but references
1211        # have not been resolved to array indices.  Instruct objects
1212        # to do that now.
1213        for cls, items in objs.items():
1214            for obj in items:
1215                obj.setup(objs)
1216
1217        return Everything(**objs)
1218
1219    def __init__(self, *a, **kw):
1220        self.pathmeta = kw.pop('path', [])
1221        self.paths = kw.pop('pathname', [])
1222        self.meta = kw.pop('meta', [])
1223        self.pathgroups = kw.pop('pathgroup', [])
1224        self.interfaces = kw.pop('interface', [])
1225        self.properties = kw.pop('property', [])
1226        self.propertynames = kw.pop('propertyname', [])
1227        self.propertygroups = kw.pop('propertygroup', [])
1228        self.instances = kw.pop('instance', [])
1229        self.pathinstances = kw.pop('pathinstance', [])
1230        self.instancegroups = kw.pop('instancegroup', [])
1231        self.pathinstancegroups = kw.pop('pathinstancegroup', [])
1232        self.watches = kw.pop('watch', [])
1233        self.pathwatches = kw.pop('pathwatch', [])
1234        self.callbacks = kw.pop('callback', [])
1235        self.pathcallbacks = kw.pop('pathcallback', [])
1236        self.callbackgroups = kw.pop('callbackgroup', [])
1237        self.pathcallbackgroups = kw.pop('pathcallbackgroup', [])
1238        self.conditions = kw.pop('condition', [])
1239
1240        super(Everything, self).__init__(**kw)
1241
1242    def generate_cpp(self, loader):
1243        '''Render the template with the provided data.'''
1244        # errors.hpp is used by generated.hpp to included any error.hpp files
1245        open('errors.hpp', 'w+')
1246
1247        with open(args.output, 'w') as fd:
1248            fd.write(
1249                self.render(
1250                    loader,
1251                    args.template,
1252                    meta=self.meta,
1253                    properties=self.properties,
1254                    propertynames=self.propertynames,
1255                    interfaces=self.interfaces,
1256                    paths=self.paths,
1257                    pathmeta=self.pathmeta,
1258                    pathgroups=self.pathgroups,
1259                    propertygroups=self.propertygroups,
1260                    instances=self.instances,
1261                    pathinstances=self.pathinstances,
1262                    watches=self.watches,
1263                    pathwatches=self.pathwatches,
1264                    instancegroups=self.instancegroups,
1265                    pathinstancegroups=self.pathinstancegroups,
1266                    callbacks=self.callbacks,
1267                    pathcallbacks=self.pathcallbacks,
1268                    callbackgroups=self.callbackgroups,
1269                    pathcallbackgroups=self.pathcallbackgroups,
1270                    conditions=self.conditions,
1271                    indent=Indent()))
1272
1273if __name__ == '__main__':
1274    script_dir = os.path.dirname(os.path.realpath(__file__))
1275    valid_commands = {
1276        'generate-cpp': 'generate_cpp',
1277    }
1278
1279    parser = ArgumentParser(
1280        description='Phosphor DBus Monitor (PDM) YAML '
1281        'scanner and code generator.')
1282
1283    parser.add_argument(
1284        "-o", "--out", dest="output",
1285        default='generated.cpp',
1286        help="Generated output file name and path.")
1287    parser.add_argument(
1288        '-t', '--template', dest='template',
1289        default='generated.mako.hpp',
1290        help='The top level template to render.')
1291    parser.add_argument(
1292        '-p', '--template-path', dest='template_search',
1293        default=script_dir,
1294        help='The space delimited mako template search path.')
1295    parser.add_argument(
1296        '-d', '--dir', dest='inputdir',
1297        default=os.path.join(script_dir, 'example'),
1298        help='Location of files to process.')
1299    parser.add_argument(
1300        'command', metavar='COMMAND', type=str,
1301        choices=valid_commands.keys(),
1302        help='%s.' % " | ".join(valid_commands.keys()))
1303
1304    args = parser.parse_args()
1305
1306    if sys.version_info < (3, 0):
1307        lookup = mako.lookup.TemplateLookup(
1308            directories=args.template_search.split(),
1309            disable_unicode=True)
1310    else:
1311        lookup = mako.lookup.TemplateLookup(
1312            directories=args.template_search.split())
1313    try:
1314        function = getattr(
1315            Everything.load(args),
1316            valid_commands[args.command])
1317        function(lookup)
1318    except InvalidConfigError as e:
1319        sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1320        raise
1321