1#!/usr/bin/env python 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 PropertyWatch(HasPropertyIndex): 640 '''Handle the property watch config file directive.''' 641 642 def __init__(self, *a, **kw): 643 self.filters = [OpArgument(**x) for x in kw.pop('filters', {})] 644 self.callback = kw.pop('callback', None) 645 super(PropertyWatch, self).__init__(**kw) 646 647 def setup(self, objs): 648 '''Resolve optional callback.''' 649 650 if self.callback: 651 self.callback = get_index( 652 objs, 653 'callback', 654 self.callback, 655 config=self.configfile) 656 657 super(PropertyWatch, self).setup(objs) 658 659class PathWatch(HasPathIndex): 660 '''Handle the path watch config file directive.''' 661 662 def __init__(self, *a, **kw): 663 self.pathcallback = kw.pop('pathcallback', None) 664 super(PathWatch, self).__init__(**kw) 665 666 def setup(self, objs): 667 '''Resolve optional callback.''' 668 if self.pathcallback: 669 self.pathcallback = get_index( 670 objs, 671 'pathcallback', 672 self.pathcallback, 673 config=self.configfile) 674 super(PathWatch, self).setup(objs) 675 676 677class Callback(HasPropertyIndex): 678 '''Interface and common logic for callbacks.''' 679 680 def __init__(self, *a, **kw): 681 super(Callback, self).__init__(**kw) 682 683class PathCallback(HasPathIndex): 684 '''Interface and common logic for callbacks.''' 685 686 def __init__(self, *a, **kw): 687 super(PathCallback, self).__init__(**kw) 688 689class ConditionCallback(ConfigEntry, Renderer): 690 '''Handle the journal callback config file directive.''' 691 692 def __init__(self, *a, **kw): 693 self.condition = kw.pop('condition') 694 self.instance = kw.pop('instance') 695 self.defer = kw.pop('defer', None) 696 super(ConditionCallback, self).__init__(**kw) 697 698 def factory(self, objs): 699 '''Create a graph instance for this callback.''' 700 701 args = { 702 'configfile': self.configfile, 703 'members': [self.instance], 704 'class': 'callbackgroup', 705 'callbackgroup': 'callback', 706 'name': [self.instance] 707 } 708 709 entry = CallbackGraphEntry(**args) 710 add_unique(entry, objs, config=self.configfile) 711 712 super(ConditionCallback, self).factory(objs) 713 714 def setup(self, objs): 715 '''Resolve condition and graph entry.''' 716 717 self.graph = get_index( 718 objs, 719 'callbackgroup', 720 [self.instance], 721 config=self.configfile) 722 723 self.condition = get_index( 724 objs, 725 'condition', 726 self.name, 727 config=self.configfile) 728 729 super(ConditionCallback, self).setup(objs) 730 731 def construct(self, loader, indent): 732 return self.render( 733 loader, 734 'conditional.mako.cpp', 735 c=self, 736 indent=indent) 737 738 739class Condition(HasPropertyIndex): 740 '''Interface and common logic for conditions.''' 741 742 def __init__(self, *a, **kw): 743 self.callback = kw.pop('callback') 744 self.defer = kw.pop('defer', None) 745 super(Condition, self).__init__(**kw) 746 747 def factory(self, objs): 748 '''Create a callback instance for this conditional.''' 749 750 args = { 751 'configfile': self.configfile, 752 'condition': self.name, 753 'class': 'callback', 754 'callback': 'conditional', 755 'instance': self.callback, 756 'name': self.name, 757 'defer': self.defer 758 } 759 760 callback = ConditionCallback(**args) 761 add_unique(callback, objs, config=self.configfile) 762 callback.factory(objs) 763 764 super(Condition, self).factory(objs) 765 766 767class CountCondition(Condition, Renderer): 768 '''Handle the count condition config file directive.''' 769 770 def __init__(self, *a, **kw): 771 self.countop = kw.pop('countop') 772 self.countbound = kw.pop('countbound') 773 self.op = kw.pop('op') 774 self.bound = kw.pop('bound') 775 self.oneshot = TrivialArgument( 776 type='boolean', 777 value=kw.pop('oneshot', False)) 778 super(CountCondition, self).__init__(**kw) 779 780 def setup(self, objs): 781 '''Resolve type.''' 782 783 super(CountCondition, self).setup(objs) 784 self.bound = TrivialArgument( 785 type=self.type, 786 value=self.bound) 787 788 def construct(self, loader, indent): 789 return self.render( 790 loader, 791 'count.mako.cpp', 792 c=self, 793 indent=indent) 794 795 796class MedianCondition(Condition, Renderer): 797 '''Handle the median condition config file directive.''' 798 799 def __init__(self, *a, **kw): 800 self.op = kw.pop('op') 801 self.bound = kw.pop('bound') 802 self.oneshot = TrivialArgument( 803 type='boolean', 804 value=kw.pop('oneshot', False)) 805 super(MedianCondition, self).__init__(**kw) 806 807 def setup(self, objs): 808 '''Resolve type.''' 809 810 super(MedianCondition, self).setup(objs) 811 self.bound = TrivialArgument( 812 type=self.type, 813 value=self.bound) 814 815 def construct(self, loader, indent): 816 return self.render( 817 loader, 818 'median.mako.cpp', 819 c=self, 820 indent=indent) 821 822 823class Journal(Callback, Renderer): 824 '''Handle the journal callback config file directive.''' 825 826 def __init__(self, *a, **kw): 827 self.severity = kw.pop('severity') 828 self.message = kw.pop('message') 829 super(Journal, self).__init__(**kw) 830 831 def construct(self, loader, indent): 832 return self.render( 833 loader, 834 'journal.mako.cpp', 835 c=self, 836 indent=indent) 837 838 839class Elog(Callback, Renderer): 840 '''Handle the elog callback config file directive.''' 841 842 def __init__(self, *a, **kw): 843 self.error = kw.pop('error') 844 self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})] 845 super(Elog, self).__init__(**kw) 846 847 def construct(self, loader, indent): 848 with open('errors.hpp', 'a') as fd: 849 fd.write( 850 self.render( 851 loader, 852 'errors.mako.hpp', 853 c=self)) 854 return self.render( 855 loader, 856 'elog.mako.cpp', 857 c=self, 858 indent=indent) 859 860class Event(Callback, Renderer): 861 '''Handle the event callback config file directive.''' 862 863 def __init__(self, *a, **kw): 864 self.eventName = kw.pop('eventName') 865 self.eventMessage = kw.pop('eventMessage') 866 super(Event, self).__init__(**kw) 867 868 def construct(self, loader, indent): 869 return self.render( 870 loader, 871 'event.mako.cpp', 872 c=self, 873 indent=indent) 874 875class EventPath(PathCallback, Renderer): 876 '''Handle the event path callback config file directive.''' 877 878 def __init__(self, *a, **kw): 879 self.eventType = kw.pop('eventType') 880 super(EventPath, self).__init__(**kw) 881 882 def construct(self, loader, indent): 883 return self.render( 884 loader, 885 'eventpath.mako.cpp', 886 c=self, 887 indent=indent) 888 889class ElogWithMetadata(Callback, Renderer): 890 '''Handle the elog_with_metadata callback config file directive.''' 891 892 def __init__(self, *a, **kw): 893 self.error = kw.pop('error') 894 self.metadata = kw.pop('metadata') 895 super(ElogWithMetadata, self).__init__(**kw) 896 897 def construct(self, loader, indent): 898 with open('errors.hpp', 'a') as fd: 899 fd.write( 900 self.render( 901 loader, 902 'errors.mako.hpp', 903 c=self)) 904 return self.render( 905 loader, 906 'elog_with_metadata.mako.cpp', 907 c=self, 908 indent=indent) 909 910 911class ResolveCallout(Callback, Renderer): 912 '''Handle the 'resolve callout' callback config file directive.''' 913 914 def __init__(self, *a, **kw): 915 self.callout = kw.pop('callout') 916 super(ResolveCallout, self).__init__(**kw) 917 918 def construct(self, loader, indent): 919 return self.render( 920 loader, 921 'resolve_errors.mako.cpp', 922 c=self, 923 indent=indent) 924 925 926class Method(ConfigEntry, Renderer): 927 '''Handle the method callback config file directive.''' 928 929 def __init__(self, *a, **kw): 930 self.service = kw.pop('service') 931 self.path = kw.pop('path') 932 self.interface = kw.pop('interface') 933 self.method = kw.pop('method') 934 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})] 935 super(Method, self).__init__(**kw) 936 937 def factory(self, objs): 938 args = { 939 'class': 'interface', 940 'interface': 'element', 941 'name': self.service 942 } 943 add_unique(ConfigEntry( 944 configfile=self.configfile, **args), objs) 945 946 args = { 947 'class': 'pathname', 948 'pathname': 'element', 949 'name': self.path 950 } 951 add_unique(ConfigEntry( 952 configfile=self.configfile, **args), objs) 953 954 args = { 955 'class': 'interface', 956 'interface': 'element', 957 'name': self.interface 958 } 959 add_unique(ConfigEntry( 960 configfile=self.configfile, **args), objs) 961 962 args = { 963 'class': 'propertyname', 964 'propertyname': 'element', 965 'name': self.method 966 } 967 add_unique(ConfigEntry( 968 configfile=self.configfile, **args), objs) 969 970 super(Method, self).factory(objs) 971 972 def setup(self, objs): 973 '''Resolve elements.''' 974 975 self.service = get_index( 976 objs, 977 'interface', 978 self.service) 979 980 self.path = get_index( 981 objs, 982 'pathname', 983 self.path) 984 985 self.interface = get_index( 986 objs, 987 'interface', 988 self.interface) 989 990 self.method = get_index( 991 objs, 992 'propertyname', 993 self.method) 994 995 super(Method, self).setup(objs) 996 997 def construct(self, loader, indent): 998 return self.render( 999 loader, 1000 'method.mako.cpp', 1001 c=self, 1002 indent=indent) 1003 1004 1005class CallbackGraphEntry(Group): 1006 '''An entry in a traversal list for groups of callbacks.''' 1007 1008 def __init__(self, *a, **kw): 1009 super(CallbackGraphEntry, self).__init__(**kw) 1010 1011 def setup(self, objs): 1012 '''Resolve group members.''' 1013 1014 def map_member(x): 1015 return get_index( 1016 objs, 'callback', x, config=self.configfile) 1017 1018 self.members = map( 1019 map_member, 1020 self.members) 1021 1022 super(CallbackGraphEntry, self).setup(objs) 1023 1024class PathCallbackGraphEntry(Group): 1025 '''An entry in a traversal list for groups of callbacks.''' 1026 1027 def __init__(self, *a, **kw): 1028 super(PathCallbackGraphEntry, self).__init__(**kw) 1029 1030 def setup(self, objs): 1031 '''Resolve group members.''' 1032 1033 def map_member(x): 1034 return get_index( 1035 objs, 'pathcallback', x, config=self.configfile) 1036 1037 self.members = map( 1038 map_member, 1039 self.members) 1040 1041 super(PathCallbackGraphEntry, self).setup(objs) 1042 1043class GroupOfCallbacks(ConfigEntry, Renderer): 1044 '''Handle the callback group config file directive.''' 1045 1046 def __init__(self, *a, **kw): 1047 self.members = kw.pop('members') 1048 super(GroupOfCallbacks, self).__init__(**kw) 1049 1050 def factory(self, objs): 1051 '''Create a graph instance for this group of callbacks.''' 1052 1053 args = { 1054 'configfile': self.configfile, 1055 'members': self.members, 1056 'class': 'callbackgroup', 1057 'callbackgroup': 'callback', 1058 'name': self.members 1059 } 1060 1061 entry = CallbackGraphEntry(**args) 1062 add_unique(entry, objs, config=self.configfile) 1063 1064 super(GroupOfCallbacks, self).factory(objs) 1065 1066 def setup(self, objs): 1067 '''Resolve graph entry.''' 1068 1069 self.graph = get_index( 1070 objs, 'callbackgroup', self.members, config=self.configfile) 1071 1072 super(GroupOfCallbacks, self).setup(objs) 1073 1074 def construct(self, loader, indent): 1075 return self.render( 1076 loader, 1077 'callbackgroup.mako.cpp', 1078 c=self, 1079 indent=indent) 1080 1081class GroupOfPathCallbacks(ConfigEntry, Renderer): 1082 '''Handle the callback group config file directive.''' 1083 1084 def __init__(self, *a, **kw): 1085 self.members = kw.pop('members') 1086 super(GroupOfPathCallbacks, self).__init__(**kw) 1087 1088 def factory(self, objs): 1089 '''Create a graph instance for this group of callbacks.''' 1090 1091 args = { 1092 'configfile': self.configfile, 1093 'members': self.members, 1094 'class': 'pathcallbackgroup', 1095 'pathcallbackgroup': 'pathcallback', 1096 'name': self.members 1097 } 1098 1099 entry = PathCallbackGraphEntry(**args) 1100 add_unique(entry, objs, config=self.configfile) 1101 super(GroupOfPathCallbacks, self).factory(objs) 1102 1103 def setup(self, objs): 1104 '''Resolve graph entry.''' 1105 1106 self.graph = get_index( 1107 objs, 'callbackpathgroup', self.members, config=self.configfile) 1108 1109 super(GroupOfPathCallbacks, self).setup(objs) 1110 1111 def construct(self, loader, indent): 1112 return self.render( 1113 loader, 1114 'callbackpathgroup.mako.cpp', 1115 c=self, 1116 indent=indent) 1117 1118class Everything(Renderer): 1119 '''Parse/render entry point.''' 1120 1121 @staticmethod 1122 def classmap(cls, sub=None): 1123 '''Map render item class and subclass entries to the appropriate 1124 handler methods.''' 1125 class_map = { 1126 'path': { 1127 'element': Path, 1128 }, 1129 'pathgroup': { 1130 'path': GroupOfPaths, 1131 }, 1132 'propertygroup': { 1133 'property': GroupOfProperties, 1134 }, 1135 'property': { 1136 'element': Property, 1137 }, 1138 'watch': { 1139 'property': PropertyWatch, 1140 }, 1141 'pathwatch': { 1142 'path': PathWatch, 1143 }, 1144 'instance': { 1145 'element': Instance, 1146 }, 1147 'pathinstance': { 1148 'element': PathInstance, 1149 }, 1150 'callback': { 1151 'journal': Journal, 1152 'elog': Elog, 1153 'elog_with_metadata': ElogWithMetadata, 1154 'event': Event, 1155 'group': GroupOfCallbacks, 1156 'method': Method, 1157 'resolve callout': ResolveCallout, 1158 }, 1159 'pathcallback': { 1160 'eventpath': EventPath, 1161 'grouppath': GroupOfPathCallbacks, 1162 }, 1163 'condition': { 1164 'count': CountCondition, 1165 'median': MedianCondition, 1166 }, 1167 } 1168 1169 if cls not in class_map: 1170 raise NotImplementedError('Unknown class: "{0}"'.format(cls)) 1171 if sub not in class_map[cls]: 1172 raise NotImplementedError('Unknown {0} type: "{1}"'.format( 1173 cls, sub)) 1174 1175 return class_map[cls][sub] 1176 1177 @staticmethod 1178 def load_one_yaml(path, fd, objs): 1179 '''Parse a single YAML file. Parsing occurs in three phases. 1180 In the first phase a factory method associated with each 1181 configuration file directive is invoked. These factory 1182 methods generate more factory methods. In the second 1183 phase the factory methods created in the first phase 1184 are invoked. In the last phase a callback is invoked on 1185 each object created in phase two. Typically the callback 1186 resolves references to other configuration file directives.''' 1187 1188 factory_objs = {} 1189 for x in yaml.safe_load(fd.read()) or {}: 1190 1191 # Create factory object for this config file directive. 1192 cls = x['class'] 1193 sub = x.get(cls) 1194 if cls == 'group': 1195 cls = '{0}group'.format(sub) 1196 1197 factory = Everything.classmap(cls, sub) 1198 obj = factory(configfile=path, **x) 1199 1200 # For a given class of directive, validate the file 1201 # doesn't have any duplicate names (duplicates are 1202 # ok across config files). 1203 if exists(factory_objs, obj.cls, obj.name, config=path): 1204 raise NotUniqueError(path, cls, obj.name) 1205 1206 factory_objs.setdefault(cls, []).append(obj) 1207 objs.setdefault(cls, []).append(obj) 1208 1209 for cls, items in factory_objs.items(): 1210 for obj in items: 1211 # Add objects for template consumption. 1212 obj.factory(objs) 1213 1214 @staticmethod 1215 def load(args): 1216 '''Aggregate all the YAML in the input directory 1217 into a single aggregate.''' 1218 1219 objs = {} 1220 yaml_files = filter( 1221 lambda x: x.endswith('.yaml'), 1222 os.listdir(args.inputdir)) 1223 1224 yaml_files.sort() 1225 1226 for x in yaml_files: 1227 path = os.path.join(args.inputdir, x) 1228 with open(path, 'r') as fd: 1229 Everything.load_one_yaml(path, fd, objs) 1230 1231 # Configuration file directives reference each other via 1232 # the name attribute; however, when rendered the reference 1233 # is just an array index. 1234 # 1235 # At this point all objects have been created but references 1236 # have not been resolved to array indices. Instruct objects 1237 # to do that now. 1238 for cls, items in objs.items(): 1239 for obj in items: 1240 obj.setup(objs) 1241 1242 return Everything(**objs) 1243 1244 def __init__(self, *a, **kw): 1245 self.pathmeta = kw.pop('path', []) 1246 self.paths = kw.pop('pathname', []) 1247 self.meta = kw.pop('meta', []) 1248 self.pathgroups = kw.pop('pathgroup', []) 1249 self.interfaces = kw.pop('interface', []) 1250 self.properties = kw.pop('property', []) 1251 self.propertynames = kw.pop('propertyname', []) 1252 self.propertygroups = kw.pop('propertygroup', []) 1253 self.instances = kw.pop('instance', []) 1254 self.pathinstances = kw.pop('pathinstance', []) 1255 self.instancegroups = kw.pop('instancegroup', []) 1256 self.pathinstancegroups = kw.pop('pathinstancegroup', []) 1257 self.watches = kw.pop('watch', []) 1258 self.pathwatches = kw.pop('pathwatch', []) 1259 self.callbacks = kw.pop('callback', []) 1260 self.pathcallbacks = kw.pop('pathcallback', []) 1261 self.callbackgroups = kw.pop('callbackgroup', []) 1262 self.pathcallbackgroups = kw.pop('pathcallbackgroup', []) 1263 self.conditions = kw.pop('condition', []) 1264 1265 super(Everything, self).__init__(**kw) 1266 1267 def generate_cpp(self, loader): 1268 '''Render the template with the provided data.''' 1269 # errors.hpp is used by generated.hpp to included any error.hpp files 1270 open('errors.hpp', 'w+') 1271 1272 with open(args.output, 'w') as fd: 1273 fd.write( 1274 self.render( 1275 loader, 1276 args.template, 1277 meta=self.meta, 1278 properties=self.properties, 1279 propertynames=self.propertynames, 1280 interfaces=self.interfaces, 1281 paths=self.paths, 1282 pathmeta=self.pathmeta, 1283 pathgroups=self.pathgroups, 1284 propertygroups=self.propertygroups, 1285 instances=self.instances, 1286 pathinstances=self.pathinstances, 1287 watches=self.watches, 1288 pathwatches=self.pathwatches, 1289 instancegroups=self.instancegroups, 1290 pathinstancegroups=self.pathinstancegroups, 1291 callbacks=self.callbacks, 1292 pathcallbacks=self.pathcallbacks, 1293 callbackgroups=self.callbackgroups, 1294 pathcallbackgroups=self.pathcallbackgroups, 1295 conditions=self.conditions, 1296 indent=Indent())) 1297 1298if __name__ == '__main__': 1299 script_dir = os.path.dirname(os.path.realpath(__file__)) 1300 valid_commands = { 1301 'generate-cpp': 'generate_cpp', 1302 } 1303 1304 parser = ArgumentParser( 1305 description='Phosphor DBus Monitor (PDM) YAML ' 1306 'scanner and code generator.') 1307 1308 parser.add_argument( 1309 "-o", "--out", dest="output", 1310 default='generated.cpp', 1311 help="Generated output file name and path.") 1312 parser.add_argument( 1313 '-t', '--template', dest='template', 1314 default='generated.mako.hpp', 1315 help='The top level template to render.') 1316 parser.add_argument( 1317 '-p', '--template-path', dest='template_search', 1318 default=script_dir, 1319 help='The space delimited mako template search path.') 1320 parser.add_argument( 1321 '-d', '--dir', dest='inputdir', 1322 default=os.path.join(script_dir, 'example'), 1323 help='Location of files to process.') 1324 parser.add_argument( 1325 'command', metavar='COMMAND', type=str, 1326 choices=valid_commands.keys(), 1327 help='%s.' % " | ".join(valid_commands.keys())) 1328 1329 args = parser.parse_args() 1330 1331 if sys.version_info < (3, 0): 1332 lookup = mako.lookup.TemplateLookup( 1333 directories=args.template_search.split(), 1334 disable_unicode=True) 1335 else: 1336 lookup = mako.lookup.TemplateLookup( 1337 directories=args.template_search.split()) 1338 try: 1339 function = getattr( 1340 Everything.load(args), 1341 valid_commands[args.command]) 1342 function(lookup) 1343 except InvalidConfigError as e: 1344 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg)) 1345 raise 1346