blob: d878500fe558342f301069e7ac7624f9d5f68c3f [file] [log] [blame]
Matthew Barthdb440d42017-04-17 15:49:37 -05001#!/usr/bin/env python
2
Brad Bishop34a7acd2017-04-27 23:47:23 -04003'''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
Matthew Barthdb440d42017-04-17 15:49:37 -050018import os
19import sys
20import yaml
Brad Bishop34a7acd2017-04-27 23:47:23 -040021import mako.lookup
Matthew Barthdb440d42017-04-17 15:49:37 -050022from argparse import ArgumentParser
Brad Bishop34a7acd2017-04-27 23:47:23 -040023from sdbusplus.renderer import Renderer
Brad Bishop05b0c1e2017-05-23 00:24:01 -040024from sdbusplus.namedelement import NamedElement
Brad Bishope73b2c32017-05-23 18:01:54 -040025import sdbusplus.property
Brad Bishop05b0c1e2017-05-23 00:24:01 -040026
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)
Matthew Barthdb440d42017-04-17 15:49:37 -050085
86
Brad Bishop01079892017-05-26 10:56:45 -040087class 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 'int8',
105 'int16',
106 'int32',
107 'int64',
108 'uint8',
109 'uint16',
110 'uint32',
111 'uint64'
112 ]
113
114 def __init__(self, type):
115 self.type = type
116
117 def __call__(self, arg):
118 if 'uint' in self.type:
119 arg = '{0}ull'.format(arg)
120 elif 'int' in self.type:
121 arg = '{0}ll'.format(arg)
122
123 if self.type in self.integer_types:
124 return Cast('static', '{0}_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
Brad Bishop34a7acd2017-04-27 23:47:23 -0400180class Indent(object):
181 '''Help templates be depth agnostic.'''
Matthew Barthdb440d42017-04-17 15:49:37 -0500182
Brad Bishop34a7acd2017-04-27 23:47:23 -0400183 def __init__(self, depth=0):
184 self.depth = depth
Matthew Barthdb440d42017-04-17 15:49:37 -0500185
Brad Bishop34a7acd2017-04-27 23:47:23 -0400186 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
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400194class 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
Brad Bishop0e7df132017-05-23 17:58:12 -0400218class Path(ConfigEntry):
219 '''Path/metadata association.'''
220
221 def __init__(self, *a, **kw):
222 super(Path, self).__init__(**kw)
223
Brad Bishopbabf3b72017-05-31 19:44:53 -0400224 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
Brad Bishop0e7df132017-05-23 17:58:12 -0400230 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
Brad Bishope73b2c32017-05-23 18:01:54 -0400262class Property(ConfigEntry):
263 '''Property/interface/metadata association.'''
264
265 def __init__(self, *a, **kw):
266 super(Property, self).__init__(**kw)
267
Brad Bishopbabf3b72017-05-31 19:44:53 -0400268 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
Brad Bishope73b2c32017-05-23 18:01:54 -0400274 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
Brad Bishop4b916f12017-05-23 18:06:38 -0400316class 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
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400339class 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
Brad Bishop0e7df132017-05-23 17:58:12 -0400372class 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
Brad Bishope73b2c32017-05-23 18:01:54 -0400395class GroupOfProperties(ImplicitGroup):
396 '''Property group config file directive.'''
397
398 def __init__(self, *a, **kw):
399 self.datatype = sdbusplus.property.Property(
400 name=kw.get('name'),
401 type=kw.pop('type')).cppTypeName
402
403 super(GroupOfProperties, self).__init__(**kw)
404
405 def setup(self, objs):
406 '''Resolve group members.'''
407
408 def map_member(x):
409 iface = get_index(
410 objs, 'interface', x['interface'])
411 prop = get_index(
412 objs, 'propertyname', x['property'])
413 meta = get_index(
414 objs, 'meta', x['meta'])
415
416 return (iface, prop, meta)
417
418 self.members = map(
419 map_member,
420 self.members)
421
422 super(GroupOfProperties, self).setup(objs)
423
424
Brad Bishop4b916f12017-05-23 18:06:38 -0400425class GroupOfInstances(ImplicitGroup):
426 '''A group of property instances.'''
427
428 def __init__(self, *a, **kw):
429 super(GroupOfInstances, self).__init__(**kw)
430
431 def setup(self, objs):
432 '''Resolve group members.'''
433
434 def map_member(x):
435 path = get_index(objs, 'pathname', x['path']['path'])
436 pathmeta = get_index(objs, 'meta', x['path']['meta'])
437 interface = get_index(
438 objs, 'interface', x['property']['interface'])
439 prop = get_index(objs, 'propertyname', x['property']['property'])
440 propmeta = get_index(objs, 'meta', x['property']['meta'])
441 instance = get_index(objs, 'instance', x)
442
443 return (path, pathmeta, interface, prop, propmeta, instance)
444
445 self.members = map(
446 map_member,
447 self.members)
448
449 super(GroupOfInstances, self).setup(objs)
450
451
452class HasPropertyIndex(ConfigEntry):
453 '''Handle config file directives that require an index to be
454 constructed.'''
455
456 def __init__(self, *a, **kw):
457 self.paths = kw.pop('paths')
458 self.properties = kw.pop('properties')
459 super(HasPropertyIndex, self).__init__(**kw)
460
461 def factory(self, objs):
462 '''Create a group of instances for this index.'''
463
464 members = []
465 path_group = get_index(
466 objs, 'pathgroup', self.paths, config=self.configfile)
467 property_group = get_index(
468 objs, 'propertygroup', self.properties, config=self.configfile)
469
470 for path in objs['pathgroup'][path_group].members:
471 for prop in objs['propertygroup'][property_group].members:
472 member = {
473 'path': path,
474 'property': prop,
475 }
476 members.append(member)
477
478 args = {
479 'members': members,
480 'class': 'instancegroup',
481 'instancegroup': 'instance',
482 'name': '{0} {1}'.format(self.paths, self.properties)
483 }
484
485 group = GroupOfInstances(configfile=self.configfile, **args)
486 add_unique(group, objs, config=self.configfile)
487 group.factory(objs)
488
489 super(HasPropertyIndex, self).factory(objs)
490
491 def setup(self, objs):
492 '''Resolve path, property, and instance groups.'''
493
494 self.instances = get_index(
495 objs,
496 'instancegroup',
497 '{0} {1}'.format(self.paths, self.properties),
498 config=self.configfile)
499 self.paths = get_index(
500 objs,
501 'pathgroup',
502 self.paths,
503 config=self.configfile)
504 self.properties = get_index(
505 objs,
506 'propertygroup',
507 self.properties,
508 config=self.configfile)
509 self.datatype = objs['propertygroup'][self.properties].datatype
510
511 super(HasPropertyIndex, self).setup(objs)
512
513
514class PropertyWatch(HasPropertyIndex):
515 '''Handle the property watch config file directive.'''
516
517 def __init__(self, *a, **kw):
Brad Bishopfccdc392017-05-22 21:11:09 -0400518 self.callback = kw.pop('callback', None)
Brad Bishop4b916f12017-05-23 18:06:38 -0400519 super(PropertyWatch, self).__init__(**kw)
520
Brad Bishopfccdc392017-05-22 21:11:09 -0400521 def setup(self, objs):
522 '''Resolve optional callback.'''
523
524 if self.callback:
525 self.callback = get_index(
526 objs,
527 'callback',
528 self.callback,
529 config=self.configfile)
530
531 super(PropertyWatch, self).setup(objs)
532
Brad Bishop4b916f12017-05-23 18:06:38 -0400533
Brad Bishopc1283ae2017-05-20 21:42:38 -0400534class Callback(HasPropertyIndex):
535 '''Interface and common logic for callbacks.'''
536
537 def __init__(self, *a, **kw):
538 super(Callback, self).__init__(**kw)
539
540
Brad Bishop4041d722017-05-21 10:06:07 -0400541class ConditionCallback(ConfigEntry, Renderer):
542 '''Handle the journal callback config file directive.'''
543
544 def __init__(self, *a, **kw):
545 self.condition = kw.pop('condition')
546 self.instance = kw.pop('instance')
Brad Bishop3539db62017-05-30 14:21:12 -0400547 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400548 super(ConditionCallback, self).__init__(**kw)
549
550 def factory(self, objs):
551 '''Create a graph instance for this callback.'''
552
553 args = {
554 'configfile': self.configfile,
555 'members': [self.instance],
556 'class': 'callbackgroup',
557 'callbackgroup': 'callback',
558 'name': [self.instance]
559 }
560
561 entry = CallbackGraphEntry(**args)
562 add_unique(entry, objs, config=self.configfile)
563
564 super(ConditionCallback, self).factory(objs)
565
566 def setup(self, objs):
567 '''Resolve condition and graph entry.'''
568
569 self.graph = get_index(
570 objs,
571 'callbackgroup',
572 [self.instance],
573 config=self.configfile)
574
575 self.condition = get_index(
576 objs,
577 'condition',
578 self.name,
579 config=self.configfile)
580
581 super(ConditionCallback, self).setup(objs)
582
583 def construct(self, loader, indent):
584 return self.render(
585 loader,
586 'conditional.mako.cpp',
587 c=self,
588 indent=indent)
589
590
591class Condition(HasPropertyIndex):
592 '''Interface and common logic for conditions.'''
593
594 def __init__(self, *a, **kw):
595 self.callback = kw.pop('callback')
Brad Bishop3539db62017-05-30 14:21:12 -0400596 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400597 super(Condition, self).__init__(**kw)
598
599 def factory(self, objs):
600 '''Create a callback instance for this conditional.'''
601
602 args = {
603 'configfile': self.configfile,
604 'condition': self.name,
605 'class': 'callback',
606 'callback': 'conditional',
607 'instance': self.callback,
608 'name': self.name,
Brad Bishop3539db62017-05-30 14:21:12 -0400609 'defer': self.defer
Brad Bishop4041d722017-05-21 10:06:07 -0400610 }
611
612 callback = ConditionCallback(**args)
613 add_unique(callback, objs, config=self.configfile)
614 callback.factory(objs)
615
616 super(Condition, self).factory(objs)
617
618
619class CountCondition(Condition, Renderer):
620 '''Handle the count condition config file directive.'''
621
622 def __init__(self, *a, **kw):
623 self.countop = kw.pop('countop')
624 self.countbound = kw.pop('countbound')
625 self.op = kw.pop('op')
626 self.bound = kw.pop('bound')
627 super(CountCondition, self).__init__(**kw)
628
629 def construct(self, loader, indent):
630 return self.render(
631 loader,
632 'count.mako.cpp',
633 c=self,
634 indent=indent)
635
636
Brad Bishopc1283ae2017-05-20 21:42:38 -0400637class Journal(Callback, Renderer):
638 '''Handle the journal callback config file directive.'''
639
640 def __init__(self, *a, **kw):
641 self.severity = kw.pop('severity')
642 self.message = kw.pop('message')
643 super(Journal, self).__init__(**kw)
644
645 def construct(self, loader, indent):
646 return self.render(
647 loader,
648 'journal.mako.cpp',
649 c=self,
650 indent=indent)
651
652
Brad Bishop0df00be2017-05-25 23:38:37 -0400653class Method(ConfigEntry, Renderer):
654 '''Handle the method callback config file directive.'''
655
656 def __init__(self, *a, **kw):
657 self.service = kw.pop('service')
658 self.path = kw.pop('path')
659 self.interface = kw.pop('interface')
660 self.method = kw.pop('method')
661 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
662 super(Method, self).__init__(**kw)
663
664 def factory(self, objs):
665 args = {
666 'class': 'interface',
667 'interface': 'element',
668 'name': self.service
669 }
670 add_unique(ConfigEntry(
671 configfile=self.configfile, **args), objs)
672
673 args = {
674 'class': 'pathname',
675 'pathname': 'element',
676 'name': self.path
677 }
678 add_unique(ConfigEntry(
679 configfile=self.configfile, **args), objs)
680
681 args = {
682 'class': 'interface',
683 'interface': 'element',
684 'name': self.interface
685 }
686 add_unique(ConfigEntry(
687 configfile=self.configfile, **args), objs)
688
689 args = {
690 'class': 'propertyname',
691 'propertyname': 'element',
692 'name': self.method
693 }
694 add_unique(ConfigEntry(
695 configfile=self.configfile, **args), objs)
696
697 super(Method, self).factory(objs)
698
699 def setup(self, objs):
700 '''Resolve elements.'''
701
702 self.service = get_index(
703 objs,
704 'interface',
705 self.service)
706
707 self.path = get_index(
708 objs,
709 'pathname',
710 self.path)
711
712 self.interface = get_index(
713 objs,
714 'interface',
715 self.interface)
716
717 self.method = get_index(
718 objs,
719 'propertyname',
720 self.method)
721
722 super(Method, self).setup(objs)
723
724 def construct(self, loader, indent):
725 return self.render(
726 loader,
727 'method.mako.cpp',
728 c=self,
729 indent=indent)
730
731
Brad Bishop49e66172017-05-23 19:16:21 -0400732class CallbackGraphEntry(Group):
733 '''An entry in a traversal list for groups of callbacks.'''
734
735 def __init__(self, *a, **kw):
736 super(CallbackGraphEntry, self).__init__(**kw)
737
738 def setup(self, objs):
739 '''Resolve group members.'''
740
741 def map_member(x):
742 return get_index(
743 objs, 'callback', x, config=self.configfile)
744
745 self.members = map(
746 map_member,
747 self.members)
748
749 super(CallbackGraphEntry, self).setup(objs)
750
751
752class GroupOfCallbacks(ConfigEntry, Renderer):
753 '''Handle the callback group config file directive.'''
754
755 def __init__(self, *a, **kw):
756 self.members = kw.pop('members')
757 super(GroupOfCallbacks, self).__init__(**kw)
758
759 def factory(self, objs):
760 '''Create a graph instance for this group of callbacks.'''
761
762 args = {
763 'configfile': self.configfile,
764 'members': self.members,
765 'class': 'callbackgroup',
766 'callbackgroup': 'callback',
767 'name': self.members
768 }
769
770 entry = CallbackGraphEntry(**args)
771 add_unique(entry, objs, config=self.configfile)
772
773 super(GroupOfCallbacks, self).factory(objs)
774
775 def setup(self, objs):
776 '''Resolve graph entry.'''
777
778 self.graph = get_index(
779 objs, 'callbackgroup', self.members, config=self.configfile)
780
781 super(GroupOfCallbacks, self).setup(objs)
782
783 def construct(self, loader, indent):
784 return self.render(
785 loader,
786 'callbackgroup.mako.cpp',
787 c=self,
788 indent=indent)
789
790
Brad Bishop34a7acd2017-04-27 23:47:23 -0400791class Everything(Renderer):
792 '''Parse/render entry point.'''
793
794 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400795 def classmap(cls, sub=None):
796 '''Map render item class and subclass entries to the appropriate
797 handler methods.'''
798
799 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400800 'path': {
801 'element': Path,
802 },
803 'pathgroup': {
804 'path': GroupOfPaths,
805 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400806 'propertygroup': {
807 'property': GroupOfProperties,
808 },
809 'property': {
810 'element': Property,
811 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400812 'watch': {
813 'property': PropertyWatch,
814 },
815 'instance': {
816 'element': Instance,
817 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400818 'callback': {
819 'journal': Journal,
Brad Bishop49e66172017-05-23 19:16:21 -0400820 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400821 'method': Method,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400822 },
Brad Bishop4041d722017-05-21 10:06:07 -0400823 'condition': {
824 'count': CountCondition,
825 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400826 }
827
828 if cls not in class_map:
829 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
830 if sub not in class_map[cls]:
831 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
832 cls, sub))
833
834 return class_map[cls][sub]
835
836 @staticmethod
837 def load_one_yaml(path, fd, objs):
838 '''Parse a single YAML file. Parsing occurs in three phases.
839 In the first phase a factory method associated with each
840 configuration file directive is invoked. These factory
841 methods generate more factory methods. In the second
842 phase the factory methods created in the first phase
843 are invoked. In the last phase a callback is invoked on
844 each object created in phase two. Typically the callback
845 resolves references to other configuration file directives.'''
846
847 factory_objs = {}
848 for x in yaml.safe_load(fd.read()) or {}:
849
850 # Create factory object for this config file directive.
851 cls = x['class']
852 sub = x.get(cls)
853 if cls == 'group':
854 cls = '{0}group'.format(sub)
855
856 factory = Everything.classmap(cls, sub)
857 obj = factory(configfile=path, **x)
858
859 # For a given class of directive, validate the file
860 # doesn't have any duplicate names (duplicates are
861 # ok across config files).
862 if exists(factory_objs, obj.cls, obj.name, config=path):
863 raise NotUniqueError(path, cls, obj.name)
864
865 factory_objs.setdefault(cls, []).append(obj)
866 objs.setdefault(cls, []).append(obj)
867
868 for cls, items in factory_objs.items():
869 for obj in items:
870 # Add objects for template consumption.
871 obj.factory(objs)
872
873 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400874 def load(args):
875 '''Aggregate all the YAML in the input directory
876 into a single aggregate.'''
877
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400878 objs = {}
879 yaml_files = filter(
880 lambda x: x.endswith('.yaml'),
881 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400882
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400883 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400884
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400885 for x in yaml_files:
886 path = os.path.join(args.inputdir, x)
887 with open(path, 'r') as fd:
888 Everything.load_one_yaml(path, fd, objs)
889
890 # Configuration file directives reference each other via
891 # the name attribute; however, when rendered the reference
892 # is just an array index.
893 #
894 # At this point all objects have been created but references
895 # have not been resolved to array indicies. Instruct objects
896 # to do that now.
897 for cls, items in objs.items():
898 for obj in items:
899 obj.setup(objs)
900
901 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400902
903 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400904 self.pathmeta = kw.pop('path', [])
905 self.paths = kw.pop('pathname', [])
906 self.meta = kw.pop('meta', [])
907 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400908 self.interfaces = kw.pop('interface', [])
909 self.properties = kw.pop('property', [])
910 self.propertynames = kw.pop('propertyname', [])
911 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400912 self.instances = kw.pop('instance', [])
913 self.instancegroups = kw.pop('instancegroup', [])
914 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400915 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400916 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400917 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400918
Brad Bishop34a7acd2017-04-27 23:47:23 -0400919 super(Everything, self).__init__(**kw)
920
921 def generate_cpp(self, loader):
922 '''Render the template with the provided data.'''
Brad Bishope3a01af2017-05-15 17:09:04 -0400923 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400924 fd.write(
925 self.render(
926 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400927 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400928 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400929 properties=self.properties,
930 propertynames=self.propertynames,
931 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -0400932 paths=self.paths,
933 pathmeta=self.pathmeta,
934 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -0400935 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -0400936 instances=self.instances,
937 watches=self.watches,
938 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400939 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -0400940 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -0400941 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -0400942 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500943
944if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -0400945 script_dir = os.path.dirname(os.path.realpath(__file__))
946 valid_commands = {
947 'generate-cpp': 'generate_cpp',
948 }
949
950 parser = ArgumentParser(
951 description='Phosphor DBus Monitor (PDM) YAML '
952 'scanner and code generator.')
953
Matthew Barthdb440d42017-04-17 15:49:37 -0500954 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -0400955 "-o", "--out", dest="output",
956 default='generated.cpp',
957 help="Generated output file name and path.")
958 parser.add_argument(
959 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -0400960 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -0400961 help='The top level template to render.')
962 parser.add_argument(
963 '-p', '--template-path', dest='template_search',
964 default=script_dir,
965 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -0400966 parser.add_argument(
967 '-d', '--dir', dest='inputdir',
968 default=os.path.join(script_dir, 'example'),
969 help='Location of files to process.')
970 parser.add_argument(
971 'command', metavar='COMMAND', type=str,
972 choices=valid_commands.keys(),
973 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500974
Brad Bishop34a7acd2017-04-27 23:47:23 -0400975 args = parser.parse_args()
976
977 if sys.version_info < (3, 0):
978 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400979 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -0400980 disable_unicode=True)
981 else:
982 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400983 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400984 try:
985 function = getattr(
986 Everything.load(args),
987 valid_commands[args.command])
988 function(lookup)
989 except InvalidConfigError as e:
990 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
991 raise