#1682942: add some ConfigParser features: alternate delimiters, alternate comments, empty lines in values.  Also enhance the docs with more examples and mention SafeConfigParser before ConfigParser.  Patch by Lukas Langa, review by myself, Eric and Ezio.
diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py
index c5a2595..e00a51a 100644
--- a/Lib/test/test_cfgparser.py
+++ b/Lib/test/test_cfgparser.py
@@ -3,6 +3,7 @@
 import io
 import os
 import unittest
+import textwrap
 
 from test import support
 
@@ -23,15 +24,26 @@
     def itervalues(self): return iter(self.values())
 
 
-class TestCaseBase(unittest.TestCase):
+class CfgParserTestCaseClass(unittest.TestCase):
     allow_no_value = False
+    delimiters = ('=', ':')
+    comment_prefixes = (';', '#')
+    empty_lines_in_values = True
+    dict_type = configparser._default_dict
 
     def newconfig(self, defaults=None):
+        arguments = dict(
+            allow_no_value=self.allow_no_value,
+            delimiters=self.delimiters,
+            comment_prefixes=self.comment_prefixes,
+            empty_lines_in_values=self.empty_lines_in_values,
+            dict_type=self.dict_type,
+        )
         if defaults is None:
-            self.cf = self.config_class(allow_no_value=self.allow_no_value)
+            self.cf = self.config_class(**arguments)
         else:
             self.cf = self.config_class(defaults,
-                                        allow_no_value=self.allow_no_value)
+                                        **arguments)
         return self.cf
 
     def fromstring(self, string, defaults=None):
@@ -40,27 +52,33 @@
         cf.readfp(sio)
         return cf
 
+class BasicTestCase(CfgParserTestCaseClass):
+
     def test_basic(self):
-        config_string = (
-            "[Foo Bar]\n"
-            "foo=bar\n"
-            "[Spacey Bar]\n"
-            "foo = bar\n"
-            "[Commented Bar]\n"
-            "foo: bar ; comment\n"
-            "[Long Line]\n"
-            "foo: this line is much, much longer than my editor\n"
-            "   likes it.\n"
-            "[Section\\with$weird%characters[\t]\n"
-            "[Internationalized Stuff]\n"
-            "foo[bg]: Bulgarian\n"
-            "foo=Default\n"
-            "foo[en]=English\n"
-            "foo[de]=Deutsch\n"
-            "[Spaces]\n"
-            "key with spaces : value\n"
-            "another with spaces = splat!\n"
-            )
+        config_string = """\
+[Foo Bar]
+foo{0[0]}bar
+[Spacey Bar]
+foo {0[0]} bar
+[Spacey Bar From The Beginning]
+  foo {0[0]} bar
+  baz {0[0]} qwe
+[Commented Bar]
+foo{0[1]} bar {1[1]} comment
+baz{0[0]}qwe {1[0]}another one
+[Long Line]
+foo{0[1]} this line is much, much longer than my editor
+   likes it.
+[Section\\with$weird%characters[\t]
+[Internationalized Stuff]
+foo[bg]{0[1]} Bulgarian
+foo{0[0]}Default
+foo[en]{0[0]}English
+foo[de]{0[0]}Deutsch
+[Spaces]
+key with spaces {0[1]} value
+another with spaces {0[0]} splat!
+""".format(self.delimiters, self.comment_prefixes)
         if self.allow_no_value:
             config_string += (
                 "[NoValue]\n"
@@ -70,13 +88,14 @@
         cf = self.fromstring(config_string)
         L = cf.sections()
         L.sort()
-        E = [r'Commented Bar',
-             r'Foo Bar',
-             r'Internationalized Stuff',
-             r'Long Line',
-             r'Section\with$weird%characters[' '\t',
-             r'Spaces',
-             r'Spacey Bar',
+        E = ['Commented Bar',
+             'Foo Bar',
+             'Internationalized Stuff',
+             'Long Line',
+             'Section\\with$weird%characters[\t',
+             'Spaces',
+             'Spacey Bar',
+             'Spacey Bar From The Beginning',
              ]
         if self.allow_no_value:
             E.append(r'NoValue')
@@ -89,7 +108,10 @@
         # http://www.python.org/sf/583248
         eq(cf.get('Foo Bar', 'foo'), 'bar')
         eq(cf.get('Spacey Bar', 'foo'), 'bar')
+        eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar')
+        eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe')
         eq(cf.get('Commented Bar', 'foo'), 'bar')
+        eq(cf.get('Commented Bar', 'baz'), 'qwe')
         eq(cf.get('Spaces', 'key with spaces'), 'value')
         eq(cf.get('Spaces', 'another with spaces'), 'splat!')
         if self.allow_no_value:
@@ -140,12 +162,14 @@
 
         # SF bug #432369:
         cf = self.fromstring(
-            "[MySection]\nOption: first line\n\tsecond line\n")
+            "[MySection]\nOption{} first line\n\tsecond line\n".format(
+                self.delimiters[0]))
         eq(cf.options("MySection"), ["option"])
         eq(cf.get("MySection", "Option"), "first line\nsecond line")
 
         # SF bug #561822:
-        cf = self.fromstring("[section]\nnekey=nevalue\n",
+        cf = self.fromstring("[section]\n"
+                             "nekey{}nevalue\n".format(self.delimiters[0]),
                              defaults={"key":"value"})
         self.assertTrue(cf.has_option("section", "Key"))
 
@@ -162,18 +186,19 @@
 
     def test_parse_errors(self):
         self.newconfig()
-        e = self.parse_error(configparser.ParsingError,
-                             "[Foo]\n  extra-spaces: splat\n")
-        self.assertEqual(e.args, ('<???>',))
         self.parse_error(configparser.ParsingError,
-                         "[Foo]\n  extra-spaces= splat\n")
+                         "[Foo]\n"
+                         "{}val-without-opt-name\n".format(self.delimiters[0]))
         self.parse_error(configparser.ParsingError,
-                         "[Foo]\n:value-without-option-name\n")
-        self.parse_error(configparser.ParsingError,
-                         "[Foo]\n=value-without-option-name\n")
+                         "[Foo]\n"
+                         "{}val-without-opt-name\n".format(self.delimiters[1]))
         e = self.parse_error(configparser.MissingSectionHeaderError,
                              "No Section!\n")
         self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
