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