blob: 55df3e373bc285b311ae15c63a92e74f760bc4a2 [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):
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
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
357
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400358class 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
Brad Bishop0e7df132017-05-23 17:58:12 -0400391class 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
Brad Bishope73b2c32017-05-23 18:01:54 -0400414class GroupOfProperties(ImplicitGroup):
415 '''Property group config file directive.'''
416
417 def __init__(self, *a, **kw):
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400418 self.type = kw.pop('type')
Brad Bishope73b2c32017-05-23 18:01:54 -0400419 self.datatype = sdbusplus.property.Property(
420 name=kw.get('name'),
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400421 type=self.type).cppTypeName
Brad Bishope73b2c32017-05-23 18:01:54 -0400422
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
Brad Bishop4b916f12017-05-23 18:06:38 -0400445class 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
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400530 self.type = objs['propertygroup'][self.properties].type
Brad Bishop4b916f12017-05-23 18:06:38 -0400531
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):
Brad Bishopfccdc392017-05-22 21:11:09 -0400539 self.callback = kw.pop('callback', None)
Brad Bishop4b916f12017-05-23 18:06:38 -0400540 super(PropertyWatch, self).__init__(**kw)
541
Brad Bishopfccdc392017-05-22 21:11:09 -0400542 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
Brad Bishop4b916f12017-05-23 18:06:38 -0400554
Brad Bishopc1283ae2017-05-20 21:42:38 -0400555class Callback(HasPropertyIndex):
556 '''Interface and common logic for callbacks.'''
557
558 def __init__(self, *a, **kw):
559 super(Callback, self).__init__(**kw)
560
561
Brad Bishop4041d722017-05-21 10:06:07 -0400562class 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')
Brad Bishop3539db62017-05-30 14:21:12 -0400568 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400569 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')
Brad Bishop3539db62017-05-30 14:21:12 -0400617 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400618 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,
Brad Bishop3539db62017-05-30 14:21:12 -0400630 'defer': self.defer
Brad Bishop4041d722017-05-21 10:06:07 -0400631 }
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')
Matt Spinlerc458dee2018-02-19 13:09:10 -0600648 self.oneshot = TrivialArgument(
649 type='boolean',
650 value=kw.pop('oneshot', False))
Brad Bishop4041d722017-05-21 10:06:07 -0400651 super(CountCondition, self).__init__(**kw)
652
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400653 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
Brad Bishop4041d722017-05-21 10:06:07 -0400661 def construct(self, loader, indent):
662 return self.render(
663 loader,
664 'count.mako.cpp',
665 c=self,
666 indent=indent)
667
668
Brad Bishopc1283ae2017-05-20 21:42:38 -0400669class 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
Gunnar Millsd5faea32017-08-08 14:19:36 -0500685class Elog(Callback, Renderer):
686 '''Handle the elog callback config file directive.'''
687
688 def __init__(self, *a, **kw):
689 self.error = kw.pop('error')
Gunnar Mills30474cf2017-08-11 09:38:54 -0500690 self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
Gunnar Millsd5faea32017-08-08 14:19:36 -0500691 super(Elog, self).__init__(**kw)
692
693 def construct(self, loader, indent):
Gunnar Millsd5faea32017-08-08 14:19:36 -0500694 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
Ratan Gupta90bfaea2017-10-06 20:56:31 +0530706class 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)
Gunnar Millsd5faea32017-08-08 14:19:36 -0500720
Matt Spinler3c5318d2018-02-19 14:03:05 -0600721
722class ElogWithMetadata(Callback, Renderer):
723 '''Handle the elog_with_metadata callback config file directive.'''
724
725 def __init__(self, *a, **kw):
726 self.error = kw.pop('error')
727 self.metadata = kw.pop('metadata')
728 super(ElogWithMetadata, self).__init__(**kw)
729
730 def construct(self, loader, indent):
731 with open('errors.hpp', 'a') as fd:
732 fd.write(
733 self.render(
734 loader,
735 'errors.mako.hpp',
736 c=self))
737 return self.render(
738 loader,
739 'elog_with_metadata.mako.cpp',
740 c=self,
741 indent=indent)
742
743
Matt Spinler1d6ca482017-11-01 10:48:02 -0500744class ResolveCallout(Callback, Renderer):
745 '''Handle the 'resolve callout' callback config file directive.'''
746
747 def __init__(self, *a, **kw):
748 self.callout = kw.pop('callout')
749 super(ResolveCallout, self).__init__(**kw)
750
751 def construct(self, loader, indent):
752 return self.render(
753 loader,
754 'resolve_errors.mako.cpp',
755 c=self,
756 indent=indent)
757
758
Brad Bishop0df00be2017-05-25 23:38:37 -0400759class Method(ConfigEntry, Renderer):
760 '''Handle the method callback config file directive.'''
761
762 def __init__(self, *a, **kw):
763 self.service = kw.pop('service')
764 self.path = kw.pop('path')
765 self.interface = kw.pop('interface')
766 self.method = kw.pop('method')
767 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
768 super(Method, self).__init__(**kw)
769
770 def factory(self, objs):
771 args = {
772 'class': 'interface',
773 'interface': 'element',
774 'name': self.service
775 }
776 add_unique(ConfigEntry(
777 configfile=self.configfile, **args), objs)
778
779 args = {
780 'class': 'pathname',
781 'pathname': 'element',
782 'name': self.path
783 }
784 add_unique(ConfigEntry(
785 configfile=self.configfile, **args), objs)
786
787 args = {
788 'class': 'interface',
789 'interface': 'element',
790 'name': self.interface
791 }
792 add_unique(ConfigEntry(
793 configfile=self.configfile, **args), objs)
794
795 args = {
796 'class': 'propertyname',
797 'propertyname': 'element',
798 'name': self.method
799 }
800 add_unique(ConfigEntry(
801 configfile=self.configfile, **args), objs)
802
803 super(Method, self).factory(objs)
804
805 def setup(self, objs):
806 '''Resolve elements.'''
807
808 self.service = get_index(
809 objs,
810 'interface',
811 self.service)
812
813 self.path = get_index(
814 objs,
815 'pathname',
816 self.path)
817
818 self.interface = get_index(
819 objs,
820 'interface',
821 self.interface)
822
823 self.method = get_index(
824 objs,
825 'propertyname',
826 self.method)
827
828 super(Method, self).setup(objs)
829
830 def construct(self, loader, indent):
831 return self.render(
832 loader,
833 'method.mako.cpp',
834 c=self,
835 indent=indent)
836
837
Brad Bishop49e66172017-05-23 19:16:21 -0400838class CallbackGraphEntry(Group):
839 '''An entry in a traversal list for groups of callbacks.'''
840
841 def __init__(self, *a, **kw):
842 super(CallbackGraphEntry, self).__init__(**kw)
843
844 def setup(self, objs):
845 '''Resolve group members.'''
846
847 def map_member(x):
848 return get_index(
849 objs, 'callback', x, config=self.configfile)
850
851 self.members = map(
852 map_member,
853 self.members)
854
855 super(CallbackGraphEntry, self).setup(objs)
856
857
858class GroupOfCallbacks(ConfigEntry, Renderer):
859 '''Handle the callback group config file directive.'''
860
861 def __init__(self, *a, **kw):
862 self.members = kw.pop('members')
863 super(GroupOfCallbacks, self).__init__(**kw)
864
865 def factory(self, objs):
866 '''Create a graph instance for this group of callbacks.'''
867
868 args = {
869 'configfile': self.configfile,
870 'members': self.members,
871 'class': 'callbackgroup',
872 'callbackgroup': 'callback',
873 'name': self.members
874 }
875
876 entry = CallbackGraphEntry(**args)
877 add_unique(entry, objs, config=self.configfile)
878
879 super(GroupOfCallbacks, self).factory(objs)
880
881 def setup(self, objs):
882 '''Resolve graph entry.'''
883
884 self.graph = get_index(
885 objs, 'callbackgroup', self.members, config=self.configfile)
886
887 super(GroupOfCallbacks, self).setup(objs)
888
889 def construct(self, loader, indent):
890 return self.render(
891 loader,
892 'callbackgroup.mako.cpp',
893 c=self,
894 indent=indent)
895
896
Brad Bishop34a7acd2017-04-27 23:47:23 -0400897class Everything(Renderer):
898 '''Parse/render entry point.'''
899
900 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400901 def classmap(cls, sub=None):
902 '''Map render item class and subclass entries to the appropriate
903 handler methods.'''
904
905 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400906 'path': {
907 'element': Path,
908 },
909 'pathgroup': {
910 'path': GroupOfPaths,
911 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400912 'propertygroup': {
913 'property': GroupOfProperties,
914 },
915 'property': {
916 'element': Property,
917 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400918 'watch': {
919 'property': PropertyWatch,
920 },
921 'instance': {
922 'element': Instance,
923 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400924 'callback': {
925 'journal': Journal,
Gunnar Millsd5faea32017-08-08 14:19:36 -0500926 'elog': Elog,
Matt Spinler3c5318d2018-02-19 14:03:05 -0600927 'elog_with_metadata': ElogWithMetadata,
Ratan Gupta90bfaea2017-10-06 20:56:31 +0530928 'event': Event,
Brad Bishop49e66172017-05-23 19:16:21 -0400929 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400930 'method': Method,
Matt Spinler1d6ca482017-11-01 10:48:02 -0500931 'resolve callout': ResolveCallout,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400932 },
Brad Bishop4041d722017-05-21 10:06:07 -0400933 'condition': {
934 'count': CountCondition,
935 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400936 }
937
938 if cls not in class_map:
939 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
940 if sub not in class_map[cls]:
941 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
942 cls, sub))
943
944 return class_map[cls][sub]
945
946 @staticmethod
947 def load_one_yaml(path, fd, objs):
948 '''Parse a single YAML file. Parsing occurs in three phases.
949 In the first phase a factory method associated with each
950 configuration file directive is invoked. These factory
951 methods generate more factory methods. In the second
952 phase the factory methods created in the first phase
953 are invoked. In the last phase a callback is invoked on
954 each object created in phase two. Typically the callback
955 resolves references to other configuration file directives.'''
956
957 factory_objs = {}
958 for x in yaml.safe_load(fd.read()) or {}:
959
960 # Create factory object for this config file directive.
961 cls = x['class']
962 sub = x.get(cls)
963 if cls == 'group':
964 cls = '{0}group'.format(sub)
965
966 factory = Everything.classmap(cls, sub)
967 obj = factory(configfile=path, **x)
968
969 # For a given class of directive, validate the file
970 # doesn't have any duplicate names (duplicates are
971 # ok across config files).
972 if exists(factory_objs, obj.cls, obj.name, config=path):
973 raise NotUniqueError(path, cls, obj.name)
974
975 factory_objs.setdefault(cls, []).append(obj)
976 objs.setdefault(cls, []).append(obj)
977
978 for cls, items in factory_objs.items():
979 for obj in items:
980 # Add objects for template consumption.
981 obj.factory(objs)
982
983 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400984 def load(args):
985 '''Aggregate all the YAML in the input directory
986 into a single aggregate.'''
987
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400988 objs = {}
989 yaml_files = filter(
990 lambda x: x.endswith('.yaml'),
991 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400992
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400993 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400994
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400995 for x in yaml_files:
996 path = os.path.join(args.inputdir, x)
997 with open(path, 'r') as fd:
998 Everything.load_one_yaml(path, fd, objs)
999
1000 # Configuration file directives reference each other via
1001 # the name attribute; however, when rendered the reference
1002 # is just an array index.
1003 #
1004 # At this point all objects have been created but references
Gunnar Mills78199b42017-10-25 16:30:18 -05001005 # have not been resolved to array indices. Instruct objects
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001006 # to do that now.
1007 for cls, items in objs.items():
1008 for obj in items:
1009 obj.setup(objs)
1010
1011 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -04001012
1013 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -04001014 self.pathmeta = kw.pop('path', [])
1015 self.paths = kw.pop('pathname', [])
1016 self.meta = kw.pop('meta', [])
1017 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -04001018 self.interfaces = kw.pop('interface', [])
1019 self.properties = kw.pop('property', [])
1020 self.propertynames = kw.pop('propertyname', [])
1021 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -04001022 self.instances = kw.pop('instance', [])
1023 self.instancegroups = kw.pop('instancegroup', [])
1024 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -04001025 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -04001026 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -04001027 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -04001028
Brad Bishop34a7acd2017-04-27 23:47:23 -04001029 super(Everything, self).__init__(**kw)
1030
1031 def generate_cpp(self, loader):
1032 '''Render the template with the provided data.'''
Gunnar Millsd5faea32017-08-08 14:19:36 -05001033 # errors.hpp is used by generated.hpp to included any error.hpp files
1034 open('errors.hpp', 'w+')
1035
Brad Bishope3a01af2017-05-15 17:09:04 -04001036 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -04001037 fd.write(
1038 self.render(
1039 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -04001040 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -04001041 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -04001042 properties=self.properties,
1043 propertynames=self.propertynames,
1044 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -04001045 paths=self.paths,
1046 pathmeta=self.pathmeta,
1047 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -04001048 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -04001049 instances=self.instances,
1050 watches=self.watches,
1051 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -04001052 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -04001053 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -04001054 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -04001055 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001056
1057if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -04001058 script_dir = os.path.dirname(os.path.realpath(__file__))
1059 valid_commands = {
1060 'generate-cpp': 'generate_cpp',
1061 }
1062
1063 parser = ArgumentParser(
1064 description='Phosphor DBus Monitor (PDM) YAML '
1065 'scanner and code generator.')
1066
Matthew Barthdb440d42017-04-17 15:49:37 -05001067 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -04001068 "-o", "--out", dest="output",
1069 default='generated.cpp',
1070 help="Generated output file name and path.")
1071 parser.add_argument(
1072 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -04001073 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -04001074 help='The top level template to render.')
1075 parser.add_argument(
1076 '-p', '--template-path', dest='template_search',
1077 default=script_dir,
1078 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -04001079 parser.add_argument(
1080 '-d', '--dir', dest='inputdir',
1081 default=os.path.join(script_dir, 'example'),
1082 help='Location of files to process.')
1083 parser.add_argument(
1084 'command', metavar='COMMAND', type=str,
1085 choices=valid_commands.keys(),
1086 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001087
Brad Bishop34a7acd2017-04-27 23:47:23 -04001088 args = parser.parse_args()
1089
1090 if sys.version_info < (3, 0):
1091 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001092 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -04001093 disable_unicode=True)
1094 else:
1095 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001096 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001097 try:
1098 function = getattr(
1099 Everything.load(args),
1100 valid_commands[args.command])
1101 function(lookup)
1102 except InvalidConfigError as e:
1103 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1104 raise