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