From: Brian Curtin Date: Mon, 26 Jul 2010 02:30:15 +0000 (+0000) Subject: Fix #7113. Patch by Łukasz Langa. X-Git-Tag: v2.7.1rc1~556 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e4334b4949be3cbb1e8602291deb650bb04642ab;p=python Fix #7113. Patch by Łukasz Langa. Changes include using a list of lines instead of patching together using string interpolation, and a multi-line value test cases. --- diff --git a/Lib/ConfigParser.py b/Lib/ConfigParser.py index cee6b2aec5..3f041c734f 100644 --- a/Lib/ConfigParser.py +++ b/Lib/ConfigParser.py @@ -398,12 +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__": - if value is None: - fp.write("%s\n" % (key)) - else: - fp.write("%s = %s\n" % - (key, str(value).replace('\n', '\n\t'))) + if key == "__name__": + continue + if value is not None: + key = " = ".join((key, str(value).replace('\n', '\n\t'))) + fp.write("%s\n" % (key)) fp.write("\n") def remove_option(self, section, option): @@ -464,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: @@ -483,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? @@ -508,6 +507,7 @@ class RawConfigParser: mo = self._optcre.match(line) if mo: optname, vi, optval = mo.group('option', 'vi', 'value') + optname = self.optionxform(optname.rstrip()) # This check is fine because the OPTCRE cannot # match if it would set optval to None if optval is not None: @@ -518,11 +518,13 @@ class RawConfigParser: 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 + # 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 @@ -535,6 +537,13 @@ 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) class ConfigParser(RawConfigParser): diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py index 6c50287802..22a314d779 100644 --- a/Lib/test/test_cfgparser.py +++ b/Lib/test/test_cfgparser.py @@ -1,5 +1,6 @@ import ConfigParser import StringIO +import os import unittest import UserDict @@ -186,7 +187,8 @@ class TestCaseBase(unittest.TestCase): self.assertEqual(cf.sections(), [], "new ConfigParser should have no defined sections") self.assertFalse(cf.has_section("Foo"), - "new ConfigParser should have no acknowledged sections") + "new ConfigParser should have no acknowledged " + "sections") self.assertRaises(ConfigParser.NoSectionError, cf.options, "Foo") self.assertRaises(ConfigParser.NoSectionError, @@ -357,6 +359,11 @@ class ConfigParserTestCase(TestCaseBase): config_class = ConfigParser.ConfigParser def test_interpolation(self): + rawval = { + ConfigParser.ConfigParser: ("something %(with11)s " + "lots of interpolation (11 steps)"), + ConfigParser.SafeConfigParser: "%(with1)s", + } cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "getname"), "Foo") @@ -403,6 +410,33 @@ class ConfigParserTestCase(TestCaseBase): self.assertRaises(ValueError, cf.get, 'non-string', 'string_with_interpolation', raw=False) +class MultilineValuesTestCase(TestCaseBase): + config_class = ConfigParser.ConfigParser + wonderful_spam = ("I'm having spam spam spam spam " + "spam spam spam beaked beans spam " + "spam spam and spam!").replace(' ', '\t\n') + + def setUp(self): + cf = self.newconfig() + for i in range(100): + s = 'section{}'.format(i) + cf.add_section(s) + for j in range(10): + cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam) + with open(test_support.TESTFN, 'w') as f: + cf.write(f) + + def tearDown(self): + os.unlink(test_support.TESTFN) + + def test_dominating_multiline_values(self): + # we're reading from file because this is where the code changed + # during performance updates in Python 3.2 + cf_from_file = self.newconfig() + with open(test_support.TESTFN) as f: + cf_from_file.readfp(f) + self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'), + self.wonderful_spam.replace('\t\n', '\n')) class RawConfigParserTestCase(TestCaseBase): config_class = ConfigParser.RawConfigParser @@ -521,10 +555,11 @@ class SortedTestCase(RawConfigParserTestCase): def test_main(): test_support.run_unittest( ConfigParserTestCase, + MultilineValuesTestCase, RawConfigParserTestCase, SafeConfigParserTestCase, - SortedTestCase, SafeConfigParserTestCaseNoValue, + SortedTestCase, )