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