diff options
Diffstat (limited to 'Lib/ConfigParser.py')
-rw-r--r-- | Lib/ConfigParser.py | 151 |
1 files changed, 113 insertions, 38 deletions
diff --git a/Lib/ConfigParser.py b/Lib/ConfigParser.py index c72abb7d37..6cf3c2ac5d 100644 --- a/Lib/ConfigParser.py +++ b/Lib/ConfigParser.py @@ -87,6 +87,12 @@ ConfigParser -- responsible for parsing a list of write the configuration state in .ini format """ +try: + from collections import OrderedDict as _default_dict +except ImportError: + # fallback for setup.py which hasn't yet built _collections + _default_dict = dict + import re __all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError", @@ -215,10 +221,15 @@ class MissingSectionHeaderError(ParsingError): class RawConfigParser: - def __init__(self, defaults=None, dict_type=dict): + def __init__(self, defaults=None, dict_type=_default_dict, + allow_no_value=False): self._dict = dict_type self._sections = self._dict() self._defaults = self._dict() + if allow_no_value: + self._optcre = self.OPTCRE_NV + else: + self._optcre = self.OPTCRE if defaults: for key, value in defaults.items(): self._defaults[self.optionxform(key)] = value @@ -366,7 +377,7 @@ class RawConfigParser: return (option in self._sections[section] or option in self._defaults) - def set(self, section, option, value): + def set(self, section, option, value=None): """Set an option.""" if not section or section == DEFAULTSECT: sectdict = self._defaults @@ -387,9 +398,11 @@ class RawConfigParser: for section in self._sections: fp.write("[%s]\n" % section) for (key, value) in self._sections[section].items(): - if key != "__name__": - fp.write("%s = %s\n" % - (key, str(value).replace('\n', '\n\t'))) + if key == "__name__": + continue + if (value is not None) or (self._optcre == self.OPTCRE): + key = " = ".join((key, str(value).replace('\n', '\n\t'))) + fp.write("%s\n" % (key)) fp.write("\n") def remove_option(self, section, option): @@ -430,6 +443,15 @@ class RawConfigParser: # by any # space/tab r'(?P<value>.*)$' # everything up to eol ) + OPTCRE_NV = re.compile( + r'(?P<option>[^:=\s][^:=]*)' # very permissive! + r'\s*(?:' # any number of space/tab, + r'(?P<vi>[:=])\s*' # optionally followed by + # separator (either : or + # =), followed by any # + # space/tab + r'(?P<value>.*))?$' # everything up to eol + ) def _read(self, fp, fpname): """Parse a sectioned setup file. @@ -441,10 +463,10 @@ class RawConfigParser: leading whitespace. Blank lines, lines beginning with a '#', and just about everything else are ignored. """ - cursect = None # None, or a dictionary + cursect = None # None, or a dictionary optname = None lineno = 0 - e = None # None, or an exception + e = None # None, or an exception while True: line = fp.readline() if not line: @@ -460,7 +482,7 @@ class RawConfigParser: if line[0].isspace() and cursect is not None and optname: value = line.strip() if value: - cursect[optname] = "%s\n%s" % (cursect[optname], value) + cursect[optname].append(value) # a section header or option header? else: # is it a section header? @@ -482,21 +504,27 @@ class RawConfigParser: raise MissingSectionHeaderError(fpname, lineno, line) # an option line? else: - mo = self.OPTCRE.match(line) + mo = self._optcre.match(line) if mo: optname, vi, optval = mo.group('option', 'vi', 'value') - if vi in ('=', ':') and ';' in optval: - # ';' is a comment delimiter only if it follows - # a spacing character - pos = optval.find(';') - if pos != -1 and optval[pos-1].isspace(): - optval = optval[:pos] - optval = optval.strip() - # allow empty values - if optval == '""': - optval = '' optname = self.optionxform(optname.rstrip()) - cursect[optname] = optval + # This check is fine because the OPTCRE cannot + # match if it would set optval to None + if optval is not None: + if vi in ('=', ':') and ';' in optval: + # ';' is a comment delimiter only if it follows + # a spacing character + pos = optval.find(';') + if pos != -1 and optval[pos-1].isspace(): + optval = optval[:pos] + optval = optval.strip() + # allow empty values + if optval == '""': + optval = '' + cursect[optname] = [optval] + else: + # valueless option handling + cursect[optname] = optval else: # a non-fatal parsing error occurred. set up the # exception but keep going. the exception will be @@ -509,6 +537,45 @@ class RawConfigParser: if e: raise e + # join the multi-line values collected while reading + all_sections = [self._defaults] + all_sections.extend(self._sections.values()) + for options in all_sections: + for name, val in options.items(): + if isinstance(val, list): + options[name] = '\n'.join(val) + +import UserDict as _UserDict + +class _Chainmap(_UserDict.DictMixin): + """Combine multiple mappings for successive lookups. + + For example, to emulate Python's normal lookup sequence: + + import __builtin__ + pylookup = _Chainmap(locals(), globals(), vars(__builtin__)) + """ + + def __init__(self, *maps): + self._maps = maps + + def __getitem__(self, key): + for mapping in self._maps: + try: + return mapping[key] + except KeyError: + pass + raise KeyError(key) + + def keys(self): + result = [] + seen = set() + for mapping in self_maps: + for key in mapping: + if key not in seen: + result.append(key) + seen.add(key) + return result class ConfigParser(RawConfigParser): @@ -524,23 +591,25 @@ class ConfigParser(RawConfigParser): The section DEFAULT is special. """ - d = self._defaults.copy() + sectiondict = {} try: - d.update(self._sections[section]) + sectiondict = self._sections[section] except KeyError: if section != DEFAULTSECT: raise NoSectionError(section) # Update with the entry specific variables + vardict = {} if vars: for key, value in vars.items(): - d[self.optionxform(key)] = value + vardict[self.optionxform(key)] = value + d = _Chainmap(vardict, sectiondict, self._defaults) option = self.optionxform(option) try: value = d[option] except KeyError: raise NoOptionError(option, section) - if raw: + if raw or value is None: return value else: return self._interpolate(section, option, value, d) @@ -583,7 +652,7 @@ class ConfigParser(RawConfigParser): depth = MAX_INTERPOLATION_DEPTH while depth: # Loop through this until it's done depth -= 1 - if "%(" in value: + if value and "%(" in value: value = self._KEYCRE.sub(self._interpolation_replace, value) try: value = value % vars @@ -592,7 +661,7 @@ class ConfigParser(RawConfigParser): option, section, rawval, e.args[0]) else: break - if "%(" in value: + if value and "%(" in value: raise InterpolationDepthError(option, section, rawval) return value @@ -654,17 +723,23 @@ class SafeConfigParser(ConfigParser): option, section, "'%%' must be followed by '%%' or '(', found: %r" % (rest,)) - def set(self, section, option, value): + def set(self, section, option, value=None): """Set an option. Extend ConfigParser.set: check for string values.""" - if not isinstance(value, basestring): - raise TypeError("option values must be strings") - # check for bad percent signs: - # first, replace all "good" interpolations - tmp_value = value.replace('%%', '') - tmp_value = self._interpvar_re.sub('', tmp_value) - # then, check if there's a lone percent sign left - percent_index = tmp_value.find('%') - if percent_index != -1: - raise ValueError("invalid interpolation syntax in %r at " - "position %d" % (value, percent_index)) + # The only legal non-string value if we allow valueless + # options is None, so we need to check if the value is a + # string if: + # - we do not allow valueless options, or + # - we allow valueless options but the value is not None + if self._optcre is self.OPTCRE or value: + if not isinstance(value, basestring): + raise TypeError("option values must be strings") + if value is not None: + # check for bad percent signs: + # first, replace all "good" interpolations + tmp_value = value.replace('%%', '') + tmp_value = self._interpvar_re.sub('', tmp_value) + # then, check if there's a lone percent sign left + if '%' in tmp_value: + raise ValueError("invalid interpolation syntax in %r at " + "position %d" % (value, tmp_value.find('%'))) ConfigParser.set(self, section, option, value) |