blob: ac2b8e0cd97ee81afade456c8df323c76cddc493 [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
Brad Bishop0df00be2017-05-25 23:38:37 -0400663class Method(ConfigEntry, Renderer):
664 '''Handle the method callback config file directive.'''
665
666 def __init__(self, *a, **kw):
667 self.service = kw.pop('service')
668 self.path = kw.pop('path')
669 self.interface = kw.pop('interface')
670 self.method = kw.pop('method')
671 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
672 super(Method, self).__init__(**kw)
673
674 def factory(self, objs):
675 args = {
676 'class': 'interface',
677 'interface': 'element',
678 'name': self.service
679 }
680 add_unique(ConfigEntry(
681 configfile=self.configfile, **args), objs)
682
683 args = {
684 'class': 'pathname',
685 'pathname': 'element',
686 'name': self.path
687 }
688 add_unique(ConfigEntry(
689 configfile=self.configfile, **args), objs)
690
691 args = {
692 'class': 'interface',
693 'interface': 'element',
694 'name': self.interface
695 }
696 add_unique(ConfigEntry(
697 configfile=self.configfile, **args), objs)
698
699 args = {
700 'class': 'propertyname',
701 'propertyname': 'element',
702 'name': self.method
703 }
704 add_unique(ConfigEntry(
705 configfile=self.configfile, **args), objs)
706
707 super(Method, self).factory(objs)
708
709 def setup(self, objs):
710 '''Resolve elements.'''
711
712 self.service = get_index(
713 objs,
714 'interface',
715 self.service)
716
717 self.path = get_index(
718 objs,
719 'pathname',
720 self.path)
721
722 self.interface = get_index(
723 objs,
724 'interface',
725 self.interface)
726
727 self.method = get_index(
728 objs,
729 'propertyname',
730 self.method)
731
732 super(Method, self).setup(objs)
733
734 def construct(self, loader, indent):
735 return self.render(
736 loader,
737 'method.mako.cpp',
738 c=self,
739 indent=indent)
740
741
Brad Bishop49e66172017-05-23 19:16:21 -0400742class CallbackGraphEntry(Group):
743 '''An entry in a traversal list for groups of callbacks.'''
744
745 def __init__(self, *a, **kw):
746 super(CallbackGraphEntry, self).__init__(**kw)
747
748 def setup(self, objs):
749 '''Resolve group members.'''
750
751 def map_member(x):
752 return get_index(
753 objs, 'callback', x, config=self.configfile)
754
755 self.members = map(
756 map_member,
757 self.members)
758
759 super(CallbackGraphEntry, self).setup(objs)
760
761
762class GroupOfCallbacks(ConfigEntry, Renderer):
763 '''Handle the callback group config file directive.'''
764
765 def __init__(self, *a, **kw):
766 self.members = kw.pop('members')
767 super(GroupOfCallbacks, self).__init__(**kw)
768
769 def factory(self, objs):
770 '''Create a graph instance for this group of callbacks.'''
771
772 args = {
773 'configfile': self.configfile,
774 'members': self.members,
775 'class': 'callbackgroup',
776 'callbackgroup': 'callback',
777 'name': self.members
778 }
779
780 entry = CallbackGraphEntry(**args)
781 add_unique(entry, objs, config=self.configfile)
782
783 super(GroupOfCallbacks, self).factory(objs)
784
785 def setup(self, objs):
786 '''Resolve graph entry.'''
787
788 self.graph = get_index(
789 objs, 'callbackgroup', self.members, config=self.configfile)
790
791 super(GroupOfCallbacks, self).setup(objs)
792
793 def construct(self, loader, indent):
794 return self.render(
795 loader,
796 'callbackgroup.mako.cpp',
797 c=self,
798 indent=indent)
799
800
Brad Bishop34a7acd2017-04-27 23:47:23 -0400801class Everything(Renderer):
802 '''Parse/render entry point.'''
803
804 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400805 def classmap(cls, sub=None):
806 '''Map render item class and subclass entries to the appropriate
807 handler methods.'''
808
809 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400810 'path': {
811 'element': Path,
812 },
813 'pathgroup': {
814 'path': GroupOfPaths,
815 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400816 'propertygroup': {
817 'property': GroupOfProperties,
818 },
819 'property': {
820 'element': Property,
821 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400822 'watch': {
823 'property': PropertyWatch,
824 },
825 'instance': {
826 'element': Instance,
827 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400828 'callback': {
829 'journal': Journal,
Brad Bishop49e66172017-05-23 19:16:21 -0400830 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400831 'method': Method,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400832 },
Brad Bishop4041d722017-05-21 10:06:07 -0400833 'condition': {
834 'count': CountCondition,
835 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400836 }
837
838 if cls not in class_map:
839 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
840 if sub not in class_map[cls]:
841 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
842 cls, sub))
843
844 return class_map[cls][sub]
845
846 @staticmethod
847 def load_one_yaml(path, fd, objs):
848 '''Parse a single YAML file. Parsing occurs in three phases.
849 In the first phase a factory method associated with each
850 configuration file directive is invoked. These factory
851 methods generate more factory methods. In the second
852 phase the factory methods created in the first phase
853 are invoked. In the last phase a callback is invoked on
854 each object created in phase two. Typically the callback
855 resolves references to other configuration file directives.'''
856
857 factory_objs = {}
858 for x in yaml.safe_load(fd.read()) or {}:
859
860 # Create factory object for this config file directive.
861 cls = x['class']
862 sub = x.get(cls)
863 if cls == 'group':
864 cls = '{0}group'.format(sub)
865
866 factory = Everything.classmap(cls, sub)
867 obj = factory(configfile=path, **x)
868
869 # For a given class of directive, validate the file
870 # doesn't have any duplicate names (duplicates are
871 # ok across config files).
872 if exists(factory_objs, obj.cls, obj.name, config=path):
873 raise NotUniqueError(path, cls, obj.name)
874
875 factory_objs.setdefault(cls, []).append(obj)
876 objs.setdefault(cls, []).append(obj)
877
878 for cls, items in factory_objs.items():
879 for obj in items:
880 # Add objects for template consumption.
881 obj.factory(objs)
882
883 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400884 def load(args):
885 '''Aggregate all the YAML in the input directory
886 into a single aggregate.'''
887
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400888 objs = {}
889 yaml_files = filter(
890 lambda x: x.endswith('.yaml'),
891 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400892
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400893 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400894
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400895 for x in yaml_files:
896 path = os.path.join(args.inputdir, x)
897 with open(path, 'r') as fd:
898 Everything.load_one_yaml(path, fd, objs)
899
900 # Configuration file directives reference each other via
901 # the name attribute; however, when rendered the reference
902 # is just an array index.
903 #
904 # At this point all objects have been created but references
905 # have not been resolved to array indicies. Instruct objects
906 # to do that now.
907 for cls, items in objs.items():
908 for obj in items:
909 obj.setup(objs)
910
911 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400912
913 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400914 self.pathmeta = kw.pop('path', [])
915 self.paths = kw.pop('pathname', [])
916 self.meta = kw.pop('meta', [])
917 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400918 self.interfaces = kw.pop('interface', [])
919 self.properties = kw.pop('property', [])
920 self.propertynames = kw.pop('propertyname', [])
921 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400922 self.instances = kw.pop('instance', [])
923 self.instancegroups = kw.pop('instancegroup', [])
924 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400925 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400926 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400927 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400928
Brad Bishop34a7acd2017-04-27 23:47:23 -0400929 super(Everything, self).__init__(**kw)
930
931 def generate_cpp(self, loader):
932 '''Render the template with the provided data.'''
Brad Bishope3a01af2017-05-15 17:09:04 -0400933 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400934 fd.write(
935 self.render(
936 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400937 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400938 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400939 properties=self.properties,
940 propertynames=self.propertynames,
941 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -0400942 paths=self.paths,
943 pathmeta=self.pathmeta,
944 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -0400945 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -0400946 instances=self.instances,
947 watches=self.watches,
948 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400949 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -0400950 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -0400951 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -0400952 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500953
954if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -0400955 script_dir = os.path.dirname(os.path.realpath(__file__))
956 valid_commands = {
957 'generate-cpp': 'generate_cpp',
958 }
959
960 parser = ArgumentParser(
961 description='Phosphor DBus Monitor (PDM) YAML '
962 'scanner and code generator.')
963
Matthew Barthdb440d42017-04-17 15:49:37 -0500964 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -0400965 "-o", "--out", dest="output",
966 default='generated.cpp',
967 help="Generated output file name and path.")
968 parser.add_argument(
969 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -0400970 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -0400971 help='The top level template to render.')
972 parser.add_argument(
973 '-p', '--template-path', dest='template_search',
974 default=script_dir,
975 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -0400976 parser.add_argument(
977 '-d', '--dir', dest='inputdir',
978 default=os.path.join(script_dir, 'example'),
979 help='Location of files to process.')
980 parser.add_argument(
981 'command', metavar='COMMAND', type=str,
982 choices=valid_commands.keys(),
983 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500984
Brad Bishop34a7acd2017-04-27 23:47:23 -0400985 args = parser.parse_args()
986
987 if sys.version_info < (3, 0):
988 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400989 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -0400990 disable_unicode=True)
991 else:
992 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400993 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400994 try:
995 function = getattr(
996 Everything.load(args),
997 valid_commands[args.command])
998 function(lookup)
999 except InvalidConfigError as e:
1000 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
1001 raise