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