xref: /openbmc/phosphor-dbus-monitor/src/pdmgen.py (revision 80e9b65fdf9b1172e02de606cc193a6723791828)
1#!/usr/bin/env python
2
3'''Phosphor DBus Monitor YAML parser and code generator.
4
5The parser workflow is broken down as follows:
6  1 - Import YAML files as native python type(s) instance(s).
7  2 - Create an instance of the Everything class from the
8        native python type instance(s) with the Everything.load
9        method.
10  3 - The Everything class constructor orchestrates conversion of the
11        native python type(s) instances(s) to render helper types.
12        Each render helper type constructor imports its attributes
13        from the native python type(s) instances(s).
14  4 - Present the converted YAML to the command processing method
15        requested by the script user.
16'''
17
18import os
19import sys
20import yaml
21import mako.lookup
22from argparse import ArgumentParser
23from sdbusplus.renderer import Renderer
24from sdbusplus.namedelement import NamedElement
25import sdbusplus.property
26
27
28class InvalidConfigError(BaseException):
29    '''General purpose config file parsing error.'''
30
31    def __init__(self, path, msg):
32        '''Display configuration file with the syntax
33        error and the error message.'''
34
35        self.config = path
36        self.msg = msg
37
38
39class NotUniqueError(InvalidConfigError):
40    '''Within a config file names must be unique.
41    Display the config file with the duplicate and
42    the duplicate itself.'''
43
44    def __init__(self, path, cls, *names):
45        fmt = 'Duplicate {0}: "{1}"'
46        super(NotUniqueError, self).__init__(
47            path, fmt.format(cls, ' '.join(names)))
48
49
50def get_index(objs, cls, name, config=None):
51    '''Items are usually rendered as C++ arrays and as
52    such are stored in python lists.  Given an item name
53    its class, and an optional config file filter, find
54    the item index.'''
55
56    for i, x in enumerate(objs.get(cls, [])):
57        if config and x.configfile != config:
58            continue
59        if x.name != name:
60            continue
61
62        return i
63    raise InvalidConfigError(config, 'Could not find name: "{0}"'.format(name))
64
65
66def exists(objs, cls, name, config=None):
67    '''Check to see if an item already exists in a list given
68    the item name.'''
69
70    try:
71        get_index(objs, cls, name, config)
72    except:
73        return False
74
75    return True
76
77
78def add_unique(obj, *a, **kw):
79    '''Add an item to one or more lists unless already present,
80    with an option to constrain the search to a specific config file.'''
81
82    for container in a:
83        if not exists(container, obj.cls, obj.name, config=kw.get('config')):
84            container.setdefault(obj.cls, []).append(obj)
85
86
87class Cast(object):
88    '''Decorate an argument by casting it.'''
89
90    def __init__(self, cast, target):
91        '''cast is the cast type (static, const, etc...).
92           target is the cast target type.'''
93        self.cast = cast
94        self.target = target
95
96    def __call__(self, arg):
97        return '{0}_cast<{1}>({2})'.format(self.cast, self.target, arg)
98
99
100class Literal(object):
101    '''Decorate an argument with a literal operator.'''
102
103    integer_types = [
104        'int16',
105        'int32',
106        'int64',
107        'uint16',
108        'uint32',
109        'uint64'
110    ]
111
112    def __init__(self, type):
113        self.type = type
114
115    def __call__(self, arg):
116        if 'uint' in self.type:
117            arg = '{0}ull'.format(arg)
118        elif 'int' in self.type:
119            arg = '{0}ll'.format(arg)
120
121        if self.type in self.integer_types:
122            return Cast('static', '{0}_t'.format(self.type))(arg)
123        elif self.type == 'byte':
124            return Cast('static', 'uint8_t'.format(self.type))(arg)
125
126        if self.type == 'string':
127            return '{0}s'.format(arg)
128
129        return arg
130
131
132class FixBool(object):
133    '''Un-capitalize booleans.'''
134
135    def __call__(self, arg):
136        return '{0}'.format(arg.lower())
137
138
139class Quote(object):
140    '''Decorate an argument by quoting it.'''
141
142    def __call__(self, arg):
143        return '"{0}"'.format(arg)
144
145
146class Argument(NamedElement, Renderer):
147    '''Define argument type inteface.'''
148
149    def __init__(self, **kw):
150        self.type = kw.pop('type', None)
151        super(Argument, self).__init__(**kw)
152
153    def argument(self, loader, indent):
154        raise NotImplementedError
155
156
157class TrivialArgument(Argument):
158    '''Non-array type arguments.'''
159
160    def __init__(self, **kw):
161        self.value = kw.pop('value')
162        self.decorators = kw.pop('decorators', [])
163        if kw.get('type', None):
164            self.decorators.insert(0, Literal(kw['type']))
165        if kw.get('type', None) == 'string':
166            self.decorators.insert(0, Quote())
167        if kw.get('type', None) == 'boolean':
168            self.decorators.insert(0, FixBool())
169
170        super(TrivialArgument, self).__init__(**kw)
171
172    def argument(self, loader, indent):
173        a = str(self.value)
174        for d in self.decorators:
175            a = d(a)
176
177        return a
178
179
180class Metadata(Argument):
181    '''Metadata type arguments.'''
182
183    def __init__(self, **kw):
184        self.value = kw.pop('value')
185        self.decorators = kw.pop('decorators', [])
186        if kw.get('type', None) == 'string':
187            self.decorators.insert(0, Quote())
188
189        super(Metadata, self).__init__(**kw)
190
191    def argument(self, loader, indent):
192        a = str(self.value)
193        for d in self.decorators:
194            a = d(a)
195
196        return a
197
198
199class Indent(object):
200    '''Help templates be depth agnostic.'''
201
202    def __init__(self, depth=0):
203        self.depth = depth
204
205    def __add__(self, depth):
206        return Indent(self.depth + depth)
207
208    def __call__(self, depth):
209        '''Render an indent at the current depth plus depth.'''
210        return 4*' '*(depth + self.depth)
211
212
213class ConfigEntry(NamedElement):
214    '''Base interface for rendered items.'''
215
216    def __init__(self, *a, **kw):
217        '''Pop the configfile/class/subclass keywords.'''
218
219        self.configfile = kw.pop('configfile')
220        self.cls = kw.pop('class')
221        self.subclass = kw.pop(self.cls)
222        super(ConfigEntry, self).__init__(**kw)
223
224    def factory(self, objs):
225        ''' Optional factory interface for subclasses to add
226        additional items to be rendered.'''
227
228        pass
229
230    def setup(self, objs):
231        ''' Optional setup interface for subclasses, invoked
232        after all factory methods have been run.'''
233
234        pass
235
236
237class Path(ConfigEntry):
238    '''Path/metadata association.'''
239
240    def __init__(self, *a, **kw):
241        super(Path, self).__init__(**kw)
242
243        if self.name['meta'].upper() != self.name['meta']:
244            raise InvalidConfigError(
245                self.configfile,
246                'Metadata tag "{0}" must be upper case.'.format(
247                    self.name['meta']))
248
249    def factory(self, objs):
250        '''Create path and metadata elements.'''
251
252        args = {
253            'class': 'pathname',
254            'pathname': 'element',
255            'name': self.name['path']
256        }
257        add_unique(ConfigEntry(
258            configfile=self.configfile, **args), objs)
259
260        args = {
261            'class': 'meta',
262            'meta': 'element',
263            'name': self.name['meta']
264        }
265        add_unique(ConfigEntry(
266            configfile=self.configfile, **args), objs)
267
268        super(Path, self).factory(objs)
269
270    def setup(self, objs):
271        '''Resolve path and metadata names to indices.'''
272
273        self.path = get_index(
274            objs, 'pathname', self.name['path'])
275        self.meta = get_index(
276            objs, 'meta', self.name['meta'])
277
278        super(Path, self).setup(objs)
279
280
281class Property(ConfigEntry):
282    '''Property/interface/metadata association.'''
283
284    def __init__(self, *a, **kw):
285        super(Property, self).__init__(**kw)
286
287        if self.name['meta'].upper() != self.name['meta']:
288            raise InvalidConfigError(
289                self.configfile,
290                'Metadata tag "{0}" must be upper case.'.format(
291                    self.name['meta']))
292
293    def factory(self, objs):
294        '''Create interface, property name and metadata elements.'''
295
296        args = {
297            'class': 'interface',
298            'interface': 'element',
299            'name': self.name['interface']
300        }
301        add_unique(ConfigEntry(
302            configfile=self.configfile, **args), objs)
303
304        args = {
305            'class': 'propertyname',
306            'propertyname': 'element',
307            'name': self.name['property']
308        }
309        add_unique(ConfigEntry(
310            configfile=self.configfile, **args), objs)
311
312        args = {
313            'class': 'meta',
314            'meta': 'element',
315            'name': self.name['meta']
316        }
317        add_unique(ConfigEntry(
318            configfile=self.configfile, **args), objs)
319
320        super(Property, self).factory(objs)
321
322    def setup(self, objs):
323        '''Resolve interface, property and metadata to indices.'''
324
325        self.interface = get_index(
326            objs, 'interface', self.name['interface'])
327        self.prop = get_index(
328            objs, 'propertyname', self.name['property'])
329        self.meta = get_index(
330            objs, 'meta', self.name['meta'])
331
332        super(Property, self).setup(objs)
333
334
335class Instance(ConfigEntry):
336    '''Property/Path association.'''
337
338    def __init__(self, *a, **kw):
339        super(Instance, self).__init__(**kw)
340
341    def setup(self, objs):
342        '''Resolve elements to indices.'''
343
344        self.interface = get_index(
345            objs, 'interface', self.name['property']['interface'])
346        self.prop = get_index(
347            objs, 'propertyname', self.name['property']['property'])
348        self.propmeta = get_index(
349            objs, 'meta', self.name['property']['meta'])
350        self.path = get_index(
351            objs, 'pathname', self.name['path']['path'])
352        self.pathmeta = get_index(
353            objs, 'meta', self.name['path']['meta'])
354
355        super(Instance, self).setup(objs)
356
357
358class Group(ConfigEntry):
359    '''Pop the members keyword for groups.'''
360
361    def __init__(self, *a, **kw):
362        self.members = kw.pop('members')
363        super(Group, self).__init__(**kw)
364
365
366class ImplicitGroup(Group):
367    '''Provide a factory method for groups whose members are
368    not explicitly declared in the config files.'''
369
370    def __init__(self, *a, **kw):
371        super(ImplicitGroup, self).__init__(**kw)
372
373    def factory(self, objs):
374        '''Create group members.'''
375
376        factory = Everything.classmap(self.subclass, 'element')
377        for m in self.members:
378            args = {
379                'class': self.subclass,
380                self.subclass: 'element',
381                'name': m
382            }
383
384            obj = factory(configfile=self.configfile, **args)
385            add_unique(obj, objs)
386            obj.factory(objs)
387
388        super(ImplicitGroup, self).factory(objs)
389
390
391class GroupOfPaths(ImplicitGroup):
392    '''Path group config file directive.'''
393
394    def __init__(self, *a, **kw):
395        super(GroupOfPaths, self).__init__(**kw)
396
397    def setup(self, objs):
398        '''Resolve group members.'''
399
400        def map_member(x):
401            path = get_index(
402                objs, 'pathname', x['path'])
403            meta = get_index(
404                objs, 'meta', x['meta'])
405            return (path, meta)
406
407        self.members = map(
408            map_member,
409            self.members)
410
411        super(GroupOfPaths, self).setup(objs)
412
413
414class GroupOfProperties(ImplicitGroup):
415    '''Property group config file directive.'''
416
417    def __init__(self, *a, **kw):
418        self.type = kw.pop('type')
419        self.datatype = sdbusplus.property.Property(
420            name=kw.get('name'),
421            type=self.type).cppTypeName
422
423        super(GroupOfProperties, self).__init__(**kw)
424
425    def setup(self, objs):
426        '''Resolve group members.'''
427
428        def map_member(x):
429            iface = get_index(
430                objs, 'interface', x['interface'])
431            prop = get_index(
432                objs, 'propertyname', x['property'])
433            meta = get_index(
434                objs, 'meta', x['meta'])
435
436            return (iface, prop, meta)
437
438        self.members = map(
439            map_member,
440            self.members)
441
442        super(GroupOfProperties, self).setup(objs)
443
444
445class GroupOfInstances(ImplicitGroup):
446    '''A group of property instances.'''
447
448    def __init__(self, *a, **kw):
449        super(GroupOfInstances, self).__init__(**kw)
450
451    def setup(self, objs):
452        '''Resolve group members.'''
453
454        def map_member(x):
455            path = get_index(objs, 'pathname', x['path']['path'])
456            pathmeta = get_index(objs, 'meta', x['path']['meta'])
457            interface = get_index(
458                objs, 'interface', x['property']['interface'])
459            prop = get_index(objs, 'propertyname', x['property']['property'])
460            propmeta = get_index(objs, 'meta', x['property']['meta'])
461            instance = get_index(objs, 'instance', x)
462
463            return (path, pathmeta, interface, prop, propmeta, instance)
464
465        self.members = map(
466            map_member,
467            self.members)
468
469        super(GroupOfInstances, self).setup(objs)
470
471
472class HasPropertyIndex(ConfigEntry):
473    '''Handle config file directives that require an index to be
474    constructed.'''
475
476    def __init__(self, *a, **kw):
477        self.paths = kw.pop('paths')
478        self.properties = kw.pop('properties')
479        super(HasPropertyIndex, self).__init__(**kw)
480
481    def factory(self, objs):
482        '''Create a group of instances for this index.'''
483
484        members = []
485        path_group = get_index(
486            objs, 'pathgroup', self.paths, config=self.configfile)
487        property_group = get_index(
488            objs, 'propertygroup', self.properties, config=self.configfile)
489
490        for path in objs['pathgroup'][path_group].members:
491            for prop in objs['propertygroup'][property_group].members:
492                member = {
493                    'path': path,
494                    'property': prop,
495                }
496                members.append(member)
497
498        args = {
499            'members': members,
500            'class': 'instancegroup',
501            'instancegroup': 'instance',
502            'name': '{0} {1}'.format(self.paths, self.properties)
503        }
504
505        group = GroupOfInstances(configfile=self.configfile, **args)
506        add_unique(group, objs, config=self.configfile)
507        group.factory(objs)
508
509        super(HasPropertyIndex, self).factory(objs)
510
511    def setup(self, objs):
512        '''Resolve path, property, and instance groups.'''
513
514        self.instances = get_index(
515            objs,
516            'instancegroup',
517            '{0} {1}'.format(self.paths, self.properties),
518            config=self.configfile)
519        self.paths = get_index(
520            objs,
521            'pathgroup',
522            self.paths,
523            config=self.configfile)
524        self.properties = get_index(
525            objs,
526            'propertygroup',
527            self.properties,
528            config=self.configfile)
529        self.datatype = objs['propertygroup'][self.properties].datatype
530        self.type = objs['propertygroup'][self.properties].type
531
532        super(HasPropertyIndex, self).setup(objs)
533
534
535class PropertyWatch(HasPropertyIndex):
536    '''Handle the property watch config file directive.'''
537
538    def __init__(self, *a, **kw):
539        self.callback = kw.pop('callback', None)
540        super(PropertyWatch, self).__init__(**kw)
541
542    def setup(self, objs):
543        '''Resolve optional callback.'''
544
545        if self.callback:
546            self.callback = get_index(
547                objs,
548                'callback',
549                self.callback,
550                config=self.configfile)
551
552        super(PropertyWatch, self).setup(objs)
553
554
555class Callback(HasPropertyIndex):
556    '''Interface and common logic for callbacks.'''
557
558    def __init__(self, *a, **kw):
559        super(Callback, self).__init__(**kw)
560
561
562class ConditionCallback(ConfigEntry, Renderer):
563    '''Handle the journal callback config file directive.'''
564
565    def __init__(self, *a, **kw):
566        self.condition = kw.pop('condition')
567        self.instance = kw.pop('instance')
568        self.defer = kw.pop('defer', None)
569        super(ConditionCallback, self).__init__(**kw)
570
571    def factory(self, objs):
572        '''Create a graph instance for this callback.'''
573
574        args = {
575            'configfile': self.configfile,
576            'members': [self.instance],
577            'class': 'callbackgroup',
578            'callbackgroup': 'callback',
579            'name': [self.instance]
580        }
581
582        entry = CallbackGraphEntry(**args)
583        add_unique(entry, objs, config=self.configfile)
584
585        super(ConditionCallback, self).factory(objs)
586
587    def setup(self, objs):
588        '''Resolve condition and graph entry.'''
589
590        self.graph = get_index(
591            objs,
592            'callbackgroup',
593            [self.instance],
594            config=self.configfile)
595
596        self.condition = get_index(
597            objs,
598            'condition',
599            self.name,
600            config=self.configfile)
601
602        super(ConditionCallback, self).setup(objs)
603
604    def construct(self, loader, indent):
605        return self.render(
606            loader,
607            'conditional.mako.cpp',
608            c=self,
609            indent=indent)
610
611
612class Condition(HasPropertyIndex):
613    '''Interface and common logic for conditions.'''
614
615    def __init__(self, *a, **kw):
616        self.callback = kw.pop('callback')
617        self.defer = kw.pop('defer', None)
618        super(Condition, self).__init__(**kw)
619
620    def factory(self, objs):
621        '''Create a callback instance for this conditional.'''
622
623        args = {
624            'configfile': self.configfile,
625            'condition': self.name,
626            'class': 'callback',
627            'callback': 'conditional',
628            'instance': self.callback,
629            'name': self.name,
630            'defer': self.defer
631        }
632
633        callback = ConditionCallback(**args)
634        add_unique(callback, objs, config=self.configfile)
635        callback.factory(objs)
636
637        super(Condition, self).factory(objs)
638
639
640class CountCondition(Condition, Renderer):
641    '''Handle the count condition config file directive.'''
642
643    def __init__(self, *a, **kw):
644        self.countop = kw.pop('countop')
645        self.countbound = kw.pop('countbound')
646        self.op = kw.pop('op')
647        self.bound = kw.pop('bound')
648        super(CountCondition, self).__init__(**kw)
649
650    def setup(self, objs):
651        '''Resolve type.'''
652
653        super(CountCondition, self).setup(objs)
654        self.bound = TrivialArgument(
655            type=self.type,
656            value=self.bound)
657
658    def construct(self, loader, indent):
659        return self.render(
660            loader,
661            'count.mako.cpp',
662            c=self,
663            indent=indent)
664
665
666class Journal(Callback, Renderer):
667    '''Handle the journal callback config file directive.'''
668
669    def __init__(self, *a, **kw):
670        self.severity = kw.pop('severity')
671        self.message = kw.pop('message')
672        super(Journal, self).__init__(**kw)
673
674    def construct(self, loader, indent):
675        return self.render(
676            loader,
677            'journal.mako.cpp',
678            c=self,
679            indent=indent)
680
681
682class Elog(Callback, Renderer):
683    '''Handle the elog callback config file directive.'''
684
685    def __init__(self, *a, **kw):
686        self.error = kw.pop('error')
687        self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
688        super(Elog, self).__init__(**kw)
689
690    def construct(self, loader, indent):
691        with open('errors.hpp', 'a') as fd:
692            fd.write(
693                self.render(
694                    loader,
695                    'errors.mako.hpp',
696                    c=self))
697        return self.render(
698            loader,
699            'elog.mako.cpp',
700            c=self,
701            indent=indent)
702
703
704class ResolveCallout(Callback, Renderer):
705    '''Handle the 'resolve callout' callback config file directive.'''
706
707    def __init__(self, *a, **kw):
708        self.callout = kw.pop('callout')
709        super(ResolveCallout, self).__init__(**kw)
710
711    def construct(self, loader, indent):
712        return self.render(
713            loader,
714            'resolve_errors.mako.cpp',
715            c=self,
716            indent=indent)
717
718
719class Method(ConfigEntry, Renderer):
720    '''Handle the method callback config file directive.'''
721
722    def __init__(self, *a, **kw):
723        self.service = kw.pop('service')
724        self.path = kw.pop('path')
725        self.interface = kw.pop('interface')
726        self.method = kw.pop('method')
727        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
728        super(Method, self).__init__(**kw)
729
730    def factory(self, objs):
731        args = {
732            'class': 'interface',
733            'interface': 'element',
734            'name': self.service
735        }
736        add_unique(ConfigEntry(
737            configfile=self.configfile, **args), objs)
738
739        args = {
740            'class': 'pathname',
741            'pathname': 'element',
742            'name': self.path
743        }
744        add_unique(ConfigEntry(
745            configfile=self.configfile, **args), objs)
746
747        args = {
748            'class': 'interface',
749            'interface': 'element',
750            'name': self.interface
751        }
752        add_unique(ConfigEntry(
753            configfile=self.configfile, **args), objs)
754
755        args = {
756            'class': 'propertyname',
757            'propertyname': 'element',
758            'name': self.method
759        }
760        add_unique(ConfigEntry(
761            configfile=self.configfile, **args), objs)
762
763        super(Method, self).factory(objs)
764
765    def setup(self, objs):
766        '''Resolve elements.'''
767
768        self.service = get_index(
769            objs,
770            'interface',
771            self.service)
772
773        self.path = get_index(
774            objs,
775            'pathname',
776            self.path)
777
778        self.interface = get_index(
779            objs,
780            'interface',
781            self.interface)
782
783        self.method = get_index(
784            objs,
785            'propertyname',
786            self.method)
787
788        super(Method, self).setup(objs)
789
790    def construct(self, loader, indent):
791        return self.render(
792            loader,
793            'method.mako.cpp',
794            c=self,
795            indent=indent)
796
797
798class CallbackGraphEntry(Group):
799    '''An entry in a traversal list for groups of callbacks.'''
800
801    def __init__(self, *a, **kw):
802        super(CallbackGraphEntry, self).__init__(**kw)
803
804    def setup(self, objs):
805        '''Resolve group members.'''
806
807        def map_member(x):
808            return get_index(
809                objs, 'callback', x, config=self.configfile)
810
811        self.members = map(
812            map_member,
813            self.members)
814
815        super(CallbackGraphEntry, self).setup(objs)
816
817
818class GroupOfCallbacks(ConfigEntry, Renderer):
819    '''Handle the callback group config file directive.'''
820
821    def __init__(self, *a, **kw):
822        self.members = kw.pop('members')
823        super(GroupOfCallbacks, self).__init__(**kw)
824
825    def factory(self, objs):
826        '''Create a graph instance for this group of callbacks.'''
827
828        args = {
829            'configfile': self.configfile,
830            'members': self.members,
831            'class': 'callbackgroup',
832            'callbackgroup': 'callback',
833            'name': self.members
834        }
835
836        entry = CallbackGraphEntry(**args)
837        add_unique(entry, objs, config=self.configfile)
838
839        super(GroupOfCallbacks, self).factory(objs)
840
841    def setup(self, objs):
842        '''Resolve graph entry.'''
843
844        self.graph = get_index(
845            objs, 'callbackgroup', self.members, config=self.configfile)
846
847        super(GroupOfCallbacks, self).setup(objs)
848
849    def construct(self, loader, indent):
850        return self.render(
851            loader,
852            'callbackgroup.mako.cpp',
853            c=self,
854            indent=indent)
855
856
857class Everything(Renderer):
858    '''Parse/render entry point.'''
859
860    @staticmethod
861    def classmap(cls, sub=None):
862        '''Map render item class and subclass entries to the appropriate
863        handler methods.'''
864
865        class_map = {
866            'path': {
867                'element': Path,
868            },
869            'pathgroup': {
870                'path': GroupOfPaths,
871            },
872            'propertygroup': {
873                'property': GroupOfProperties,
874            },
875            'property': {
876                'element': Property,
877            },
878            'watch': {
879                'property': PropertyWatch,
880            },
881            'instance': {
882                'element': Instance,
883            },
884            'callback': {
885                'journal': Journal,
886                'elog': Elog,
887                'group': GroupOfCallbacks,
888                'method': Method,
889                'resolve callout': ResolveCallout,
890            },
891            'condition': {
892                'count': CountCondition,
893            },
894        }
895
896        if cls not in class_map:
897            raise NotImplementedError('Unknown class: "{0}"'.format(cls))
898        if sub not in class_map[cls]:
899            raise NotImplementedError('Unknown {0} type: "{1}"'.format(
900                cls, sub))
901
902        return class_map[cls][sub]
903
904    @staticmethod
905    def load_one_yaml(path, fd, objs):
906        '''Parse a single YAML file.  Parsing occurs in three phases.
907        In the first phase a factory method associated with each
908        configuration file directive is invoked.  These factory
909        methods generate more factory methods.  In the second
910        phase the factory methods created in the first phase
911        are invoked.  In the last phase a callback is invoked on
912        each object created in phase two.  Typically the callback
913        resolves references to other configuration file directives.'''
914
915        factory_objs = {}
916        for x in yaml.safe_load(fd.read()) or {}:
917
918            # Create factory object for this config file directive.
919            cls = x['class']
920            sub = x.get(cls)
921            if cls == 'group':
922                cls = '{0}group'.format(sub)
923
924            factory = Everything.classmap(cls, sub)
925            obj = factory(configfile=path, **x)
926
927            # For a given class of directive, validate the file
928            # doesn't have any duplicate names (duplicates are
929            # ok across config files).
930            if exists(factory_objs, obj.cls, obj.name, config=path):
931                raise NotUniqueError(path, cls, obj.name)
932
933            factory_objs.setdefault(cls, []).append(obj)
934            objs.setdefault(cls, []).append(obj)
935
936        for cls, items in factory_objs.items():
937            for obj in items:
938                # Add objects for template consumption.
939                obj.factory(objs)
940
941    @staticmethod
942    def load(args):
943        '''Aggregate all the YAML in the input directory
944        into a single aggregate.'''
945
946        objs = {}
947        yaml_files = filter(
948            lambda x: x.endswith('.yaml'),
949            os.listdir(args.inputdir))
950
951        yaml_files.sort()
952
953        for x in yaml_files:
954            path = os.path.join(args.inputdir, x)
955            with open(path, 'r') as fd:
956                Everything.load_one_yaml(path, fd, objs)
957
958        # Configuration file directives reference each other via
959        # the name attribute; however, when rendered the reference
960        # is just an array index.
961        #
962        # At this point all objects have been created but references
963        # have not been resolved to array indices.  Instruct objects
964        # to do that now.
965        for cls, items in objs.items():
966            for obj in items:
967                obj.setup(objs)
968
969        return Everything(**objs)
970
971    def __init__(self, *a, **kw):
972        self.pathmeta = kw.pop('path', [])
973        self.paths = kw.pop('pathname', [])
974        self.meta = kw.pop('meta', [])
975        self.pathgroups = kw.pop('pathgroup', [])
976        self.interfaces = kw.pop('interface', [])
977        self.properties = kw.pop('property', [])
978        self.propertynames = kw.pop('propertyname', [])
979        self.propertygroups = kw.pop('propertygroup', [])
980        self.instances = kw.pop('instance', [])
981        self.instancegroups = kw.pop('instancegroup', [])
982        self.watches = kw.pop('watch', [])
983        self.callbacks = kw.pop('callback', [])
984        self.callbackgroups = kw.pop('callbackgroup', [])
985        self.conditions = kw.pop('condition', [])
986
987        super(Everything, self).__init__(**kw)
988
989    def generate_cpp(self, loader):
990        '''Render the template with the provided data.'''
991        # errors.hpp is used by generated.hpp to included any error.hpp files
992        open('errors.hpp', 'w+')
993
994        with open(args.output, 'w') as fd:
995            fd.write(
996                self.render(
997                    loader,
998                    args.template,
999                    meta=self.meta,
1000                    properties=self.properties,
1001                    propertynames=self.propertynames,
1002                    interfaces=self.interfaces,
1003                    paths=self.paths,
1004                    pathmeta=self.pathmeta,
1005                    pathgroups=self.pathgroups,
1006                    propertygroups=self.propertygroups,
1007                    instances=self.instances,
1008                    watches=self.watches,
1009                    instancegroups=self.instancegroups,
1010                    callbacks=self.callbacks,
1011                    callbackgroups=self.callbackgroups,
1012                    conditions=self.conditions,
1013                    indent=Indent()))
1014
1015if __name__ == '__main__':
1016    script_dir = os.path.dirname(os.path.realpath(__file__))
1017    valid_commands = {
1018        'generate-cpp': 'generate_cpp',
1019    }
1020
1021    parser = ArgumentParser(
1022        description='Phosphor DBus Monitor (PDM) YAML '
1023        'scanner and code generator.')
1024
1025    parser.add_argument(
1026        "-o", "--out", dest="output",
1027        default='generated.cpp',
1028        help="Generated output file name and path.")
1029    parser.add_argument(
1030        '-t', '--template', dest='template',
1031        default='generated.mako.hpp',
1032        help='The top level template to render.')
1033    parser.add_argument(
1034        '-p', '--template-path', dest='template_search',
1035        default=script_dir,
1036        help='The space delimited mako template search path.')
1037    parser.add_argument(
1038        '-d', '--dir', dest='inputdir',
1039        default=os.path.join(script_dir, 'example'),
1040        help='Location of files to process.')
1041    parser.add_argument(
1042        'command', metavar='COMMAND', type=str,
1043        choices=valid_commands.keys(),
1044        help='%s.' % " | ".join(valid_commands.keys()))
1045
1046    args = parser.parse_args()
1047
1048    if sys.version_info < (3, 0):
1049        lookup = mako.lookup.TemplateLookup(
1050            directories=args.template_search.split(),
1051            disable_unicode=True)
1052    else:
1053        lookup = mako.lookup.TemplateLookup(
1054            directories=args.template_search.split())
1055    try:
1056        function = getattr(
1057            Everything.load(args),
1058            valid_commands[args.command])
1059        function(lookup)
1060    except InvalidConfigError as e:
1061        sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1062        raise
1063