Merged revisions 78232 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r78232 | fred.drake | 2010-02-19 00:24:30 -0500 (Fri, 19 Feb 2010) | 3 lines

  - apply patch from issue 7005
  - add corresponding documentation
........
diff --git a/Lib/configparser.py b/Lib/configparser.py
index bfbe4cb..d0b03f9 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -221,10 +221,15 @@
 
 
 class RawConfigParser:
-    def __init__(self, defaults=None, dict_type=_default_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
@@ -372,7 +377,7 @@
             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
@@ -394,8 +399,11 @@
             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 value is None:
+                        fp.write("%s\n" % (key))
+                    else:
+                        fp.write("%s = %s\n" %
+                                 (key, str(value).replace('\n', '\n\t')))
             fp.write("\n")
 
     def remove_option(self, section, option):
@@ -436,6 +444,15 @@
                                               # 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.
@@ -488,16 +505,19 @@
                     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()
+                        # 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 = ''
@@ -545,7 +565,7 @@
         except KeyError:
             raise NoOptionError(option, section)
 
-        if raw:
+        if raw or value is None:
             return value
         else:
             return self._interpolate(section, option, value, d)
@@ -588,7 +608,7 @@
         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
@@ -597,7 +617,7 @@
                         option, section, rawval, e.args[0])
             else:
                 break
-        if "%(" in value:
+        if value and "%(" in value:
             raise InterpolationDepthError(option, section, rawval)
         return value
 
@@ -659,10 +679,16 @@
                     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, str):
-            raise TypeError("option values must be strings")
+        # 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, str):
+                raise TypeError("option values must be strings")
         # check for bad percent signs:
         # first, replace all "good" interpolations
         tmp_value = value.replace('%%', '')