+        if not self.allow_no_value:
+            e = self.parse_error(configparser.ParsingError,
+                                "[Foo]\n  wrong-indent\n")
+            self.assertEqual(e.args, ('<???>',))
 
     def parse_error(self, exc, src):
         sio = io.StringIO(src)
@@ -188,9 +213,9 @@
         self.assertFalse(cf.has_section("Foo"),
                          "new ConfigParser should have no acknowledged "
                          "sections")
-        with self.assertRaises(configparser.NoSectionError) as cm:
+        with self.assertRaises(configparser.NoSectionError):
             cf.options("Foo")
-        with self.assertRaises(configparser.NoSectionError) as cm:
+        with self.assertRaises(configparser.NoSectionError):
             cf.set("foo", "bar", "value")
         e = self.get_error(configparser.NoSectionError, "foo", "bar")
         self.assertEqual(e.args, ("foo",))
@@ -210,21 +235,21 @@
     def test_boolean(self):
         cf = self.fromstring(
             "[BOOLTEST]\n"
-            "T1=1\n"
-            "T2=TRUE\n"
-            "T3=True\n"
-            "T4=oN\n"
-            "T5=yes\n"
-            "F1=0\n"
-            "F2=FALSE\n"
-            "F3=False\n"
-            "F4=oFF\n"
-            "F5=nO\n"
-            "E1=2\n"
-            "E2=foo\n"
-            "E3=-1\n"
-            "E4=0.1\n"
-            "E5=FALSE AND MORE"
+            "T1{equals}1\n"
+            "T2{equals}TRUE\n"
+            "T3{equals}True\n"
+            "T4{equals}oN\n"
+            "T5{equals}yes\n"
+            "F1{equals}0\n"
+            "F2{equals}FALSE\n"
+            "F3{equals}False\n"
+            "F4{equals}oFF\n"
+            "F5{equals}nO\n"
+            "E1{equals}2\n"
+            "E2{equals}foo\n"
+            "E3{equals}-1\n"
+            "E4{equals}0.1\n"
+            "E5{equals}FALSE AND MORE".format(equals=self.delimiters[0])
             )
         for x in range(1, 5):
             self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x))
