blob: 9ed27cd444593ff8f3301e99905c126275d0f4bc [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):
Gunnar Mills78199b42017-10-25 16:30:18 -0500269 '''Resolve path and metadata names to indices.'''
Brad Bishop0e7df132017-05-23 17:58:12 -0400270
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):
Gunnar Mills78199b42017-10-25 16:30:18 -0500321 '''Resolve interface, property and metadata to indices.'''
Brad Bishope73b2c32017-05-23 18:01:54 -0400322
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):
Gunnar Mills78199b42017-10-25 16:30:18 -0500340 '''Resolve elements to indices.'''
Brad Bishop4b916f12017-05-23 18:06:38 -0400341
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
Matt Spinler1d6ca482017-11-01 10:48:02 -0500702class ResolveCallout(Callback, Renderer):
703 '''Handle the 'resolve callout' callback config file directive.'''
704
705 def __init__(self, *a, **kw):
706 self.callout = kw.pop('callout')
707 super(ResolveCallout, self).__init__(**kw)
708
709 def construct(self, loader, indent):
710 return self.render(
711 loader,
712 'resolve_errors.mako.cpp',
713 c=self,
714 indent=indent)
715
716
Brad Bishop0df00be2017-05-25 23:38:37 -0400717class Method(ConfigEntry, Renderer):
718 '''Handle the method callback config file directive.'''
719
720 def __init__(self, *a, **kw):
721 self.service = kw.pop('service')
722 self.path = kw.pop('path')
723 self.interface = kw.pop('interface')
724 self.method = kw.pop('method')
725 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
726 super(Method, self).__init__(**kw)
727
728 def factory(self, objs):
729 args = {
730 'class': 'interface',
731 'interface': 'element',
732 'name': self.service
733 }
734 add_unique(ConfigEntry(
735 configfile=self.configfile, **args), objs)
736
737 args = {
738 'class': 'pathname',
739 'pathname': 'element',
740 'name': self.path
741 }
742 add_unique(ConfigEntry(
743 configfile=self.configfile, **args), objs)
744
745 args = {
746 'class': 'interface',
747 'interface': 'element',
748 'name': self.interface
749 }
750 add_unique(ConfigEntry(
751 configfile=self.configfile, **args), objs)
752
753 args = {
754 'class': 'propertyname',
755 'propertyname': 'element',
756 'name': self.method
757 }
758 add_unique(ConfigEntry(
759 configfile=self.configfile, **args), objs)
760
761 super(Method, self).factory(objs)
762
763 def setup(self, objs):
764 '''Resolve elements.'''
765
766 self.service = get_index(
767 objs,
768 'interface',
769 self.service)
770
771 self.path = get_index(
772 objs,
773 'pathname',
774 self.path)
775
776 self.interface = get_index(
777 objs,
778 'interface',
779 self.interface)
780
781 self.method = get_index(
782 objs,
783 'propertyname',
784 self.method)
785
786 super(Method, self).setup(objs)
787
788 def construct(self, loader, indent):
789 return self.render(
790 loader,
791 'method.mako.cpp',
792 c=self,
793 indent=indent)
794
795
Brad Bishop49e66172017-05-23 19:16:21 -0400796class CallbackGraphEntry(Group):
797 '''An entry in a traversal list for groups of callbacks.'''
798
799 def __init__(self, *a, **kw):
800 super(CallbackGraphEntry, self).__init__(**kw)
801
802 def setup(self, objs):
803 '''Resolve group members.'''
804
805 def map_member(x):
806 return get_index(
807 objs, 'callback', x, config=self.configfile)
808
809 self.members = map(
810 map_member,
811 self.members)
812
813 super(CallbackGraphEntry, self).setup(objs)
814
815
816class GroupOfCallbacks(ConfigEntry, Renderer):
817 '''Handle the callback group config file directive.'''
818
819 def __init__(self, *a, **kw):
820 self.members = kw.pop('members')
821 super(GroupOfCallbacks, self).__init__(**kw)
822
823 def factory(self, objs):
824 '''Create a graph instance for this group of callbacks.'''
825
826 args = {
827 'configfile': self.configfile,
828 'members': self.members,
829 'class': 'callbackgroup',
830 'callbackgroup': 'callback',
831 'name': self.members
832 }
833
834 entry = CallbackGraphEntry(**args)
835 add_unique(entry, objs, config=self.configfile)
836
837 super(GroupOfCallbacks, self).factory(objs)
838
839 def setup(self, objs):
840 '''Resolve graph entry.'''
841
842 self.graph = get_index(
843 objs, 'callbackgroup', self.members, config=self.configfile)
844
845 super(GroupOfCallbacks, self).setup(objs)
846
847 def construct(self, loader, indent):
848 return self.render(
849 loader,
850 'callbackgroup.mako.cpp',
851 c=self,
852 indent=indent)
853
854
Brad Bishop34a7acd2017-04-27 23:47:23 -0400855class Everything(Renderer):
856 '''Parse/render entry point.'''
857
858 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400859 def classmap(cls, sub=None):
860 '''Map render item class and subclass entries to the appropriate
861 handler methods.'''
862
863 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400864 'path': {
865 'element': Path,
866 },
867 'pathgroup': {
868 'path': GroupOfPaths,
869 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400870 'propertygroup': {
871 'property': GroupOfProperties,
872 },
873 'property': {
874 'element': Property,
875 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400876 'watch': {
877 'property': PropertyWatch,
878 },
879 'instance': {
880 'element': Instance,
881 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400882 'callback': {
883 'journal': Journal,
Gunnar Millsd5faea32017-08-08 14:19:36 -0500884 'elog': Elog,
Brad Bishop49e66172017-05-23 19:16:21 -0400885 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400886 'method': Method,
Matt Spinler1d6ca482017-11-01 10:48:02 -0500887 'resolve callout': ResolveCallout,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400888 },
Brad Bishop4041d722017-05-21 10:06:07 -0400889 'condition': {
890 'count': CountCondition,
891 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400892 }
893
894 if cls not in class_map:
895 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
896 if sub not in class_map[cls]:
897 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
898 cls, sub))
899
900 return class_map[cls][sub]
901
902 @staticmethod
903 def load_one_yaml(path, fd, objs):
904 '''Parse a single YAML file. Parsing occurs in three phases.
905 In the first phase a factory method associated with each
906 configuration file directive is invoked. These factory
907 methods generate more factory methods. In the second
908 phase the factory methods created in the first phase
909 are invoked. In the last phase a callback is invoked on
910 each object created in phase two. Typically the callback
911 resolves references to other configuration file directives.'''
912
913 factory_objs = {}
914 for x in yaml.safe_load(fd.read()) or {}:
915
916 # Create factory object for this config file directive.
917 cls = x['class']
918 sub = x.get(cls)
919 if cls == 'group':
920 cls = '{0}group'.format(sub)
921
922 factory = Everything.classmap(cls, sub)
923 obj = factory(configfile=path, **x)
924
925 # For a given class of directive, validate the file
926 # doesn't have any duplicate names (duplicates are
927 # ok across config files).
928 if exists(factory_objs, obj.cls, obj.name, config=path):
929 raise NotUniqueError(path, cls, obj.name)
930
931 factory_objs.setdefault(cls, []).append(obj)
932 objs.setdefault(cls, []).append(obj)
933
934 for cls, items in factory_objs.items():
935 for obj in items:
936 # Add objects for template consumption.
937 obj.factory(objs)
938
939 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400940 def load(args):
941 '''Aggregate all the YAML in the input directory
942 into a single aggregate.'''
943
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400944 objs = {}
945 yaml_files = filter(
946 lambda x: x.endswith('.yaml'),
947 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400948
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400949 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400950
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400951 for x in yaml_files:
952 path = os.path.join(args.inputdir, x)
953 with open(path, 'r') as fd:
954 Everything.load_one_yaml(path, fd, objs)
955
956 # Configuration file directives reference each other via
957 # the name attribute; however, when rendered the reference
958 # is just an array index.
959 #
960 # At this point all objects have been created but references
Gunnar Mills78199b42017-10-25 16:30:18 -0500961 # have not been resolved to array indices. Instruct objects
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400962 # to do that now.
963 for cls, items in objs.items():
964 for obj in items:
965 obj.setup(objs)
966
967 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400968
969 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400970 self.pathmeta = kw.pop('path', [])
971 self.paths = kw.pop('pathname', [])
972 self.meta = kw.pop('meta', [])
973 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400974 self.interfaces = kw.pop('interface', [])
975 self.properties = kw.pop('property', [])
976 self.propertynames = kw.pop('propertyname', [])
977 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400978 self.instances = kw.pop('instance', [])
979 self.instancegroups = kw.pop('instancegroup', [])
980 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400981 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400982 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400983 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400984
Brad Bishop34a7acd2017-04-27 23:47:23 -0400985 super(Everything, self).__init__(**kw)
986
987 def generate_cpp(self, loader):
988 '''Render the template with the provided data.'''
Gunnar Millsd5faea32017-08-08 14:19:36 -0500989 # errors.hpp is used by generated.hpp to included any error.hpp files
990 open('errors.hpp', 'w+')
991
Brad Bishope3a01af2017-05-15 17:09:04 -0400992 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400993 fd.write(
994 self.render(
995 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400996 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400997 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400998 properties=self.properties,
999 propertynames=self.propertynames,
1000 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -04001001 paths=self.paths,
1002 pathmeta=self.pathmeta,
1003 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -04001004 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -04001005 instances=self.instances,
1006 watches=self.watches,
1007 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -04001008 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -04001009 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -04001010 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -04001011 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001012
1013if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -04001014 script_dir = os.path.dirname(os.path.realpath(__file__))
1015 valid_commands = {
1016 'generate-cpp': 'generate_cpp',
1017 }
1018
1019 parser = ArgumentParser(
1020 description='Phosphor DBus Monitor (PDM) YAML '
1021 'scanner and code generator.')
1022
Matthew Barthdb440d42017-04-17 15:49:37 -05001023 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -04001024 "-o", "--out", dest="output",
1025 default='generated.cpp',
1026 help="Generated output file name and path.")
1027 parser.add_argument(
1028 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -04001029 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -04001030 help='The top level template to render.')
1031 parser.add_argument(
1032 '-p', '--template-path', dest='template_search',
1033 default=script_dir,
1034 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -04001035 parser.add_argument(
1036 '-d', '--dir', dest='inputdir',
1037 default=os.path.join(script_dir, 'example'),
1038 help='Location of files to process.')
1039 parser.add_argument(
1040 'command', metavar='COMMAND', type=str,
1041 choices=valid_commands.keys(),
1042 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -05001043
Brad Bishop34a7acd2017-04-27 23:47:23 -04001044 args = parser.parse_args()
1045
1046 if sys.version_info < (3, 0):
1047 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001048 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -04001049 disable_unicode=True)
1050 else:
1051 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -04001052 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -04001053 try:
1054 function = getattr(
1055 Everything.load(args),
1056 valid_commands[args.command])
1057 function(lookup)
1058 except InvalidConfigError as e:
1059 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1060 raise