summaryrefslogtreecommitdiff
path: root/Lib/ConfigParser.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/ConfigParser.py')
-rw-r--r--Lib/ConfigParser.py151
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)