blob: 5d4bf295d58e50af52dcfd6c72ef67053db4e547 [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')
535 super(ConditionCallback, self).__init__(**kw)
536
537 def factory(self, objs):
538 '''Create a graph instance for this callback.'''
539
540 args = {
541 'configfile': self.configfile,
542 'members': [self.instance],
543 'class': 'callbackgroup',
544 'callbackgroup': 'callback',
545 'name': [self.instance]
546 }
547
548 entry = CallbackGraphEntry(**args)
549 add_unique(entry, objs, config=self.configfile)
550
551 super(ConditionCallback, self).factory(objs)
552
553 def setup(self, objs):
554 '''Resolve condition and graph entry.'''
555
556 self.graph = get_index(
557 objs,
558 'callbackgroup',
559 [self.instance],
560 config=self.configfile)
561
562 self.condition = get_index(
563 objs,
564 'condition',
565 self.name,
566 config=self.configfile)
567
568 super(ConditionCallback, self).setup(objs)
569
570 def construct(self, loader, indent):
571 return self.render(
572 loader,
573 'conditional.mako.cpp',
574 c=self,
575 indent=indent)
576
577
578class Condition(HasPropertyIndex):
579 '''Interface and common logic for conditions.'''
580
581 def __init__(self, *a, **kw):
582 self.callback = kw.pop('callback')
583 super(Condition, self).__init__(**kw)
584
585 def factory(self, objs):
586 '''Create a callback instance for this conditional.'''
587
588 args = {
589 'configfile': self.configfile,
590 'condition': self.name,
591 'class': 'callback',
592 'callback': 'conditional',
593 'instance': self.callback,
594 'name': self.name,
595 }
596
597 callback = ConditionCallback(**args)
598 add_unique(callback, objs, config=self.configfile)
599 callback.factory(objs)
600
601 super(Condition, self).factory(objs)
602
603
604class CountCondition(Condition, Renderer):
605 '''Handle the count condition config file directive.'''
606
607 def __init__(self, *a, **kw):
608 self.countop = kw.pop('countop')
609 self.countbound = kw.pop('countbound')
610 self.op = kw.pop('op')
611 self.bound = kw.pop('bound')
612 super(CountCondition, self).__init__(**kw)
613
614 def construct(self, loader, indent):
615 return self.render(
616 loader,
617 'count.mako.cpp',
618 c=self,
619 indent=indent)
620
621
Brad Bishopc1283ae2017-05-20 21:42:38 -0400622class Journal(Callback, Renderer):
623 '''Handle the journal callback config file directive.'''
624
625 def __init__(self, *a, **kw):
626 self.severity = kw.pop('severity')
627 self.message = kw.pop('message')
628 super(Journal, self).__init__(**kw)
629
630 def construct(self, loader, indent):
631 return self.render(
632 loader,
633 'journal.mako.cpp',
634 c=self,
635 indent=indent)
636
637
Brad Bishop0df00be2017-05-25 23:38:37 -0400638class Method(ConfigEntry, Renderer):
639 '''Handle the method callback config file directive.'''
640
641 def __init__(self, *a, **kw):
642 self.service = kw.pop('service')
643 self.path = kw.pop('path')
644 self.interface = kw.pop('interface')
645 self.method = kw.pop('method')
646 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})]
647 super(Method, self).__init__(**kw)
648
649 def factory(self, objs):
650 args = {
651 'class': 'interface',
652 'interface': 'element',
653 'name': self.service
654 }
655 add_unique(ConfigEntry(
656 configfile=self.configfile, **args), objs)
657
658 args = {
659 'class': 'pathname',
660 'pathname': 'element',
661 'name': self.path
662 }
663 add_unique(ConfigEntry(
664 configfile=self.configfile, **args), objs)
665
666 args = {
667 'class': 'interface',
668 'interface': 'element',
669 'name': self.interface
670 }
671 add_unique(ConfigEntry(
672 configfile=self.configfile, **args), objs)
673
674 args = {
675 'class': 'propertyname',
676 'propertyname': 'element',
677 'name': self.method
678 }
679 add_unique(ConfigEntry(
680 configfile=self.configfile, **args), objs)
681
682 super(Method, self).factory(objs)
683
684 def setup(self, objs):
685 '''Resolve elements.'''
686
687 self.service = get_index(
688 objs,
689 'interface',
690 self.service)
691
692 self.path = get_index(
693 objs,
694 'pathname',
695 self.path)
696
697 self.interface = get_index(
698 objs,
699 'interface',
700 self.interface)
701
702 self.method = get_index(
703 objs,
704 'propertyname',
705 self.method)
706
707 super(Method, self).setup(objs)
708
709 def construct(self, loader, indent):
710 return self.render(
711 loader,
712 'method.mako.cpp',
713 c=self,
714 indent=indent)
715
716
Brad Bishop49e66172017-05-23 19:16:21 -0400717class CallbackGraphEntry(Group):
718 '''An entry in a traversal list for groups of callbacks.'''
719
720 def __init__(self, *a, **kw):
721 super(CallbackGraphEntry, self).__init__(**kw)
722
723 def setup(self, objs):
724 '''Resolve group members.'''
725
726 def map_member(x):
727 return get_index(
728 objs, 'callback', x, config=self.configfile)
729
730 self.members = map(
731 map_member,
732 self.members)
733
734 super(CallbackGraphEntry, self).setup(objs)
735
736
737class GroupOfCallbacks(ConfigEntry, Renderer):
738 '''Handle the callback group config file directive.'''
739
740 def __init__(self, *a, **kw):
741 self.members = kw.pop('members')
742 super(GroupOfCallbacks, self).__init__(**kw)
743
744 def factory(self, objs):
745 '''Create a graph instance for this group of callbacks.'''
746
747 args = {
748 'configfile': self.configfile,
749 'members': self.members,
750 'class': 'callbackgroup',
751 'callbackgroup': 'callback',
752 'name': self.members
753 }
754
755 entry = CallbackGraphEntry(**args)
756 add_unique(entry, objs, config=self.configfile)
757
758 super(GroupOfCallbacks, self).factory(objs)
759
760 def setup(self, objs):
761 '''Resolve graph entry.'''
762
763 self.graph = get_index(
764 objs, 'callbackgroup', self.members, config=self.configfile)
765
766 super(GroupOfCallbacks, self).setup(objs)
767
768 def construct(self, loader, indent):
769 return self.render(
770 loader,
771 'callbackgroup.mako.cpp',
772 c=self,
773 indent=indent)
774
775
Brad Bishop34a7acd2017-04-27 23:47:23 -0400776class Everything(Renderer):
777 '''Parse/render entry point.'''
778
779 @staticmethod
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400780 def classmap(cls, sub=None):
781 '''Map render item class and subclass entries to the appropriate
782 handler methods.'''
783
784 class_map = {
Brad Bishop0e7df132017-05-23 17:58:12 -0400785 'path': {
786 'element': Path,
787 },
788 'pathgroup': {
789 'path': GroupOfPaths,
790 },
Brad Bishope73b2c32017-05-23 18:01:54 -0400791 'propertygroup': {
792 'property': GroupOfProperties,
793 },
794 'property': {
795 'element': Property,
796 },
Brad Bishop4b916f12017-05-23 18:06:38 -0400797 'watch': {
798 'property': PropertyWatch,
799 },
800 'instance': {
801 'element': Instance,
802 },
Brad Bishopc1283ae2017-05-20 21:42:38 -0400803 'callback': {
804 'journal': Journal,
Brad Bishop49e66172017-05-23 19:16:21 -0400805 'group': GroupOfCallbacks,
Brad Bishop0df00be2017-05-25 23:38:37 -0400806 'method': Method,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400807 },
Brad Bishop4041d722017-05-21 10:06:07 -0400808 'condition': {
809 'count': CountCondition,
810 },
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400811 }
812
813 if cls not in class_map:
814 raise NotImplementedError('Unknown class: "{0}"'.format(cls))
815 if sub not in class_map[cls]:
816 raise NotImplementedError('Unknown {0} type: "{1}"'.format(
817 cls, sub))
818
819 return class_map[cls][sub]
820
821 @staticmethod
822 def load_one_yaml(path, fd, objs):
823 '''Parse a single YAML file. Parsing occurs in three phases.
824 In the first phase a factory method associated with each
825 configuration file directive is invoked. These factory
826 methods generate more factory methods. In the second
827 phase the factory methods created in the first phase
828 are invoked. In the last phase a callback is invoked on
829 each object created in phase two. Typically the callback
830 resolves references to other configuration file directives.'''
831
832 factory_objs = {}
833 for x in yaml.safe_load(fd.read()) or {}:
834
835 # Create factory object for this config file directive.
836 cls = x['class']
837 sub = x.get(cls)
838 if cls == 'group':
839 cls = '{0}group'.format(sub)
840
841 factory = Everything.classmap(cls, sub)
842 obj = factory(configfile=path, **x)
843
844 # For a given class of directive, validate the file
845 # doesn't have any duplicate names (duplicates are
846 # ok across config files).
847 if exists(factory_objs, obj.cls, obj.name, config=path):
848 raise NotUniqueError(path, cls, obj.name)
849
850 factory_objs.setdefault(cls, []).append(obj)
851 objs.setdefault(cls, []).append(obj)
852
853 for cls, items in factory_objs.items():
854 for obj in items:
855 # Add objects for template consumption.
856 obj.factory(objs)
857
858 @staticmethod
Brad Bishop34a7acd2017-04-27 23:47:23 -0400859 def load(args):
860 '''Aggregate all the YAML in the input directory
861 into a single aggregate.'''
862
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400863 objs = {}
864 yaml_files = filter(
865 lambda x: x.endswith('.yaml'),
866 os.listdir(args.inputdir))
Brad Bishop34a7acd2017-04-27 23:47:23 -0400867
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400868 yaml_files.sort()
Brad Bishop34a7acd2017-04-27 23:47:23 -0400869
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400870 for x in yaml_files:
871 path = os.path.join(args.inputdir, x)
872 with open(path, 'r') as fd:
873 Everything.load_one_yaml(path, fd, objs)
874
875 # Configuration file directives reference each other via
876 # the name attribute; however, when rendered the reference
877 # is just an array index.
878 #
879 # At this point all objects have been created but references
880 # have not been resolved to array indicies. Instruct objects
881 # to do that now.
882 for cls, items in objs.items():
883 for obj in items:
884 obj.setup(objs)
885
886 return Everything(**objs)
Brad Bishop34a7acd2017-04-27 23:47:23 -0400887
888 def __init__(self, *a, **kw):
Brad Bishop0e7df132017-05-23 17:58:12 -0400889 self.pathmeta = kw.pop('path', [])
890 self.paths = kw.pop('pathname', [])
891 self.meta = kw.pop('meta', [])
892 self.pathgroups = kw.pop('pathgroup', [])
Brad Bishope73b2c32017-05-23 18:01:54 -0400893 self.interfaces = kw.pop('interface', [])
894 self.properties = kw.pop('property', [])
895 self.propertynames = kw.pop('propertyname', [])
896 self.propertygroups = kw.pop('propertygroup', [])
Brad Bishop4b916f12017-05-23 18:06:38 -0400897 self.instances = kw.pop('instance', [])
898 self.instancegroups = kw.pop('instancegroup', [])
899 self.watches = kw.pop('watch', [])
Brad Bishopc1283ae2017-05-20 21:42:38 -0400900 self.callbacks = kw.pop('callback', [])
Brad Bishop49e66172017-05-23 19:16:21 -0400901 self.callbackgroups = kw.pop('callbackgroup', [])
Brad Bishop4041d722017-05-21 10:06:07 -0400902 self.conditions = kw.pop('condition', [])
Brad Bishop0e7df132017-05-23 17:58:12 -0400903
Brad Bishop34a7acd2017-04-27 23:47:23 -0400904 super(Everything, self).__init__(**kw)
905
906 def generate_cpp(self, loader):
907 '''Render the template with the provided data.'''
Brad Bishope3a01af2017-05-15 17:09:04 -0400908 with open(args.output, 'w') as fd:
Brad Bishop34a7acd2017-04-27 23:47:23 -0400909 fd.write(
910 self.render(
911 loader,
Brad Bishope3a01af2017-05-15 17:09:04 -0400912 args.template,
Brad Bishop0e7df132017-05-23 17:58:12 -0400913 meta=self.meta,
Brad Bishope73b2c32017-05-23 18:01:54 -0400914 properties=self.properties,
915 propertynames=self.propertynames,
916 interfaces=self.interfaces,
Brad Bishop0e7df132017-05-23 17:58:12 -0400917 paths=self.paths,
918 pathmeta=self.pathmeta,
919 pathgroups=self.pathgroups,
Brad Bishope73b2c32017-05-23 18:01:54 -0400920 propertygroups=self.propertygroups,
Brad Bishop4b916f12017-05-23 18:06:38 -0400921 instances=self.instances,
922 watches=self.watches,
923 instancegroups=self.instancegroups,
Brad Bishopc1283ae2017-05-20 21:42:38 -0400924 callbacks=self.callbacks,
Brad Bishop49e66172017-05-23 19:16:21 -0400925 callbackgroups=self.callbackgroups,
Brad Bishop4041d722017-05-21 10:06:07 -0400926 conditions=self.conditions,
Brad Bishop34a7acd2017-04-27 23:47:23 -0400927 indent=Indent()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500928
929if __name__ == '__main__':
Brad Bishop34a7acd2017-04-27 23:47:23 -0400930 script_dir = os.path.dirname(os.path.realpath(__file__))
931 valid_commands = {
932 'generate-cpp': 'generate_cpp',
933 }
934
935 parser = ArgumentParser(
936 description='Phosphor DBus Monitor (PDM) YAML '
937 'scanner and code generator.')
938
Matthew Barthdb440d42017-04-17 15:49:37 -0500939 parser.add_argument(
Brad Bishope3a01af2017-05-15 17:09:04 -0400940 "-o", "--out", dest="output",
941 default='generated.cpp',
942 help="Generated output file name and path.")
943 parser.add_argument(
944 '-t', '--template', dest='template',
Brad Bishop870c3fc2017-05-22 23:23:13 -0400945 default='generated.mako.hpp',
Brad Bishope3a01af2017-05-15 17:09:04 -0400946 help='The top level template to render.')
947 parser.add_argument(
948 '-p', '--template-path', dest='template_search',
949 default=script_dir,
950 help='The space delimited mako template search path.')
Brad Bishop34a7acd2017-04-27 23:47:23 -0400951 parser.add_argument(
952 '-d', '--dir', dest='inputdir',
953 default=os.path.join(script_dir, 'example'),
954 help='Location of files to process.')
955 parser.add_argument(
956 'command', metavar='COMMAND', type=str,
957 choices=valid_commands.keys(),
958 help='%s.' % " | ".join(valid_commands.keys()))
Matthew Barthdb440d42017-04-17 15:49:37 -0500959
Brad Bishop34a7acd2017-04-27 23:47:23 -0400960 args = parser.parse_args()
961
962 if sys.version_info < (3, 0):
963 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400964 directories=args.template_search.split(),
Brad Bishop34a7acd2017-04-27 23:47:23 -0400965 disable_unicode=True)
966 else:
967 lookup = mako.lookup.TemplateLookup(
Brad Bishope3a01af2017-05-15 17:09:04 -0400968 directories=args.template_search.split())
Brad Bishop05b0c1e2017-05-23 00:24:01 -0400969 try:
970 function = getattr(
971 Everything.load(args),
972 valid_commands[args.command])
973 function(lookup)
974 except InvalidConfigError as e:
975 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg))
976 raise