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