@@ -242,11 +267,17 @@
     def test_write(self):
         config_string = (
             "[Long Line]\n"
-            "foo: this line is much, much longer than my editor\n"
+            "foo{0[0]} this line is much, much longer than my editor\n"
             "   likes it.\n"
             "[DEFAULT]\n"
-            "foo: another very\n"
+            "foo{0[1]} another very\n"
             " long line\n"
+            "[Long Line - With Comments!]\n"
+            "test {0[1]} we        {comment} can\n"
+            "            also      {comment} place\n"
+            "            comments  {comment} in\n"
+            "            multiline {comment} values"
+            "\n".format(self.delimiters, comment=self.comment_prefixes[0])
             )
         if self.allow_no_value:
             config_string += (
@@ -259,13 +290,19 @@
         cf.write(output)
         expect_string = (
             "[DEFAULT]\n"
-            "foo = another very\n"
+            "foo {equals} another very\n"
             "\tlong line\n"
             "\n"
             "[Long Line]\n"
-            "foo = this line is much, much longer than my editor\n"
+            "foo {equals} this line is much, much longer than my editor\n"
             "\tlikes it.\n"
             "\n"
+            "[Long Line - With Comments!]\n"
+            "test {equals} we\n"
+            "\talso\n"
+            "\tcomments\n"
+            "\tmultiline\n"
+            "\n".format(equals=self.delimiters[0])
             )
         if self.allow_no_value:
             expect_string += (
@@ -277,7 +314,7 @@
 
     def test_set_string_types(self):
         cf = self.fromstring("[sect]\n"
-                             "option1=foo\n")
+                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
         # Check that we don't get an exception when setting values in
         # an existing section using strings:
         class mystr(str):
@@ -290,6 +327,9 @@
         cf.set("sect", "option2", "splat")
 
     def test_read_returns_file_list(self):
+        if self.delimiters[0] != '=':
+            # skip reading the file if we're using an incompatible format
+            return
         file1 = support.findfile("cfgparser.1")
         # check when we pass a mix of readable and non-readable files:
         cf = self.newconfig()
@@ -314,45 +354,45 @@
     def get_interpolation_config(self):
         return self.fromstring(
             "[Foo]\n"
-            "bar=something %(with1)s interpolation (1 step)\n"
-            "bar9=something %(with9)s lots of interpolation (9 steps)\n"
-            "bar10=something %(with10)s lots of interpolation (10 steps)\n"
-            "bar11=something %(with11)s lots of interpolation (11 steps)\n"
-            "with11=%(with10)s\n"
-            "with10=%(with9)s\n"
-            "with9=%(with8)s\n"
-            "with8=%(With7)s\n"
-            "with7=%(WITH6)s\n"
-            "with6=%(with5)s\n"
-            "With5=%(with4)s\n"
-            "WITH4=%(with3)s\n"
-            "with3=%(with2)s\n"
-            "with2=%(with1)s\n"
-            "with1=with\n"
+            "bar{equals}something %(with1)s interpolation (1 step)\n"
+            "bar9{equals}something %(with9)s lots of interpolation (9 steps)\n"
+            "bar10{equals}something %(with10)s lots of interpolation (10 steps)\n"
+            "bar11{equals}something %(with11)s lots of interpolation (11 steps)\n"
+            "with11{equals}%(with10)s\n"
+            "with10{equals}%(with9)s\n"
+            "with9{equals}%(with8)s\n"
+            "with8{equals}%(With7)s\n"
+            "with7{equals}%(WITH6)s\n"
+            "with6{equals}%(with5)s\n"
+            "With5{equals}%(with4)s\n"
+            "WITH4{equals}%(with3)s\n"
+            "with3{equals}%(with2)s\n"
+            "with2{equals}%(with1)s\n"
+            "with1{equals}with\n"
             "\n"
             "[Mutual Recursion]\n"
-            "foo=%(bar)s\n"
-            "bar=%(foo)s\n"
+            "foo{equals}%(bar)s\n"
+            "bar{equals}%(foo)s\n"
             "\n"
             "[Interpolation Error]\n"
-            "name=%(reference)s\n",
+            "name{equals}%(reference)s\n".format(equals=self.delimiters[0]),
             # no definition for 'reference'
             defaults={"getname": "%(__name__)s"})
 
     def check_items_config(self, expected):
         cf = self.fromstring(
             "[section]\n"
-            "name = value\n"
-            "key: |%(name)s| \n"
-            "getdefault: |%(default)s|\n"
-            "getname: |%(__name__)s|",
+            "name {0[0]} value\n"
+            "key{0[1]} |%(name)s| \n"
+            "getdefault{0[1]} |%(default)s|\n"
+            "getname{0[1]} |%(__name__)s|".format(self.delimiters),
             defaults={"default": "<default>"})
         L = list(cf.items("section"))
         L.sort()
         self.assertEqual(L, expected)
 
 
-class ConfigParserTestCase(TestCaseBase):
+class ConfigParserTestCase(BasicTestCase):
     config_class = configparser.ConfigParser
 
     def test_interpolation(self):
@@ -414,7 +454,11 @@
         self.assertRaises(ValueError, cf.get, 'non-string',
                           'string_with_interpolation', raw=False)
 
-class MultilineValuesTestCase(TestCaseBase):
+class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
+    delimiters = (':=', '$')
+    comment_prefixes = ('//', '"')
+
+class MultilineValuesTestCase(BasicTestCase):
     config_class = configparser.ConfigParser
     wonderful_spam = ("I'm having spam spam spam spam "
                       "spam spam spam beaked beans spam "
@@ -442,7 +486,7 @@
         self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
                          self.wonderful_spam.replace('\t\n', '\n'))
 
-class RawConfigParserTestCase(TestCaseBase):
+class RawConfigParserTestCase(BasicTestCase):
     config_class = configparser.RawConfigParser
 
     def test_interpolation(self):
@@ -476,6 +520,28 @@
                          [0, 1, 1, 2, 3, 5, 8, 13])
         self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159})
 
