blob: 5e88e2c257ad30dc666b25c9af3a9c2b038483f9 [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 = [
104 'int8',
105 'int16',
106 'int32',
107 'int64',
108 'uint8',
109 'uint16',
110 'uint32',
111 'uint64'
112 ]
113
114 def __init__(self, type):
115 self.type = type
116
117 def __call__(self, arg):
118 if 'uint' in self.type:
119 arg = '{0}ull'.format(arg)
120 elif 'int' in self.type:
121 arg = '{0}ll'.format(arg)
122
123 if self.type in self.integer_types:
124 return Cast('static', '{0}_t'.format(self.type))(arg)
125
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
224 def factory(self, objs):
225 '''Create path and metadata elements.'''
226
227 args = {
228 'class': 'pathname',
229 'pathname': 'element',
230 'name': self.name['path']
231 }
232 add_unique(ConfigEntry(
233 configfile=self.configfile, **args), objs)
234
235 args = {
236 'class': 'meta',
237 'meta': 'element',
238 'name': self.name['meta']
239 }
240 add_unique(ConfigEntry(
241 configfile=self.configfile, **args), objs)
242
243 super(Path, self).factory(objs)
244
245 def setup(self, objs):
246 '''Resolve path and metadata names to indicies.'''
247
248 self.path = get_index(
249 objs, 'pathname', self.name['path'])
250 self.meta = get_index(
251 objs, 'meta', self.name['meta'])
252
253 super(Path, self).setup(objs)
254
255
Brad Bishope73b2c32017-05-23 18:01:54 -0400256class Property(ConfigEntry):
257 '''Property/interface/metadata association.'''
258
259 def __init__(self, *a, **kw):
260 super(Property, self).__init__(**kw)
261
262 def factory(self, objs):
263 '''Create interface, property name and metadata elements.'''
264
265 args = {
266 'class': 'interface',
267 'interface': 'element',
268 'name': self.name['interface']
269 }
270 add_unique(ConfigEntry(
271 configfile=self.configfile, **args), objs)
272
273 args = {
274 'class': 'propertyname',
275 'propertyname': 'element',
276 'name': self.name['property']
277 }
278 add_unique(ConfigEntry(
279 configfile=self.configfile, **args), objs)
280
281 args = {
282 'class': 'meta',
283 'meta': 'element',
284 'name': self.name['meta']
285 }
286 add_unique(ConfigEntry(
287 configfile=self.configfile, **args), objs)
288
289 super(Property, self).factory(objs)
290
291 def setup(self, objs):
292 '''Resolve interface, property and metadata to indicies.'''
293
294 self.interface = get_index(
295 objs, 'interface', self.name['interface'])
296 self.prop = get_index(
297 objs, 'propertyname', self.name['property'])
298 self.meta = get_index(
299 objs, 'meta', self.name['meta'])
300
301 super(Property, self).setup(objs)
302
303
Brad Bishop4b916f12017-05-23 18:06:38 -0400304class Instance(ConfigEntry):
305 '''Property/Path association.'''
306
307 def __init__(self, *a, **kw):
308 super(Instance, self).__init__(**kw)
309
310 def setup(self, objs):
311 '''Resolve elements to indicies.'''
312
313 self.interface = get_index(
314 objs, 'interface', self.name['property']['interface'])
315 self.prop = get_index(
316 objs, 'propertyname', self.name['property']['property'])
317 self.propmeta = get_index(
318 objs, 'meta', self.name['property']['meta'])
319 self.path = get_index(
320 objs, 'pathname', self.name['path']['path'])
321 self.pathmeta = get_index(
322 objs, 'meta', self.name['path']['meta'])
323
324 super(Instance, self).setup(objs)
325
326
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400327class Group(ConfigEntry):
328 '''Pop the members keyword for groups.'''
329
330 def __init__(self, *a, **kw):
331 self.members = kw.pop('members')
332 super(Group, self).__init__(**kw)
333
334
335class ImplicitGroup(Group):
336 '''Provide a factory method for groups whose members are
337 not explicitly declared in the config files.'''
338
339 def __init__(self, *a, **kw):
340 super(ImplicitGroup, self).__init__(**kw)
341
342 def factory(self, objs):
343 '''Create group members.'''
344
345 factory = Everything.classmap(self.subclass, 'element')
346 for m in self.members:
347 args = {
348 'class': self.subclass,
349 self.subclass: 'element',
350 'name': m
351 }
352
353 obj = factory(configfile=self.configfile, **args)
354 add_unique(obj, objs)
355 obj.factory(objs)
356
357 super(ImplicitGroup, self).factory(objs)
358
359
Brad Bishop0e7df132017-05-23 17:58:12 -0400360class GroupOfPaths(ImplicitGroup):
361 '''Path group config file directive.'''
362
363 def __init__(self, *a, **kw):
364 super(GroupOfPaths, self).__init__(**kw)
365
366 def setup(self, objs):
367 '''Resolve group members.'''
368
369 def map_member(x):
370 path = get_index(
371 objs, 'pathname', x['path'])
372 meta = get_index(
373 objs, 'meta', x['meta'])
374 return (path, meta)
375
376 self.members = map(
377 map_member,
378 self.members)
379
380 super(GroupOfPaths, self).setup(objs)
381
382
Brad Bishope73b2c32017-05-23 18:01:54 -0400383class GroupOfProperties(ImplicitGroup):
384 '''Property group config file directive.'''
385
386 def __init__(self, *a, **kw):
387 self.datatype = sdbusplus.property.Property(
388 name=kw.get('name'),
389 type=kw.pop('type')).cppTypeName
390
391 super(GroupOfProperties, self).__init__(**kw)
392
393 def setup(self, objs):
394 '''Resolve group members.'''
395
396 def map_member(x):
397 iface = get_index(
398 objs, 'interface', x['interface'])
399 prop = get_index(
400 objs, 'propertyname', x['property'])
401 meta = get_index(
402 objs, 'meta', x['meta'])
403
404 return (iface, prop, meta)
405
406 self.members = map(
407 map_member,
408 self.members)
409
410 super(GroupOfProperties, self).setup(objs)
411
412
Brad Bishop4b916f12017-05-23 18:06:38 -0400413class GroupOfInstances(ImplicitGroup):
414 '''A group of property instances.'''
415
416 def __init__(self, *a, **kw):
417 super(GroupOfInstances, self).__init__(**kw)
418
419 def setup(self, objs):
420 '''Resolve group members.'''
421
422 def map_member(x):
423 path = get_index(objs, 'pathname', x['path']['path'])
424 pathmeta = get_index(objs, 'meta', x['path']['meta'])
425 interface = get_index(
426 objs, 'interface', x['property']['interface'])
427 prop = get_index(objs, 'propertyname', x['property']['property'])
428 propmeta = get_index(objs, 'meta', x['property']['meta'])
429 instance = get_index(objs, 'instance', x)
430
431 return (path, pathmeta, interface, prop, propmeta, instance)
432
433 self.members = map(
434 map_member,
435 self.members)
436
437 super(GroupOfInstances, self).setup(objs)
438
439
440class HasPropertyIndex(ConfigEntry):
441 '''Handle config file directives that require an index to be
442 constructed.'''
443
444 def __init__(self, *a, **kw):
445 self.paths = kw.pop('paths')
446 self.properties = kw.pop('properties')
447 super(HasPropertyIndex, self).__init__(**kw)
448
449 def factory(self, objs):
450 '''Create a group of instances for this index.'''
451
452 members = []
453 path_group = get_index(
454 objs, 'pathgroup', self.paths, config=self.configfile)
455 property_group = get_index(
456 objs, 'propertygroup', self.properties, config=self.configfile)
457
458 for path in objs['pathgroup'][path_group].members:
459 for prop in objs['propertygroup'][property_group].members:
460 member = {
461 'path': path,
462 'property': prop,
463 }
464 members.append(member)
465
466 args = {
467 'members': members,
468 'class': 'instancegroup',
469 'instancegroup': 'instance',
470 'name': '{0} {1}'.format(self.paths, self.properties)
471 }
472
473 group = GroupOfInstances(configfile=self.configfile, **args)
474 add_unique(group, objs, config=self.configfile)
475 group.factory(objs)
476
477 super(HasPropertyIndex, self).factory(objs)
478
479 def setup(self, objs):
480 '''Resolve path, property, and instance groups.'''
481
482 self.instances = get_index(
483 objs,
484 'instancegroup',
485 '{0} {1}'.format(self.paths, self.properties),
486 config=self.configfile)
487 self.paths = get_index(
488 objs,
489 'pathgroup',
490 self.paths,
491 config=self.configfile)
492 self.properties = get_index(
493 objs,
494 'propertygroup',
495 self.properties,
496 config=self.configfile)
497 self.datatype = objs['propertygroup'][self.properties].datatype
498
499 super(HasPropertyIndex, self).setup(objs)
500
501
502class PropertyWatch(HasPropertyIndex):
503 '''Handle the property watch config file directive.'''
504
505 def __init__(self, *a, **kw):
Brad Bishopfccdc392017-05-22 21:11:09 -0400506 self.callback = kw.pop('callback', None)
Brad Bishop4b916f12017-05-23 18:06:38 -0400507 super(PropertyWatch, self).__init__(**kw)
508
Brad Bishopfccdc392017-05-22 21:11:09 -0400509 def setup(self, objs):
510 '''Resolve optional callback.'''
511
512 if self.callback:
513 self.callback = get_index(
514 objs,
515 'callback',
516 self.callback,
517 config=self.configfile)
518
519 super(PropertyWatch, self).setup(objs)
520
Brad Bishop4b916f12017-05-23 18:06:38 -0400521
Brad Bishopc1283ae2017-05-20 21:42:38 -0400522class Callback(HasPropertyIndex):
523 '''Interface and common logic for callbacks.'''
524
525 def __init__(self, *a, **kw):
526 super(Callback, self).__init__(**kw)
527
528
Brad Bishop4041d722017-05-21 10:06:07 -0400529class ConditionCallback(ConfigEntry, Renderer):
530 '''Handle the journal callback config file directive.'''
531
532 def __init__(self, *a, **kw):
533 self.condition = kw.pop('condition')
534 self.instance = kw.pop('instance')
Brad Bishop3539db62017-05-30 14:21:12 -0400535 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400536 super(ConditionCallback, self).__init__(**kw)
537
538 def factory(self, objs):
539 '''Create a graph instance for this callback.'''
540
541 args = {
542 'configfile': self.configfile,
543 'members': [self.instance],
544 'class': 'callbackgroup',
545 'callbackgroup': 'callback',
546 'name': [self.instance]
547 }
548
549 entry = CallbackGraphEntry(**args)
550 add_unique(entry, objs, config=self.configfile)
551
552 super(ConditionCallback, self).factory(objs)
553
554 def setup(self, objs):
555 '''Resolve condition and graph entry.'''
556
557 self.graph = get_index(
558 objs,
559 'callbackgroup',
560 [self.instance],
561 config=self.configfile)
562
563 self.condition = get_index(
564 objs,
565 'condition',
566 self.name,
567 config=self.configfile)
568
569 super(ConditionCallback, self).setup(objs)
570
571 def construct(self, loader, indent):
572 return self.render(
573 loader,
574 'conditional.mako.cpp',
575 c=self,
576 indent=indent)
577
578
579class Condition(HasPropertyIndex):
580 '''Interface and common logic for conditions.'''
581
582 def __init__(self, *a, **kw):
583 self.callback = kw.pop('callback')
Brad Bishop3539db62017-05-30 14:21:12 -0400584 self.defer = kw.pop('defer', None)
Brad Bishop4041d722017-05-21 10:06:07 -0400585 super(Condition, self).__init__(**kw)
586
587 def factory(self, objs):
588 '''Create a callback instance for this conditional.'''
589
590 args = {
591 'configfile': self.configfile,
592 'condition': self.name,
593 'class': 'callback',
594 'callback': 'conditional',
595 'instance': self.callback,
596 'name': self.name,
Brad Bishop3539db62017-05-30 14:21:12 -0400597 'defer': self.defer
Brad Bishop4041d722017-05-21 10:06:07 -0400598 }
599
600 callback = ConditionCallback(**args)
601 add_unique(callback, objs, config=self.configfile)
602 callback.factory(objs)
603
604 super(Condition, self).factory(objs)
605
606
607class CountCondition(Condition, Renderer):
608 '''Handle the count condition config file directive.'''
609
610 def __init__(self, *a, **kw):
611 self.countop = kw.pop('countop')
612 self.countbound = kw.pop('countbound')
613 self.op = kw.pop('op')
614 self.bound = kw.pop('bound')
615 super(CountCondition, self).__init__(**kw)
616
617 def construct(self, loader, indent):
618 return self.render(
619 loader,
620 'count.mako.cpp',
621 c=self,
622 indent=indent)
623
624
Brad Bishopc1283ae2017-05-20 21:42:38 -0400625class Journal(Callback, Renderer):
626 '''Handle the journal callback config file directive.'''
627
628 def __init__(self, *a, **kw):
629 self.severity = kw.pop('severity')
630 self.message = kw.pop('message')
631 super(Journal, self).__init__(**kw)
632
633 def construct(self, loader, indent):
634 return self.render(
635 loader,
636 'journal.mako.cpp',
637 c=self,
638 indent=indent)
639
640
Brad Bishop0df00be2017-05-25 23:38:37 -0400641class Method(ConfigEntry, Renderer):
642 '''Handle the method callback config file directive.'''
643
644 def __init__(self, *a, **kw):
645 self.service = kw.pop('service')
646 self.path = kw.pop('path')
647 self.interface = kw.pop('interface')
648 self.method = kw.pop('method')
649 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
650 super(Method, self).__init__(**kw)
651
652 def factory(self, objs):
653 args = {
654 'class': 'interface',
655 'interface': 'element',
656 'name': self.service
657 }
658 add_unique(ConfigEntry(
659 configfile=self.configfile, **args), objs)
660
661 args = {
662 'class': 'pathname',
663 'pathname': 'element',
664 'name': self.path
665 }
666 add_unique(ConfigEntry(
667 configfile=self.configfile, **args), objs)
668
669 args = {
670 'class': 'interface',
671 'interface': 'element',
672 'name': self.interface
673 }
674 add_unique(ConfigEntry(
675 configfile=self.configfile, **args), objs)
676
677 args = {
678 'class': 'propertyname',
679 'propertyname': 'element',
680 'name': self.method
681 }
682 add_unique(ConfigEntry(
683 configfile=self.configfile, **args), objs)
684
685 super(Method, self).factory(objs)
686
687 def setup(self, objs):
688 '''Resolve elements.'''
689
690 self.service = get_index(
691 objs,
692 'interface',
693 self.service)
694
695 self.path = get_index(
696 objs,
697 'pathname',
698 self.path)
699
700 self.interface = get_index(
701 objs,
702 'interface',
703 self.interface)
704
705 self.method = get_index(
706 objs,
707 'propertyname',
708 self.method)
709
710 super(Method, self).setup(objs)
711
712 def construct(self, loader, indent):
713 return self.render(
714 loader,
715 'method.mako.cpp',
716 c=self,
717 indent=indent)
718
719
Brad Bishop49e66172017-05-23 19:16:21 -0400720class CallbackGraphEntry(Group):
721 '''An entry in a traversal list for groups of callbacks.'''
722
723 def __init__(self, *a, **kw):
724 super(CallbackGraphEntry, self).__init__(**kw)
725
726 def setup(self, objs):
727 '''Resolve group members.'''
728
729 def map_member(x):
730 return get_index(
731 objs, 'callback', x, config=self.configfile)
732
733 self.members = map(
734 map_member,
735 self.members)
736
737 super(CallbackGraphEntry, self).setup(objs)
738
739
740class GroupOfCallbacks(ConfigEntry, Renderer):
741 '''Handle the callback group config file directive.'''
742
743 def __init__(self, *a, **kw):
744 self.members = kw.pop('members')
745 super(GroupOfCallbacks, self).__init__(**kw)
746
747 def factory(self, objs):
748 '''Create a graph instance for this group of callbacks.'''
749
750 args = {
751 'configfile': self.configfile,
752 'members': self.members,
753 'class': 'callbackgroup',
754 'callbackgroup': 'callback',
755 'name': self.members
756 }
757
758 entry = CallbackGraphEntry(**args)
759 add_unique(entry, objs, config=self.configfile)
760
761 super(GroupOfCallbacks, self).factory(objs)
762
763 def setup(self, objs):
764 '''Resolve graph entry.'''
765
766 self.graph = get_index(
767 objs, 'callbackgroup', self.members, config=self.configfile)
768
769 super(GroupOfCallbacks, self).setup(objs)
770
771 def construct(self, loader, indent):
772 return self.render(
773 loader,
774 'callbackgroup.mako.cpp',
775 c=self,
776 indent=indent)
777
778
Brad Bishop34a7acd2017-04-27 23:47:23 -0400779class Everything(Renderer):
780 '''Parse/render entry point.'''
781
782 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400783 def classmap(cls, sub=None):
784 '''Map render item class and subclass entries to the appropriate
785 handler methods.'''
786
787 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400788 'path': {
789 'element': Path,
790 },
791 'pathgroup': {
792 'path': GroupOfPaths,
793 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400794 'propertygroup': {
795 'property': GroupOfProperties,
796 },
797 'property': {
798 'element': Property,
799 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400800 'watch': {
801 'property': PropertyWatch,
802 },
803 'instance': {
804 'element': Instance,
805 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400806 'callback': {
807 'journal': Journal,
Brad Bishop49e66172017-05-23 19:16:21 -0400808 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400809 'method': Method,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400810 },
Brad Bishop4041d722017-05-21 10:06:07 -0400811 'condition': {
812 'count': CountCondition,
813 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400814 }
815
816 if cls not in class_map:
817 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
818 if sub not in class_map[cls]:
819 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
820 cls, sub))
821
822 return class_map[cls][sub]
823
824 @staticmethod
825 def load_one_yaml(path, fd, objs):
826 '''Parse a single YAML file. Parsing occurs in three phases.
827 In the first phase a factory method associated with each
828 configuration file directive is invoked. These factory
829 methods generate more factory methods. In the second
830 phase the factory methods created in the first phase
831 are invoked. In the last phase a callback is invoked on
832 each object created in phase two. Typically the callback
833 resolves references to other configuration file directives.'''
834
835 factory_objs = {}
836 for x in yaml.safe_load(fd.read()) or {}:
837
838 # Create factory object for this config file directive.
839 cls = x['class']
840 sub = x.get(cls)
841 if cls == 'group':
842 cls = '{0}group'.format(sub)
843
844 factory = Everything.classmap(cls, sub)
845 obj = factory(configfile=path, **x)
846
847 # For a given class of directive, validate the file
848 # doesn't have any duplicate names (duplicates are
849 # ok across config files).
850 if exists(factory_objs, obj.cls, obj.name, config=path):
851 raise NotUniqueError(path, cls, obj.name)
852
853 factory_objs.setdefault(cls, []).append(obj)
854 objs.setdefault(cls, []).append(obj)
855
856 for cls, items in factory_objs.items():
857 for obj in items:
858 # Add objects for template consumption.
859 obj.factory(objs)
860
861 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400862 def load(args):
863 '''Aggregate all the YAML in the input directory
864 into a single aggregate.'''
865
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400866 objs = {}
867 yaml_files = filter(
868 lambda x: x.endswith('.yaml'),
869 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400870
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400871 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400872
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400873 for x in yaml_files:
874 path = os.path.join(args.inputdir, x)
875 with open(path, 'r') as fd:
876 Everything.load_one_yaml(path, fd, objs)
877
878 # Configuration file directives reference each other via
879 # the name attribute; however, when rendered the reference
880 # is just an array index.
881 #
882 # At this point all objects have been created but references
883 # have not been resolved to array indicies. Instruct objects
884 # to do that now.
885 for cls, items in objs.items():
886 for obj in items:
887 obj.setup(objs)
888
889 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400890
891 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400892 self.pathmeta = kw.pop('path', [])
893 self.paths = kw.pop('pathname', [])
894 self.meta = kw.pop('meta', [])
895 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400896 self.interfaces = kw.pop('interface', [])
897 self.properties = kw.pop('property', [])
898 self.propertynames = kw.pop('propertyname', [])
899 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400900 self.instances = kw.pop('instance', [])
901 self.instancegroups = kw.pop('instancegroup', [])
902 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400903 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400904 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400905 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400906
Brad Bishop34a7acd2017-04-27 23:47:23 -0400907 super(Everything, self).__init__(**kw)
908
909 def generate_cpp(self, loader):
910 '''Render the template with the provided data.'''
Brad Bishope3a01af2017-05-15 17:09:04 -0400911 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400912 fd.write(
913 self.render(
914 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400915 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400916 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400917 properties=self.properties,
918 propertynames=self.propertynames,
919 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -0400920 paths=self.paths,
921 pathmeta=self.pathmeta,
922 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -0400923 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -0400924 instances=self.instances,
925 watches=self.watches,
926 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400927 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -0400928 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -0400929 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -0400930 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500931
932if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -0400933 script_dir = os.path.dirname(os.path.realpath(__file__))
934 valid_commands = {
935 'generate-cpp': 'generate_cpp',
936 }
937
938 parser = ArgumentParser(
939 description='Phosphor DBus Monitor (PDM) YAML '
940 'scanner and code generator.')
941
Matthew Barthdb440d42017-04-17 15:49:37 -0500942 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -0400943 "-o", "--out", dest="output",
944 default='generated.cpp',
945 help="Generated output file name and path.")
946 parser.add_argument(
947 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -0400948 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -0400949 help='The top level template to render.')
950 parser.add_argument(
951 '-p', '--template-path', dest='template_search',
952 default=script_dir,
953 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -0400954 parser.add_argument(
955 '-d', '--dir', dest='inputdir',
956 default=os.path.join(script_dir, 'example'),
957 help='Location of files to process.')
958 parser.add_argument(
959 'command', metavar='COMMAND', type=str,
960 choices=valid_commands.keys(),
961 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500962
Brad Bishop34a7acd2017-04-27 23:47:23 -0400963 args = parser.parse_args()
964
965 if sys.version_info < (3, 0):
966 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400967 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -0400968 disable_unicode=True)
969 else:
970 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400971 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400972 try:
973 function = getattr(
974 Everything.load(args),
975 valid_commands[args.command])
976 function(lookup)
977 except InvalidConfigError as e:
978 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
979 raise