blob: 3691d59c78ad3a64b4da7f1bbb051611a7238f31 [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
Gunnar Mills30474cf2017-08-11 09:38:54 -0500179class Metadata(Argument):
180 '''Metadata type arguments.'''
181
182 def __init__(self, **kw):
183 self.value = kw.pop('value')
184 self.decorators = kw.pop('decorators', [])
185 if kw.get('type', None) == 'string':
186 self.decorators.insert(0, Quote())
187
188 super(Metadata, self).__init__(**kw)
189
190 def argument(self, loader, indent):
191 a = str(self.value)
192 for d in self.decorators:
193 a = d(a)
194
195 return a
Brad Bishop01079892017-05-26 10:56:45 -0400196
Brad Bishop34a7acd2017-04-27 23:47:23 -0400197class Indent(object):
198 '''Help templates be depth agnostic.'''
Matthew Barthdb440d42017-04-17 15:49:37 -0500199
Brad Bishop34a7acd2017-04-27 23:47:23 -0400200 def __init__(self, depth=0):
201 self.depth = depth
Matthew Barthdb440d42017-04-17 15:49:37 -0500202
Brad Bishop34a7acd2017-04-27 23:47:23 -0400203 def __add__(self, depth):
204 return Indent(self.depth + depth)
205
206 def __call__(self, depth):
207 '''Render an indent at the current depth plus depth.'''
208 return 4*' '*(depth + self.depth)
209
210
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400211class ConfigEntry(NamedElement):
212 '''Base interface for rendered items.'''
213
214 def __init__(self, *a, **kw):
215 '''Pop the configfile/class/subclass keywords.'''
216
217 self.configfile = kw.pop('configfile')
218 self.cls = kw.pop('class')
219 self.subclass = kw.pop(self.cls)
220 super(ConfigEntry, self).__init__(**kw)
221
222 def factory(self, objs):
223 ''' Optional factory interface for subclasses to add
224 additional items to be rendered.'''
225
226 pass
227
228 def setup(self, objs):
229 ''' Optional setup interface for subclasses, invoked
230 after all factory methods have been run.'''
231
232 pass
233
234
Brad Bishop0e7df132017-05-23 17:58:12 -0400235class Path(ConfigEntry):
236 '''Path/metadata association.'''
237
238 def __init__(self, *a, **kw):
239 super(Path, self).__init__(**kw)
240
Brad Bishopbabf3b72017-05-31 19:44:53 -0400241 if self.name['meta'].upper() != self.name['meta']:
242 raise InvalidConfigError(
243 self.configfile,
244 'Metadata tag "{0}" must be upper case.'.format(
245 self.name['meta']))
246
Brad Bishop0e7df132017-05-23 17:58:12 -0400247 def factory(self, objs):
248 '''Create path and metadata elements.'''
249
250 args = {
251 'class': 'pathname',
252 'pathname': 'element',
253 'name': self.name['path']
254 }
255 add_unique(ConfigEntry(
256 configfile=self.configfile, **args), objs)
257
258 args = {
259 'class': 'meta',
260 'meta': 'element',
261 'name': self.name['meta']
262 }
263 add_unique(ConfigEntry(
264 configfile=self.configfile, **args), objs)
265
266 super(Path, self).factory(objs)
267
268 def setup(self, objs):
269 '''Resolve path and metadata names to indicies.'''
270
271 self.path = get_index(
272 objs, 'pathname', self.name['path'])
273 self.meta = get_index(
274 objs, 'meta', self.name['meta'])
275
276 super(Path, self).setup(objs)
277
278
Brad Bishope73b2c32017-05-23 18:01:54 -0400279class Property(ConfigEntry):
280 '''Property/interface/metadata association.'''
281
282 def __init__(self, *a, **kw):
283 super(Property, self).__init__(**kw)
284
Brad Bishopbabf3b72017-05-31 19:44:53 -0400285 if self.name['meta'].upper() != self.name['meta']:
286 raise InvalidConfigError(
287 self.configfile,
288 'Metadata tag "{0}" must be upper case.'.format(
289 self.name['meta']))
290
Brad Bishope73b2c32017-05-23 18:01:54 -0400291 def factory(self, objs):
292 '''Create interface, property name and metadata elements.'''
293
294 args = {
295 'class': 'interface',
296 'interface': 'element',
297 'name': self.name['interface']
298 }
299 add_unique(ConfigEntry(
300 configfile=self.configfile, **args), objs)
301
302 args = {
303 'class': 'propertyname',
304 'propertyname': 'element',
305 'name': self.name['property']
306 }
307 add_unique(ConfigEntry(
308 configfile=self.configfile, **args), objs)
309
310 args = {
311 'class': 'meta',
312 'meta': 'element',
313 'name': self.name['meta']
314 }
315 add_unique(ConfigEntry(
316 configfile=self.configfile, **args), objs)
317
318 super(Property, self).factory(objs)
319
320 def setup(self, objs):
321 '''Resolve interface, property and metadata to indicies.'''
322
323 self.interface = get_index(
324 objs, 'interface', self.name['interface'])
325 self.prop = get_index(
326 objs, 'propertyname', self.name['property'])
327 self.meta = get_index(
328 objs, 'meta', self.name['meta'])
329
330 super(Property, self).setup(objs)
331
332
Brad Bishop4b916f12017-05-23 18:06:38 -0400333class Instance(ConfigEntry):
334 '''Property/Path association.'''
335
336 def __init__(self, *a, **kw):
337 super(Instance, self).__init__(**kw)
338
339 def setup(self, objs):
340 '''Resolve elements to indicies.'''
341
342 self.interface = get_index(
343 objs, 'interface', self.name['property']['interface'])
344 self.prop = get_index(
345 objs, 'propertyname', self.name['property']['property'])
346 self.propmeta = get_index(
347 objs, 'meta', self.name['property']['meta'])
348 self.path = get_index(
349 objs, 'pathname', self.name['path']['path'])
350 self.pathmeta = get_index(
351 objs, 'meta', self.name['path']['meta'])
352
353 super(Instance, self).setup(objs)
354
355
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400356class Group(ConfigEntry):
357 '''Pop the members keyword for groups.'''
358
359 def __init__(self, *a, **kw):
360 self.members = kw.pop('members')
361 super(Group, self).__init__(**kw)
362
363
364class ImplicitGroup(Group):
365 '''Provide a factory method for groups whose members are
366 not explicitly declared in the config files.'''
367
368 def __init__(self, *a, **kw):
369 super(ImplicitGroup, self).__init__(**kw)
370
371 def factory(self, objs):
372 '''Create group members.'''
373
374 factory = Everything.classmap(self.subclass, 'element')
375 for m in self.members:
376 args = {
377 'class': self.subclass,
378 self.subclass: 'element',
379 'name': m
380 }
381
382 obj = factory(configfile=self.configfile, **args)
383 add_unique(obj, objs)
384 obj.factory(objs)
385
386 super(ImplicitGroup, self).factory(objs)
387
388
Brad Bishop0e7df132017-05-23 17:58:12 -0400389class GroupOfPaths(ImplicitGroup):
390 '''Path group config file directive.'''
391
392 def __init__(self, *a, **kw):
393 super(GroupOfPaths, self).__init__(**kw)
394
395 def setup(self, objs):
396 '''Resolve group members.'''
397
398 def map_member(x):
399 path = get_index(
400 objs, 'pathname', x['path'])
401 meta = get_index(
402 objs, 'meta', x['meta'])
403 return (path, meta)
404
405 self.members = map(
406 map_member,
407 self.members)
408
409 super(GroupOfPaths, self).setup(objs)
410
411
Brad Bishope73b2c32017-05-23 18:01:54 -0400412class GroupOfProperties(ImplicitGroup):
413 '''Property group config file directive.'''
414
415 def __init__(self, *a, **kw):
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400416 self.type = kw.pop('type')
Brad Bishope73b2c32017-05-23 18:01:54 -0400417 self.datatype = sdbusplus.property.Property(
418 name=kw.get('name'),
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400419 type=self.type).cppTypeName
Brad Bishope73b2c32017-05-23 18:01:54 -0400420
421 super(GroupOfProperties, self).__init__(**kw)
422
423 def setup(self, objs):
424 '''Resolve group members.'''
425
426 def map_member(x):
427 iface = get_index(
428 objs, 'interface', x['interface'])
429 prop = get_index(
430 objs, 'propertyname', x['property'])
431 meta = get_index(
432 objs, 'meta', x['meta'])
433
434 return (iface, prop, meta)
435
436 self.members = map(
437 map_member,
438 self.members)
439
440 super(GroupOfProperties, self).setup(objs)
441
442
Brad Bishop4b916f12017-05-23 18:06:38 -0400443class GroupOfInstances(ImplicitGroup):
444 '''A group of property instances.'''
445
446 def __init__(self, *a, **kw):
447 super(GroupOfInstances, self).__init__(**kw)
448
449 def setup(self, objs):
450 '''Resolve group members.'''
451
452 def map_member(x):
453 path = get_index(objs, 'pathname', x['path']['path'])
454 pathmeta = get_index(objs, 'meta', x['path']['meta'])
455 interface = get_index(
456 objs, 'interface', x['property']['interface'])
457 prop = get_index(objs, 'propertyname', x['property']['property'])
458 propmeta = get_index(objs, 'meta', x['property']['meta'])
459 instance = get_index(objs, 'instance', x)
460
461 return (path, pathmeta, interface, prop, propmeta, instance)
462
463 self.members = map(
464 map_member,
465 self.members)
466
467 super(GroupOfInstances, self).setup(objs)
468
469
470class HasPropertyIndex(ConfigEntry):
471 '''Handle config file directives that require an index to be
472 constructed.'''
473
474 def __init__(self, *a, **kw):
475 self.paths = kw.pop('paths')
476 self.properties = kw.pop('properties')
477 super(HasPropertyIndex, self).__init__(**kw)
478
479 def factory(self, objs):
480 '''Create a group of instances for this index.'''
481
482 members = []
483 path_group = get_index(
484 objs, 'pathgroup', self.paths, config=self.configfile)
485 property_group = get_index(
486 objs, 'propertygroup', self.properties, config=self.configfile)
487
488 for path in objs['pathgroup'][path_group].members:
489 for prop in objs['propertygroup'][property_group].members:
490 member = {
491 'path': path,
492 'property': prop,
493 }
494 members.append(member)
495
496 args = {
497 'members': members,
498 'class': 'instancegroup',
499 'instancegroup': 'instance',
500 'name': '{0} {1}'.format(self.paths, self.properties)
501 }
502
503 group = GroupOfInstances(configfile=self.configfile, **args)
504 add_unique(group, objs, config=self.configfile)
505 group.factory(objs)
506
507 super(HasPropertyIndex, self).factory(objs)
508
509 def setup(self, objs):
510 '''Resolve path, property, and instance groups.'''
511
512 self.instances = get_index(
513 objs,
514 'instancegroup',
515 '{0} {1}'.format(self.paths, self.properties),
516 config=self.configfile)
517 self.paths = get_index(
518 objs,
519 'pathgroup',
520 self.paths,
521 config=self.configfile)
522 self.properties = get_index(
523 objs,
524 'propertygroup',
525 self.properties,
526 config=self.configfile)
527 self.datatype = objs['propertygroup'][self.properties].datatype
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400528 self.type = objs['propertygroup'][self.properties].type
Brad Bishop4b916f12017-05-23 18:06:38 -0400529
530 super(HasPropertyIndex, self).setup(objs)
531
532
533class PropertyWatch(HasPropertyIndex):
534 '''Handle the property watch config file directive.'''
535
536 def __init__(self, *a, **kw):
Brad Bishopfccdc392017-05-22 21:11:09 -0400537 self.callback = kw.pop('callback', None)
Brad Bishop4b916f12017-05-23 18:06:38 -0400538 super(PropertyWatch, self).__init__(**kw)
539
Brad Bishopfccdc392017-05-22 21:11:09 -0400540 def setup(self, objs):
541 '''Resolve optional callback.'''
542
543 if self.callback:
544 self.callback = get_index(
545 objs,
546 'callback',
547 self.callback,
548 config=self.configfile)
549
550 super(PropertyWatch, self).setup(objs)
551
Brad Bishop4b916f12017-05-23 18:06:38 -0400552
Brad Bishopc1283ae2017-05-20 21:42:38 -0400553class Callback(HasPropertyIndex):
554 '''Interface and common logic for callbacks.'''
555
556 def __init__(self, *a, **kw):
557 super(Callback, self).__init__(**kw)
558
559
Brad Bishop4041d722017-05-21 10:06:07 -0400560class ConditionCallback(ConfigEntry, Renderer):
561 '''Handle the journal callback config file directive.'''
562
563 def __init__(self, *a, **kw):
564 self.condition = kw.pop('condition')
565 self.instance = kw.pop('instance')
Brad Bishop3539db62017-05-30 14:21:12 -0400566 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400567 super(ConditionCallback, self).__init__(**kw)
568
569 def factory(self, objs):
570 '''Create a graph instance for this callback.'''
571
572 args = {
573 'configfile': self.configfile,
574 'members': [self.instance],
575 'class': 'callbackgroup',
576 'callbackgroup': 'callback',
577 'name': [self.instance]
578 }
579
580 entry = CallbackGraphEntry(**args)
581 add_unique(entry, objs, config=self.configfile)
582
583 super(ConditionCallback, self).factory(objs)
584
585 def setup(self, objs):
586 '''Resolve condition and graph entry.'''
587
588 self.graph = get_index(
589 objs,
590 'callbackgroup',
591 [self.instance],
592 config=self.configfile)
593
594 self.condition = get_index(
595 objs,
596 'condition',
597 self.name,
598 config=self.configfile)
599
600 super(ConditionCallback, self).setup(objs)
601
602 def construct(self, loader, indent):
603 return self.render(
604 loader,
605 'conditional.mako.cpp',
606 c=self,
607 indent=indent)
608
609
610class Condition(HasPropertyIndex):
611 '''Interface and common logic for conditions.'''
612
613 def __init__(self, *a, **kw):
614 self.callback = kw.pop('callback')
Brad Bishop3539db62017-05-30 14:21:12 -0400615 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400616 super(Condition, self).__init__(**kw)
617
618 def factory(self, objs):
619 '''Create a callback instance for this conditional.'''
620
621 args = {
622 'configfile': self.configfile,
623 'condition': self.name,
624 'class': 'callback',
625 'callback': 'conditional',
626 'instance': self.callback,
627 'name': self.name,
Brad Bishop3539db62017-05-30 14:21:12 -0400628 'defer': self.defer
Brad Bishop4041d722017-05-21 10:06:07 -0400629 }
630
631 callback = ConditionCallback(**args)
632 add_unique(callback, objs, config=self.configfile)
633 callback.factory(objs)
634
635 super(Condition, self).factory(objs)
636
637
638class CountCondition(Condition, Renderer):
639 '''Handle the count condition config file directive.'''
640
641 def __init__(self, *a, **kw):
642 self.countop = kw.pop('countop')
643 self.countbound = kw.pop('countbound')
644 self.op = kw.pop('op')
645 self.bound = kw.pop('bound')
646 super(CountCondition, self).__init__(**kw)
647
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400648 def setup(self, objs):
649 '''Resolve type.'''
650
651 super(CountCondition, self).setup(objs)
652 self.bound = TrivialArgument(
653 type=self.type,
654 value=self.bound)
655
Brad Bishop4041d722017-05-21 10:06:07 -0400656 def construct(self, loader, indent):
657 return self.render(
658 loader,
659 'count.mako.cpp',
660 c=self,
661 indent=indent)
662
663
Brad Bishopc1283ae2017-05-20 21:42:38 -0400664class Journal(Callback, Renderer):
665 '''Handle the journal callback config file directive.'''
666
667 def __init__(self, *a, **kw):
668 self.severity = kw.pop('severity')
669 self.message = kw.pop('message')
670 super(Journal, self).__init__(**kw)
671
672 def construct(self, loader, indent):
673 return self.render(
674 loader,
675 'journal.mako.cpp',
676 c=self,
677 indent=indent)
678
679
Gunnar Millsd5faea32017-08-08 14:19:36 -0500680class Elog(Callback, Renderer):
681 '''Handle the elog callback config file directive.'''
682
683 def __init__(self, *a, **kw):
684 self.error = kw.pop('error')
Gunnar Mills30474cf2017-08-11 09:38:54 -0500685 self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})]
Gunnar Millsd5faea32017-08-08 14:19:36 -0500686 super(Elog, self).__init__(**kw)
687
688 def construct(self, loader, indent):
Gunnar Millsd5faea32017-08-08 14:19:36 -0500689 with open('errors.hpp', 'a') as fd:
690 fd.write(
691 self.render(
692 loader,
693 'errors.mako.hpp',
694 c=self))
695 return self.render(
696 loader,
697 'elog.mako.cpp',
698 c=self,
699 indent=indent)
700
701
Brad Bishop0df00be2017-05-25 23:38:37 -0400702class Method(ConfigEntry, Renderer):
703 '''Handle the method callback config file directive.'''
704
705 def __init__(self, *a, **kw):
706 self.service = kw.pop('service')
707 self.path = kw.pop('path')
708 self.interface = kw.pop('interface')
709 self.method = kw.pop('method')
710 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
711 super(Method, self).__init__(**kw)
712
713 def factory(self, objs):
714 args = {
715 'class': 'interface',
716 'interface': 'element',
717 'name': self.service
718 }
719 add_unique(ConfigEntry(
720 configfile=self.configfile, **args), objs)
721
722 args = {
723 'class': 'pathname',
724 'pathname': 'element',
725 'name': self.path
726 }
727 add_unique(ConfigEntry(
728 configfile=self.configfile, **args), objs)
729
730 args = {
731 'class': 'interface',
732 'interface': 'element',
733 'name': self.interface
734 }
735 add_unique(ConfigEntry(
736 configfile=self.configfile, **args), objs)
737
738 args = {
739 'class': 'propertyname',
740 'propertyname': 'element',
741 'name': self.method
742 }
743 add_unique(ConfigEntry(
744 configfile=self.configfile, **args), objs)
745
746 super(Method, self).factory(objs)
747
748 def setup(self, objs):
749 '''Resolve elements.'''
750
751 self.service = get_index(
752 objs,
753 'interface',
754 self.service)
755
756 self.path = get_index(
757 objs,
758 'pathname',
759 self.path)
760
761 self.interface = get_index(
762 objs,
763 'interface',
764 self.interface)
765
766 self.method = get_index(
767 objs,
768 'propertyname',
769 self.method)
770
771 super(Method, self).setup(objs)
772
773 def construct(self, loader, indent):
774 return self.render(
775 loader,
776 'method.mako.cpp',
777 c=self,
778 indent=indent)
779
780
Brad Bishop49e66172017-05-23 19:16:21 -0400781class CallbackGraphEntry(Group):
782 '''An entry in a traversal list for groups of callbacks.'''
783
784 def __init__(self, *a, **kw):
785 super(CallbackGraphEntry, self).__init__(**kw)
786
787 def setup(self, objs):
788 '''Resolve group members.'''
789
790 def map_member(x):
791 return get_index(
792 objs, 'callback', x, config=self.configfile)
793
794 self.members = map(
795 map_member,
796 self.members)
797
798 super(CallbackGraphEntry, self).setup(objs)
799
800
801class GroupOfCallbacks(ConfigEntry, Renderer):
802 '''Handle the callback group config file directive.'''
803
804 def __init__(self, *a, **kw):
805 self.members = kw.pop('members')
806 super(GroupOfCallbacks, self).__init__(**kw)
807
808 def factory(self, objs):
809 '''Create a graph instance for this group of callbacks.'''
810
811 args = {
812 'configfile': self.configfile,
813 'members': self.members,
814 'class': 'callbackgroup',
815 'callbackgroup': 'callback',
816 'name': self.members
817 }
818
819 entry = CallbackGraphEntry(**args)
820 add_unique(entry, objs, config=self.configfile)
821
822 super(GroupOfCallbacks, self).factory(objs)
823
824 def setup(self, objs):
825 '''Resolve graph entry.'''
826
827 self.graph = get_index(
828 objs, 'callbackgroup', self.members, config=self.configfile)
829
830 super(GroupOfCallbacks, self).setup(objs)
831
832 def construct(self, loader, indent):
833 return self.render(
834 loader,
835 'callbackgroup.mako.cpp',
836 c=self,
837 indent=indent)
838
839
Brad Bishop34a7acd2017-04-27 23:47:23 -0400840class Everything(Renderer):
841 '''Parse/render entry point.'''
842
843 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400844 def classmap(cls, sub=None):
845 '''Map render item class and subclass entries to the appropriate
846 handler methods.'''
847
848 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400849 'path': {
850 'element': Path,
851 },
852 'pathgroup': {
853 'path': GroupOfPaths,
854 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400855 'propertygroup': {
856 'property': GroupOfProperties,
857 },
858 'property': {
859 'element': Property,
860 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400861 'watch': {
862 'property': PropertyWatch,
863 },
864 'instance': {
865 'element': Instance,
866 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400867 'callback': {
868 'journal': Journal,
Gunnar Millsd5faea32017-08-08 14:19:36 -0500869 'elog': Elog,
Brad Bishop49e66172017-05-23 19:16:21 -0400870 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400871 'method': Method,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400872 },
Brad Bishop4041d722017-05-21 10:06:07 -0400873 'condition': {
874 'count': CountCondition,
875 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400876 }
877
878 if cls not in class_map:
879 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
880 if sub not in class_map[cls]:
881 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
882 cls, sub))
883
884 return class_map[cls][sub]
885
886 @staticmethod
887 def load_one_yaml(path, fd, objs):
888 '''Parse a single YAML file. Parsing occurs in three phases.
889 In the first phase a factory method associated with each
890 configuration file directive is invoked. These factory
891 methods generate more factory methods. In the second
892 phase the factory methods created in the first phase
893 are invoked. In the last phase a callback is invoked on
894 each object created in phase two. Typically the callback
895 resolves references to other configuration file directives.'''
896
897 factory_objs = {}
898 for x in yaml.safe_load(fd.read()) or {}:
899
900 # Create factory object for this config file directive.
901 cls = x['class']
902 sub = x.get(cls)
903 if cls == 'group':
904 cls = '{0}group'.format(sub)
905
906 factory = Everything.classmap(cls, sub)
907 obj = factory(configfile=path, **x)
908
909 # For a given class of directive, validate the file
910 # doesn't have any duplicate names (duplicates are
911 # ok across config files).
912 if exists(factory_objs, obj.cls, obj.name, config=path):
913 raise NotUniqueError(path, cls, obj.name)
914
915 factory_objs.setdefault(cls, []).append(obj)
916 objs.setdefault(cls, []).append(obj)
917
918 for cls, items in factory_objs.items():
919 for obj in items:
920 # Add objects for template consumption.
921 obj.factory(objs)
922
923 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400924 def load(args):
925 '''Aggregate all the YAML in the input directory
926 into a single aggregate.'''
927
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400928 objs = {}
929 yaml_files = filter(
930 lambda x: x.endswith('.yaml'),
931 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400932
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400933 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400934
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400935 for x in yaml_files:
936 path = os.path.join(args.inputdir, x)
937 with open(path, 'r') as fd:
938 Everything.load_one_yaml(path, fd, objs)
939
940 # Configuration file directives reference each other via
941 # the name attribute; however, when rendered the reference
942 # is just an array index.
943 #
944 # At this point all objects have been created but references
945 # have not been resolved to array indicies. Instruct objects
946 # to do that now.
947 for cls, items in objs.items():
948 for obj in items:
949 obj.setup(objs)
950
951 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400952
953 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400954 self.pathmeta = kw.pop('path', [])
955 self.paths = kw.pop('pathname', [])
956 self.meta = kw.pop('meta', [])
957 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400958 self.interfaces = kw.pop('interface', [])
959 self.properties = kw.pop('property', [])
960 self.propertynames = kw.pop('propertyname', [])
961 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400962 self.instances = kw.pop('instance', [])
963 self.instancegroups = kw.pop('instancegroup', [])
964 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400965 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400966 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400967 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400968
Brad Bishop34a7acd2017-04-27 23:47:23 -0400969 super(Everything, self).__init__(**kw)
970
971 def generate_cpp(self, loader):
972 '''Render the template with the provided data.'''
Gunnar Millsd5faea32017-08-08 14:19:36 -0500973 # errors.hpp is used by generated.hpp to included any error.hpp files
974 open('errors.hpp', 'w+')
975
Brad Bishope3a01af2017-05-15 17:09:04 -0400976 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400977 fd.write(
978 self.render(
979 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400980 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400981 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400982 properties=self.properties,
983 propertynames=self.propertynames,
984 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -0400985 paths=self.paths,
986 pathmeta=self.pathmeta,
987 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -0400988 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -0400989 instances=self.instances,
990 watches=self.watches,
991 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400992 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -0400993 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -0400994 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -0400995 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500996
997if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -0400998 script_dir = os.path.dirname(os.path.realpath(__file__))
999 valid_commands = {
1000 'generate-cpp': 'generate_cpp',
1001 }
1002
1003 parser = ArgumentParser(
1004 description='Phosphor DBus Monitor (PDM) YAML '
1005 'scanner and code generator.')
1006
Matthew Barthdb440d42017-04-17 15:49:37 -05001007 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -04001008 "-o", "--out", dest="output",
1009 default='generated.cpp',
1010 help="Generated output file name and path.")
1011 parser.add_argument(
1012 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -04001013 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -04001014 help='The top level template to render.')
1015 parser.add_argument(
1016 '-p', '--template-path', dest='template_search',
1017 default=script_dir,
1018 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -04001019 parser.add_argument(
1020 '-d', '--dir', dest='inputdir',
1021 default=os.path.join(script_dir, 'example'),
1022 help='Location of files to process.')
1023 parser.add_argument(
1024 'command', metavar='COMMAND', type=str,
1025 choices=valid_commands.keys(),
1026 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001027
Brad Bishop34a7acd2017-04-27 23:47:23 -04001028 args = parser.parse_args()
1029
1030 if sys.version_info < (3, 0):
1031 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001032 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -04001033 disable_unicode=True)
1034 else:
1035 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001036 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001037 try:
1038 function = getattr(
1039 Everything.load(args),
1040 valid_commands[args.command])
1041 function(lookup)
1042 except InvalidConfigError as e:
1043 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1044 raise