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 indicies.''' 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 indicies.''' 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 indicies.''' 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 Method(ConfigEntry, Renderer): 703 '''Handle the method callback config file directive.''' 704 705 def __init__(self, *a, **kw): 706 self.service = kw.pop('service') 707 self.path = kw.pop('path') 708 self.interface = kw.pop('interface') 709 self.method = kw.pop('method') 710 self.args = [TrivialArgument(**x) for x in kw.pop('args', {})] 711 super(Method, self).__init__(**kw) 712 713 def factory(self, objs): 714 args = { 715 'class': 'interface', 716 'interface': 'element', 717 'name': self.service 718 } 719 add_unique(ConfigEntry( 720 configfile=self.configfile, **args), objs) 721 722 args = { 723 'class': 'pathname', 724 'pathname': 'element', 725 'name': self.path 726 } 727 add_unique(ConfigEntry( 728 configfile=self.configfile, **args), objs) 729 730 args = { 731 'class': 'interface', 732 'interface': 'element', 733 'name': self.interface 734 } 735 add_unique(ConfigEntry( 736 configfile=self.configfile, **args), objs) 737 738 args = { 739 'class': 'propertyname', 740 'propertyname': 'element', 741 'name': self.method 742 } 743 add_unique(ConfigEntry( 744 configfile=self.configfile, **args), objs) 745 746 super(Method, self).factory(objs) 747 748 def setup(self, objs): 749 '''Resolve elements.''' 750 751 self.service = get_index( 752 objs, 753 'interface', 754 self.service) 755 756 self.path = get_index( 757 objs, 758 'pathname', 759 self.path) 760 761 self.interface = get_index( 762 objs, 763 'interface', 764 self.interface) 765 766 self.method = get_index( 767 objs, 768 'propertyname', 769 self.method) 770 771 super(Method, self).setup(objs) 772 773 def construct(self, loader, indent): 774 return self.render( 775 loader, 776 'method.mako.cpp', 777 c=self, 778 indent=indent) 779 780 781class CallbackGraphEntry(Group): 782 '''An entry in a traversal list for groups of callbacks.''' 783 784 def __init__(self, *a, **kw): 785 super(CallbackGraphEntry, self).__init__(**kw) 786 787 def setup(self, objs): 788 '''Resolve group members.''' 789 790 def map_member(x): 791 return get_index( 792 objs, 'callback', x, config=self.configfile) 793 794 self.members = map( 795 map_member, 796 self.members) 797 798 super(CallbackGraphEntry, self).setup(objs) 799 800 801class GroupOfCallbacks(ConfigEntry, Renderer): 802 '''Handle the callback group config file directive.''' 803 804 def __init__(self, *a, **kw): 805 self.members = kw.pop('members') 806 super(GroupOfCallbacks, self).__init__(**kw) 807 808 def factory(self, objs): 809 '''Create a graph instance for this group of callbacks.''' 810 811 args = { 812 'configfile': self.configfile, 813 'members': self.members, 814 'class': 'callbackgroup', 815 'callbackgroup': 'callback', 816 'name': self.members 817 } 818 819 entry = CallbackGraphEntry(**args) 820 add_unique(entry, objs, config=self.configfile) 821 822 super(GroupOfCallbacks, self).factory(objs) 823 824 def setup(self, objs): 825 '''Resolve graph entry.''' 826 827 self.graph = get_index( 828 objs, 'callbackgroup', self.members, config=self.configfile) 829 830 super(GroupOfCallbacks, self).setup(objs) 831 832 def construct(self, loader, indent): 833 return self.render( 834 loader, 835 'callbackgroup.mako.cpp', 836 c=self, 837 indent=indent) 838 839 840class Everything(Renderer): 841 '''Parse/render entry point.''' 842 843 @staticmethod 844 def classmap(cls, sub=None): 845 '''Map render item class and subclass entries to the appropriate 846 handler methods.''' 847 848 class_map = { 849 'path': { 850 'element': Path, 851 }, 852 'pathgroup': { 853 'path': GroupOfPaths, 854 }, 855 'propertygroup': { 856 'property': GroupOfProperties, 857 }, 858 'property': { 859 'element': Property, 860 }, 861 'watch': { 862 'property': PropertyWatch, 863 }, 864 'instance': { 865 'element': Instance, 866 }, 867 'callback': { 868 'journal': Journal, 869 'elog': Elog, 870 'group': GroupOfCallbacks, 871 'method': Method, 872 }, 873 'condition': { 874 'count': CountCondition, 875 }, 876 } 877 878 if cls not in class_map: 879 raise NotImplementedError('Unknown class: "{0}"'.format(cls)) 880 if sub not in class_map[cls]: 881 raise NotImplementedError('Unknown {0} type: "{1}"'.format( 882 cls, sub)) 883 884 return class_map[cls][sub] 885 886 @staticmethod 887 def load_one_yaml(path, fd, objs): 888 '''Parse a single YAML file. Parsing occurs in three phases. 889 In the first phase a factory method associated with each 890 configuration file directive is invoked. These factory 891 methods generate more factory methods. In the second 892 phase the factory methods created in the first phase 893 are invoked. In the last phase a callback is invoked on 894 each object created in phase two. Typically the callback 895 resolves references to other configuration file directives.''' 896 897 factory_objs = {} 898 for x in yaml.safe_load(fd.read()) or {}: 899 900 # Create factory object for this config file directive. 901 cls = x['class'] 902 sub = x.get(cls) 903 if cls == 'group': 904 cls = '{0}group'.format(sub) 905 906 factory = Everything.classmap(cls, sub) 907 obj = factory(configfile=path, **x) 908 909 # For a given class of directive, validate the file 910 # doesn't have any duplicate names (duplicates are 911 # ok across config files). 912 if exists(factory_objs, obj.cls, obj.name, config=path): 913 raise NotUniqueError(path, cls, obj.name) 914 915 factory_objs.setdefault(cls, []).append(obj) 916 objs.setdefault(cls, []).append(obj) 917 918 for cls, items in factory_objs.items(): 919 for obj in items: 920 # Add objects for template consumption. 921 obj.factory(objs) 922 923 @staticmethod 924 def load(args): 925 '''Aggregate all the YAML in the input directory 926 into a single aggregate.''' 927 928 objs = {} 929 yaml_files = filter( 930 lambda x: x.endswith('.yaml'), 931 os.listdir(args.inputdir)) 932 933 yaml_files.sort() 934 935 for x in yaml_files: 936 path = os.path.join(args.inputdir, x) 937 with open(path, 'r') as fd: 938 Everything.load_one_yaml(path, fd, objs) 939 940 # Configuration file directives reference each other via 941 # the name attribute; however, when rendered the reference 942 # is just an array index. 943 # 944 # At this point all objects have been created but references 945 # have not been resolved to array indicies. Instruct objects 946 # to do that now. 947 for cls, items in objs.items(): 948 for obj in items: 949 obj.setup(objs) 950 951 return Everything(**objs) 952 953 def __init__(self, *a, **kw): 954 self.pathmeta = kw.pop('path', []) 955 self.paths = kw.pop('pathname', []) 956 self.meta = kw.pop('meta', []) 957 self.pathgroups = kw.pop('pathgroup', []) 958 self.interfaces = kw.pop('interface', []) 959 self.properties = kw.pop('property', []) 960 self.propertynames = kw.pop('propertyname', []) 961 self.propertygroups = kw.pop('propertygroup', []) 962 self.instances = kw.pop('instance', []) 963 self.instancegroups = kw.pop('instancegroup', []) 964 self.watches = kw.pop('watch', []) 965 self.callbacks = kw.pop('callback', []) 966 self.callbackgroups = kw.pop('callbackgroup', []) 967 self.conditions = kw.pop('condition', []) 968 969 super(Everything, self).__init__(**kw) 970 971 def generate_cpp(self, loader): 972 '''Render the template with the provided data.''' 973 # errors.hpp is used by generated.hpp to included any error.hpp files 974 open('errors.hpp', 'w+') 975 976 with open(args.output, 'w') as fd: 977 fd.write( 978 self.render( 979 loader, 980 args.template, 981 meta=self.meta, 982 properties=self.properties, 983 propertynames=self.propertynames, 984 interfaces=self.interfaces, 985 paths=self.paths, 986 pathmeta=self.pathmeta, 987 pathgroups=self.pathgroups, 988 propertygroups=self.propertygroups, 989 instances=self.instances, 990 watches=self.watches, 991 instancegroups=self.instancegroups, 992 callbacks=self.callbacks, 993 callbackgroups=self.callbackgroups, 994 conditions=self.conditions, 995 indent=Indent())) 996 997if __name__ == '__main__': 998 script_dir = os.path.dirname(os.path.realpath(__file__)) 999 valid_commands = { 1000 'generate-cpp': 'generate_cpp', 1001 } 1002 1003 parser = ArgumentParser( 1004 description='Phosphor DBus Monitor (PDM) YAML ' 1005 'scanner and code generator.') 1006 1007 parser.add_argument( 1008 "-o", "--out", dest="output", 1009 default='generated.cpp', 1010 help="Generated output file name and path.") 1011 parser.add_argument( 1012 '-t', '--template', dest='template', 1013 default='generated.mako.hpp', 1014 help='The top level template to render.') 1015 parser.add_argument( 1016 '-p', '--template-path', dest='template_search', 1017 default=script_dir, 1018 help='The space delimited mako template search path.') 1019 parser.add_argument( 1020 '-d', '--dir', dest='inputdir', 1021 default=os.path.join(script_dir, 'example'), 1022 help='Location of files to process.') 1023 parser.add_argument( 1024 'command', metavar='COMMAND', type=str, 1025 choices=valid_commands.keys(), 1026 help='%s.' % " | ".join(valid_commands.keys())) 1027 1028 args = parser.parse_args() 1029 1030 if sys.version_info < (3, 0): 1031 lookup = mako.lookup.TemplateLookup( 1032 directories=args.template_search.split(), 1033 disable_unicode=True) 1034 else: 1035 lookup = mako.lookup.TemplateLookup( 1036 directories=args.template_search.split()) 1037 try: 1038 function = getattr( 1039 Everything.load(args), 1040 valid_commands[args.command]) 1041 function(lookup) 1042 except InvalidConfigError as e: 1043 sys.stdout.write('{0}: {1}\n\n'.format(e.config, e.msg)) 1044 raise 1045