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