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