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 inteface.''' 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 357 358class Group(ConfigEntry): 359 '''Pop the members keyword for groups.''' 360 361 def __init__(self, *a, **kw): 362 self.members = kw.pop('members') 363 super(Group, self).__init__(**kw) 364 365 366class ImplicitGroup(Group): 367 '''Provide a factory method for groups whose members are 368 not explicitly declared in the config files.''' 369 370 def __init__(self, *a, **kw): 371 super(ImplicitGroup, self).__init__(**kw) 372 373 def factory(self, objs): 374 '''Create group members.''' 375 376 factory = Everything.classmap(self.subclass, 'element') 377 for m in self.members: 378 args = { 379 'class': self.subclass, 380 self.subclass: 'element', 381 'name': m 382 } 383 384 obj = factory(configfile=self.configfile, **args) 385 add_unique(obj, objs) 386 obj.factory(objs) 387 388 super(ImplicitGroup, self).factory(objs) 389 390 391class GroupOfPaths(ImplicitGroup): 392 '''Path group config file directive.''' 393 394 def __init__(self, *a, **kw): 395 super(GroupOfPaths, self).__init__(**kw) 396 397 def setup(self, objs): 398 '''Resolve group members.''' 399 400 def map_member(x): 401 path = get_index( 402 objs, 'pathname', x['path']) 403 meta = get_index( 404 objs, 'meta', x['meta']) 405 return (path, meta) 406 407 self.members = map( 408 map_member, 409 self.members) 410 411 super(GroupOfPaths, self).setup(objs) 412 413 414class GroupOfProperties(ImplicitGroup): 415 '''Property group config file directive.''' 416 417 def __init__(self, *a, **kw): 418 self.type = kw.pop('type') 419 self.datatype = sdbusplus.property.Property( 420 name=kw.get('name'), 421 type=self.type).cppTypeName 422 423 super(GroupOfProperties, self).__init__(**kw) 424 425 def setup(self, objs): 426 '''Resolve group members.''' 427 428 def map_member(x): 429 iface = get_index( 430 objs, 'interface', x['interface']) 431 prop = get_index( 432 objs, 'propertyname', x['property']) 433 meta = get_index( 434 objs, 'meta', x['meta']) 435 436 return (iface, prop, meta) 437 438 self.members = map( 439 map_member, 440 self.members) 441 442 super(GroupOfProperties, self).setup(objs) 443 444 445class GroupOfInstances(ImplicitGroup): 446 '''A group of property instances.''' 447 448 def __init__(self, *a, **kw): 449 super(GroupOfInstances, self).__init__(**kw) 450 451 def setup(self, objs): 452 '''Resolve group members.''' 453 454 def map_member(x): 455 path = get_index(objs, 'pathname', x['path']['path']) 456 pathmeta = get_index(objs, 'meta', x['path']['meta']) 457 interface = get_index( 458 objs, 'interface', x['property']['interface']) 459 prop = get_index(objs, 'propertyname', x['property']['property']) 460 propmeta = get_index(objs, 'meta', x['property']['meta']) 461 instance = get_index(objs, 'instance', x) 462 463 return (path, pathmeta, interface, prop, propmeta, instance) 464 465 self.members = map( 466 map_member, 467 self.members) 468 469 super(GroupOfInstances, self).setup(objs) 470 471 472class HasPropertyIndex(ConfigEntry): 473 '''Handle config file directives that require an index to be 474 constructed.''' 475 476 def __init__(self, *a, **kw): 477 self.paths = kw.pop('paths') 478 self.properties = kw.pop('properties') 479 super(HasPropertyIndex, self).__init__(**kw) 480 481 def factory(self, objs): 482 '''Create a group of instances for this index.''' 483 484 members = [] 485 path_group = get_index( 486 objs, 'pathgroup', self.paths, config=self.configfile) 487 property_group = get_index( 488 objs, 'propertygroup', self.properties, config=self.configfile) 489 490 for path in objs['pathgroup'][path_group].members: 491 for prop in objs['propertygroup'][property_group].members: 492 member = { 493 'path': path, 494 'property': prop, 495 } 496 members.append(member) 497 498 args = { 499 'members': members, 500 'class': 'instancegroup', 501 'instancegroup': 'instance', 502 'name': '{0} {1}'.format(self.paths, self.properties) 503 } 504 505 group = GroupOfInstances(configfile=self.configfile, **args) 506 add_unique(group, objs, config=self.configfile) 507 group.factory(objs) 508 509 super(HasPropertyIndex, self).factory(objs) 510 511 def setup(self, objs): 512 '''Resolve path, property, and instance groups.''' 513 514 self.instances = get_index( 515 objs, 516 'instancegroup', 517 '{0} {1}'.format(self.paths, self.properties), 518 config=self.configfile) 519 self.paths = get_index( 520 objs, 521 'pathgroup', 522 self.paths, 523 config=self.configfile) 524 self.properties = get_index( 525 objs, 526 'propertygroup', 527 self.properties, 528 config=self.configfile) 529 self.datatype = objs['propertygroup'][self.properties].datatype 530 self.type = objs['propertygroup'][self.properties].type 531 532 super(HasPropertyIndex, self).setup(objs) 533 534 535class PropertyWatch(HasPropertyIndex): 536 '''Handle the property watch config file directive.''' 537 538 def __init__(self, *a, **kw): 539 self.callback = kw.pop('callback', None) 540 super(PropertyWatch, self).__init__(**kw) 541 542 def setup(self, objs): 543 '''Resolve optional callback.''' 544 545 if self.callback: 546 self.callback = get_index( 547 objs, 548 'callback', 549 self.callback, 550 config=self.configfile) 551 552 super(PropertyWatch, self).setup(objs) 553 554 555class Callback(HasPropertyIndex): 556 '''Interface and common logic for callbacks.''' 557 558 def __init__(self, *a, **kw): 559 super(Callback, self).__init__(**kw) 560 561 562class ConditionCallback(ConfigEntry, Renderer): 563 '''Handle the journal callback config file directive.''' 564 565 def __init__(self, *a, **kw): 566 self.condition = kw.pop('condition') 567 self.instance = kw.pop('instance') 568 self.defer = kw.pop('defer', None) 569 super(ConditionCallback, self).__init__(**kw) 570 571 def factory(self, objs): 572 '''Create a graph instance for this callback.''' 573 574 args = { 575 'configfile': self.configfile, 576 'members': [self.instance], 577 'class': 'callbackgroup', 578 'callbackgroup': 'callback', 579 'name': [self.instance] 580 } 581 582 entry = CallbackGraphEntry(**args) 583 add_unique(entry, objs, config=self.configfile) 584 585 super(ConditionCallback, self).factory(objs) 586 587 def setup(self, objs): 588 '''Resolve condition and graph entry.''' 589 590 self.graph = get_index( 591 objs, 592 'callbackgroup', 593 [self.instance], 594 config=self.configfile) 595 596 self.condition = get_index( 597 objs, 598 'condition', 599 self.name, 600 config=self.configfile) 601 602 super(ConditionCallback, self).setup(objs) 603 604 def construct(self, loader, indent): 605 return self.render( 606 loader, 607 'conditional.mako.cpp', 608 c=self, 609 indent=indent) 610 611 612class Condition(HasPropertyIndex): 613 '''Interface and common logic for conditions.''' 614 615 def __init__(self, *a, **kw): 616 self.callback = kw.pop('callback') 617 self.defer = kw.pop('defer', None) 618 super(Condition, self).__init__(**kw) 619 620 def factory(self, objs): 621 '''Create a callback instance for this conditional.''' 622 623 args = { 624 'configfile': self.configfile, 625 'condition': self.name, 626 'class': 'callback', 627 'callback': 'conditional', 628 'instance': self.callback, 629 'name': self.name, 630 'defer': self.defer 631 } 632 633 callback = ConditionCallback(**args) 634 add_unique(callback, objs, config=self.configfile) 635 callback.factory(objs) 636 637 super(Condition, self).factory(objs) 638 639 640class CountCondition(Condition, Renderer): 641 '''Handle the count condition config file directive.''' 642 643 def __init__(self, *a, **kw): 644 self.countop = kw.pop('countop') 645 self.countbound = kw.pop('countbound') 646 self.op = kw.pop('op') 647 self.bound = kw.pop('bound') 648 self.oneshot = TrivialArgument( 649 type='boolean', 650 value=kw.pop('oneshot', False)) 651 super(CountCondition, self).__init__(**kw) 652 653 def setup(self, objs): 654 '''Resolve type.''' 655 656 super(CountCondition, self).setup(objs) 657 self.bound = TrivialArgument( 658 type=self.type, 659 value=self.bound) 660 661 def construct(self, loader, indent): 662 return self.render( 663 loader, 664 'count.mako.cpp', 665 c=self, 666 indent=indent) 667 668 669class Journal(Callback, Renderer): 670 '''Handle the journal callback config file directive.''' 671 672 def __init__(self, *a, **kw): 673 self.severity = kw.pop('severity') 674 self.message = kw.pop('message') 675 super(Journal, self).__init__(**kw) 676 677 def construct(self, loader, indent): 678 return self.render( 679 loader, 680 'journal.mako.cpp', 681 c=self, 682 indent=indent) 683 684 685class Elog(Callback, Renderer): 686 '''Handle the elog callback config file directive.''' 687 688 def __init__(self, *a, **kw): 689 self.error = kw.pop('error') 690 self.metadata = [Metadata(**x) for x in kw.pop('metadata', {})] 691 super(Elog, self).__init__(**kw) 692 693 def construct(self, loader, indent): 694 with open('errors.hpp', 'a') as fd: 695 fd.write( 696 self.render( 697 loader, 698 'errors.mako.hpp', 699 c=self)) 700 return self.render( 701 loader, 702 'elog.mako.cpp', 703 c=self, 704 indent=indent) 705 706class Event(Callback, Renderer): 707 '''Handle the event callback config file directive.''' 708 709 def __init__(self, *a, **kw): 710 self.eventName = kw.pop('eventName') 711 self.eventMessage = kw.pop('eventMessage') 712 super(Event, self).__init__(**kw) 713 714 def construct(self, loader, indent): 715 return self.render( 716 loader, 717 'event.mako.cpp', 718 c=self, 719 indent=indent) 720 721 722class ElogWithMetadata(Callback, Renderer): 723 '''Handle the elog_with_metadata callback config file directive.''' 724 725 def __init__(self, *a, **kw): 726 self.error = kw.pop('error') 727 self.metadata = kw.pop('metadata') 728 super(ElogWithMetadata, self).__init__(**kw) 729 730 def construct(self, loader, indent): 731 with open('errors.hpp', 'a') as fd: 732 fd.write( 733 self.render( 734 loader, 735 'errors.mako.hpp', 736 c=self)) 737 return self.render( 738 loader, 739 'elog_with_metadata.mako.cpp', 740 c=self, 741 indent=indent) 742 743 744class ResolveCallout(Callback, Renderer): 745 '''Handle the 'resolve callout' callback config file directive.''' 746 747 def __init__(self, *a, **kw): 748 self.callout = kw.pop('callout') 749 super(ResolveCallout, self).__init__(**kw) 750 751 def construct(self, loader, indent): 752 return self.render( 753 loader, 754 'resolve_errors.mako.cpp', 755 c=self, 756 indent=indent) 757 758 759class Method(ConfigEntry, Renderer): 760 '''Handle the method callback config file directive.''' 761 762 def __init__(self, *a, **kw): 763 self.service = kw.pop('service') 764 self.path = kw.pop('path') 765 self.interface = kw.pop('interface') 766 self.method = kw.pop('method') 767 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})] 768 super(Method, self).__init__(**kw) 769 770 def factory(self, objs): 771 args = { 772 'class': 'interface', 773 'interface': 'element', 774 'name': self.service 775 } 776 add_unique(ConfigEntry( 777 configfile=self.configfile, **args), objs) 778 779 args = { 780 'class': 'pathname', 781 'pathname': 'element', 782 'name': self.path 783 } 784 add_unique(ConfigEntry( 785 configfile=self.configfile, **args), objs) 786 787 args = { 788 'class': 'interface', 789 'interface': 'element', 790 'name': self.interface 791 } 792 add_unique(ConfigEntry( 793 configfile=self.configfile, **args), objs) 794 795 args = { 796 'class': 'propertyname', 797 'propertyname': 'element', 798 'name': self.method 799 } 800 add_unique(ConfigEntry( 801 configfile=self.configfile, **args), objs) 802 803 super(Method, self).factory(objs) 804 805 def setup(self, objs): 806 '''Resolve elements.''' 807 808 self.service = get_index( 809 objs, 810 'interface', 811 self.service) 812 813 self.path = get_index( 814 objs, 815 'pathname', 816 self.path) 817 818 self.interface = get_index( 819 objs, 820 'interface', 821 self.interface) 822 823 self.method = get_index( 824 objs, 825 'propertyname', 826 self.method) 827 828 super(Method, self).setup(objs) 829 830 def construct(self, loader, indent): 831 return self.render( 832 loader, 833 'method.mako.cpp', 834 c=self, 835 indent=indent) 836 837 838class CallbackGraphEntry(Group): 839 '''An entry in a traversal list for groups of callbacks.''' 840 841 def __init__(self, *a, **kw): 842 super(CallbackGraphEntry, self).__init__(**kw) 843 844 def setup(self, objs): 845 '''Resolve group members.''' 846 847 def map_member(x): 848 return get_index( 849 objs, 'callback', x, config=self.configfile) 850 851 self.members = map( 852 map_member, 853 self.members) 854 855 super(CallbackGraphEntry, self).setup(objs) 856 857 858class GroupOfCallbacks(ConfigEntry, Renderer): 859 '''Handle the callback group config file directive.''' 860 861 def __init__(self, *a, **kw): 862 self.members = kw.pop('members') 863 super(GroupOfCallbacks, self).__init__(**kw) 864 865 def factory(self, objs): 866 '''Create a graph instance for this group of callbacks.''' 867 868 args = { 869 'configfile': self.configfile, 870 'members': self.members, 871 'class': 'callbackgroup', 872 'callbackgroup': 'callback', 873 'name': self.members 874 } 875 876 entry = CallbackGraphEntry(**args) 877 add_unique(entry, objs, config=self.configfile) 878 879 super(GroupOfCallbacks, self).factory(objs) 880 881 def setup(self, objs): 882 '''Resolve graph entry.''' 883 884 self.graph = get_index( 885 objs, 'callbackgroup', self.members, config=self.configfile) 886 887 super(GroupOfCallbacks, self).setup(objs) 888 889 def construct(self, loader, indent): 890 return self.render( 891 loader, 892 'callbackgroup.mako.cpp', 893 c=self, 894 indent=indent) 895 896 897class Everything(Renderer): 898 '''Parse/render entry point.''' 899 900 @staticmethod 901 def classmap(cls, sub=None): 902 '''Map render item class and subclass entries to the appropriate 903 handler methods.''' 904 905 class_map = { 906 'path': { 907 'element': Path, 908 }, 909 'pathgroup': { 910 'path': GroupOfPaths, 911 }, 912 'propertygroup': { 913 'property': GroupOfProperties, 914 }, 915 'property': { 916 'element': Property, 917 }, 918 'watch': { 919 'property': PropertyWatch, 920 }, 921 'instance': { 922 'element': Instance, 923 }, 924 'callback': { 925 'journal': Journal, 926 'elog': Elog, 927 'elog_with_metadata': ElogWithMetadata, 928 'event': Event, 929 'group': GroupOfCallbacks, 930 'method': Method, 931 'resolve callout': ResolveCallout, 932 }, 933 'condition': { 934 'count': CountCondition, 935 }, 936 } 937 938 if cls not in class_map: 939 raise NotImplementedError('Unknown class: "{0}"'.format(cls)) 940 if sub not in class_map[cls]: 941 raise NotImplementedError('Unknown {0} type: "{1}"'.format( 942 cls, sub)) 943 944 return class_map[cls][sub] 945 946 @staticmethod 947 def load_one_yaml(path, fd, objs): 948 '''Parse a single YAML file. Parsing occurs in three phases. 949 In the first phase a factory method associated with each 950 configuration file directive is invoked. These factory 951 methods generate more factory methods. In the second 952 phase the factory methods created in the first phase 953 are invoked. In the last phase a callback is invoked on 954 each object created in phase two. Typically the callback 955 resolves references to other configuration file directives.''' 956 957 factory_objs = {} 958 for x in yaml.safe_load(fd.read()) or {}: 959 960 # Create factory object for this config file directive. 961 cls = x['class'] 962 sub = x.get(cls) 963 if cls == 'group': 964 cls = '{0}group'.format(sub) 965 966 factory = Everything.classmap(cls, sub) 967 obj = factory(configfile=path, **x) 968 969 # For a given class of directive, validate the file 970 # doesn't have any duplicate names (duplicates are 971 # ok across config files). 972 if exists(factory_objs, obj.cls, obj.name, config=path): 973 raise NotUniqueError(path, cls, obj.name) 974 975 factory_objs.setdefault(cls, []).append(obj) 976 objs.setdefault(cls, []).append(obj) 977 978 for cls, items in factory_objs.items(): 979 for obj in items: 980 # Add objects for template consumption. 981 obj.factory(objs) 982 983 @staticmethod 984 def load(args): 985 '''Aggregate all the YAML in the input directory 986 into a single aggregate.''' 987 988 objs = {} 989 yaml_files = filter( 990 lambda x: x.endswith('.yaml'), 991 os.listdir(args.inputdir)) 992 993 yaml_files.sort() 994 995 for x in yaml_files: 996 path = os.path.join(args.inputdir, x) 997 with open(path, 'r') as fd: 998 Everything.load_one_yaml(path, fd, objs) 999 1000 # Configuration file directives reference each other via 1001 # the name attribute; however, when rendered the reference 1002 # is just an array index. 1003 # 1004 # At this point all objects have been created but references 1005 # have not been resolved to array indices. Instruct objects 1006 # to do that now. 1007 for cls, items in objs.items(): 1008 for obj in items: 1009 obj.setup(objs) 1010 1011 return Everything(**objs) 1012 1013 def __init__(self, *a, **kw): 1014 self.pathmeta = kw.pop('path', []) 1015 self.paths = kw.pop('pathname', []) 1016 self.meta = kw.pop('meta', []) 1017 self.pathgroups = kw.pop('pathgroup', []) 1018 self.interfaces = kw.pop('interface', []) 1019 self.properties = kw.pop('property', []) 1020 self.propertynames = kw.pop('propertyname', []) 1021 self.propertygroups = kw.pop('propertygroup', []) 1022 self.instances = kw.pop('instance', []) 1023 self.instancegroups = kw.pop('instancegroup', []) 1024 self.watches = kw.pop('watch', []) 1025 self.callbacks = kw.pop('callback', []) 1026 self.callbackgroups = kw.pop('callbackgroup', []) 1027 self.conditions = kw.pop('condition', []) 1028 1029 super(Everything, self).__init__(**kw) 1030 1031 def generate_cpp(self, loader): 1032 '''Render the template with the provided data.''' 1033 # errors.hpp is used by generated.hpp to included any error.hpp files 1034 open('errors.hpp', 'w+') 1035 1036 with open(args.output, 'w') as fd: 1037 fd.write( 1038 self.render( 1039 loader, 1040 args.template, 1041 meta=self.meta, 1042 properties=self.properties, 1043 propertynames=self.propertynames, 1044 interfaces=self.interfaces, 1045 paths=self.paths, 1046 pathmeta=self.pathmeta, 1047 pathgroups=self.pathgroups, 1048 propertygroups=self.propertygroups, 1049 instances=self.instances, 1050 watches=self.watches, 1051 instancegroups=self.instancegroups, 1052 callbacks=self.callbacks, 1053 callbackgroups=self.callbackgroups, 1054 conditions=self.conditions, 1055 indent=Indent())) 1056 1057if __name__ == '__main__': 1058 script_dir = os.path.dirname(os.path.realpath(__file__)) 1059 valid_commands = { 1060 'generate-cpp': 'generate_cpp', 1061 } 1062 1063 parser = ArgumentParser( 1064 description='Phosphor DBus Monitor (PDM) YAML ' 1065 'scanner and code generator.') 1066 1067 parser.add_argument( 1068 "-o", "--out", dest="output", 1069 default='generated.cpp', 1070 help="Generated output file name and path.") 1071 parser.add_argument( 1072 '-t', '--template', dest='template', 1073 default='generated.mako.hpp', 1074 help='The top level template to render.') 1075 parser.add_argument( 1076 '-p', '--template-path', dest='template_search', 1077 default=script_dir, 1078 help='The space delimited mako template search path.') 1079 parser.add_argument( 1080 '-d', '--dir', dest='inputdir', 1081 default=os.path.join(script_dir, 'example'), 1082 help='Location of files to process.') 1083 parser.add_argument( 1084 'command', metavar='COMMAND', type=str, 1085 choices=valid_commands.keys(), 1086 help='%s.' % " | ".join(valid_commands.keys())) 1087 1088 args = parser.parse_args() 1089 1090 if sys.version_info < (3, 0): 1091 lookup = mako.lookup.TemplateLookup( 1092 directories=args.template_search.split(), 1093 disable_unicode=True) 1094 else: 1095 lookup = mako.lookup.TemplateLookup( 1096 directories=args.template_search.split()) 1097 try: 1098 function = getattr( 1099 Everything.load(args), 1100 valid_commands[args.command]) 1101 function(lookup) 1102 except InvalidConfigError as e: 1103 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg)) 1104 raise 1105