blob: dc2c704e8281895b4fcd960df0fe86ba9e2216e0 [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
179
Brad Bishop34a7acd2017-04-27 23:47:23 -0400180class Indent(object):
181 '''Help templates be depth agnostic.'''
Matthew Barthdb440d42017-04-17 15:49:37 -0500182
Brad Bishop34a7acd2017-04-27 23:47:23 -0400183 def __init__(self, depth=0):
184 self.depth = depth
Matthew Barthdb440d42017-04-17 15:49:37 -0500185
Brad Bishop34a7acd2017-04-27 23:47:23 -0400186 def __add__(self, depth):
187 return Indent(self.depth + depth)
188
189 def __call__(self, depth):
190 '''Render an indent at the current depth plus depth.'''
191 return 4*' '*(depth + self.depth)
192
193
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400194class ConfigEntry(NamedElement):
195 '''Base interface for rendered items.'''
196
197 def __init__(self, *a, **kw):
198 '''Pop the configfile/class/subclass keywords.'''
199
200 self.configfile = kw.pop('configfile')
201 self.cls = kw.pop('class')
202 self.subclass = kw.pop(self.cls)
203 super(ConfigEntry, self).__init__(**kw)
204
205 def factory(self, objs):
206 ''' Optional factory interface for subclasses to add
207 additional items to be rendered.'''
208
209 pass
210
211 def setup(self, objs):
212 ''' Optional setup interface for subclasses, invoked
213 after all factory methods have been run.'''
214
215 pass
216
217
Brad Bishop0e7df132017-05-23 17:58:12 -0400218class Path(ConfigEntry):
219 '''Path/metadata association.'''
220
221 def __init__(self, *a, **kw):
222 super(Path, self).__init__(**kw)
223
Brad Bishopbabf3b72017-05-31 19:44:53 -0400224 if self.name['meta'].upper() != self.name['meta']:
225 raise InvalidConfigError(
226 self.configfile,
227 'Metadata tag "{0}" must be upper case.'.format(
228 self.name['meta']))
229
Brad Bishop0e7df132017-05-23 17:58:12 -0400230 def factory(self, objs):
231 '''Create path and metadata elements.'''
232
233 args = {
234 'class': 'pathname',
235 'pathname': 'element',
236 'name': self.name['path']
237 }
238 add_unique(ConfigEntry(
239 configfile=self.configfile, **args), objs)
240
241 args = {
242 'class': 'meta',
243 'meta': 'element',
244 'name': self.name['meta']
245 }
246 add_unique(ConfigEntry(
247 configfile=self.configfile, **args), objs)
248
249 super(Path, self).factory(objs)
250
251 def setup(self, objs):
252 '''Resolve path and metadata names to indicies.'''
253
254 self.path = get_index(
255 objs, 'pathname', self.name['path'])
256 self.meta = get_index(
257 objs, 'meta', self.name['meta'])
258
259 super(Path, self).setup(objs)
260
261
Brad Bishope73b2c32017-05-23 18:01:54 -0400262class Property(ConfigEntry):
263 '''Property/interface/metadata association.'''
264
265 def __init__(self, *a, **kw):
266 super(Property, self).__init__(**kw)
267
Brad Bishopbabf3b72017-05-31 19:44:53 -0400268 if self.name['meta'].upper() != self.name['meta']:
269 raise InvalidConfigError(
270 self.configfile,
271 'Metadata tag "{0}" must be upper case.'.format(
272 self.name['meta']))
273
Brad Bishope73b2c32017-05-23 18:01:54 -0400274 def factory(self, objs):
275 '''Create interface, property name and metadata elements.'''
276
277 args = {
278 'class': 'interface',
279 'interface': 'element',
280 'name': self.name['interface']
281 }
282 add_unique(ConfigEntry(
283 configfile=self.configfile, **args), objs)
284
285 args = {
286 'class': 'propertyname',
287 'propertyname': 'element',
288 'name': self.name['property']
289 }
290 add_unique(ConfigEntry(
291 configfile=self.configfile, **args), objs)
292
293 args = {
294 'class': 'meta',
295 'meta': 'element',
296 'name': self.name['meta']
297 }
298 add_unique(ConfigEntry(
299 configfile=self.configfile, **args), objs)
300
301 super(Property, self).factory(objs)
302
303 def setup(self, objs):
304 '''Resolve interface, property and metadata to indicies.'''
305
306 self.interface = get_index(
307 objs, 'interface', self.name['interface'])
308 self.prop = get_index(
309 objs, 'propertyname', self.name['property'])
310 self.meta = get_index(
311 objs, 'meta', self.name['meta'])
312
313 super(Property, self).setup(objs)
314
315
Brad Bishop4b916f12017-05-23 18:06:38 -0400316class Instance(ConfigEntry):
317 '''Property/Path association.'''
318
319 def __init__(self, *a, **kw):
320 super(Instance, self).__init__(**kw)
321
322 def setup(self, objs):
323 '''Resolve elements to indicies.'''
324
325 self.interface = get_index(
326 objs, 'interface', self.name['property']['interface'])
327 self.prop = get_index(
328 objs, 'propertyname', self.name['property']['property'])
329 self.propmeta = get_index(
330 objs, 'meta', self.name['property']['meta'])
331 self.path = get_index(
332 objs, 'pathname', self.name['path']['path'])
333 self.pathmeta = get_index(
334 objs, 'meta', self.name['path']['meta'])
335
336 super(Instance, self).setup(objs)
337
338
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400339class Group(ConfigEntry):
340 '''Pop the members keyword for groups.'''
341
342 def __init__(self, *a, **kw):
343 self.members = kw.pop('members')
344 super(Group, self).__init__(**kw)
345
346
347class ImplicitGroup(Group):
348 '''Provide a factory method for groups whose members are
349 not explicitly declared in the config files.'''
350
351 def __init__(self, *a, **kw):
352 super(ImplicitGroup, self).__init__(**kw)
353
354 def factory(self, objs):
355 '''Create group members.'''
356
357 factory = Everything.classmap(self.subclass, 'element')
358 for m in self.members:
359 args = {
360 'class': self.subclass,
361 self.subclass: 'element',
362 'name': m
363 }
364
365 obj = factory(configfile=self.configfile, **args)
366 add_unique(obj, objs)
367 obj.factory(objs)
368
369 super(ImplicitGroup, self).factory(objs)
370
371
Brad Bishop0e7df132017-05-23 17:58:12 -0400372class GroupOfPaths(ImplicitGroup):
373 '''Path group config file directive.'''
374
375 def __init__(self, *a, **kw):
376 super(GroupOfPaths, self).__init__(**kw)
377
378 def setup(self, objs):
379 '''Resolve group members.'''
380
381 def map_member(x):
382 path = get_index(
383 objs, 'pathname', x['path'])
384 meta = get_index(
385 objs, 'meta', x['meta'])
386 return (path, meta)
387
388 self.members = map(
389 map_member,
390 self.members)
391
392 super(GroupOfPaths, self).setup(objs)
393
394
Brad Bishope73b2c32017-05-23 18:01:54 -0400395class GroupOfProperties(ImplicitGroup):
396 '''Property group config file directive.'''
397
398 def __init__(self, *a, **kw):
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400399 self.type = kw.pop('type')
Brad Bishope73b2c32017-05-23 18:01:54 -0400400 self.datatype = sdbusplus.property.Property(
401 name=kw.get('name'),
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400402 type=self.type).cppTypeName
Brad Bishope73b2c32017-05-23 18:01:54 -0400403
404 super(GroupOfProperties, self).__init__(**kw)
405
406 def setup(self, objs):
407 '''Resolve group members.'''
408
409 def map_member(x):
410 iface = get_index(
411 objs, 'interface', x['interface'])
412 prop = get_index(
413 objs, 'propertyname', x['property'])
414 meta = get_index(
415 objs, 'meta', x['meta'])
416
417 return (iface, prop, meta)
418
419 self.members = map(
420 map_member,
421 self.members)
422
423 super(GroupOfProperties, self).setup(objs)
424
425
Brad Bishop4b916f12017-05-23 18:06:38 -0400426class GroupOfInstances(ImplicitGroup):
427 '''A group of property instances.'''
428
429 def __init__(self, *a, **kw):
430 super(GroupOfInstances, self).__init__(**kw)
431
432 def setup(self, objs):
433 '''Resolve group members.'''
434
435 def map_member(x):
436 path = get_index(objs, 'pathname', x['path']['path'])
437 pathmeta = get_index(objs, 'meta', x['path']['meta'])
438 interface = get_index(
439 objs, 'interface', x['property']['interface'])
440 prop = get_index(objs, 'propertyname', x['property']['property'])
441 propmeta = get_index(objs, 'meta', x['property']['meta'])
442 instance = get_index(objs, 'instance', x)
443
444 return (path, pathmeta, interface, prop, propmeta, instance)
445
446 self.members = map(
447 map_member,
448 self.members)
449
450 super(GroupOfInstances, self).setup(objs)
451
452
453class HasPropertyIndex(ConfigEntry):
454 '''Handle config file directives that require an index to be
455 constructed.'''
456
457 def __init__(self, *a, **kw):
458 self.paths = kw.pop('paths')
459 self.properties = kw.pop('properties')
460 super(HasPropertyIndex, self).__init__(**kw)
461
462 def factory(self, objs):
463 '''Create a group of instances for this index.'''
464
465 members = []
466 path_group = get_index(
467 objs, 'pathgroup', self.paths, config=self.configfile)
468 property_group = get_index(
469 objs, 'propertygroup', self.properties, config=self.configfile)
470
471 for path in objs['pathgroup'][path_group].members:
472 for prop in objs['propertygroup'][property_group].members:
473 member = {
474 'path': path,
475 'property': prop,
476 }
477 members.append(member)
478
479 args = {
480 'members': members,
481 'class': 'instancegroup',
482 'instancegroup': 'instance',
483 'name': '{0} {1}'.format(self.paths, self.properties)
484 }
485
486 group = GroupOfInstances(configfile=self.configfile, **args)
487 add_unique(group, objs, config=self.configfile)
488 group.factory(objs)
489
490 super(HasPropertyIndex, self).factory(objs)
491
492 def setup(self, objs):
493 '''Resolve path, property, and instance groups.'''
494
495 self.instances = get_index(
496 objs,
497 'instancegroup',
498 '{0} {1}'.format(self.paths, self.properties),
499 config=self.configfile)
500 self.paths = get_index(
501 objs,
502 'pathgroup',
503 self.paths,
504 config=self.configfile)
505 self.properties = get_index(
506 objs,
507 'propertygroup',
508 self.properties,
509 config=self.configfile)
510 self.datatype = objs['propertygroup'][self.properties].datatype
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400511 self.type = objs['propertygroup'][self.properties].type
Brad Bishop4b916f12017-05-23 18:06:38 -0400512
513 super(HasPropertyIndex, self).setup(objs)
514
515
516class PropertyWatch(HasPropertyIndex):
517 '''Handle the property watch config file directive.'''
518
519 def __init__(self, *a, **kw):
Brad Bishopfccdc392017-05-22 21:11:09 -0400520 self.callback = kw.pop('callback', None)
Brad Bishop4b916f12017-05-23 18:06:38 -0400521 super(PropertyWatch, self).__init__(**kw)
522
Brad Bishopfccdc392017-05-22 21:11:09 -0400523 def setup(self, objs):
524 '''Resolve optional callback.'''
525
526 if self.callback:
527 self.callback = get_index(
528 objs,
529 'callback',
530 self.callback,
531 config=self.configfile)
532
533 super(PropertyWatch, self).setup(objs)
534
Brad Bishop4b916f12017-05-23 18:06:38 -0400535
Brad Bishopc1283ae2017-05-20 21:42:38 -0400536class Callback(HasPropertyIndex):
537 '''Interface and common logic for callbacks.'''
538
539 def __init__(self, *a, **kw):
540 super(Callback, self).__init__(**kw)
541
542
Brad Bishop4041d722017-05-21 10:06:07 -0400543class ConditionCallback(ConfigEntry, Renderer):
544 '''Handle the journal callback config file directive.'''
545
546 def __init__(self, *a, **kw):
547 self.condition = kw.pop('condition')
548 self.instance = kw.pop('instance')
Brad Bishop3539db62017-05-30 14:21:12 -0400549 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400550 super(ConditionCallback, self).__init__(**kw)
551
552 def factory(self, objs):
553 '''Create a graph instance for this callback.'''
554
555 args = {
556 'configfile': self.configfile,
557 'members': [self.instance],
558 'class': 'callbackgroup',
559 'callbackgroup': 'callback',
560 'name': [self.instance]
561 }
562
563 entry = CallbackGraphEntry(**args)
564 add_unique(entry, objs, config=self.configfile)
565
566 super(ConditionCallback, self).factory(objs)
567
568 def setup(self, objs):
569 '''Resolve condition and graph entry.'''
570
571 self.graph = get_index(
572 objs,
573 'callbackgroup',
574 [self.instance],
575 config=self.configfile)
576
577 self.condition = get_index(
578 objs,
579 'condition',
580 self.name,
581 config=self.configfile)
582
583 super(ConditionCallback, self).setup(objs)
584
585 def construct(self, loader, indent):
586 return self.render(
587 loader,
588 'conditional.mako.cpp',
589 c=self,
590 indent=indent)
591
592
593class Condition(HasPropertyIndex):
594 '''Interface and common logic for conditions.'''
595
596 def __init__(self, *a, **kw):
597 self.callback = kw.pop('callback')
Brad Bishop3539db62017-05-30 14:21:12 -0400598 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400599 super(Condition, self).__init__(**kw)
600
601 def factory(self, objs):
602 '''Create a callback instance for this conditional.'''
603
604 args = {
605 'configfile': self.configfile,
606 'condition': self.name,
607 'class': 'callback',
608 'callback': 'conditional',
609 'instance': self.callback,
610 'name': self.name,
Brad Bishop3539db62017-05-30 14:21:12 -0400611 'defer': self.defer
Brad Bishop4041d722017-05-21 10:06:07 -0400612 }
613
614 callback = ConditionCallback(**args)
615 add_unique(callback, objs, config=self.configfile)
616 callback.factory(objs)
617
618 super(Condition, self).factory(objs)
619
620
621class CountCondition(Condition, Renderer):
622 '''Handle the count condition config file directive.'''
623
624 def __init__(self, *a, **kw):
625 self.countop = kw.pop('countop')
626 self.countbound = kw.pop('countbound')
627 self.op = kw.pop('op')
628 self.bound = kw.pop('bound')
629 super(CountCondition, self).__init__(**kw)
630
Brad Bishopec2ed2f2017-05-31 21:10:43 -0400631 def setup(self, objs):
632 '''Resolve type.'''
633
634 super(CountCondition, self).setup(objs)
635 self.bound = TrivialArgument(
636 type=self.type,
637 value=self.bound)
638
Brad Bishop4041d722017-05-21 10:06:07 -0400639 def construct(self, loader, indent):
640 return self.render(
641 loader,
642 'count.mako.cpp',
643 c=self,
644 indent=indent)
645
646
Brad Bishopc1283ae2017-05-20 21:42:38 -0400647class Journal(Callback, Renderer):
648 '''Handle the journal callback config file directive.'''
649
650 def __init__(self, *a, **kw):
651 self.severity = kw.pop('severity')
652 self.message = kw.pop('message')
653 super(Journal, self).__init__(**kw)
654
655 def construct(self, loader, indent):
656 return self.render(
657 loader,
658 'journal.mako.cpp',
659 c=self,
660 indent=indent)
661
662
Gunnar Millsd5faea32017-08-08 14:19:36 -0500663class Elog(Callback, Renderer):
664 '''Handle the elog callback config file directive.'''
665
666 def __init__(self, *a, **kw):
667 self.error = kw.pop('error')
668 super(Elog, self).__init__(**kw)
669
670 def construct(self, loader, indent):
671
672 with open('errors.hpp', 'a') as fd:
673 fd.write(
674 self.render(
675 loader,
676 'errors.mako.hpp',
677 c=self))
678 return self.render(
679 loader,
680 'elog.mako.cpp',
681 c=self,
682 indent=indent)
683
684
Brad Bishop0df00be2017-05-25 23:38:37 -0400685class Method(ConfigEntry, Renderer):
686 '''Handle the method callback config file directive.'''
687
688 def __init__(self, *a, **kw):
689 self.service = kw.pop('service')
690 self.path = kw.pop('path')
691 self.interface = kw.pop('interface')
692 self.method = kw.pop('method')
693 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
694 super(Method, self).__init__(**kw)
695
696 def factory(self, objs):
697 args = {
698 'class': 'interface',
699 'interface': 'element',
700 'name': self.service
701 }
702 add_unique(ConfigEntry(
703 configfile=self.configfile, **args), objs)
704
705 args = {
706 'class': 'pathname',
707 'pathname': 'element',
708 'name': self.path
709 }
710 add_unique(ConfigEntry(
711 configfile=self.configfile, **args), objs)
712
713 args = {
714 'class': 'interface',
715 'interface': 'element',
716 'name': self.interface
717 }
718 add_unique(ConfigEntry(
719 configfile=self.configfile, **args), objs)
720
721 args = {
722 'class': 'propertyname',
723 'propertyname': 'element',
724 'name': self.method
725 }
726 add_unique(ConfigEntry(
727 configfile=self.configfile, **args), objs)
728
729 super(Method, self).factory(objs)
730
731 def setup(self, objs):
732 '''Resolve elements.'''
733
734 self.service = get_index(
735 objs,
736 'interface',
737 self.service)
738
739 self.path = get_index(
740 objs,
741 'pathname',
742 self.path)
743
744 self.interface = get_index(
745 objs,
746 'interface',
747 self.interface)
748
749 self.method = get_index(
750 objs,
751 'propertyname',
752 self.method)
753
754 super(Method, self).setup(objs)
755
756 def construct(self, loader, indent):
757 return self.render(
758 loader,
759 'method.mako.cpp',
760 c=self,
761 indent=indent)
762
763
Brad Bishop49e66172017-05-23 19:16:21 -0400764class CallbackGraphEntry(Group):
765 '''An entry in a traversal list for groups of callbacks.'''
766
767 def __init__(self, *a, **kw):
768 super(CallbackGraphEntry, self).__init__(**kw)
769
770 def setup(self, objs):
771 '''Resolve group members.'''
772
773 def map_member(x):
774 return get_index(
775 objs, 'callback', x, config=self.configfile)
776
777 self.members = map(
778 map_member,
779 self.members)
780
781 super(CallbackGraphEntry, self).setup(objs)
782
783
784class GroupOfCallbacks(ConfigEntry, Renderer):
785 '''Handle the callback group config file directive.'''
786
787 def __init__(self, *a, **kw):
788 self.members = kw.pop('members')
789 super(GroupOfCallbacks, self).__init__(**kw)
790
791 def factory(self, objs):
792 '''Create a graph instance for this group of callbacks.'''
793
794 args = {
795 'configfile': self.configfile,
796 'members': self.members,
797 'class': 'callbackgroup',
798 'callbackgroup': 'callback',
799 'name': self.members
800 }
801
802 entry = CallbackGraphEntry(**args)
803 add_unique(entry, objs, config=self.configfile)
804
805 super(GroupOfCallbacks, self).factory(objs)
806
807 def setup(self, objs):
808 '''Resolve graph entry.'''
809
810 self.graph = get_index(
811 objs, 'callbackgroup', self.members, config=self.configfile)
812
813 super(GroupOfCallbacks, self).setup(objs)
814
815 def construct(self, loader, indent):
816 return self.render(
817 loader,
818 'callbackgroup.mako.cpp',
819 c=self,
820 indent=indent)
821
822
Brad Bishop34a7acd2017-04-27 23:47:23 -0400823class Everything(Renderer):
824 '''Parse/render entry point.'''
825
826 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400827 def classmap(cls, sub=None):
828 '''Map render item class and subclass entries to the appropriate
829 handler methods.'''
830
831 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400832 'path': {
833 'element': Path,
834 },
835 'pathgroup': {
836 'path': GroupOfPaths,
837 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400838 'propertygroup': {
839 'property': GroupOfProperties,
840 },
841 'property': {
842 'element': Property,
843 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400844 'watch': {
845 'property': PropertyWatch,
846 },
847 'instance': {
848 'element': Instance,
849 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400850 'callback': {
851 'journal': Journal,
Gunnar Millsd5faea32017-08-08 14:19:36 -0500852 'elog': Elog,
Brad Bishop49e66172017-05-23 19:16:21 -0400853 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400854 'method': Method,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400855 },
Brad Bishop4041d722017-05-21 10:06:07 -0400856 'condition': {
857 'count': CountCondition,
858 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400859 }
860
861 if cls not in class_map:
862 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
863 if sub not in class_map[cls]:
864 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
865 cls, sub))
866
867 return class_map[cls][sub]
868
869 @staticmethod
870 def load_one_yaml(path, fd, objs):
871 '''Parse a single YAML file. Parsing occurs in three phases.
872 In the first phase a factory method associated with each
873 configuration file directive is invoked. These factory
874 methods generate more factory methods. In the second
875 phase the factory methods created in the first phase
876 are invoked. In the last phase a callback is invoked on
877 each object created in phase two. Typically the callback
878 resolves references to other configuration file directives.'''
879
880 factory_objs = {}
881 for x in yaml.safe_load(fd.read()) or {}:
882
883 # Create factory object for this config file directive.
884 cls = x['class']
885 sub = x.get(cls)
886 if cls == 'group':
887 cls = '{0}group'.format(sub)
888
889 factory = Everything.classmap(cls, sub)
890 obj = factory(configfile=path, **x)
891
892 # For a given class of directive, validate the file
893 # doesn't have any duplicate names (duplicates are
894 # ok across config files).
895 if exists(factory_objs, obj.cls, obj.name, config=path):
896 raise NotUniqueError(path, cls, obj.name)
897
898 factory_objs.setdefault(cls, []).append(obj)
899 objs.setdefault(cls, []).append(obj)
900
901 for cls, items in factory_objs.items():
902 for obj in items:
903 # Add objects for template consumption.
904 obj.factory(objs)
905
906 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400907 def load(args):
908 '''Aggregate all the YAML in the input directory
909 into a single aggregate.'''
910
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400911 objs = {}
912 yaml_files = filter(
913 lambda x: x.endswith('.yaml'),
914 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400915
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400916 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400917
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400918 for x in yaml_files:
919 path = os.path.join(args.inputdir, x)
920 with open(path, 'r') as fd:
921 Everything.load_one_yaml(path, fd, objs)
922
923 # Configuration file directives reference each other via
924 # the name attribute; however, when rendered the reference
925 # is just an array index.
926 #
927 # At this point all objects have been created but references
928 # have not been resolved to array indicies. Instruct objects
929 # to do that now.
930 for cls, items in objs.items():
931 for obj in items:
932 obj.setup(objs)
933
934 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400935
936 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400937 self.pathmeta = kw.pop('path', [])
938 self.paths = kw.pop('pathname', [])
939 self.meta = kw.pop('meta', [])
940 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400941 self.interfaces = kw.pop('interface', [])
942 self.properties = kw.pop('property', [])
943 self.propertynames = kw.pop('propertyname', [])
944 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400945 self.instances = kw.pop('instance', [])
946 self.instancegroups = kw.pop('instancegroup', [])
947 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400948 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400949 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400950 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400951
Brad Bishop34a7acd2017-04-27 23:47:23 -0400952 super(Everything, self).__init__(**kw)
953
954 def generate_cpp(self, loader):
955 '''Render the template with the provided data.'''
Gunnar Millsd5faea32017-08-08 14:19:36 -0500956 # errors.hpp is used by generated.hpp to included any error.hpp files
957 open('errors.hpp', 'w+')
958
Brad Bishope3a01af2017-05-15 17:09:04 -0400959 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400960 fd.write(
961 self.render(
962 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400963 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400964 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400965 properties=self.properties,
966 propertynames=self.propertynames,
967 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -0400968 paths=self.paths,
969 pathmeta=self.pathmeta,
970 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -0400971 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -0400972 instances=self.instances,
973 watches=self.watches,
974 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400975 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -0400976 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -0400977 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -0400978 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500979
980if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -0400981 script_dir = os.path.dirname(os.path.realpath(__file__))
982 valid_commands = {
983 'generate-cpp': 'generate_cpp',
984 }
985
986 parser = ArgumentParser(
987 description='Phosphor DBus Monitor (PDM) YAML '
988 'scanner and code generator.')
989
Matthew Barthdb440d42017-04-17 15:49:37 -0500990 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -0400991 "-o", "--out", dest="output",
992 default='generated.cpp',
993 help="Generated output file name and path.")
994 parser.add_argument(
995 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -0400996 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -0400997 help='The top level template to render.')
998 parser.add_argument(
999 '-p', '--template-path', dest='template_search',
1000 default=script_dir,
1001 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -04001002 parser.add_argument(
1003 '-d', '--dir', dest='inputdir',
1004 default=os.path.join(script_dir, 'example'),
1005 help='Location of files to process.')
1006 parser.add_argument(
1007 'command', metavar='COMMAND', type=str,
1008 choices=valid_commands.keys(),
1009 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001010
Brad Bishop34a7acd2017-04-27 23:47:23 -04001011 args = parser.parse_args()
1012
1013 if sys.version_info < (3, 0):
1014 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001015 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -04001016 disable_unicode=True)
1017 else:
1018 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001019 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001020 try:
1021 function = getattr(
1022 Everything.load(args),
1023 valid_commands[args.command])
1024 function(lookup)
1025 except InvalidConfigError as e:
1026 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1027 raise