xref: /openbmc/phosphor-dbus-monitor/src/pdmgen.py (revision 1d6ca484ee58c9ab44b4efc66c5b7dfc89b0c2f1)
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 indices.'''
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 indices.'''
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 indices.'''
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 ResolveCallout(Callback, Renderer):
703    '''Handle the 'resolve callout' callback config file directive.'''
704
705    def __init__(self, *a, **kw):
706        self.callout = kw.pop('callout')
707        super(ResolveCallout, self).__init__(**kw)
708
709    def construct(self, loader, indent):
710        return self.render(
711            loader,
712            'resolve_errors.mako.cpp',
713            c=self,
714            indent=indent)
715
716
717class Method(ConfigEntry, Renderer):
718    '''Handle the method callback config file directive.'''
719
720    def __init__(self, *a, **kw):
721        self.service = kw.pop('service')
722        self.path = kw.pop('path')
723        self.interface = kw.pop('interface')
724        self.method = kw.pop('method')
725        self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
726        super(Method, self).__init__(**kw)
727
728    def factory(self, objs):
729        args = {
730            'class': 'interface',
731            'interface': 'element',
732            'name': self.service
733        }
734        add_unique(ConfigEntry(
735            configfile=self.configfile, **args), objs)
736
737        args = {
738            'class': 'pathname',
739            'pathname': 'element',
740            'name': self.path
741        }
742        add_unique(ConfigEntry(
743            configfile=self.configfile, **args), objs)
744
745        args = {
746            'class': 'interface',
747            'interface': 'element',
748            'name': self.interface
749        }
750        add_unique(ConfigEntry(
751            configfile=self.configfile, **args), objs)
752
753        args = {
754            'class': 'propertyname',
755            'propertyname': 'element',
756            'name': self.method
757        }
758        add_unique(ConfigEntry(
759            configfile=self.configfile, **args), objs)
760
761        super(Method, self).factory(objs)
762
763    def setup(self, objs):
764        '''Resolve elements.'''
765
766        self.service = get_index(
767            objs,
768            'interface',
769            self.service)
770
771        self.path = get_index(
772            objs,
773            'pathname',
774            self.path)
775
776        self.interface = get_index(
777            objs,
778            'interface',
779            self.interface)
780
781        self.method = get_index(
782            objs,
783            'propertyname',
784            self.method)
785
786        super(Method, self).setup(objs)
787
788    def construct(self, loader, indent):
789        return self.render(
790            loader,
791            'method.mako.cpp',
792            c=self,
793            indent=indent)
794
795
796class CallbackGraphEntry(Group):
797    '''An entry in a traversal list for groups of callbacks.'''
798
799    def __init__(self, *a, **kw):
800        super(CallbackGraphEntry, self).__init__(**kw)
801
802    def setup(self, objs):
803        '''Resolve group members.'''
804
805        def map_member(x):
806            return get_index(
807                objs, 'callback', x, config=self.configfile)
808
809        self.members = map(
810            map_member,
811            self.members)
812
813        super(CallbackGraphEntry, self).setup(objs)
814
815
816class GroupOfCallbacks(ConfigEntry, Renderer):
817    '''Handle the callback group config file directive.'''
818
819    def __init__(self, *a, **kw):
820        self.members = kw.pop('members')
821        super(GroupOfCallbacks, self).__init__(**kw)
822
823    def factory(self, objs):
824        '''Create a graph instance for this group of callbacks.'''
825
826        args = {
827            'configfile': self.configfile,
828            'members': self.members,
829            'class': 'callbackgroup',
830            'callbackgroup': 'callback',
831            'name': self.members
832        }
833
834        entry = CallbackGraphEntry(**args)
835        add_unique(entry, objs, config=self.configfile)
836
837        super(GroupOfCallbacks, self).factory(objs)
838
839    def setup(self, objs):
840        '''Resolve graph entry.'''
841
842        self.graph = get_index(
843            objs, 'callbackgroup', self.members, config=self.configfile)
844
845        super(GroupOfCallbacks, self).setup(objs)
846
847    def construct(self, loader, indent):
848        return self.render(
849            loader,
850            'callbackgroup.mako.cpp',
851            c=self,
852            indent=indent)
853
854
855class Everything(Renderer):
856    '''Parse/render entry point.'''
857
858    @staticmethod
859    def classmap(cls, sub=None):
860        '''Map render item class and subclass entries to the appropriate
861        handler methods.'''
862
863        class_map = {
864            'path': {
865                'element': Path,
866            },
867            'pathgroup': {
868                'path': GroupOfPaths,
869            },
870            'propertygroup': {
871                'property': GroupOfProperties,
872            },
873            'property': {
874                'element': Property,
875            },
876            'watch': {
877                'property': PropertyWatch,
878            },
879            'instance': {
880                'element': Instance,
881            },
882            'callback': {
883                'journal': Journal,
884                'elog': Elog,
885                'group': GroupOfCallbacks,
886                'method': Method,
887                'resolve callout': ResolveCallout,
888            },
889            'condition': {
890                'count': CountCondition,
891            },
892        }
893
894        if cls not in class_map:
895            raise NotImplementedError('Unknown class: "{0}"'.format(cls))
896        if sub not in class_map[cls]:
897            raise NotImplementedError('Unknown {0} type: "{1}"'.format(
898                cls, sub))
899
900        return class_map[cls][sub]
901
902    @staticmethod
903    def load_one_yaml(path, fd, objs):
904        '''Parse a single YAML file.  Parsing occurs in three phases.
905        In the first phase a factory method associated with each
906        configuration file directive is invoked.  These factory
907        methods generate more factory methods.  In the second
908        phase the factory methods created in the first phase
909        are invoked.  In the last phase a callback is invoked on
910        each object created in phase two.  Typically the callback
911        resolves references to other configuration file directives.'''
912
913        factory_objs = {}
914        for x in yaml.safe_load(fd.read()) or {}:
915
916            # Create factory object for this config file directive.
917            cls = x['class']
918            sub = x.get(cls)
919            if cls == 'group':
920                cls = '{0}group'.format(sub)
921
922            factory = Everything.classmap(cls, sub)
923            obj = factory(configfile=path, **x)
924
925            # For a given class of directive, validate the file
926            # doesn't have any duplicate names (duplicates are
927            # ok across config files).
928            if exists(factory_objs, obj.cls, obj.name, config=path):
929                raise NotUniqueError(path, cls, obj.name)
930
931            factory_objs.setdefault(cls, []).append(obj)
932            objs.setdefault(cls, []).append(obj)
933
934        for cls, items in factory_objs.items():
935            for obj in items:
936                # Add objects for template consumption.
937                obj.factory(objs)
938
939    @staticmethod
940    def load(args):
941        '''Aggregate all the YAML in the input directory
942        into a single aggregate.'''
943
944        objs = {}
945        yaml_files = filter(
946            lambda x: x.endswith('.yaml'),
947            os.listdir(args.inputdir))
948
949        yaml_files.sort()
950
951        for x in yaml_files:
952            path = os.path.join(args.inputdir, x)
953            with open(path, 'r') as fd:
954                Everything.load_one_yaml(path, fd, objs)
955
956        # Configuration file directives reference each other via
957        # the name attribute; however, when rendered the reference
958        # is just an array index.
959        #
960        # At this point all objects have been created but references
961        # have not been resolved to array indices.  Instruct objects
962        # to do that now.
963        for cls, items in objs.items():
964            for obj in items:
965                obj.setup(objs)
966
967        return Everything(**objs)
968
969    def __init__(self, *a, **kw):
970        self.pathmeta = kw.pop('path', [])
971        self.paths = kw.pop('pathname', [])
972        self.meta = kw.pop('meta', [])
973        self.pathgroups = kw.pop('pathgroup', [])
974        self.interfaces = kw.pop('interface', [])
975        self.properties = kw.pop('property', [])
976        self.propertynames = kw.pop('propertyname', [])
977        self.propertygroups = kw.pop('propertygroup', [])
978        self.instances = kw.pop('instance', [])
979        self.instancegroups = kw.pop('instancegroup', [])
980        self.watches = kw.pop('watch', [])
981        self.callbacks = kw.pop('callback', [])
982        self.callbackgroups = kw.pop('callbackgroup', [])
983        self.conditions = kw.pop('condition', [])
984
985        super(Everything, self).__init__(**kw)
986
987    def generate_cpp(self, loader):
988        '''Render the template with the provided data.'''
989        # errors.hpp is used by generated.hpp to included any error.hpp files
990        open('errors.hpp', 'w+')
991
992        with open(args.output, 'w') as fd:
993            fd.write(
994                self.render(
995                    loader,
996                    args.template,
997                    meta=self.meta,
998                    properties=self.properties,
999                    propertynames=self.propertynames,
1000                    interfaces=self.interfaces,
1001                    paths=self.paths,
1002                    pathmeta=self.pathmeta,
1003                    pathgroups=self.pathgroups,
1004                    propertygroups=self.propertygroups,
1005                    instances=self.instances,
1006                    watches=self.watches,
1007                    instancegroups=self.instancegroups,
1008                    callbacks=self.callbacks,
1009                    callbackgroups=self.callbackgroups,
1010                    conditions=self.conditions,
1011                    indent=Indent()))
1012
1013if __name__ == '__main__':
1014    script_dir = os.path.dirname(os.path.realpath(__file__))
1015    valid_commands = {
1016        'generate-cpp': 'generate_cpp',
1017    }
1018
1019    parser = ArgumentParser(
1020        description='Phosphor DBus Monitor (PDM) YAML '
1021        'scanner and code generator.')
1022
1023    parser.add_argument(
1024        "-o", "--out", dest="output",
1025        default='generated.cpp',
1026        help="Generated output file name and path.")
1027    parser.add_argument(
1028        '-t', '--template', dest='template',
1029        default='generated.mako.hpp',
1030        help='The top level template to render.')
1031    parser.add_argument(
1032        '-p', '--template-path', dest='template_search',
1033        default=script_dir,
1034        help='The space delimited mako template search path.')
1035    parser.add_argument(
1036        '-d', '--dir', dest='inputdir',
1037        default=os.path.join(script_dir, 'example'),
1038        help='Location of files to process.')
1039    parser.add_argument(
1040        'command', metavar='COMMAND', type=str,
1041        choices=valid_commands.keys(),
1042        help='%s.' % " | ".join(valid_commands.keys()))
1043
1044    args = parser.parse_args()
1045
1046    if sys.version_info < (3, 0):
1047        lookup = mako.lookup.TemplateLookup(
1048            directories=args.template_search.split(),
1049            disable_unicode=True)
1050    else:
1051        lookup = mako.lookup.TemplateLookup(
1052            directories=args.template_search.split())
1053    try:
1054        function = getattr(
1055            Everything.load(args),
1056            valid_commands[args.command])
1057        function(lookup)
1058    except InvalidConfigError as e:
1059        sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1060        raise
1061