+class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
+    delimiters = (':=', '$')
+    comment_prefixes = ('//', '"')
+
+class RawConfigParserTestSambaConf(BasicTestCase):
+    config_class = configparser.RawConfigParser
+    comment_prefixes = ('#', ';', '//', '----')
+    empty_lines_in_values = False
+
+    def test_reading(self):
+        smbconf = support.findfile("cfgparser.2")
+        # check when we pass a mix of readable and non-readable files:
+        cf = self.newconfig()
+        parsed_files = cf.read([smbconf, "nonexistent-file"])
+        self.assertEqual(parsed_files, [smbconf])
+        sections = ['global', 'homes', 'printers',
+                    'print$', 'pdf-generator', 'tmp', 'Agustin']
+        self.assertEqual(cf.sections(), sections)
+        self.assertEqual(cf.get("global", "workgroup"), "MDKGROUP")
+        self.assertEqual(cf.getint("global", "max log size"), 50)
+        self.assertEqual(cf.get("global", "hosts allow"), "127.")
+        self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s")
 
 class SafeConfigParserTestCase(ConfigParserTestCase):
     config_class = configparser.SafeConfigParser
@@ -483,16 +549,17 @@
     def test_safe_interpolation(self):
         # See http://www.python.org/sf/511737
         cf = self.fromstring("[section]\n"
-                             "option1=xxx\n"
-                             "option2=%(option1)s/xxx\n"
-                             "ok=%(option1)s/%%s\n"
-                             "not_ok=%(option2)s/%%s")
+                             "option1{eq}xxx\n"
+                             "option2{eq}%(option1)s/xxx\n"
+                             "ok{eq}%(option1)s/%%s\n"
+                             "not_ok{eq}%(option2)s/%%s".format(
+                                 eq=self.delimiters[0]))
         self.assertEqual(cf.get("section", "ok"), "xxx/%s")
         self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s")
 
     def test_set_malformatted_interpolation(self):
         cf = self.fromstring("[sect]\n"
-                             "option1=foo\n")
+                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
 
         self.assertEqual(cf.get('sect', "option1"), "foo")
 
@@ -508,7 +575,7 @@
 
     def test_set_nonstring_types(self):
         cf = self.fromstring("[sect]\n"
-                             "option1=foo\n")
+                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
         # Check that we get a TypeError when setting non-string values
         # in an existing section:
         self.assertRaises(TypeError, cf.set, "sect", "option1", 1)
@@ -526,15 +593,16 @@
         cf = self.newconfig()
         self.assertRaises(ValueError, cf.add_section, "DEFAULT")
 
+class SafeConfigParserTestCaseNonStandardDelimiters(SafeConfigParserTestCase):
+    delimiters = (':=', '$')
+    comment_prefixes = ('//', '"')
 
 class SafeConfigParserTestCaseNoValue(SafeConfigParserTestCase):
     allow_no_value = True
 
 
 class SortedTestCase(RawConfigParserTestCase):
-    def newconfig(self, defaults=None):
-        self.cf = self.config_class(defaults=defaults, dict_type=SortedDict)
-        return self.cf
+    dict_type = SortedDict
 
     def test_sorted(self):
         self.fromstring("[b]\n"
@@ -556,14 +624,36 @@
                           "o4 = 1\n\n")
 
 
+class CompatibleTestCase(CfgParserTestCaseClass):
+    config_class = configparser.RawConfigParser
+    comment_prefixes = configparser.RawConfigParser._COMPATIBLE
+
+    def test_comment_handling(self):
+        config_string = textwrap.dedent("""\
+        [Commented Bar]
+        baz=qwe ; a comment
+        foo: bar # not a comment!
+        # but this is a comment
+        ; another comment
+        """)
+        cf = self.fromstring(config_string)
+        self.assertEqual(cf.get('Commented Bar', 'foo'), 'bar # not a comment!')
+        self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe')
+
+
 def test_main():
     support.run_unittest(
         ConfigParserTestCase,
+        ConfigParserTestCaseNonStandardDelimiters,
         MultilineValuesTestCase,
         RawConfigParserTestCase,
+        RawConfigParserTestCaseNonStandardDelimiters,
+        RawConfigParserTestSambaConf,
         SafeConfigParserTestCase,
+        SafeConfigParserTestCaseNonStandardDelimiters,
         SafeConfigParserTestCaseNoValue,
         SortedTestCase,
+        CompatibleTestCase,
         )