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