1#!/usr/bin/env python3 2 3'''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 18import os 19import sys 20import yaml 21import mako.lookup 22from argparse import ArgumentParser 23from sdbusplus.renderer import Renderer 24from sdbusplus.namedelement import NamedElement 25import sdbusplus.property 26 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) 85 86 87class 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 'int16', 105 'int32', 106 'int64', 107 '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) 123 elif self.type == 'byte': 124 return Cast('static', 'uint8_t'.format(self.type))(arg) 125 elif self.type == 'double': 126 return Cast('static', 'double'.format(self.type))(arg) 127 128 if self.type == 'string': 129 return '{0}s'.format(arg) 130 131 return arg 132 133 134class FixBool(object): 135 '''Un-capitalize booleans.''' 136 137 def __call__(self, arg): 138 return '{0}'.format(arg.lower()) 139 140 141class Quote(object): 142 '''Decorate an argument by quoting it.''' 143 144 def __call__(self, arg): 145 return '"{0}"'.format(arg) 146 147 148class Argument(NamedElement, Renderer): 149 '''Define argument type interface.''' 150 151 def __init__(self, **kw): 152 self.type = kw.pop('type', None) 153 super(Argument, self).__init__(**kw) 154 155 def argument(self, loader, indent): 156 raise NotImplementedError 157 158 159class TrivialArgument(Argument): 160 '''Non-array type arguments.''' 161 162 def __init__(self, **kw): 163 self.value = kw.pop('value') 164 self.decorators = kw.pop('decorators', []) 165 if kw.get('type', None): 166 self.decorators.insert(0, Literal(kw['type'])) 167 if kw.get('type', None) == 'string': 168 self.decorators.insert(0, Quote()) 169 if kw.get('type', None) == 'boolean': 170 self.decorators.insert(0, FixBool()) 171 172 super(TrivialArgument, self).__init__(**kw) 173 174 def argument(self, loader, indent): 175 a = str(self.value) 176 for d in self.decorators: 177 a = d(a) 178 179 return a 180 181 182class Metadata(Argument): 183 '''Metadata type arguments.''' 184 185 def __init__(self, **kw): 186 self.value = kw.pop('value') 187 self.decorators = kw.pop('decorators', []) 188 if kw.get('type', None) == 'string': 189 self.decorators.insert(0, Quote()) 190 191 super(Metadata, self).__init__(**kw) 192 193 def argument(self, loader, indent): 194 a = str(self.value) 195 for d in self.decorators: 196 a = d(a) 197 198 return a 199 200 201class OpArgument(Argument): 202 '''Operation type arguments.''' 203 204 def __init__(self, **kw): 205 self.op = kw.pop('op') 206 self.bound = kw.pop('bound') 207 self.decorators = kw.pop('decorators', []) 208 if kw.get('type', None): 209 self.decorators.insert(0, Literal(kw['type'])) 210 if kw.get('type', None) == 'string': 211 self.decorators.insert(0, Quote()) 212 if kw.get('type', None) == 'boolean': 213 self.decorators.insert(0, FixBool()) 214 215 super(OpArgument, self).__init__(**kw) 216 217 def argument(self, loader, indent): 218 a = str(self.bound) 219 for d in self.decorators: 220 a = d(a) 221 222 return a 223 224 225class Indent(object): 226 '''Help templates be depth agnostic.''' 227 228 def __init__(self, depth=0): 229 self.depth = depth 230 231 def __add__(self, depth): 232 return Indent(self.depth + depth) 233 234 def __call__(self, depth): 235 '''Render an indent at the current depth plus depth.''' 236 return 4*' '*(depth + self.depth) 237 238 239class ConfigEntry(NamedElement): 240 '''Base interface for rendered items.''' 241 242 def __init__(self, *a, **kw): 243 '''Pop the configfile/class/subclass keywords.''' 244 245 self.configfile = kw.pop('configfile') 246 self.cls = kw.pop('class') 247 self.subclass = kw.pop(self.cls) 248 249 # TODO: NamedElement requires 'name' to be a string, but in many cases 250 # this script treats 'name' as a dict. Save the property off and 251 # insert it after ConfigEntry does its own thing to avoid 252 # exceptions. This should be refactored throughout the whole 253 # script to not overload 'name' as a dict. 254 name_save = kw.pop('name') 255 super(ConfigEntry, self).__init__(**kw) 256 self.name = name_save 257 258 def factory(self, objs): 259 ''' Optional factory interface for subclasses to add 260 additional items to be rendered.''' 261 262 pass 263 264 def setup(self, objs): 265 ''' Optional setup interface for subclasses, invoked 266 after all factory methods have been run.''' 267 268 pass 269 270 271class Path(ConfigEntry): 272 '''Path/metadata association.''' 273 274 def __init__(self, *a, **kw): 275 super(Path, self).__init__(**kw) 276 277 if self.name['meta'].upper() != self.name['meta']: 278 raise InvalidConfigError( 279 self.configfile, 280 'Metadata tag "{0}" must be upper case.'.format( 281 self.name['meta'])) 282 283 def factory(self, objs): 284 '''Create path and metadata elements.''' 285 286 args = { 287 'class': 'pathname', 288 'pathname': 'element', 289 'name': self.name['path'] 290 } 291 add_unique(ConfigEntry( 292 configfile=self.configfile, **args), objs) 293 294 args = { 295 'class': 'meta', 296 'meta': 'element', 297 'name': self.name['meta'] 298 } 299 add_unique(ConfigEntry( 300 configfile=self.configfile, **args), objs) 301 302 super(Path, self).factory(objs) 303 304 def setup(self, objs): 305 '''Resolve path and metadata names to indices.''' 306 307 self.path = get_index( 308 objs, 'pathname', self.name['path']) 309 self.meta = get_index( 310 objs, 'meta', self.name['meta']) 311 312 super(Path, self).setup(objs) 313 314 315class Property(ConfigEntry): 316 '''Property/interface/metadata association.''' 317 318 def __init__(self, *a, **kw): 319 super(Property, self).__init__(**kw) 320 321 if self.name['meta'].upper() != self.name['meta']: 322 raise InvalidConfigError( 323 self.configfile, 324 'Metadata tag "{0}" must be upper case.'.format( 325 self.name['meta'])) 326 327 def factory(self, objs): 328 '''Create interface, property name and metadata elements.''' 329 330 args = { 331 'class': 'interface', 332 'interface': 'element', 333 'name': self.name['interface'] 334 } 335 add_unique(ConfigEntry( 336 configfile=self.configfile, **args), objs) 337 338 args = { 339 'class': 'propertyname', 340 'propertyname': 'element', 341 'name': self.name['property'] 342 } 343 add_unique(ConfigEntry( 344 configfile=self.configfile, **args), objs) 345 346 args = { 347 'class': 'meta', 348 'meta': 'element', 349 'name': self.name['meta'] 350 } 351 add_unique(ConfigEntry( 352 configfile=self.configfile, **args), objs) 353 354 super(Property, self).factory(objs) 355 356 def setup(self, objs): 357 '''Resolve interface, property and metadata to indices.''' 358 359 self.interface = get_index( 360 objs, 'interface', self.name['interface']) 361 self.prop = get_index( 362 objs, 'propertyname', self.name['property']) 363 self.meta = get_index( 364 objs, 'meta', self.name['meta']) 365 366 super(Property, self).setup(objs) 367 368 369class Instance(ConfigEntry): 370 '''Property/Path association.''' 371 372 def __init__(self, *a, **kw): 373 super(Instance, self).__init__(**kw) 374 375 def setup(self, objs): 376 '''Resolve elements to indices.''' 377 378 self.interface = get_index( 379 objs, 'interface', self.name['property']['interface']) 380 self.prop = get_index( 381 objs, 'propertyname', self.name['property']['property']) 382 self.propmeta = get_index( 383 objs, 'meta', self.name['property']['meta']) 384 self.path = get_index( 385 objs, 'pathname', self.name['path']['path']) 386 self.pathmeta = get_index( 387 objs, 'meta', self.name['path']['meta']) 388 389 super(Instance, self).setup(objs) 390 391class PathInstance(ConfigEntry): 392 '''Path association.''' 393 394 def __init__(self, *a, **kw): 395 super(PathInstance, self).__init__(**kw) 396 397 def setup(self, objs): 398 '''Resolve elements to indices.''' 399 self.path = self.name['path']['path'] 400 self.pathmeta = self.name['path']['meta'] 401 super(PathInstance, self).setup(objs) 402 403class Group(ConfigEntry): 404 '''Pop the members keyword for groups.''' 405 406 def __init__(self, *a, **kw): 407 self.members = kw.pop('members') 408 super(Group, self).__init__(**kw) 409 410 411class ImplicitGroup(Group): 412 '''Provide a factory method for groups whose members are 413 not explicitly declared in the config files.''' 414 415 def __init__(self, *a, **kw): 416 super(ImplicitGroup, self).__init__(**kw) 417 418 def factory(self, objs): 419 '''Create group members.''' 420 421 factory = Everything.classmap(self.subclass, 'element') 422 for m in self.members: 423 args = { 424 'class': self.subclass, 425 self.subclass: 'element', 426 'name': m 427 } 428 429 obj = factory(configfile=self.configfile, **args) 430 add_unique(obj, objs) 431 obj.factory(objs) 432 433 super(ImplicitGroup, self).factory(objs) 434 435 436class GroupOfPaths(ImplicitGroup): 437 '''Path group config file directive.''' 438 439 def __init__(self, *a, **kw): 440 super(GroupOfPaths, self).__init__(**kw) 441 442 def setup(self, objs): 443 '''Resolve group members.''' 444 445 def map_member(x): 446 path = get_index( 447 objs, 'pathname', x['path']) 448 meta = get_index( 449 objs, 'meta', x['meta']) 450 return (path, meta) 451 452 self.members = map( 453 map_member, 454 self.members) 455 456 super(GroupOfPaths, self).setup(objs) 457 458 459class GroupOfProperties(ImplicitGroup): 460 '''Property group config file directive.''' 461 462 def __init__(self, *a, **kw): 463 self.type = kw.pop('type') 464 self.datatype = sdbusplus.property.Property( 465 name=kw.get('name'), 466 type=self.type).cppTypeName 467 468 super(GroupOfProperties, self).__init__(**kw) 469 470 def setup(self, objs): 471 '''Resolve group members.''' 472 473 def map_member(x): 474 iface = get_index( 475 objs, 'interface', x['interface']) 476 prop = get_index( 477 objs, 'propertyname', x['property']) 478 meta = get_index( 479 objs, 'meta', x['meta']) 480 481 return (iface, prop, meta) 482 483 self.members = map( 484 map_member, 485 self.members) 486 487 super(GroupOfProperties, self).setup(objs) 488 489 490class GroupOfInstances(ImplicitGroup): 491 '''A group of property instances.''' 492 493 def __init__(self, *a, **kw): 494 super(GroupOfInstances, self).__init__(**kw) 495 496 def setup(self, objs): 497 '''Resolve group members.''' 498 499 def map_member(x): 500 path = get_index(objs, 'pathname', x['path']['path']) 501 pathmeta = get_index(objs, 'meta', x['path']['meta']) 502 interface = get_index( 503 objs, 'interface', x['property']['interface']) 504 prop = get_index(objs, 'propertyname', x['property']['property']) 505 propmeta = get_index(objs, 'meta', x['property']['meta']) 506 instance = get_index(objs, 'instance', x) 507 508 return (path, pathmeta, interface, prop, propmeta, instance) 509 510 self.members = map( 511 map_member, 512 self.members) 513 514 super(GroupOfInstances, self).setup(objs) 515 516class GroupOfPathInstances(ImplicitGroup): 517 '''A group of path instances.''' 518 519 def __init__(self, *a, **kw): 520 super(GroupOfPathInstances, self).__init__(**kw) 521 522 def setup(self, objs): 523 '''Resolve group members.''' 524 525 def map_member(x): 526 path = get_index(objs, 'pathname', x['path']['path']) 527 pathmeta = get_index(objs, 'meta', x['path']['meta']) 528 pathinstance = get_index(objs, 'pathinstance', x) 529 return (path, pathmeta, pathinstance) 530 531 self.members = map( 532 map_member, 533 self.members) 534 535 super(GroupOfPathInstances, self).setup(objs) 536 537 538class HasPropertyIndex(ConfigEntry): 539 '''Handle config file directives that require an index to be 540 constructed.''' 541 542 def __init__(self, *a, **kw): 543 self.paths = kw.pop('paths') 544 self.properties = kw.pop('properties') 545 super(HasPropertyIndex, self).__init__(**kw) 546 547 def factory(self, objs): 548 '''Create a group of instances for this index.''' 549 550 members = [] 551 path_group = get_index( 552 objs, 'pathgroup', self.paths, config=self.configfile) 553 property_group = get_index( 554 objs, 'propertygroup', self.properties, config=self.configfile) 555 556 for path in objs['pathgroup'][path_group].members: 557 for prop in objs['propertygroup'][property_group].members: 558 member = { 559 'path': path, 560 'property': prop, 561 } 562 members.append(member) 563 564 args = { 565 'members': members, 566 'class': 'instancegroup', 567 'instancegroup': 'instance', 568 'name': '{0} {1}'.format(self.paths, self.properties) 569 } 570 571 group = GroupOfInstances(configfile=self.configfile, **args) 572 add_unique(group, objs, config=self.configfile) 573 group.factory(objs) 574 575 super(HasPropertyIndex, self).factory(objs) 576 577 def setup(self, objs): 578 '''Resolve path, property, and instance groups.''' 579 580 self.instances = get_index( 581 objs, 582 'instancegroup', 583 '{0} {1}'.format(self.paths, self.properties), 584 config=self.configfile) 585 self.paths = get_index( 586 objs, 587 'pathgroup', 588 self.paths, 589 config=self.configfile) 590 self.properties = get_index( 591 objs, 592 'propertygroup', 593 self.properties, 594 config=self.configfile) 595 self.datatype = objs['propertygroup'][self.properties].datatype 596 self.type = objs['propertygroup'][self.properties].type 597 598 super(HasPropertyIndex, self).setup(objs) 599 600class HasPathIndex(ConfigEntry): 601 '''Handle config file directives that require an index to be 602 constructed.''' 603 604 def __init__(self, *a, **kw): 605 self.paths = kw.pop('paths') 606 super(HasPathIndex, self).__init__(**kw) 607 608 def factory(self, objs): 609 '''Create a group of instances for this index.''' 610 611 members = [] 612 path_group = get_index( 613 objs, 'pathgroup', self.paths, config=self.configfile) 614 615 for path in objs['pathgroup'][path_group].members: 616 member = { 617 'path': path, 618 } 619 members.append(member) 620 621 args = { 622 'members': members, 623 'class': 'pathinstancegroup', 624 'pathinstancegroup': 'pathinstance', 625 'name': '{0}'.format(self.paths) 626 } 627 628 group = GroupOfPathInstances(configfile=self.configfile, **args) 629 add_unique(group, objs, config=self.configfile) 630 group.factory(objs) 631 632 super(HasPathIndex, self).factory(objs) 633 634 def setup(self, objs): 635 '''Resolve path and instance groups.''' 636 637 self.pathinstances = get_index( 638 objs, 639 'pathinstancegroup', 640 '{0}'.format(self.paths), 641 config=self.configfile) 642 self.paths = get_index( 643 objs, 644 'pathgroup', 645 self.paths, 646 config=self.configfile) 647 super(HasPathIndex, self).setup(objs) 648 649class GroupOfFilters(ConfigEntry): 650 '''Handle config file directives that require an index for filters.''' 651 652 def __init__(self, *a, **kw): 653 # Pop filters data for adding to the available filters array 654 self.type = kw.pop('type') 655 self.datatype = kw.pop('datatype', None) 656 self.filters = kw.pop('filters', None) 657 658 super(GroupOfFilters, self).__init__(**kw) 659 660 def factory(self, objs): 661 '''Modify filters to add the property value type and 662 make them of operation argument type.''' 663 if self.filters: 664 # 'type' used within OpArgument to generate filter 665 # argument values so add to each filter 666 for f in self.filters: 667 f['type'] = self.type 668 self.filters = [OpArgument(**x) for x in self.filters] 669 670 super(GroupOfFilters, self).factory(objs) 671 672class PropertyWatch(HasPropertyIndex): 673 '''Handle the property watch config file directive.''' 674 675 def __init__(self, *a, **kw): 676 # Pop optional filters for the properties being watched 677 self.filters = kw.pop('filters', None) 678 self.callback = kw.pop('callback', None) 679 super(PropertyWatch, self).__init__(**kw) 680 681 def factory(self, objs): 682 '''Create any filters for this property watch.''' 683 684 if self.filters: 685 # Get the datatype(i.e. "int64_t") of the properties in this watch 686 # (Made available after all `super` classes init'd) 687 datatype = objs['propertygroup'][get_index( 688 objs, 689 'propertygroup', 690 self.properties, 691 config=self.configfile)].datatype 692 # Get the type(i.e. "int64") of the properties in this watch 693 # (Made available after all `super` classes init'd) 694 type = objs['propertygroup'][get_index( 695 objs, 696 'propertygroup', 697 self.properties, 698 config=self.configfile)].type 699 # Construct the data needed to make the filters for 700 # this watch available. 701 # *Note: 'class', 'subclass', 'name' are required for 702 # storing the filter data(i.e. 'type', 'datatype', & 'filters') 703 args = { 704 'type': type, 705 'datatype': datatype, 706 'filters': self.filters, 707 'class': 'filtersgroup', 708 'filtersgroup': 'filters', 709 'name': self.name, 710 } 711 # Init GroupOfFilters class with this watch's filters' arguments 712 group = GroupOfFilters(configfile=self.configfile, **args) 713 # Store this group of filters so it can be indexed later 714 add_unique(group, objs, config=self.configfile) 715 group.factory(objs) 716 717 super(PropertyWatch, self).factory(objs) 718 719 def setup(self, objs): 720 '''Resolve optional filters and callback.''' 721 722 if self.filters: 723 # Watch has filters, provide array index to access them 724 self.filters = get_index( 725 objs, 726 'filtersgroup', 727 self.name, 728 config=self.configfile) 729 730 if self.callback: 731 self.callback = get_index( 732 objs, 733 'callback', 734 self.callback, 735 config=self.configfile) 736 737 super(PropertyWatch, self).setup(objs) 738 739class PathWatch(HasPathIndex): 740 '''Handle the path watch config file directive.''' 741 742 def __init__(self, *a, **kw): 743 self.pathcallback = kw.pop('pathcallback', None) 744 super(PathWatch, self).__init__(**kw) 745 746 def setup(self, objs): 747 '''Resolve optional callback.''' 748 if self.pathcallback: 749 self.pathcallback = get_index( 750 objs, 751 'pathcallback', 752 self.pathcallback, 753 config=self.configfile) 754 super(PathWatch, self).setup(objs) 755 756class Callback(HasPropertyIndex): 757 '''Interface and common logic for callbacks.''' 758 759 def __init__(self, *a, **kw): 760 super(Callback, self).__init__(**kw) 761 762class PathCallback(HasPathIndex): 763 '''Interface and common logic for callbacks.''' 764 765 def __init__(self, *a, **kw): 766 super(PathCallback, self).__init__(**kw) 767 768class ConditionCallback(ConfigEntry, Renderer): 769 '''Handle the journal callback config file directive.''' 770 771 def __init__(self, *a, **kw): 772 self.condition = kw.pop('condition') 773 self.instance = kw.pop('instance') 774 self.defer = kw.pop('defer', None) 775 super(ConditionCallback, self).__init__(**kw) 776 777 def factory(self, objs): 778 '''Create a graph instance for this callback.''' 779 780 args = { 781 'configfile': self.configfile, 782 'members': [self.instance], 783 'class': 'callbackgroup', 784 'callbackgroup': 'callback', 785 'name': [self.instance] 786 } 787 788 entry = CallbackGraphEntry(**args) 789 add_unique(entry, objs, config=self.configfile) 790 791 super(ConditionCallback, self).factory(objs) 792 793 def setup(self, objs): 794 '''Resolve condition and graph entry.''' 795 796 self.graph = get_index( 797 objs, 798 'callbackgroup', 799 [self.instance], 800 config=self.configfile) 801 802 self.condition = get_index( 803 objs, 804 'condition', 805 self.name, 806 config=self.configfile) 807 808 super(ConditionCallback, self).setup(objs) 809 810 def construct(self, loader, indent): 811 return self.render( 812 loader, 813 'conditional.mako.cpp', 814 c=self, 815 indent=indent) 816 817 818class Condition(HasPropertyIndex): 819 '''Interface and common logic for conditions.''' 820 821 def __init__(self, *a, **kw): 822 self.callback = kw.pop('callback') 823 self.defer = kw.pop('defer', None) 824 super(Condition, self).__init__(**kw) 825 826 def factory(self, objs): 827 '''Create a callback instance for this conditional.''' 828 829 args = { 830 'configfile': self.configfile, 831 'condition': self.name, 832 'class': 'callback', 833 'callback': 'conditional', 834 'instance': self.callback, 835 'name': self.name, 836 'defer': self.defer 837 } 838 839 callback = ConditionCallback(**args) 840 add_unique(callback, objs, config=self.configfile) 841 callback.factory(objs) 842 843 super(Condition, self).factory(objs) 844 845 846class CountCondition(Condition, Renderer): 847 '''Handle the count condition config file directive.''' 848 849 def __init__(self, *a, **kw): 850 self.countop = kw.pop('countop') 851 self.countbound = kw.pop('countbound') 852 self.op = kw.pop('op') 853 self.bound = kw.pop('bound') 854 self.oneshot = TrivialArgument( 855 type='boolean', 856 value=kw.pop('oneshot', False)) 857 super(CountCondition, self).__init__(**kw) 858 859 def setup(self, objs): 860 '''Resolve type.''' 861 862 super(CountCondition, self).setup(objs) 863 self.bound = TrivialArgument( 864 type=self.type, 865 value=self.bound) 866 867 def construct(self, loader, indent): 868 return self.render( 869 loader, 870 'count.mako.cpp', 871 c=self, 872 indent=indent) 873 874 875class MedianCondition(Condition, Renderer): 876 '''Handle the median condition config file directive.''' 877 878 def __init__(self, *a, **kw): 879 self.op = kw.pop('op') 880 self.bound = kw.pop('bound') 881 self.oneshot = TrivialArgument( 882 type='boolean', 883 value=kw.pop('oneshot', False)) 884 super(MedianCondition, self).__init__(**kw) 885 886 def setup(self, objs): 887 '''Resolve type.''' 888 889 super(MedianCondition, self).setup(objs) 890 self.bound = TrivialArgument( 891 type=self.type, 892 value=self.bound) 893 894 def construct(self, loader, indent): 895 return self.render( 896 loader, 897 'median.mako.cpp', 898 c=self, 899 indent=indent) 900 901 902class Journal(Callback, Renderer): 903 '''Handle the journal callback config file directive.''' 904 905 def __init__(self, *a, **kw): 906 self.severity = kw.pop('severity') 907 self.message = kw.pop('message') 908 super(Journal, self).__init__(**kw) 909 910 def construct(self, loader, indent): 911 return self.render( 912 loader, 913 'journal.mako.cpp', 914 c=self, 915 indent=indent) 916 917 918class Elog(Callback, Renderer): 919 '''Handle the elog callback config file directive.''' 920 921 def __init__(self, *a, **kw): 922 self.error = kw.pop('error') 923 self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})] 924 super(Elog, self).__init__(**kw) 925 926 def construct(self, loader, indent): 927 with open('errors.hpp', 'a') as fd: 928 fd.write( 929 self.render( 930 loader, 931 'errors.mako.hpp', 932 c=self)) 933 return self.render( 934 loader, 935 'elog.mako.cpp', 936 c=self, 937 indent=indent) 938 939class Event(Callback, Renderer): 940 '''Handle the event callback config file directive.''' 941 942 def __init__(self, *a, **kw): 943 self.eventName = kw.pop('eventName') 944 self.eventMessage = kw.pop('eventMessage') 945 super(Event, self).__init__(**kw) 946 947 def construct(self, loader, indent): 948 return self.render( 949 loader, 950 'event.mako.cpp', 951 c=self, 952 indent=indent) 953 954class EventPath(PathCallback, Renderer): 955 '''Handle the event path callback config file directive.''' 956 957 def __init__(self, *a, **kw): 958 self.eventType = kw.pop('eventType') 959 super(EventPath, self).__init__(**kw) 960 961 def construct(self, loader, indent): 962 return self.render( 963 loader, 964 'eventpath.mako.cpp', 965 c=self, 966 indent=indent) 967 968class ElogWithMetadata(Callback, Renderer): 969 '''Handle the elog_with_metadata callback config file directive.''' 970 971 def __init__(self, *a, **kw): 972 self.error = kw.pop('error') 973 self.metadata = kw.pop('metadata') 974 super(ElogWithMetadata, self).__init__(**kw) 975 976 def construct(self, loader, indent): 977 with open('errors.hpp', 'a') as fd: 978 fd.write( 979 self.render( 980 loader, 981 'errors.mako.hpp', 982 c=self)) 983 return self.render( 984 loader, 985 'elog_with_metadata.mako.cpp', 986 c=self, 987 indent=indent) 988 989 990class ResolveCallout(Callback, Renderer): 991 '''Handle the 'resolve callout' callback config file directive.''' 992 993 def __init__(self, *a, **kw): 994 self.callout = kw.pop('callout') 995 super(ResolveCallout, self).__init__(**kw) 996 997 def construct(self, loader, indent): 998 return self.render( 999 loader, 1000 'resolve_errors.mako.cpp', 1001 c=self, 1002 indent=indent) 1003 1004 1005class Method(ConfigEntry, Renderer): 1006 '''Handle the method callback config file directive.''' 1007 1008 def __init__(self, *a, **kw): 1009 self.service = kw.pop('service') 1010 self.path = kw.pop('path') 1011 self.interface = kw.pop('interface') 1012 self.method = kw.pop('method') 1013 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})] 1014 super(Method, self).__init__(**kw) 1015 1016 def factory(self, objs): 1017 args = { 1018 'class': 'interface', 1019 'interface': 'element', 1020 'name': self.service 1021 } 1022 add_unique(ConfigEntry( 1023 configfile=self.configfile, **args), objs) 1024 1025 args = { 1026 'class': 'pathname', 1027 'pathname': 'element', 1028 'name': self.path 1029 } 1030 add_unique(ConfigEntry( 1031 configfile=self.configfile, **args), objs) 1032 1033 args = { 1034 'class': 'interface', 1035 'interface': 'element', 1036 'name': self.interface 1037 } 1038 add_unique(ConfigEntry( 1039 configfile=self.configfile, **args), objs) 1040 1041 args = { 1042 'class': 'propertyname', 1043 'propertyname': 'element', 1044 'name': self.method 1045 } 1046 add_unique(ConfigEntry( 1047 configfile=self.configfile, **args), objs) 1048 1049 super(Method, self).factory(objs) 1050 1051 def setup(self, objs): 1052 '''Resolve elements.''' 1053 1054 self.service = get_index( 1055 objs, 1056 'interface', 1057 self.service) 1058 1059 self.path = get_index( 1060 objs, 1061 'pathname', 1062 self.path) 1063 1064 self.interface = get_index( 1065 objs, 1066 'interface', 1067 self.interface) 1068 1069 self.method = get_index( 1070 objs, 1071 'propertyname', 1072 self.method) 1073 1074 super(Method, self).setup(objs) 1075 1076 def construct(self, loader, indent): 1077 return self.render( 1078 loader, 1079 'method.mako.cpp', 1080 c=self, 1081 indent=indent) 1082 1083 1084class CallbackGraphEntry(Group): 1085 '''An entry in a traversal list for groups of callbacks.''' 1086 1087 def __init__(self, *a, **kw): 1088 super(CallbackGraphEntry, self).__init__(**kw) 1089 1090 def setup(self, objs): 1091 '''Resolve group members.''' 1092 1093 def map_member(x): 1094 return get_index( 1095 objs, 'callback', x, config=self.configfile) 1096 1097 self.members = map( 1098 map_member, 1099 self.members) 1100 1101 super(CallbackGraphEntry, self).setup(objs) 1102 1103class PathCallbackGraphEntry(Group): 1104 '''An entry in a traversal list for groups of callbacks.''' 1105 1106 def __init__(self, *a, **kw): 1107 super(PathCallbackGraphEntry, self).__init__(**kw) 1108 1109 def setup(self, objs): 1110 '''Resolve group members.''' 1111 1112 def map_member(x): 1113 return get_index( 1114 objs, 'pathcallback', x, config=self.configfile) 1115 1116 self.members = map( 1117 map_member, 1118 self.members) 1119 1120 super(PathCallbackGraphEntry, self).setup(objs) 1121 1122class GroupOfCallbacks(ConfigEntry, Renderer): 1123 '''Handle the callback group config file directive.''' 1124 1125 def __init__(self, *a, **kw): 1126 self.members = kw.pop('members') 1127 super(GroupOfCallbacks, self).__init__(**kw) 1128 1129 def factory(self, objs): 1130 '''Create a graph instance for this group of callbacks.''' 1131 1132 args = { 1133 'configfile': self.configfile, 1134 'members': self.members, 1135 'class': 'callbackgroup', 1136 'callbackgroup': 'callback', 1137 'name': self.members 1138 } 1139 1140 entry = CallbackGraphEntry(**args) 1141 add_unique(entry, objs, config=self.configfile) 1142 1143 super(GroupOfCallbacks, self).factory(objs) 1144 1145 def setup(self, objs): 1146 '''Resolve graph entry.''' 1147 1148 self.graph = get_index( 1149 objs, 'callbackgroup', self.members, config=self.configfile) 1150 1151 super(GroupOfCallbacks, self).setup(objs) 1152 1153 def construct(self, loader, indent): 1154 return self.render( 1155 loader, 1156 'callbackgroup.mako.cpp', 1157 c=self, 1158 indent=indent) 1159 1160class GroupOfPathCallbacks(ConfigEntry, Renderer): 1161 '''Handle the callback group config file directive.''' 1162 1163 def __init__(self, *a, **kw): 1164 self.members = kw.pop('members') 1165 super(GroupOfPathCallbacks, self).__init__(**kw) 1166 1167 def factory(self, objs): 1168 '''Create a graph instance for this group of callbacks.''' 1169 1170 args = { 1171 'configfile': self.configfile, 1172 'members': self.members, 1173 'class': 'pathcallbackgroup', 1174 'pathcallbackgroup': 'pathcallback', 1175 'name': self.members 1176 } 1177 1178 entry = PathCallbackGraphEntry(**args) 1179 add_unique(entry, objs, config=self.configfile) 1180 super(GroupOfPathCallbacks, self).factory(objs) 1181 1182 def setup(self, objs): 1183 '''Resolve graph entry.''' 1184 1185 self.graph = get_index( 1186 objs, 'callbackpathgroup', self.members, config=self.configfile) 1187 1188 super(GroupOfPathCallbacks, self).setup(objs) 1189 1190 def construct(self, loader, indent): 1191 return self.render( 1192 loader, 1193 'callbackpathgroup.mako.cpp', 1194 c=self, 1195 indent=indent) 1196 1197class Everything(Renderer): 1198 '''Parse/render entry point.''' 1199 1200 @staticmethod 1201 def classmap(cls, sub=None): 1202 '''Map render item class and subclass entries to the appropriate 1203 handler methods.''' 1204 class_map = { 1205 'path': { 1206 'element': Path, 1207 }, 1208 'pathgroup': { 1209 'path': GroupOfPaths, 1210 }, 1211 'propertygroup': { 1212 'property': GroupOfProperties, 1213 }, 1214 'property': { 1215 'element': Property, 1216 }, 1217 'watch': { 1218 'property': PropertyWatch, 1219 }, 1220 'pathwatch': { 1221 'path': PathWatch, 1222 }, 1223 'instance': { 1224 'element': Instance, 1225 }, 1226 'pathinstance': { 1227 'element': PathInstance, 1228 }, 1229 'callback': { 1230 'journal': Journal, 1231 'elog': Elog, 1232 'elog_with_metadata': ElogWithMetadata, 1233 'event': Event, 1234 'group': GroupOfCallbacks, 1235 'method': Method, 1236 'resolve callout': ResolveCallout, 1237 }, 1238 'pathcallback': { 1239 'eventpath': EventPath, 1240 'grouppath': GroupOfPathCallbacks, 1241 }, 1242 'condition': { 1243 'count': CountCondition, 1244 'median': MedianCondition, 1245 }, 1246 } 1247 1248 if cls not in class_map: 1249 raise NotImplementedError('Unknown class: "{0}"'.format(cls)) 1250 if sub not in class_map[cls]: 1251 raise NotImplementedError('Unknown {0} type: "{1}"'.format( 1252 cls, sub)) 1253 1254 return class_map[cls][sub] 1255 1256 @staticmethod 1257 def load_one_yaml(path, fd, objs): 1258 '''Parse a single YAML file. Parsing occurs in three phases. 1259 In the first phase a factory method associated with each 1260 configuration file directive is invoked. These factory 1261 methods generate more factory methods. In the second 1262 phase the factory methods created in the first phase 1263 are invoked. In the last phase a callback is invoked on 1264 each object created in phase two. Typically the callback 1265 resolves references to other configuration file directives.''' 1266 1267 factory_objs = {} 1268 for x in yaml.safe_load(fd.read()) or {}: 1269 1270 # Create factory object for this config file directive. 1271 cls = x['class'] 1272 sub = x.get(cls) 1273 if cls == 'group': 1274 cls = '{0}group'.format(sub) 1275 1276 factory = Everything.classmap(cls, sub) 1277 obj = factory(configfile=path, **x) 1278 1279 # For a given class of directive, validate the file 1280 # doesn't have any duplicate names (duplicates are 1281 # ok across config files). 1282 if exists(factory_objs, obj.cls, obj.name, config=path): 1283 raise NotUniqueError(path, cls, obj.name) 1284 1285 factory_objs.setdefault(cls, []).append(obj) 1286 objs.setdefault(cls, []).append(obj) 1287 1288 for cls, items in factory_objs.items(): 1289 for obj in items: 1290 # Add objects for template consumption. 1291 obj.factory(objs) 1292 1293 @staticmethod 1294 def load(args): 1295 '''Aggregate all the YAML in the input directory 1296 into a single aggregate.''' 1297 1298 objs = {} 1299 yaml_files = filter( 1300 lambda x: x.endswith('.yaml'), 1301 os.listdir(args.inputdir)) 1302 1303 for x in sorted(yaml_files): 1304 path = os.path.join(args.inputdir, x) 1305 with open(path, 'r') as fd: 1306 Everything.load_one_yaml(path, fd, objs) 1307 1308 # Configuration file directives reference each other via 1309 # the name attribute; however, when rendered the reference 1310 # is just an array index. 1311 # 1312 # At this point all objects have been created but references 1313 # have not been resolved to array indices. Instruct objects 1314 # to do that now. 1315 for cls, items in objs.items(): 1316 for obj in items: 1317 obj.setup(objs) 1318 1319 return Everything(**objs) 1320 1321 def __init__(self, *a, **kw): 1322 self.pathmeta = kw.pop('path', []) 1323 self.paths = kw.pop('pathname', []) 1324 self.meta = kw.pop('meta', []) 1325 self.pathgroups = kw.pop('pathgroup', []) 1326 self.interfaces = kw.pop('interface', []) 1327 self.properties = kw.pop('property', []) 1328 self.propertynames = kw.pop('propertyname', []) 1329 self.propertygroups = kw.pop('propertygroup', []) 1330 self.instances = kw.pop('instance', []) 1331 self.pathinstances = kw.pop('pathinstance', []) 1332 self.instancegroups = kw.pop('instancegroup', []) 1333 self.pathinstancegroups = kw.pop('pathinstancegroup', []) 1334 self.watches = kw.pop('watch', []) 1335 self.pathwatches = kw.pop('pathwatch', []) 1336 self.callbacks = kw.pop('callback', []) 1337 self.pathcallbacks = kw.pop('pathcallback', []) 1338 self.callbackgroups = kw.pop('callbackgroup', []) 1339 self.pathcallbackgroups = kw.pop('pathcallbackgroup', []) 1340 self.conditions = kw.pop('condition', []) 1341 self.filters = kw.pop('filtersgroup', []) 1342 1343 super(Everything, self).__init__(**kw) 1344 1345 def generate_cpp(self, loader): 1346 '''Render the template with the provided data.''' 1347 # errors.hpp is used by generated.hpp to included any error.hpp files 1348 open('errors.hpp', 'w+') 1349 1350 with open(args.output, 'w') as fd: 1351 fd.write( 1352 self.render( 1353 loader, 1354 args.template, 1355 meta=self.meta, 1356 properties=self.properties, 1357 propertynames=self.propertynames, 1358 interfaces=self.interfaces, 1359 paths=self.paths, 1360 pathmeta=self.pathmeta, 1361 pathgroups=self.pathgroups, 1362 propertygroups=self.propertygroups, 1363 instances=self.instances, 1364 pathinstances=self.pathinstances, 1365 watches=self.watches, 1366 pathwatches=self.pathwatches, 1367 instancegroups=self.instancegroups, 1368 pathinstancegroups=self.pathinstancegroups, 1369 callbacks=self.callbacks, 1370 pathcallbacks=self.pathcallbacks, 1371 callbackgroups=self.callbackgroups, 1372 pathcallbackgroups=self.pathcallbackgroups, 1373 conditions=self.conditions, 1374 filters=self.filters, 1375 indent=Indent())) 1376 1377if __name__ == '__main__': 1378 script_dir = os.path.dirname(os.path.realpath(__file__)) 1379 valid_commands = { 1380 'generate-cpp': 'generate_cpp', 1381 } 1382 1383 parser = ArgumentParser( 1384 description='Phosphor DBus Monitor (PDM) YAML ' 1385 'scanner and code generator.') 1386 1387 parser.add_argument( 1388 "-o", "--out", dest="output", 1389 default='generated.cpp', 1390 help="Generated output file name and path.") 1391 parser.add_argument( 1392 '-t', '--template', dest='template', 1393 default='generated.mako.hpp', 1394 help='The top level template to render.') 1395 parser.add_argument( 1396 '-p', '--template-path', dest='template_search', 1397 default=script_dir, 1398 help='The space delimited mako template search path.') 1399 parser.add_argument( 1400 '-d', '--dir', dest='inputdir', 1401 default=os.path.join(script_dir, 'example'), 1402 help='Location of files to process.') 1403 parser.add_argument( 1404 'command', metavar='COMMAND', type=str, 1405 choices=valid_commands.keys(), 1406 help='%s.' % " | ".join(valid_commands.keys())) 1407 1408 args = parser.parse_args() 1409 1410 if sys.version_info < (3, 0): 1411 lookup = mako.lookup.TemplateLookup( 1412 directories=args.template_search.split(), 1413 disable_unicode=True) 1414 else: 1415 lookup = mako.lookup.TemplateLookup( 1416 directories=args.template_search.split()) 1417 try: 1418 function = getattr( 1419 Everything.load(args), 1420 valid_commands[args.command]) 1421 function(lookup) 1422 except InvalidConfigError as e: 1423 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg)) 1424 raise 1425