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