blob: c4184fcad8ff3478b3e631216549883129926ac4 [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 = [
Brad Bishop01079892017-05-26 10:56:45 -0400104 'int16',
105 'int32',
106 'int64',
Brad Bishop01079892017-05-26 10:56:45 -0400107 '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)
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400123 elif self.type == 'byte':
124 return Cast('static', 'uint8_t'.format(self.type))(arg)
Brad Bishop01079892017-05-26 10:56:45 -0400125
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):
Gunnar Mills06fdaff2018-04-08 16:28:18 -0500147 '''Define argument type interface.'''
Brad Bishop01079892017-05-26 10:56:45 -0400148
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
Matt Spinler80e9b652017-11-02 14:21:04 -0500179
Gunnar Mills30474cf2017-08-11 09:38:54 -0500180class 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
Brad Bishop01079892017-05-26 10:56:45 -0400197
Matt Spinler80e9b652017-11-02 14:21:04 -0500198
Brad Bishop34a7acd2017-04-27 23:47:23 -0400199class Indent(object):
200 '''Help templates be depth agnostic.'''
Matthew Barthdb440d42017-04-17 15:49:37 -0500201
Brad Bishop34a7acd2017-04-27 23:47:23 -0400202 def __init__(self, depth=0):
203 self.depth = depth
Matthew Barthdb440d42017-04-17 15:49:37 -0500204
Brad Bishop34a7acd2017-04-27 23:47:23 -0400205 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
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400213class 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
Brad Bishop0e7df132017-05-23 17:58:12 -0400237class Path(ConfigEntry):
238 '''Path/metadata association.'''
239
240 def __init__(self, *a, **kw):
241 super(Path, self).__init__(**kw)
242
Brad Bishopbabf3b72017-05-31 19:44:53 -0400243 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
Brad Bishop0e7df132017-05-23 17:58:12 -0400249 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):
Gunnar Mills78199b42017-10-25 16:30:18 -0500271 '''Resolve path and metadata names to indices.'''
Brad Bishop0e7df132017-05-23 17:58:12 -0400272
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
Brad Bishope73b2c32017-05-23 18:01:54 -0400281class Property(ConfigEntry):
282 '''Property/interface/metadata association.'''
283
284 def __init__(self, *a, **kw):
285 super(Property, self).__init__(**kw)
286
Brad Bishopbabf3b72017-05-31 19:44:53 -0400287 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
Brad Bishope73b2c32017-05-23 18:01:54 -0400293 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):
Gunnar Mills78199b42017-10-25 16:30:18 -0500323 '''Resolve interface, property and metadata to indices.'''
Brad Bishope73b2c32017-05-23 18:01:54 -0400324
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
Brad Bishop4b916f12017-05-23 18:06:38 -0400335class 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):
Gunnar Mills78199b42017-10-25 16:30:18 -0500342 '''Resolve elements to indices.'''
Brad Bishop4b916f12017-05-23 18:06:38 -0400343
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
Marri Devender Rao80c70612018-04-12 09:22:55 -0500357class PathInstance(ConfigEntry):
358 '''Path association.'''
359
360 def __init__(self, *a, **kw):
361 super(PathInstance, self).__init__(**kw)
362
363 def setup(self, objs):
364 '''Resolve elements to indices.'''
365 self.path = self.name['path']['path']
366 self.pathmeta = self.name['path']['meta']
367 super(PathInstance, self).setup(objs)
Brad Bishop4b916f12017-05-23 18:06:38 -0400368
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400369class Group(ConfigEntry):
370 '''Pop the members keyword for groups.'''
371
372 def __init__(self, *a, **kw):
373 self.members = kw.pop('members')
374 super(Group, self).__init__(**kw)
375
376
377class ImplicitGroup(Group):
378 '''Provide a factory method for groups whose members are
379 not explicitly declared in the config files.'''
380
381 def __init__(self, *a, **kw):
382 super(ImplicitGroup, self).__init__(**kw)
383
384 def factory(self, objs):
385 '''Create group members.'''
386
387 factory = Everything.classmap(self.subclass, 'element')
388 for m in self.members:
389 args = {
390 'class': self.subclass,
391 self.subclass: 'element',
392 'name': m
393 }
394
395 obj = factory(configfile=self.configfile, **args)
396 add_unique(obj, objs)
397 obj.factory(objs)
398
399 super(ImplicitGroup, self).factory(objs)
400
401
Brad Bishop0e7df132017-05-23 17:58:12 -0400402class GroupOfPaths(ImplicitGroup):
403 '''Path group config file directive.'''
404
405 def __init__(self, *a, **kw):
406 super(GroupOfPaths, self).__init__(**kw)
407
408 def setup(self, objs):
409 '''Resolve group members.'''
410
411 def map_member(x):
412 path = get_index(
413 objs, 'pathname', x['path'])
414 meta = get_index(
415 objs, 'meta', x['meta'])
416 return (path, meta)
417
418 self.members = map(
419 map_member,
420 self.members)
421
422 super(GroupOfPaths, self).setup(objs)
423
424
Brad Bishope73b2c32017-05-23 18:01:54 -0400425class GroupOfProperties(ImplicitGroup):
426 '''Property group config file directive.'''
427
428 def __init__(self, *a, **kw):
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400429 self.type = kw.pop('type')
Brad Bishope73b2c32017-05-23 18:01:54 -0400430 self.datatype = sdbusplus.property.Property(
431 name=kw.get('name'),
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400432 type=self.type).cppTypeName
Brad Bishope73b2c32017-05-23 18:01:54 -0400433
434 super(GroupOfProperties, self).__init__(**kw)
435
436 def setup(self, objs):
437 '''Resolve group members.'''
438
439 def map_member(x):
440 iface = get_index(
441 objs, 'interface', x['interface'])
442 prop = get_index(
443 objs, 'propertyname', x['property'])
444 meta = get_index(
445 objs, 'meta', x['meta'])
446
447 return (iface, prop, meta)
448
449 self.members = map(
450 map_member,
451 self.members)
452
453 super(GroupOfProperties, self).setup(objs)
454
455
Brad Bishop4b916f12017-05-23 18:06:38 -0400456class GroupOfInstances(ImplicitGroup):
457 '''A group of property instances.'''
458
459 def __init__(self, *a, **kw):
460 super(GroupOfInstances, self).__init__(**kw)
461
462 def setup(self, objs):
463 '''Resolve group members.'''
464
465 def map_member(x):
466 path = get_index(objs, 'pathname', x['path']['path'])
467 pathmeta = get_index(objs, 'meta', x['path']['meta'])
468 interface = get_index(
469 objs, 'interface', x['property']['interface'])
470 prop = get_index(objs, 'propertyname', x['property']['property'])
471 propmeta = get_index(objs, 'meta', x['property']['meta'])
472 instance = get_index(objs, 'instance', x)
473
474 return (path, pathmeta, interface, prop, propmeta, instance)
475
476 self.members = map(
477 map_member,
478 self.members)
479
480 super(GroupOfInstances, self).setup(objs)
481
Marri Devender Rao80c70612018-04-12 09:22:55 -0500482class GroupOfPathInstances(ImplicitGroup):
483 '''A group of path instances.'''
484
485 def __init__(self, *a, **kw):
486 super(GroupOfPathInstances, self).__init__(**kw)
487
488 def setup(self, objs):
489 '''Resolve group members.'''
490
491 def map_member(x):
492 path = get_index(objs, 'pathname', x['path']['path'])
493 pathmeta = get_index(objs, 'meta', x['path']['meta'])
494 pathinstance = get_index(objs, 'pathinstance', x)
495 return (path, pathmeta, pathinstance)
496
497 self.members = map(
498 map_member,
499 self.members)
500
501 super(GroupOfPathInstances, self).setup(objs)
502
Brad Bishop4b916f12017-05-23 18:06:38 -0400503
504class HasPropertyIndex(ConfigEntry):
505 '''Handle config file directives that require an index to be
506 constructed.'''
507
508 def __init__(self, *a, **kw):
509 self.paths = kw.pop('paths')
510 self.properties = kw.pop('properties')
511 super(HasPropertyIndex, self).__init__(**kw)
512
513 def factory(self, objs):
514 '''Create a group of instances for this index.'''
515
516 members = []
517 path_group = get_index(
518 objs, 'pathgroup', self.paths, config=self.configfile)
519 property_group = get_index(
520 objs, 'propertygroup', self.properties, config=self.configfile)
521
522 for path in objs['pathgroup'][path_group].members:
523 for prop in objs['propertygroup'][property_group].members:
524 member = {
525 'path': path,
526 'property': prop,
527 }
528 members.append(member)
529
530 args = {
531 'members': members,
532 'class': 'instancegroup',
533 'instancegroup': 'instance',
534 'name': '{0} {1}'.format(self.paths, self.properties)
535 }
536
537 group = GroupOfInstances(configfile=self.configfile, **args)
538 add_unique(group, objs, config=self.configfile)
539 group.factory(objs)
540
541 super(HasPropertyIndex, self).factory(objs)
542
543 def setup(self, objs):
544 '''Resolve path, property, and instance groups.'''
545
546 self.instances = get_index(
547 objs,
548 'instancegroup',
549 '{0} {1}'.format(self.paths, self.properties),
550 config=self.configfile)
551 self.paths = get_index(
552 objs,
553 'pathgroup',
554 self.paths,
555 config=self.configfile)
556 self.properties = get_index(
557 objs,
558 'propertygroup',
559 self.properties,
560 config=self.configfile)
561 self.datatype = objs['propertygroup'][self.properties].datatype
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400562 self.type = objs['propertygroup'][self.properties].type
Brad Bishop4b916f12017-05-23 18:06:38 -0400563
564 super(HasPropertyIndex, self).setup(objs)
565
Marri Devender Rao80c70612018-04-12 09:22:55 -0500566class HasPathIndex(ConfigEntry):
567 '''Handle config file directives that require an index to be
568 constructed.'''
569
570 def __init__(self, *a, **kw):
571 self.paths = kw.pop('paths')
572 super(HasPathIndex, self).__init__(**kw)
573
574 def factory(self, objs):
575 '''Create a group of instances for this index.'''
576
577 members = []
578 path_group = get_index(
579 objs, 'pathgroup', self.paths, config=self.configfile)
580
581 for path in objs['pathgroup'][path_group].members:
582 member = {
583 'path': path,
584 }
585 members.append(member)
586
587 args = {
588 'members': members,
589 'class': 'pathinstancegroup',
590 'pathinstancegroup': 'pathinstance',
591 'name': '{0}'.format(self.paths)
592 }
593
594 group = GroupOfPathInstances(configfile=self.configfile, **args)
595 add_unique(group, objs, config=self.configfile)
596 group.factory(objs)
597
598 super(HasPathIndex, self).factory(objs)
599
600 def setup(self, objs):
601 '''Resolve path and instance groups.'''
602
603 self.pathinstances = get_index(
604 objs,
605 'pathinstancegroup',
606 '{0}'.format(self.paths),
607 config=self.configfile)
608 self.paths = get_index(
609 objs,
610 'pathgroup',
611 self.paths,
612 config=self.configfile)
613 super(HasPathIndex, self).setup(objs)
Brad Bishop4b916f12017-05-23 18:06:38 -0400614
615class PropertyWatch(HasPropertyIndex):
616 '''Handle the property watch config file directive.'''
617
618 def __init__(self, *a, **kw):
Brad Bishopfccdc392017-05-22 21:11:09 -0400619 self.callback = kw.pop('callback', None)
Brad Bishop4b916f12017-05-23 18:06:38 -0400620 super(PropertyWatch, self).__init__(**kw)
621
Brad Bishopfccdc392017-05-22 21:11:09 -0400622 def setup(self, objs):
623 '''Resolve optional callback.'''
624
625 if self.callback:
626 self.callback = get_index(
627 objs,
628 'callback',
629 self.callback,
630 config=self.configfile)
631
632 super(PropertyWatch, self).setup(objs)
633
Marri Devender Rao80c70612018-04-12 09:22:55 -0500634class PathWatch(HasPathIndex):
635 '''Handle the path watch config file directive.'''
636
637 def __init__(self, *a, **kw):
638 self.pathcallback = kw.pop('pathcallback', None)
639 super(PathWatch, self).__init__(**kw)
640
641 def setup(self, objs):
642 '''Resolve optional callback.'''
643 if self.pathcallback:
644 self.pathcallback = get_index(
645 objs,
646 'pathcallback',
647 self.pathcallback,
648 config=self.configfile)
649 super(PathWatch, self).setup(objs)
650
Brad Bishop4b916f12017-05-23 18:06:38 -0400651
Brad Bishopc1283ae2017-05-20 21:42:38 -0400652class Callback(HasPropertyIndex):
653 '''Interface and common logic for callbacks.'''
654
655 def __init__(self, *a, **kw):
656 super(Callback, self).__init__(**kw)
657
Marri Devender Rao80c70612018-04-12 09:22:55 -0500658class PathCallback(HasPathIndex):
659 '''Interface and common logic for callbacks.'''
660
661 def __init__(self, *a, **kw):
662 super(PathCallback, self).__init__(**kw)
Brad Bishopc1283ae2017-05-20 21:42:38 -0400663
Brad Bishop4041d722017-05-21 10:06:07 -0400664class ConditionCallback(ConfigEntry, Renderer):
665 '''Handle the journal callback config file directive.'''
666
667 def __init__(self, *a, **kw):
668 self.condition = kw.pop('condition')
669 self.instance = kw.pop('instance')
Brad Bishop3539db62017-05-30 14:21:12 -0400670 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400671 super(ConditionCallback, self).__init__(**kw)
672
673 def factory(self, objs):
674 '''Create a graph instance for this callback.'''
675
676 args = {
677 'configfile': self.configfile,
678 'members': [self.instance],
679 'class': 'callbackgroup',
680 'callbackgroup': 'callback',
681 'name': [self.instance]
682 }
683
684 entry = CallbackGraphEntry(**args)
685 add_unique(entry, objs, config=self.configfile)
686
687 super(ConditionCallback, self).factory(objs)
688
689 def setup(self, objs):
690 '''Resolve condition and graph entry.'''
691
692 self.graph = get_index(
693 objs,
694 'callbackgroup',
695 [self.instance],
696 config=self.configfile)
697
698 self.condition = get_index(
699 objs,
700 'condition',
701 self.name,
702 config=self.configfile)
703
704 super(ConditionCallback, self).setup(objs)
705
706 def construct(self, loader, indent):
707 return self.render(
708 loader,
709 'conditional.mako.cpp',
710 c=self,
711 indent=indent)
712
713
714class Condition(HasPropertyIndex):
715 '''Interface and common logic for conditions.'''
716
717 def __init__(self, *a, **kw):
718 self.callback = kw.pop('callback')
Brad Bishop3539db62017-05-30 14:21:12 -0400719 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400720 super(Condition, self).__init__(**kw)
721
722 def factory(self, objs):
723 '''Create a callback instance for this conditional.'''
724
725 args = {
726 'configfile': self.configfile,
727 'condition': self.name,
728 'class': 'callback',
729 'callback': 'conditional',
730 'instance': self.callback,
731 'name': self.name,
Brad Bishop3539db62017-05-30 14:21:12 -0400732 'defer': self.defer
Brad Bishop4041d722017-05-21 10:06:07 -0400733 }
734
735 callback = ConditionCallback(**args)
736 add_unique(callback, objs, config=self.configfile)
737 callback.factory(objs)
738
739 super(Condition, self).factory(objs)
740
741
742class CountCondition(Condition, Renderer):
743 '''Handle the count condition config file directive.'''
744
745 def __init__(self, *a, **kw):
746 self.countop = kw.pop('countop')
747 self.countbound = kw.pop('countbound')
748 self.op = kw.pop('op')
749 self.bound = kw.pop('bound')
Matt Spinlerc458dee2018-02-19 13:09:10 -0600750 self.oneshot = TrivialArgument(
751 type='boolean',
752 value=kw.pop('oneshot', False))
Brad Bishop4041d722017-05-21 10:06:07 -0400753 super(CountCondition, self).__init__(**kw)
754
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400755 def setup(self, objs):
756 '''Resolve type.'''
757
758 super(CountCondition, self).setup(objs)
759 self.bound = TrivialArgument(
760 type=self.type,
761 value=self.bound)
762
Brad Bishop4041d722017-05-21 10:06:07 -0400763 def construct(self, loader, indent):
764 return self.render(
765 loader,
766 'count.mako.cpp',
767 c=self,
768 indent=indent)
769
770
Brad Bishopc1283ae2017-05-20 21:42:38 -0400771class Journal(Callback, Renderer):
772 '''Handle the journal callback config file directive.'''
773
774 def __init__(self, *a, **kw):
775 self.severity = kw.pop('severity')
776 self.message = kw.pop('message')
777 super(Journal, self).__init__(**kw)
778
779 def construct(self, loader, indent):
780 return self.render(
781 loader,
782 'journal.mako.cpp',
783 c=self,
784 indent=indent)
785
786
Gunnar Millsd5faea32017-08-08 14:19:36 -0500787class Elog(Callback, Renderer):
788 '''Handle the elog callback config file directive.'''
789
790 def __init__(self, *a, **kw):
791 self.error = kw.pop('error')
Gunnar Mills30474cf2017-08-11 09:38:54 -0500792 self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
Gunnar Millsd5faea32017-08-08 14:19:36 -0500793 super(Elog, self).__init__(**kw)
794
795 def construct(self, loader, indent):
Gunnar Millsd5faea32017-08-08 14:19:36 -0500796 with open('errors.hpp', 'a') as fd:
797 fd.write(
798 self.render(
799 loader,
800 'errors.mako.hpp',
801 c=self))
802 return self.render(
803 loader,
804 'elog.mako.cpp',
805 c=self,
806 indent=indent)
807
Ratan Gupta90bfaea2017-10-06 20:56:31 +0530808class Event(Callback, Renderer):
809 '''Handle the event callback config file directive.'''
810
811 def __init__(self, *a, **kw):
812 self.eventName = kw.pop('eventName')
813 self.eventMessage = kw.pop('eventMessage')
814 super(Event, self).__init__(**kw)
815
816 def construct(self, loader, indent):
817 return self.render(
818 loader,
819 'event.mako.cpp',
820 c=self,
821 indent=indent)
Gunnar Millsd5faea32017-08-08 14:19:36 -0500822
Marri Devender Rao80c70612018-04-12 09:22:55 -0500823class EventPath(PathCallback, Renderer):
824 '''Handle the event path callback config file directive.'''
825
826 def __init__(self, *a, **kw):
827 self.eventType = kw.pop('eventType')
828 super(EventPath, self).__init__(**kw)
829
830 def construct(self, loader, indent):
831 return self.render(
832 loader,
833 'eventpath.mako.cpp',
834 c=self,
835 indent=indent)
Matt Spinler3c5318d2018-02-19 14:03:05 -0600836
837class ElogWithMetadata(Callback, Renderer):
838 '''Handle the elog_with_metadata callback config file directive.'''
839
840 def __init__(self, *a, **kw):
841 self.error = kw.pop('error')
842 self.metadata = kw.pop('metadata')
843 super(ElogWithMetadata, self).__init__(**kw)
844
845 def construct(self, loader, indent):
846 with open('errors.hpp', 'a') as fd:
847 fd.write(
848 self.render(
849 loader,
850 'errors.mako.hpp',
851 c=self))
852 return self.render(
853 loader,
854 'elog_with_metadata.mako.cpp',
855 c=self,
856 indent=indent)
857
858
Matt Spinler1d6ca482017-11-01 10:48:02 -0500859class ResolveCallout(Callback, Renderer):
860 '''Handle the 'resolve callout' callback config file directive.'''
861
862 def __init__(self, *a, **kw):
863 self.callout = kw.pop('callout')
864 super(ResolveCallout, self).__init__(**kw)
865
866 def construct(self, loader, indent):
867 return self.render(
868 loader,
869 'resolve_errors.mako.cpp',
870 c=self,
871 indent=indent)
872
873
Brad Bishop0df00be2017-05-25 23:38:37 -0400874class Method(ConfigEntry, Renderer):
875 '''Handle the method callback config file directive.'''
876
877 def __init__(self, *a, **kw):
878 self.service = kw.pop('service')
879 self.path = kw.pop('path')
880 self.interface = kw.pop('interface')
881 self.method = kw.pop('method')
882 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
883 super(Method, self).__init__(**kw)
884
885 def factory(self, objs):
886 args = {
887 'class': 'interface',
888 'interface': 'element',
889 'name': self.service
890 }
891 add_unique(ConfigEntry(
892 configfile=self.configfile, **args), objs)
893
894 args = {
895 'class': 'pathname',
896 'pathname': 'element',
897 'name': self.path
898 }
899 add_unique(ConfigEntry(
900 configfile=self.configfile, **args), objs)
901
902 args = {
903 'class': 'interface',
904 'interface': 'element',
905 'name': self.interface
906 }
907 add_unique(ConfigEntry(
908 configfile=self.configfile, **args), objs)
909
910 args = {
911 'class': 'propertyname',
912 'propertyname': 'element',
913 'name': self.method
914 }
915 add_unique(ConfigEntry(
916 configfile=self.configfile, **args), objs)
917
918 super(Method, self).factory(objs)
919
920 def setup(self, objs):
921 '''Resolve elements.'''
922
923 self.service = get_index(
924 objs,
925 'interface',
926 self.service)
927
928 self.path = get_index(
929 objs,
930 'pathname',
931 self.path)
932
933 self.interface = get_index(
934 objs,
935 'interface',
936 self.interface)
937
938 self.method = get_index(
939 objs,
940 'propertyname',
941 self.method)
942
943 super(Method, self).setup(objs)
944
945 def construct(self, loader, indent):
946 return self.render(
947 loader,
948 'method.mako.cpp',
949 c=self,
950 indent=indent)
951
952
Brad Bishop49e66172017-05-23 19:16:21 -0400953class CallbackGraphEntry(Group):
954 '''An entry in a traversal list for groups of callbacks.'''
955
956 def __init__(self, *a, **kw):
957 super(CallbackGraphEntry, self).__init__(**kw)
958
959 def setup(self, objs):
960 '''Resolve group members.'''
961
962 def map_member(x):
963 return get_index(
964 objs, 'callback', x, config=self.configfile)
965
966 self.members = map(
967 map_member,
968 self.members)
969
970 super(CallbackGraphEntry, self).setup(objs)
971
Marri Devender Rao80c70612018-04-12 09:22:55 -0500972class PathCallbackGraphEntry(Group):
973 '''An entry in a traversal list for groups of callbacks.'''
974
975 def __init__(self, *a, **kw):
976 super(PathCallbackGraphEntry, self).__init__(**kw)
977
978 def setup(self, objs):
979 '''Resolve group members.'''
980
981 def map_member(x):
982 return get_index(
983 objs, 'pathcallback', x, config=self.configfile)
984
985 self.members = map(
986 map_member,
987 self.members)
988
989 super(PathCallbackGraphEntry, self).setup(objs)
Brad Bishop49e66172017-05-23 19:16:21 -0400990
991class GroupOfCallbacks(ConfigEntry, Renderer):
992 '''Handle the callback group config file directive.'''
993
994 def __init__(self, *a, **kw):
995 self.members = kw.pop('members')
996 super(GroupOfCallbacks, self).__init__(**kw)
997
998 def factory(self, objs):
999 '''Create a graph instance for this group of callbacks.'''
1000
1001 args = {
1002 'configfile': self.configfile,
1003 'members': self.members,
1004 'class': 'callbackgroup',
1005 'callbackgroup': 'callback',
1006 'name': self.members
1007 }
1008
1009 entry = CallbackGraphEntry(**args)
1010 add_unique(entry, objs, config=self.configfile)
1011
1012 super(GroupOfCallbacks, self).factory(objs)
1013
1014 def setup(self, objs):
1015 '''Resolve graph entry.'''
1016
1017 self.graph = get_index(
1018 objs, 'callbackgroup', self.members, config=self.configfile)
1019
1020 super(GroupOfCallbacks, self).setup(objs)
1021
1022 def construct(self, loader, indent):
1023 return self.render(
1024 loader,
1025 'callbackgroup.mako.cpp',
1026 c=self,
1027 indent=indent)
1028
Marri Devender Rao80c70612018-04-12 09:22:55 -05001029class GroupOfPathCallbacks(ConfigEntry, Renderer):
1030 '''Handle the callback group config file directive.'''
1031
1032 def __init__(self, *a, **kw):
1033 self.members = kw.pop('members')
1034 super(GroupOfPathCallbacks, self).__init__(**kw)
1035
1036 def factory(self, objs):
1037 '''Create a graph instance for this group of callbacks.'''
1038
1039 args = {
1040 'configfile': self.configfile,
1041 'members': self.members,
1042 'class': 'pathcallbackgroup',
1043 'pathcallbackgroup': 'pathcallback',
1044 'name': self.members
1045 }
1046
1047 entry = PathCallbackGraphEntry(**args)
1048 add_unique(entry, objs, config=self.configfile)
1049 super(GroupOfPathCallbacks, self).factory(objs)
1050
1051 def setup(self, objs):
1052 '''Resolve graph entry.'''
1053
1054 self.graph = get_index(
1055 objs, 'callbackpathgroup', self.members, config=self.configfile)
1056
1057 super(GroupOfPathCallbacks, self).setup(objs)
1058
1059 def construct(self, loader, indent):
1060 return self.render(
1061 loader,
1062 'callbackpathgroup.mako.cpp',
1063 c=self,
1064 indent=indent)
Brad Bishop49e66172017-05-23 19:16:21 -04001065
Brad Bishop34a7acd2017-04-27 23:47:23 -04001066class Everything(Renderer):
1067 '''Parse/render entry point.'''
1068
1069 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001070 def classmap(cls, sub=None):
1071 '''Map render item class and subclass entries to the appropriate
1072 handler methods.'''
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001073 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -04001074 'path': {
1075 'element': Path,
1076 },
1077 'pathgroup': {
1078 'path': GroupOfPaths,
1079 },
Brad Bishope73b2c32017-05-23 18:01:54 -04001080 'propertygroup': {
1081 'property': GroupOfProperties,
1082 },
1083 'property': {
1084 'element': Property,
1085 },
Brad Bishop4b916f12017-05-23 18:06:38 -04001086 'watch': {
1087 'property': PropertyWatch,
1088 },
Marri Devender Rao80c70612018-04-12 09:22:55 -05001089 'pathwatch': {
1090 'path': PathWatch,
1091 },
Brad Bishop4b916f12017-05-23 18:06:38 -04001092 'instance': {
1093 'element': Instance,
1094 },
Marri Devender Rao80c70612018-04-12 09:22:55 -05001095 'pathinstance': {
1096 'element': PathInstance,
1097 },
Brad Bishopc1283ae2017-05-20 21:42:38 -04001098 'callback': {
1099 'journal': Journal,
Gunnar Millsd5faea32017-08-08 14:19:36 -05001100 'elog': Elog,
Matt Spinler3c5318d2018-02-19 14:03:05 -06001101 'elog_with_metadata': ElogWithMetadata,
Ratan Gupta90bfaea2017-10-06 20:56:31 +05301102 'event': Event,
Brad Bishop49e66172017-05-23 19:16:21 -04001103 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -04001104 'method': Method,
Matt Spinler1d6ca482017-11-01 10:48:02 -05001105 'resolve callout': ResolveCallout,
Brad Bishopc1283ae2017-05-20 21:42:38 -04001106 },
Marri Devender Rao80c70612018-04-12 09:22:55 -05001107 'pathcallback': {
1108 'eventpath': EventPath,
1109 'grouppath': GroupOfPathCallbacks,
1110 },
Brad Bishop4041d722017-05-21 10:06:07 -04001111 'condition': {
1112 'count': CountCondition,
1113 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001114 }
1115
1116 if cls not in class_map:
1117 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
1118 if sub not in class_map[cls]:
1119 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
1120 cls, sub))
1121
1122 return class_map[cls][sub]
1123
1124 @staticmethod
1125 def load_one_yaml(path, fd, objs):
1126 '''Parse a single YAML file. Parsing occurs in three phases.
1127 In the first phase a factory method associated with each
1128 configuration file directive is invoked. These factory
1129 methods generate more factory methods. In the second
1130 phase the factory methods created in the first phase
1131 are invoked. In the last phase a callback is invoked on
1132 each object created in phase two. Typically the callback
1133 resolves references to other configuration file directives.'''
1134
1135 factory_objs = {}
1136 for x in yaml.safe_load(fd.read()) or {}:
1137
1138 # Create factory object for this config file directive.
1139 cls = x['class']
1140 sub = x.get(cls)
1141 if cls == 'group':
1142 cls = '{0}group'.format(sub)
1143
1144 factory = Everything.classmap(cls, sub)
1145 obj = factory(configfile=path, **x)
1146
1147 # For a given class of directive, validate the file
1148 # doesn't have any duplicate names (duplicates are
1149 # ok across config files).
1150 if exists(factory_objs, obj.cls, obj.name, config=path):
1151 raise NotUniqueError(path, cls, obj.name)
1152
1153 factory_objs.setdefault(cls, []).append(obj)
1154 objs.setdefault(cls, []).append(obj)
1155
1156 for cls, items in factory_objs.items():
1157 for obj in items:
1158 # Add objects for template consumption.
1159 obj.factory(objs)
1160
1161 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -04001162 def load(args):
1163 '''Aggregate all the YAML in the input directory
1164 into a single aggregate.'''
1165
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001166 objs = {}
1167 yaml_files = filter(
1168 lambda x: x.endswith('.yaml'),
1169 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -04001170
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001171 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -04001172
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001173 for x in yaml_files:
1174 path = os.path.join(args.inputdir, x)
1175 with open(path, 'r') as fd:
1176 Everything.load_one_yaml(path, fd, objs)
1177
1178 # Configuration file directives reference each other via
1179 # the name attribute; however, when rendered the reference
1180 # is just an array index.
1181 #
1182 # At this point all objects have been created but references
Gunnar Mills78199b42017-10-25 16:30:18 -05001183 # have not been resolved to array indices. Instruct objects
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001184 # to do that now.
1185 for cls, items in objs.items():
1186 for obj in items:
1187 obj.setup(objs)
1188
1189 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -04001190
1191 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -04001192 self.pathmeta = kw.pop('path', [])
1193 self.paths = kw.pop('pathname', [])
1194 self.meta = kw.pop('meta', [])
1195 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -04001196 self.interfaces = kw.pop('interface', [])
1197 self.properties = kw.pop('property', [])
1198 self.propertynames = kw.pop('propertyname', [])
1199 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -04001200 self.instances = kw.pop('instance', [])
Marri Devender Rao80c70612018-04-12 09:22:55 -05001201 self.pathinstances = kw.pop('pathinstance', [])
Brad Bishop4b916f12017-05-23 18:06:38 -04001202 self.instancegroups = kw.pop('instancegroup', [])
Marri Devender Rao80c70612018-04-12 09:22:55 -05001203 self.pathinstancegroups = kw.pop('pathinstancegroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -04001204 self.watches = kw.pop('watch', [])
Marri Devender Rao80c70612018-04-12 09:22:55 -05001205 self.pathwatches = kw.pop('pathwatch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -04001206 self.callbacks = kw.pop('callback', [])
Marri Devender Rao80c70612018-04-12 09:22:55 -05001207 self.pathcallbacks = kw.pop('pathcallback', [])
Brad Bishop49e66172017-05-23 19:16:21 -04001208 self.callbackgroups = kw.pop('callbackgroup', [])
Marri Devender Rao80c70612018-04-12 09:22:55 -05001209 self.pathcallbackgroups = kw.pop('pathcallbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -04001210 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -04001211
Brad Bishop34a7acd2017-04-27 23:47:23 -04001212 super(Everything, self).__init__(**kw)
1213
1214 def generate_cpp(self, loader):
1215 '''Render the template with the provided data.'''
Gunnar Millsd5faea32017-08-08 14:19:36 -05001216 # errors.hpp is used by generated.hpp to included any error.hpp files
1217 open('errors.hpp', 'w+')
1218
Brad Bishope3a01af2017-05-15 17:09:04 -04001219 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -04001220 fd.write(
1221 self.render(
1222 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -04001223 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -04001224 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -04001225 properties=self.properties,
1226 propertynames=self.propertynames,
1227 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -04001228 paths=self.paths,
1229 pathmeta=self.pathmeta,
1230 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -04001231 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -04001232 instances=self.instances,
Marri Devender Rao80c70612018-04-12 09:22:55 -05001233 pathinstances=self.pathinstances,
Brad Bishop4b916f12017-05-23 18:06:38 -04001234 watches=self.watches,
Marri Devender Rao80c70612018-04-12 09:22:55 -05001235 pathwatches=self.pathwatches,
Brad Bishop4b916f12017-05-23 18:06:38 -04001236 instancegroups=self.instancegroups,
Marri Devender Rao80c70612018-04-12 09:22:55 -05001237 pathinstancegroups=self.pathinstancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -04001238 callbacks=self.callbacks,
Marri Devender Rao80c70612018-04-12 09:22:55 -05001239 pathcallbacks=self.pathcallbacks,
Brad Bishop49e66172017-05-23 19:16:21 -04001240 callbackgroups=self.callbackgroups,
Marri Devender Rao80c70612018-04-12 09:22:55 -05001241 pathcallbackgroups=self.pathcallbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -04001242 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -04001243 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001244
1245if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -04001246 script_dir = os.path.dirname(os.path.realpath(__file__))
1247 valid_commands = {
1248 'generate-cpp': 'generate_cpp',
1249 }
1250
1251 parser = ArgumentParser(
1252 description='Phosphor DBus Monitor (PDM) YAML '
1253 'scanner and code generator.')
1254
Matthew Barthdb440d42017-04-17 15:49:37 -05001255 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -04001256 "-o", "--out", dest="output",
1257 default='generated.cpp',
1258 help="Generated output file name and path.")
1259 parser.add_argument(
1260 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -04001261 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -04001262 help='The top level template to render.')
1263 parser.add_argument(
1264 '-p', '--template-path', dest='template_search',
1265 default=script_dir,
1266 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -04001267 parser.add_argument(
1268 '-d', '--dir', dest='inputdir',
1269 default=os.path.join(script_dir, 'example'),
1270 help='Location of files to process.')
1271 parser.add_argument(
1272 'command', metavar='COMMAND', type=str,
1273 choices=valid_commands.keys(),
1274 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001275
Brad Bishop34a7acd2017-04-27 23:47:23 -04001276 args = parser.parse_args()
1277
1278 if sys.version_info < (3, 0):
1279 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001280 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -04001281 disable_unicode=True)
1282 else:
1283 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001284 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001285 try:
1286 function = getattr(
1287 Everything.load(args),
1288 valid_commands[args.command])
1289 function(lookup)
1290 except InvalidConfigError as e:
1291 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1292 raise