1#!/usr/bin/env python 2 3""" 4ConfigManager 5 6ConfigManager is a combination command-line option parser and configuration 7file. It essentially combines ConfigParser, getopt, and a lot of 8additional logic to parse the command-line the way you expect it to be 9parsed. The ConfigManager class should be backwards compatible with the 10ConfigParser class, but contains much more functionality and a more natural 11dictionary-style interface to sections and options. 12 13See examples at the bottom of this file. Try typing __init__.py 14followed by a bunch of imaginary command line options and arguments. 15 16""" 17 18import sys, string, re, urllib.request, copy, types, os 19from collections import UserList, UserDict, OrderedDict 20from textwrap import wrap 21 22__all__ = ['ConfigManager','BooleanOption','IntegerOption','CompoundOption', 23 'MultiOption','GenericOption','FloatOption','StringOption', 24 'InputFileOption','OutputFileOption','InputDirectoryOption', 25 'OutputDirectoryOption','CountedOption', 26 'BooleanArgument','IntegerArgument','CompoundArgument', 27 'MultiArgument','GenericArgument','FloatArgument','StringArgument', 28 'InputFileArgument','OutputFileArgument','InputDirectoryArgument', 29 'OutputDirectoryArgument','CountedArgument', 30 'BUILTIN','CODE','REGISTRY','CONFIG','CONFIGFILE','ENVIRON', 31 'ENVIRONMENT','COMMANDLINE','ALL','DEFAULTSECT', 32 'ON','OFF','TRUE','FALSE','YES','NO','CommandLineManager', 33 'GetoptError','ConfigError','NoOptionError'] 34 35DEFAULTSECT = "DEFAULT" 36MAX_INTERPOLATION_DEPTH = 10 37ON = TRUE = YES = 1 38OFF = FALSE = NO = 0 39 40TERMINAL_WIDTH = 76 # Maximum width of terminal 41MAX_NAME_WIDTH_RATIO = 0.25 # Max fraction of terminal to use for option 42PREPAD = 2 # Padding before each option name in usage 43GUTTER = 4 # Space between option name and description in usage 44 45# Possible values for `source'. 46BUILTIN = 2 47CODE = 4 48REGISTRY = 8 49CONFIG = CONFIGFILE = 16 50ENVIRON = ENVIRONMENT = 32 51COMMANDLINE = 64 52ALL = 0xffffff 53 54# Exception classes 55class Error(Exception): 56 """ Generic exception """ 57 def __init__(self, msg=''): 58 self.msg = msg 59 Exception.__init__(self, msg) 60 61 def __str__(self): 62 return self.msg 63 __repr__ = __str__ 64 65 66# Exceptions while parsing command line 67class GetoptError(Error): 68 """ Generic command line exception """ 69 def __init__(self, msg, opt): 70 self.msg = msg 71 self.opt = opt 72 Exception.__init__(self, msg, opt) 73 74 def __str__(self): 75 return self.msg 76 __repr__ = __str__ 77 78class RequiresArgument(GetoptError): 79 """ Exception for a missing argument """ 80 81class MandatoryOption(GetoptError): 82 """ Exception for a missing option """ 83 84class UnspecifiedArgument(GetoptError): 85 """ Exception for an argument when none was expected """ 86 87class UnrecognizedArgument(GetoptError): 88 """ Exception for an argument that is unrecognized """ 89 90class NonUniquePrefix(GetoptError): 91 """ Exception for multiple option prefixes that match a given option """ 92 93class UnknownCompoundGroup(GetoptError): 94 """ Exception for an unknown grouping character used for a compound """ 95 def __init__(self, msg=''): 96 GetoptError.__init__(self, msg, '') 97 98 99# Exceptions while reading/parsing configuration files 100class ConfigError(Error): 101 """ Generic configuration file exception """ 102 103class NoSectionError(ConfigError): 104 """ Exception for missing sections """ 105 def __init__(self, section): 106 ConfigError.__init__(self, 'No section: %s' % section) 107 self.section = section 108 109class DuplicateSectionError(ConfigError): 110 """ Exception for duplicate sections """ 111 def __init__(self, section): 112 ConfigError.__init__(self, "Section %s already exists" % section) 113 self.section = section 114 115class InvalidOptionError(GetoptError, ConfigError): 116 """ Exception for invalid values for a given option """ 117 def __init__(self, option, value, msg='', type=''): 118 if type: type += ' ' 119 if not msg: 120 msg="Invalid value for %soption `%s'" % (type, option) 121 ConfigError.__init__(self, msg+': %s' % value) 122 self.option = option 123 self.value = value 124 125class NoOptionError(ConfigError): 126 """ Exception for missing a missing option in a section """ 127 def __init__(self, option, section): 128 ConfigError.__init__(self, "No option `%s' in section: %s" % 129 (option, section)) 130 self.option = option 131 self.section = section 132 133class InterpolationError(ConfigError): 134 """ Exception for message interpolation errors """ 135 def __init__(self, reference, option, section, rawval): 136 ConfigError.__init__(self, 137 "Bad value substitution:\n" 138 "\tsection: [%s]\n" 139 "\toption : %s\n" 140 "\tkey : %s\n" 141 "\trawval : %s\n" 142 % (section, option, reference, rawval)) 143 self.reference = reference 144 self.option = option 145 self.section = section 146 147class InterpolationDepthError(ConfigError): 148 """ Exception for excessive recursion in interpolation """ 149 def __init__(self, option, section, rawval): 150 ConfigError.__init__(self, 151 "Value interpolation too deeply recursive:\n" 152 "\tsection: [%s]\n" 153 "\toption : %s\n" 154 "\trawval : %s\n" 155 % (section, option, rawval)) 156 self.option = option 157 self.section = section 158 159class ParsingError(ConfigError): 160 """ Exception for errors occurring during parsing of a config file """ 161 def __init__(self, filename): 162 ConfigError.__init__(self, 'File contains parsing errors: %s' % filename) 163 self.filename = filename 164 self.errors = [] 165 166 def append(self, lineno, line): 167 self.errors.append((lineno, line)) 168 self.msg = self.msg + '\n\t[line %2d]: %s' % (lineno, line) 169 170class TooFewValues(GetoptError): 171 """ Got fewer values than expected """ 172 def __init__(self, msg): 173 GetoptError.__init__(self, msg, '') 174 175class TooManyValues(GetoptError): 176 """ Got more values than expected """ 177 def __init__(self, msg): 178 GetoptError.__init__(self, msg, '') 179 180class MissingSectionHeaderError(ParsingError): 181 """ Exception for options that occur before a section heading """ 182 def __init__(self, filename, lineno, line): 183 ConfigError.__init__( 184 self, 185 'File contains no section headers.\nfile: %s, line: %d\n%s' % 186 (filename, lineno, line)) 187 self.filename = filename 188 self.lineno = lineno 189 self.line = line 190 191 192class ConfigSection(UserDict, object): 193 """ Section of a configuration object """ 194 195 def __init__(self, name, data=None): 196 """ 197 Initialize the section 198 199 Required Arguments: 200 name -- name of the section 201 data -- dictionary containing the initial set of options 202 203 """ 204 UserDict.__init__(self, data or {}) 205 self.name = name 206 self.parent = None 207 208 def copy(self): 209 """ Make a deep copy of self """ 210 newcopy = self.__class__(self.name) 211 for key, value in list(vars(self).items()): 212 if key == 'data': continue 213 setattr(newcopy, key, value) 214 for key, value in list(self.data.items()): 215 newcopy.data[key] = value.copy() 216 return newcopy 217 218 def setParent(self, parent): 219 """ Set the parent ConfigManager instance """ 220 self.parent = parent 221 222 def defaults(self): 223 """ Return the dictionary of defaults """ 224 return self.parent.defaults() 225 226 def __getitem__(self, key): 227 """ Return the value of the option, not the option itself """ 228 return self.get(key) 229 230 def set(self, option, value, source=BUILTIN): 231 """ 232 Create the appropriate option type 233 234 If the value is already an Option instance, just plug it in. 235 If the value is anything else, try to figure out which option 236 type it corresponds to and create an option of that type. 237 238 Required Arguments: 239 option -- dictionary key where the option should be set 240 value -- option value to store 241 source -- flag to indicate source of option 242 243 Returns: None 244 245 """ 246 typemap = {str:StringOption, int:IntegerOption, 247 float:FloatOption, list:MultiOption, tuple:MultiOption} 248 249 if option in list(self.data.keys()): 250 if self.data[option].source <= source: 251 self.data[option].source = source 252 self.data[option].setValue(value) 253 254 else: 255 if isinstance(value, GenericOption): 256 value.setParent(self) 257 value.name = str(option) 258 self.data[option] = value 259 260 elif type(value) in list(typemap.keys()): 261 for key, opttype in list(typemap.items()): 262 if isinstance(value, key): 263 # Handle booleans this way until support for 264 # true booleans shows up in Python. 265 if type(value) == str and \ 266 value.lower().strip() in ['on','off','true','false','yes','no']: 267 opttype = BooleanOption 268 self.data[option] = opttype(name=option, source=source) 269 self.data[option].setParent(self) 270 self.data[option].name = str(option) 271 self.data[option].setValue(value) 272 break 273 else: 274 raise TypeError('Could not find valid option type for "%s"' % value) 275 276 def __setitem__(self, key, value): 277 """ Set the item in the dictionary """ 278 self.set(key, value, source=BUILTIN) 279 280 def getint(self, option): 281 """ Get the option value and cast it to an integer """ 282 return int(self[option]) 283 284 def getfloat(self, option): 285 """ Get the option value and cast it to a float """ 286 return float(self[option]) 287 288 def getboolean(self, option): 289 """ Get the option value and cast it to a boolean """ 290 v = self[option] 291 val = int(v) 292 if val not in (0, 1): 293 raise ValueError('Not a boolean: %s' % v) 294 return val 295 296 def get(self, option, raw=0, vars=None): 297 """ 298 Get an option value for a given section. 299 300 All % interpolations are expanded in the return values, based on the 301 defaults passed into the constructor, unless the optional argument 302 `raw' is true. Additional substitutions may be provided using the 303 `vars' argument, which must be a dictionary whose contents overrides 304 any pre-existing defaults. 305 306 Required Arguments: 307 option -- name of the option to retrieve 308 309 Keyword Arguments: 310 raw -- boolean flag that indicates whether string values should 311 be returned as a raw value or as a string with all variable 312 interpolation applied 313 vars -- dictionary of values to use in string interpolation 314 315 Returns: 316 value of the option 317 318 """ 319 vars = vars or {} 320 value = self.getraw(option, vars) 321 322 # Raw was specified 323 if raw or not value: return value 324 325 # If we have a list, see if any strings need interpolation. 326 if type(value) in [list, tuple]: 327 strings = [s for s in value 328 if isinstance(s,str) and s.find('%(')+1] 329 if not strings: return value 330 331 # If we have a string, but no interpolation is needed, bail out. 332 elif not(isinstance(value,str)) or value.find("%(") < 0: 333 return value 334 335 # otherwise needs interpolation... 336 var_dict = self.defaults().data.copy() 337 var_dict.update(self.data) 338 var_dict.update(vars) 339 340 # Handle lists of interpolations as well as single values. 341 if type(value) in [list, tuple]: 342 new_values = [] 343 for i in value: 344 new_values.append(self.interpolate(option, var_dict, i)) 345 return new_values 346 else: 347 return self.interpolate(option, var_dict, value) 348 349 def interpolate(self, option, vars, rawval): 350 """ 351 Do the string interpolation 352 353 Required Arguments: 354 option -- name of the option 355 vars -- dictionary of values to use in interpolation 356 rawval -- raw value of the option 357 358 Returns: 359 string -- string with all variables interpolated 360 361 """ 362 value = rawval 363 depth = 0 364 # Loop through this until it's done 365 while depth < MAX_INTERPOLATION_DEPTH: 366 depth = depth + 1 367 if value.find("%(") >= 0: 368 try: 369 value = value % vars 370 except KeyError as key: 371 raise InterpolationError(key, option, self.name, rawval) 372 else: 373 break 374 if value.find("%(") >= 0: 375 raise InterpolationDepthError(option, self.name, rawval) 376 return value 377 378 def getraw(self, option, vars=None): 379 """ 380 Return raw value of option 381 382 Required Arguments: 383 option -- name of the option to retrieve 384 385 Keyword Arguments: 386 vars -- dictionary containing additional default values 387 388 """ 389 vars = vars or {} 390 if option in list(vars.keys()): 391 return vars[option].getValue() 392 393 if option in list(self.keys()): 394 return self.data[option].getValue() 395 396 defaults = self.defaults() 397 if option in list(defaults.keys()): 398 return defaults.data[option].getValue() 399 400 raise NoOptionError(option, self.name) 401 402 def to_string(self, source=COMMANDLINE): 403 """ 404 Convert the section back into INI format 405 406 Keyword Arguments: 407 source -- flag which indicates which source of information to print 408 409 Returns: 410 string -- string containing the section in INI format 411 412 """ 413 s = '' 414 keys = list(self.keys()) 415 keys.sort() 416 for key in keys: 417 if source & self.data[key].source: 418 raw = self.getraw(key) 419 option = self.data[key] 420 421 # Bypass unset options 422 if isinstance(option, MultiOption) and raw == []: continue 423 if not raw: continue 424 425 # Print description or summary of the option as well 426 comment = '' 427 if option.summary: comment = option.summary 428 if option.description: comment = option.description 429 if comment: 430 comment = comment.strip() % option.names() 431 comment = comment.split('\n') 432 s += '\n; %s\n' % '\n; '.join(comment) 433 434 value = str(option).replace('\n', '\n ') 435 if value.find('\n') + 1: value = '\n ' + value 436 s += "%s %s %s\n" % (key, ConfigManager.OPTIONSEP, value) 437 return s 438 439 def __str__(self): 440 """ Return section in INI format without builtins """ 441 return self.to_string() 442 443 def __repr__(self): 444 """ Return section in INI format with builtins """ 445 return self.to_string(ALL) 446 447 448class ConfigManager(UserDict, object): 449 450 # Regular expressions for parsing section headers and options. 451 SECTCRE = re.compile( 452 r'\[' # [ 453 r'(?P<header>[^]]+)' # very permissive! 454 r'\]' # ] 455 ) 456 457 OPTCRE = re.compile( 458 r'(?P<option>[]\-[\w_.*,(){}]+)' # a lot of stuff found by IvL 459 r'[ \t]*(?P<vi>[:=])[ \t]*' # any number of space/tab, 460 # followed by separator 461 # (either : or =), followed 462 # by any # space/tab 463 r'(?P<value>.*)$' # everything up to eol 464 ) 465 466 # Option separator used in printing out INI format 467 OPTIONSEP = '=' 468 469 # Set prefixes for options. If these are the same, all options 470 # are treated as long options. You can set either one to None 471 # to turn that type of option off as well. 472 short_prefix = '-' 473 long_prefix = '--' 474 475 def __init__(self, defaults=None): 476 """ 477 Initialize ConfigManager 478 479 Keyword Arguments: 480 defaults -- dictionary of default values. These values will 481 make up the section by the name DEFAULTSECT. 482 483 """ 484 defaults = defaults or {} 485 UserDict.__init__(self) 486 self[DEFAULTSECT] = ConfigSection(DEFAULTSECT, defaults) 487 self.strict = 1 # Raise exception for unknown options 488 self._categories = {} # Dictionary of option categories 489 self.unrecognized = [] 490 491 def copy(self): 492 """ Make a deep copy of self """ 493 newcopy = self.__class__() 494 for key, value in list(vars(self).items()): 495 if key == 'data': continue 496 setattr(newcopy, key, value) 497 for key, value in list(self.data.items()): 498 newcopy.data[key] = value.copy() 499 return newcopy 500 501 def set_prefixes(cls, arg1, arg2=None): 502 """ 503 Set the command-line option prefixes 504 505 Arguments: 506 short - prefix to use for short command-line options. If this 507 is set to 'None', then all options are treated as long options. 508 long - prefix to use for long options 509 510 """ 511 if arg1 == arg2 == None: 512 raise ValueError('Short and long prefixes cannot both be None.') 513 if arg2 is None: 514 cls.long_prefix = arg1 515 cls.short_prefix = None 516 else: 517 cls.long_prefix = arg2 518 cls.short_prefix = arg1 519 520 set_prefixes = classmethod(set_prefixes) 521 522 def add_help_on_option(self, category=None): 523 """ 524 Add a --help-on=LIST option for displaying specific option help 525 526 The --help-on= option can be added to give the command line 527 interactive help. For example, if you had an option called 528 '--debug', you could type '--help-on debug' to get the full 529 description of the --debug option printed out. 530 531 Keyword Arguments: 532 category -- category to put the --help-on option in 533 534 """ 535 self[DEFAULTSECT]['__help_on__'] = MultiOption( 536 """ Display help on listed option names """, 537 options = '%shelp-on' % self.long_prefix[0], 538 category = category, 539 callback = self.usage_on, 540 ) 541 542 def remove_help_on_option(self): 543 """ Remove the --help-on option """ 544 try: del self[DEFAULTSECT]['__help_on__'] 545 except: pass 546 547 def add_category(self, key, title): 548 """ 549 Add a category to the ConfigManager 550 551 Options can be grouped by categories for display in the usage 552 message. Categories have both a key and a title. The key is 553 what is used in the 'category' parameter when instantiating an 554 option. The title is the heading to print in the usage message. 555 556 Required Arguments: 557 key -- name of the category used in instantiating an option 558 title -- title of the category to print in the usage message 559 560 Returns: 561 string -- the same key given as the first argument 562 563 """ 564# if not self._categories: 565# self._categories['__categories__'] = 'Help Categories' 566# if not self.has_key('__categories__'): 567# self.add_section('__categories__') 568# self['__categories__'][key] = BooleanOption(title, 569# options='%shelp-%s' % (self.long_prefix, key), 570# category='__categories__') 571 self._categories[key] = title 572 return key 573 574 def get_category(self, key): 575 """ Return the title of the given category """ 576 if type(key) not in [list, tuple]: 577 key = [key] 578 if not key: 579 return '' 580 return self._categories[key[0]] 581 582 def categories(self): 583 """ Return the dictionary of categories """ 584 return self._categories 585 586 def set_strict(self, strict=True): 587 """ 588 Parse the command line strictly 589 590 If you do not want to be bothered with errors due to unrecognized 591 arguments, this method can be called with a boolean 'false'. 592 This is very useful if your program is actually a wrapper around 593 another program and you do not want to declare all of its options 594 in your ConfigManager. The ConfigManager will simply make its best 595 guess as to whether the option accepts a value and what type the 596 option is. 597 598 Keyword Arguments: 599 strict -- flag indicating whether parsing should be strict or not 600 601 """ 602 self.strict = bool(strict) 603 604 def defaults(self): 605 """ Return a dictionary of defaults """ 606 return self[DEFAULTSECT] 607 608 def sections(self): 609 """ Return a list of section names """ 610 return list(self.keys()) 611 612 def add_section(self, section): 613 """ 614 Create a new section in the configuration. 615 616 Do nothing if a section by the specified name already exists. 617 618 Required Arguments: 619 section -- name of the section to create 620 621 Returns: 622 instance -- a ConfigSection instance with the given name is returned 623 624 """ 625 if section in list(self.keys()): return self[section] 626 self[section] = ConfigSection(section) 627 return self[section] 628 629 def has_section(self, section): 630 """ Indicate whether the named section is present """ 631 return section in list(self.keys()) 632 633 def options(self, section): 634 """ Return a list of option names for the given section name """ 635 if section in list(self.keys()): 636 return list(self[section].keys()) 637 else: 638 raise NoSectionError(section) 639 640 def read(self, filenames): 641 """ 642 Read and parse a filename or a list of filenames 643 644 Files that cannot be opened are silently ignored; this is 645 designed so that you can specify a list of potential 646 configuration file locations (e.g. current directory, user's 647 home directory, systemwide directory), and all existing 648 configuration files in the list will be read. A single 649 filename may also be given. 650 651 """ 652 if isinstance(filenames, str): 653 filenames = [filenames] 654 for filename in filenames: 655 try: 656 if filename.startswith('~'): 657 filename = os.path.expanduser(filename) 658 659 fp = open(filename) 660 except (OSError, IOError): 661 continue 662 self.__read(fp, filename) 663 fp.close() 664 return self 665 666 def readfp(self, fp, filename=None): 667 """ 668 Like read() but the argument must be a file-like object. 669 670 The 'fp' argument must have a 'readline' method. Optional 671 second argument is the 'filename', which if not given, is 672 taken from fp.name. If fp has no 'name' attribute, '<???>' is 673 used. 674 675 Required Arguments: 676 fp -- file-type like object 677 filename -- name of the file in 'fp' 678 679 Returns: 680 string -- contents of the file pointer 681 682 """ 683 if filename is None: 684 try: 685 filename = fp.name 686 except AttributeError: 687 filename = '<???>' 688 self.__read(fp, filename) 689 return self 690 691 def get(self, section, option, raw=0, vars=None): 692 """ Get an option value for a given section """ 693 return self[section].get(option, raw, vars or {}) 694 695 def set(self, section, option, value, source=BUILTIN): 696 """ Set an option value """ 697 if not section or section == DEFAULTSECT: 698 sectdict = self[DEFAULTSECT] 699 else: 700 try: 701 sectdict = self[section] 702 except KeyError: 703 raise NoSectionError(section) 704 sectdict.set(option, value, source) 705 706 def __setitem__(self, key, value): 707 """ Add a section to the configuration """ 708 if isinstance(value, ConfigSection): 709 self.data[key] = value 710 self.data[key].setParent(self) 711 else: 712 self.data[key] = ConfigSection(str(key)) 713 self.data[key].setParent(self) 714 715 def __getitem__(self, key): 716 """ 717 Return section with given name 718 719 Return the section or if it's not a section try to 720 return an option by that name in the 'default' section. 721 722 """ 723 if key in list(self.data.keys()): 724 return self.data[key] 725 if key in list(self.data[DEFAULTSECT].keys()): 726 return self.data[DEFAULTSECT][key] 727 raise NoSectionError(key) 728 729 def getint(self, section, option): 730 """ Get an option value and cast it to an integer """ 731 return self[section].getint(option) 732 733 def getfloat(self, section, option): 734 """ Get an option value and cast it to a float """ 735 return self[section].getfloat(option) 736 737 def getboolean(self, section, option): 738 """ Get an option value and cast it to a boolean """ 739 return self[section].get(option) 740 741 def getraw(self, section, option): 742 """ Get the raw (un-interpolated) value of an option """ 743 return self[section].getraw(option) 744 745 def has_option(self, section, option): 746 """ Check for the existence of a given option in a given section """ 747 if not section: 748 section=DEFAULTSECT 749 elif section not in list(self.keys()): 750 return 0 751 else: 752 return option in list(self[section].keys()) 753 754 def write(self, fp): 755 """ Write an INI-format representation of the configuration state """ 756 fp.write(str(self)) 757 758 def __str__(self): 759 """ Return an INI formated string with builtins removed """ 760 return self.to_string() 761 762 def __repr__(self): 763 """ Return an INI formated string with builtins included """ 764 return self.to_string(source=COMMANDLINE|CONFIGFILE|CODE|BUILTIN|REGISTRY|ENVIRONMENT) 765 766 def to_string(self, source=COMMANDLINE|CONFIGFILE): 767 """ 768 Build an INI formatted string based on the ConfigManager contents 769 770 Keyword Arguments: 771 source -- flag indicating which sources if information to print 772 773 Returns: 774 string -- INI formatted string 775 776 """ 777 if source & BUILTIN: func = repr 778 else: func = str 779 s = '' 780 keys = [x for x in list(self.keys()) if x != DEFAULTSECT] 781 keys.sort() 782 if self[DEFAULTSECT]: 783 keys.insert(0, DEFAULTSECT) 784 for section in keys: 785 content = func(self[section]).strip() 786 if content: 787 s += "[%s]\n%s\n\n" % (section, content) 788 return s 789 790 def remove_option(self, section, option): 791 """ Remove an option from a section """ 792 if not section or section == DEFAULTSECT: 793 sectdict = self[DEFAULTSECT] 794 else: 795 try: 796 sectdict = self[section] 797 except KeyError: 798 raise NoSectionError(section) 799 try: 800 del sectdict[option] 801 return 1 802 except KeyError: 803 return 0 804 805 def remove_section(self, section): 806 """ Remove the given section """ 807 if section in list(self.keys()): 808 del self[section] 809 return 1 810 else: 811 return 0 812 813 def __read(self, fp, fpname): 814 """ 815 Parse an INI formatted file 816 817 The sections in the file contain a title line at the top, 818 indicated by a name in square brackets (`[]'), plus key/value 819 options lines, indicated by `name: value' format lines. 820 Continuation are represented by an embedded newline then 821 leading whitespace. Blank lines, lines beginning with a '#', 822 and just about everything else is ignored. 823 824 """ 825 cursect = None # None, or a dictionary 826 optname = None 827 lineno = 0 828 e = None # None, or an exception 829 while 1: 830 line = fp.readline() 831 if not line: 832 break 833 lineno = lineno + 1 834 # comment or blank line? 835 if line.strip() == '' or line[0] in '#;': 836 continue 837 if line.split()[0].lower() == 'rem' \ 838 and line[0] in "rR": # no leading whitespace 839 continue 840 # continuation line? 841 if line[0] in ' \t' and cursect is not None and optname: 842 value = line.strip() 843 if value and cursect.data[optname].source == CONFIGFILE: 844 cursect.data[optname] += "%s" % value 845 # a section header or option header? 846 else: 847 # is it a section header? 848 mo = self.SECTCRE.match(line) 849 if mo: 850 sectname = mo.group('header') 851 if sectname in list(self.keys()): 852 cursect = self[sectname] 853 else: 854 cursect = ConfigSection(sectname) 855 self[sectname] = cursect 856 # So sections can't start with a continuation line 857 optname = None 858 # no section header in the file? 859 elif cursect is None: 860 raise MissingSectionHeaderError(fpname, lineno, repr(line)) 861 # an option line? 862 else: 863 mo = self.OPTCRE.match(line) 864 if mo: 865 optname, vi, optval = mo.group('option', 'vi', 'value') 866 if vi in ('=', ':') and ';' in optval: 867 # ';' is a comment delimiter only if it follows 868 # a spacing character 869 pos = optval.find(';') 870 if pos and optval[pos-1] in string.whitespace: 871 optval = optval[:pos] 872 optval = optval.strip() 873 # allow empty values 874 if optval == '""': 875 optval = '' 876 try: 877 cursect.set(optname, optval, source=CONFIGFILE) 878 cursect.data[optname].file = fpname 879 except: 880 print("Problem occurred in section '%s' while reading file %s." % (cursect.name, fpname)) 881 raise 882 else: 883 # a non-fatal parsing error occurred. set up the 884 # exception but keep going. the exception will be 885 # raised at the end of the file and will contain a 886 # list of all bogus lines 887 if not e: 888 e = ParsingError(fpname) 889 e.append(lineno, repr(line)) 890 # if any parsing errors occurred, raise an exception 891 if e: 892 raise e 893 894 def get_default_option(self, option): 895 """ Get the given option from the default section """ 896 try: 897 return self[DEFAULTSECT][option] 898 except KeyError: 899 raise NoOptionError(option, DEFAULTSECT) 900 901 def get_opt(self, section, option): 902 """ Return the option with leading and trailing quotes removed """ 903 optionstring = self[section][option].strip() 904 if (optionstring[0] == '\'' and optionstring[-1] == '\'') or \ 905 (optionstring[0] == '\"' and optionstring[-1] == '\"'): 906 optionstring = optionstring[1:-1] 907 return optionstring 908 909 def get_optlist(self, section, option, delim=','): 910 """ 911 Return the option split into a list 912 913 Required Arguments: 914 section -- name of the section 915 option -- name of the option 916 917 Keyword Arguments: 918 delim -- delimiter to use when splitting the option 919 920 Returns: 921 list -- option value split on 'delim' with whitespace trimmed 922 923 """ 924 optionstring = self.get_opt( section, option ) 925 return [x.strip() for x in optionstring.split(delim)] 926 927 def __add__(self, other): 928 """ 929 Merge items from another ConfigManager 930 931 Sections in 'other' will overwrite sections in 'self'. 932 933 """ 934 other = other.copy() 935 for key, value in list(other.items()): 936 self[key] = value 937 try: 938 for key, value in list(other._categories.items()): 939 self._categories[key] = value 940 except AttributeError: pass 941 return self 942 943 def __iadd__(self, other): 944 """ Merge items from another ConfigManager """ 945 return self.__add__(other) 946 947 def __radd__(self, other): 948 """ 949 Merge items from another ConfigManager 950 951 Sections already in 'self' will not be overwritten. 952 953 """ 954 other = other.copy() 955 for key, value in list(other.items()): 956 if key not in list(self.keys()): 957 self[key] = value 958 try: 959 for key, values in list(other._categories.items()): 960 if key not in list(self._categories.keys()): 961 self._categories[key] = value 962 except AttributeError: pass 963 return self 964 965 def get_options_from_config(self): 966 """ 967 Locate all short and long options 968 969 Returns: 970 tuple -- two element tuple contain a list of short option 971 instances and a list of long option instances 972 973 """ 974 short_prefixes, long_prefixes = type(self).get_prefixes() 975 976 longopts = [] 977 shortopts = [] 978 for section in list(self.values()): 979 for option in list(section.data.values()): 980 for opt in option.getPossibleOptions(): 981 opt = opt.replace('!','') 982 983 # See if the option is a long option 984 for prefix in long_prefixes: 985 if prefix is None: 986 pass 987 elif not prefix.strip(): 988 pass 989 elif opt.startswith(prefix): 990 if option not in longopts: 991 longopts.append(option) 992 continue 993 994 # See if the option is a short option 995 for prefix in short_prefixes: 996 if prefix is None: 997 pass 998 elif not prefix.strip(): 999 pass 1000 elif opt.startswith(prefix): 1001 if option not in shortopts: 1002 shortopts.append(option) 1003 1004 return shortopts, longopts 1005 1006 def getopt(self, args=None, merge=1): 1007 """ 1008 Parse the command line 1009 1010 Keyword Arguments: 1011 args -- list of strings containing the command line. If this is 1012 not given, sys.argv[1:] is used. 1013 merge -- boolean flag indicating whether parsed options should 1014 be merged into the configuration or not 1015 1016 Returns: 1017 tuple -- two element tuple where the first element is a list of 1018 parsed options in the form '(option, value)' and the second 1019 element is a list of unparsed arguments 1020 1021 """ 1022 if args is None: args = sys.argv[1:] 1023 1024 shortopts, longopts = self.get_options_from_config() 1025 1026 short_prefixes, long_prefixes = type(self).get_prefixes() 1027 1028 opts = [] 1029 while args and args[0] not in short_prefixes: 1030 # If the argument is equal to one of the long prefixes, 1031 # throw it away and bail out. 1032 if args[0] in long_prefixes: 1033 args = args[1:] 1034 break 1035 1036 # Parse long options 1037 if [x for x in long_prefixes if args[0].startswith(x)]: 1038 try: 1039 opts, args = self.do_longs(opts,args[0],longopts,args[1:]) 1040 except UnrecognizedArgument as e: 1041 if self.strict: raise 1042 opts, args = self.handle_unrecognized(e[1],opts,args,'long') 1043 if merge: self.unrecognized.append(opts[-1]) 1044 1045 # Parse short options 1046 elif [x for x in short_prefixes if args[0].startswith(x)]: 1047 try: 1048 opts, args = self.do_shorts(opts,args[0],shortopts,args[1:]) 1049 except UnrecognizedArgument as e: 1050 if self.strict: raise 1051 opts, args = self.handle_unrecognized(e[1],opts,args,'short') 1052 if merge: self.unrecognized.append(opts[-1]) 1053 1054 # No option found. We're done. 1055 else: 1056 break 1057 1058 # Merge command line options with configuration 1059 if merge: 1060 self.merge_options(opts) 1061 self.check_mandatory_options() 1062 1063 return opts, args 1064 1065 def check_mandatory_options(self): 1066 """ 1067 Make sure that all mandatory options have been set 1068 1069 """ 1070 for section in self.values(): 1071 for option in section.data.values(): 1072 if not option.mandatory: continue 1073 if option.getValue() in [None,[]]: 1074 names = ', '.join(option.getPossibleOptions()) 1075 if not names: 1076 names = option.name 1077 raise MandatoryOption('The %s option is mandatory, but was not given' % names, names) 1078 1079 def handle_unrecognized(self, option, opts, args, type='long'): 1080 """ 1081 Try to parse unrecognized options and their arguments 1082 1083 Required Arguments: 1084 option -- the actual option parsed from the command-line 1085 which was not recognized 1086 opts -- tuples of already parsed options and their values 1087 args -- remaining unparsed command-line arguments 1088 1089 Keyword Arguments: 1090 type -- flag indicating which type of argument to parse. 1091 This should be either "short" or "long". 1092 1093 """ 1094 if type == 'long': 1095 1096 args.pop(0) 1097 1098 # Explicitly specified value 1099 if option.find('=') + 1: 1100 option, value = option.split('=',1) 1101 opts.append((option, value)) 1102 return opts, args 1103 1104 # Implicitly specified value 1105 if self.has_following_argument(args): 1106 opts.append((option, args.pop(0))) 1107 return opts, args 1108 1109 # No argument found 1110 opts.append((option, None)) 1111 return opts, args 1112 1113 elif type == 'short': 1114 1115 short_prefixes, long_prefixes = self.get_prefixes() 1116 prefix = [x for x in short_prefixes if option.startswith(x)][0] 1117 1118 start, end = args[0].split(option.replace(prefix,'',1),1) 1119 if end: args[0] = prefix + end 1120 else: args.pop(0) 1121 1122 opts.append((option, '')) 1123 return opts, args 1124 1125 raise ValueError('Expecting type of "short" or "long".') 1126 1127 def merge_options(self, options): 1128 """ Merge options parsed from the command line into configuration """ 1129 # Multi options that have been cleared by a command line option. 1130 # Lists will only be cleared on the first command line option, all 1131 # consecutive options will append. 1132 for option, value in options: 1133 1134# opts = self.getPossibleOptions() 1135# negative = [x.replace('!','',1) for x in opts if x.startswith('!')] 1136# positive = [x for x in opts if not x.startswith('!')] 1137 1138 if isinstance(option, GenericOption): 1139 option.source = COMMANDLINE 1140 option.file = None 1141 if type(value) in [list, tuple]: 1142 if not isinstance(option, MultiOption): 1143 value = ' '.join(value) 1144 option.occurrences += 1 1145 option.setValue(value) 1146 else: 1147 if option.occurrences: 1148 option.occurrences += 1 1149 option += value 1150 else: 1151 option.occurrences += 1 1152 option.setValue(value) 1153 else: 1154 option.occurrences += 1 1155 option.setValue(value) 1156 1157 def get_prefixes(cls): 1158 """ Prepare option prefixes to make sure that they are always lists """ 1159 long_prefixes = cls.long_prefix 1160 short_prefixes = cls.short_prefix 1161 if type(long_prefixes) not in [list, tuple]: 1162 long_prefixes = [long_prefixes] 1163 if type(short_prefixes) not in [list, tuple]: 1164 short_prefixes = [short_prefixes] 1165 return [x for x in short_prefixes if x],[x for x in long_prefixes if x] 1166 1167 get_prefixes = classmethod(get_prefixes) 1168 1169 def do_longs(self, opts, opt, longopts, args): 1170 """ 1171 Parse a long option 1172 1173 Required Arguments: 1174 opts -- list of parsed options 1175 opt -- string containing current option 1176 longopts -- list of long option instances 1177 args -- remaining argument list 1178 1179 Returns: 1180 tuple -- two element tuple where the first argument is the 1181 'opts' list with the latest argument added and the second 1182 element is the 'args' list with the arguments from the 1183 current option removed 1184 1185 """ 1186 forcedarg = False 1187 if opt.find('=') + 1: 1188 forcedarg = True 1189 opt, arg = opt.split('=', 1) 1190 args.insert(0, arg) 1191 1192 option = self.get_match(opt, longopts) 1193 option.actual = opt 1194 option.file = None 1195 1196 # If we have an argument, but the option doesn't accept one, bail out. 1197 if forcedarg and not(option.acceptsArgument()): 1198 raise UnspecifiedArgument('option %s must not have an argument' \ 1199 % opt, opt) 1200 1201 elif forcedarg: 1202 optarg, args = option.getArgument(args, forcedarg=True) 1203 1204 elif not(option.acceptsArgument()): 1205 pass 1206 1207 # Parse the following arguments 1208 else: 1209 1210 # See if we have a possible following argument 1211 if not type(self).has_following_argument(args): 1212 1213 # No argument found, but we require one. 1214 if option.requiresArgument(): 1215 raise RequiresArgument('option %s requires argument' \ 1216 % opt, opt) 1217 1218 # Parse the argument 1219 optarg, args = option.getArgument(args) 1220 1221 # Convert boolean options to proper value 1222 if not forcedarg and isinstance(option, BooleanOption): 1223 options = option.getPossibleOptions() 1224 negative = [x.replace('!','',1) for x in options 1225 if x.startswith('!')] 1226# positive = [x for x in options if not x.startswith('!')] 1227 if opt in negative: 1228 optarg = 0 1229 else: 1230 optarg = 1 1231 1232 opts.append((option, optarg)) 1233 return opts, args 1234 1235 def has_following_argument(cls, args): 1236 """ 1237 Return boolean indicating the existence of a following argument 1238 1239 Required Arguments: 1240 args -- command-line arguments to inspect for following argument. 1241 1242 Returns: 1243 boolean -- true, if following argument exists 1244 1245 """ 1246 short_prefixes, long_prefixes = cls.get_prefixes() 1247 1248 # No arguments at all 1249 if not(args): 1250 return 0 1251 1252 # The next argument has an option prefix and it doesn't consist 1253 # entirely of a prefix. 1254 if [x for x in long_prefixes+short_prefixes if args[0].startswith(x)] \ 1255 and args[0] not in long_prefixes+short_prefixes: 1256 return 0 1257 1258 # All other cases fail. This must be an argument. 1259 return 1 1260 1261 has_following_argument = classmethod(has_following_argument) 1262 1263 def get_match(self, opt, longopts): 1264 """ 1265 Get possible matches for the given option 1266 1267 Required Arguments: 1268 opt -- name of the current option 1269 longopts -- list of all long option instances 1270 1271 Returns: 1272 instance -- an instance of the option which matches 1273 1274 """ 1275 possibilities = [] 1276 for o in longopts: 1277 match = o.matches(opt) 1278 if match: 1279 possibilities.append((match, o)) 1280 1281 if not possibilities: 1282 raise UnrecognizedArgument('option %s not recognized' % opt, opt) 1283 1284 # Is there an exact match? 1285 option = [x for x in possibilities if opt == x[0]] 1286 if option: 1287 return option[0][1] 1288 1289 # No exact match, so better be unique. 1290 if len(possibilities) > 1: 1291 # XXX since possibilities contains all valid continuations, 1292 # might be nice to work them into the error msg 1293 raise NonUniquePrefix('option %s not a unique prefix' % opt, opt) 1294 1295 assert len(possibilities) == 1 1296 1297 return possibilities[0][1] 1298 1299 def do_shorts(self, opts, optstring, shortopts, args): 1300 """ 1301 Parse a short argument 1302 1303 Required Arguments: 1304 opts -- list of parsed options 1305 optstring -- string containing current option 1306 shortopts -- list of short option instances 1307 args -- remaining argument list 1308 1309 Returns: 1310 tuple -- two element tuple where the first argument is the 1311 'opts' list with the latest argument added and the second 1312 element is the 'args' list with the arguments from the 1313 current option removed 1314 1315 """ 1316 short_prefixes, long_prefixes = type(self).get_prefixes() 1317 1318 optstring.strip() 1319 1320 if not optstring: 1321 return [], args 1322 1323 prefix = optstring[0] 1324 optstring = optstring[1:] 1325 1326 while optstring != '': 1327 opt, optstring = optstring[0], optstring[1:] 1328 1329 option = self.get_match(prefix+opt, shortopts) 1330 option.actual = prefix+opt 1331 option.file = None 1332 1333 # See if we need to check for an argument 1334 if option.acceptsArgument(): 1335 1336 if optstring == '': 1337 1338 # Are there any following arguments? 1339 if not type(self).has_following_argument(args): 1340 1341 # No argument found, but we require one. 1342 if option.requiresArgument(): 1343 raise RequiresArgument('option %s requires argument' \ 1344 % opt, opt) 1345 1346 optarg, args = option.getArgument(args) 1347 1348 else: 1349 optarg, args = option.getArgument([optstring]+args) 1350 1351 # No argument was found 1352 if args and args[0] == optstring: 1353 optarg = None 1354 optstring = args.pop(0) 1355 else: 1356 optstring = '' 1357 1358 else: 1359 optarg = None 1360 1361 # Convert boolean options to proper value 1362 if optarg is None and isinstance(option, BooleanOption): 1363 options = option.getPossibleOptions() 1364 negative = [x.replace('!','',1) for x in options 1365 if x.startswith('!')] 1366# positive = [x for x in options if not x.startswith('!')] 1367 if prefix+opt in negative: 1368 optarg = 0 1369 else: 1370 optarg = 1 1371 1372 opts.append((option, optarg)) 1373 1374 return opts, args 1375 1376 def usage_on(self, options): 1377 """ 1378 Print full help for listed options and exit 1379 1380 Required Arguments: 1381 options -- list of strings indicating which options to list 1382 help on (preceding '-' and '--' should be removed) 1383 1384 """ 1385 display = [] 1386 for sectkey, section in list(self.items()): 1387 for optkey, option in list(section.items()): 1388 if option.long in options or option.short in options: 1389 display.append((sectkey, optkey, option)) 1390 1391 display.reverse() 1392 1393 err = sys.stderr.write 1394 1395 while display: 1396 sectkey, optkey, opt = display.pop() 1397 err('Command Line: %s\n' % self._option_usage(opt)) 1398 err('Configuration File: [%s] %s=\n' % (sectkey, optkey)) 1399 1400 current = opt.names()['current'] 1401 if not current: 1402 err('Current Value: %s\n\n' % current) 1403 else: 1404 err('\n') 1405 1406 err('%s\n\n' % opt.description) 1407 if display: err('\n') 1408 1409 sys.exit(1) 1410 1411 def _option_usage(self, option): 1412 """ Return the option the way it can be typed at the command line """ 1413 if option.options.strip(): 1414 short_prefixes, long_prefixes = self.get_prefixes() 1415 prefixes = long_prefixes + short_prefixes 1416 options = re.sub(r'\s+', r' ', option.options.replace('!','')) 1417 options = options.split() 1418 options = [(len(x),x) for x in options 1419 if [x for p in prefixes if x.startswith(p)]] 1420 options.sort() 1421 options = [x[1] for x in options] 1422 1423 # Determine argument separator 1424 sep = ' ' 1425 loptions = [x for x in options 1426 if [x for p in long_prefixes if x.startswith(p)]] 1427 if loptions: 1428 sep = '=' 1429 1430 options = ', '.join(options) 1431 if option.acceptsArgument() and option.requiresArgument(): 1432 return '%s%s%s' % (options, sep, option.synopsis) 1433 elif option.acceptsArgument(): 1434 return '%s[%s%s]' % (options, sep, option.synopsis) 1435 else: 1436 return options 1437 return '' 1438 1439 def usage(self, categories=None): 1440 """ Print descriptions of all command line options """ 1441 categories = categories or [] 1442 options = [] 1443 for section in list(self.values()): 1444 for option in list(section.data.values()): 1445 if option.options: 1446 options.append(option) 1447 options.sort() 1448 1449 if not options: return '' 1450 1451 name_len = 0 1452 summary_len = 0 1453 1454 # Put options into categories 1455 categorized = {} 1456 for option in options: 1457 1458 catname = self.get_category(option.category).strip() 1459 name = self._option_usage(option) 1460 1461 summary = '' 1462 if option.summary: 1463 summary = option.summary % option.names() 1464 default = option.defaultValue() 1465 if default is not None: 1466 summary += ' [%s]' % default 1467 1468 if catname not in list(categorized.keys()): 1469 categorized[catname] = [] 1470 categorized[catname].append((name,summary,option)) 1471 1472 if summary: 1473 name_len = max(name_len,len(name)) 1474 summary_len = max(summary_len,len(summary)) 1475 1476 name_len = min(name_len, int(TERMINAL_WIDTH*MAX_NAME_WIDTH_RATIO)) 1477 summary_len = TERMINAL_WIDTH - PREPAD - GUTTER - name_len 1478 1479 # Build the output string 1480 s = '' 1481 if categories: 1482 categories = [self.get_category(x) for x in list(categories.keys())] 1483 else: 1484 categories = list(categorized.keys()) 1485 categories.sort() 1486 for category in categories: 1487 options = categorized[category] 1488 if not category: 1489 if len(categories) > 1: 1490 category = 'General Options' 1491 else: 1492 category = 'Options' 1493 s += '\n%s:\n' % category 1494 for name, summary, option in options: 1495 length = len(name) 1496 summary = wrap(summary, summary_len) 1497 summary = ('\n%s' % (' '*(PREPAD+name_len+GUTTER))).join(summary) 1498 # Be lenient on the gutter if we are really close to 1499 # fitting in the allocated space 1500 format = '%s%s%s%s\n' 1501 colspace = max(GUTTER + name_len - length, GUTTER) 1502 if summary and ((name_len + GUTTER) > length): 1503 colspace = (name_len + GUTTER) - length 1504 elif summary and length > name_len: 1505 colspace = PREPAD + name_len + GUTTER 1506 format = '%s%s\n%s%s\n' 1507 s += format % (' '*PREPAD, name, ' '*colspace, summary) 1508 return s 1509 1510 1511class CommandLineManager(OrderedDict): 1512 """ Command-Line Argument Manager """ 1513 1514 def __init__(self, data=None): 1515 OrderedDict.__init__(self, data or {}) 1516 self._associations = {} 1517 1518 def usage(self): 1519 s = '' 1520 for item in list(self.values()): 1521 if isinstance(item, ConfigManager): 1522 s += item.usage() 1523 else: 1524 break 1525 return s 1526 1527 def requiresArgument(self): 1528 """ Return boolean indicating if config requires an argument """ 1529 if not self: return 0 1530 for key in list(self.keys()): 1531 item = OrderedDict.__getitem__(self, key) 1532 if isinstance(item, GenericArgument): 1533 if item.mandatory: 1534 return 1 1535 1536 def getopt(self, args=None): 1537 if not args: 1538 args = sys.argv[1:] 1539 else: 1540 args = args[:] 1541 1542 if not self: return self 1543 1544 for key in list(self.keys()): 1545 item = OrderedDict.__getitem__(self, key) 1546 association = self._associations.get(key, None) 1547 if isinstance(item, ConfigManager): 1548 if association: 1549 item.read(association) 1550 options, args = item.getopt(args) 1551 elif isinstance(item, GenericArgument): 1552 value, args = item.getArgument(args) 1553 item.setValue(value) 1554 else: 1555 raise ValueError("Unrecognized argument type.") 1556 1557 if args: 1558 raise TooManyValues('Too many command-line arguments: %s' % ' '.join(args)) 1559 1560 return self 1561 1562 def __setitem__(self, key, value): 1563 item = value 1564 if type(value) in [tuple, list]: 1565 value = list(value) 1566 item = value.pop(0) 1567 self._associations[key] = value 1568 assert isinstance(item, (ConfigManager, GenericArgument)), \ 1569 'Command-line parameters must be ConfigManagers or ' + \ 1570 'subclasses of GenericArgument' 1571 if hasattr(item, 'name') and item.name is None: 1572 item.name = key 1573 OrderedDict.__setitem__(self, key, item) 1574 1575 def __getitem__(self, key): 1576 if type(key) == slice: 1577 return self.__getslice__(key.start, key.stop) 1578 item = OrderedDict.__getitem__(self, key) 1579 if isinstance(item, ConfigManager): 1580 return item 1581 else: 1582 return item.getValue() 1583 1584 1585 1586# These must be loaded last because they depend on variables 1587# assigned in this file. 1588from plasTeX.ConfigManager.Generic import GenericOption, GenericArgument 1589from plasTeX.ConfigManager.String import StringOption, StringArgument 1590from plasTeX.ConfigManager.Integer import IntegerOption, IntegerArgument 1591from plasTeX.ConfigManager.Float import FloatOption, FloatArgument 1592from plasTeX.ConfigManager.Multi import MultiOption, MultiArgument 1593from plasTeX.ConfigManager.Compound import CompoundOption, CompoundArgument 1594from plasTeX.ConfigManager.Boolean import BooleanOption, BooleanArgument 1595from plasTeX.ConfigManager.Files import OutputFileOption, InputFileOption 1596from plasTeX.ConfigManager.Directories import OutputDirectoryOption, InputDirectoryOption 1597from plasTeX.ConfigManager.Files import OutputFileArgument, InputFileArgument 1598from plasTeX.ConfigManager.Directories import OutputDirectoryArgument, InputDirectoryArgument 1599from plasTeX.ConfigManager.Counted import CountedOption, CountedArgument 1600