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