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