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