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