Commit 9a27b0cd authored by Brian Curtin's avatar Brian Curtin

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.
parent 688b9e38
...@@ -405,12 +405,11 @@ class RawConfigParser: ...@@ -405,12 +405,11 @@ class RawConfigParser:
for section in self._sections: for section in self._sections:
fp.write("[%s]\n" % section) fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items(): for (key, value) in self._sections[section].items():
if key != "__name__": if key == "__name__":
if value is None: continue
fp.write("%s\n" % (key)) if value is not None:
else: key = " = ".join((key, str(value).replace('\n', '\n\t')))
fp.write("%s = %s\n" % fp.write("%s\n" % (key))
(key, str(value).replace('\n', '\n\t')))
fp.write("\n") fp.write("\n")
def remove_option(self, section, option): def remove_option(self, section, option):
...@@ -471,10 +470,10 @@ class RawConfigParser: ...@@ -471,10 +470,10 @@ class RawConfigParser:
leading whitespace. Blank lines, lines beginning with a '#', leading whitespace. Blank lines, lines beginning with a '#',
and just about everything else are ignored. and just about everything else are ignored.
""" """
cursect = None # None, or a dictionary cursect = None # None, or a dictionary
optname = None optname = None
lineno = 0 lineno = 0
e = None # None, or an exception e = None # None, or an exception
while True: while True:
line = fp.readline() line = fp.readline()
if not line: if not line:
...@@ -490,7 +489,7 @@ class RawConfigParser: ...@@ -490,7 +489,7 @@ class RawConfigParser:
if line[0].isspace() and cursect is not None and optname: if line[0].isspace() and cursect is not None and optname:
value = line.strip() value = line.strip()
if value: if value:
cursect[optname] = "%s\n%s" % (cursect[optname], value) cursect[optname].append(value)
# a section header or option header? # a section header or option header?
else: else:
# is it a section header? # is it a section header?
...@@ -515,6 +514,7 @@ class RawConfigParser: ...@@ -515,6 +514,7 @@ class RawConfigParser:
mo = self._optcre.match(line) mo = self._optcre.match(line)
if mo: if mo:
optname, vi, optval = mo.group('option', 'vi', 'value') optname, vi, optval = mo.group('option', 'vi', 'value')
optname = self.optionxform(optname.rstrip())
# This check is fine because the OPTCRE cannot # This check is fine because the OPTCRE cannot
# match if it would set optval to None # match if it would set optval to None
if optval is not None: if optval is not None:
...@@ -525,11 +525,13 @@ class RawConfigParser: ...@@ -525,11 +525,13 @@ class RawConfigParser:
if pos != -1 and optval[pos-1].isspace(): if pos != -1 and optval[pos-1].isspace():
optval = optval[:pos] optval = optval[:pos]
optval = optval.strip() optval = optval.strip()
# allow empty values # allow empty values
if optval == '""': if optval == '""':
optval = '' optval = ''
optname = self.optionxform(optname.rstrip()) cursect[optname] = [optval]
cursect[optname] = optval else:
# valueless option handling
cursect[optname] = optval
else: else:
# a non-fatal parsing error occurred. set up the # a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be # exception but keep going. the exception will be
...@@ -542,6 +544,13 @@ class RawConfigParser: ...@@ -542,6 +544,13 @@ class RawConfigParser:
if e: if e:
raise 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): class ConfigParser(RawConfigParser):
......
import collections
import configparser import configparser
import io import io
import os
import unittest import unittest
import collections
from test import support from test import support
...@@ -162,7 +163,7 @@ class TestCaseBase(unittest.TestCase): ...@@ -162,7 +163,7 @@ class TestCaseBase(unittest.TestCase):
def test_parse_errors(self): def test_parse_errors(self):
self.newconfig() self.newconfig()
e = self.parse_error(configparser.ParsingError, e = self.parse_error(configparser.ParsingError,
"[Foo]\n extra-spaces: splat\n") "[Foo]\n extra-spaces: splat\n")
self.assertEqual(e.args, ('<???>',)) self.assertEqual(e.args, ('<???>',))
self.parse_error(configparser.ParsingError, self.parse_error(configparser.ParsingError,
"[Foo]\n extra-spaces= splat\n") "[Foo]\n extra-spaces= splat\n")
...@@ -171,7 +172,7 @@ class TestCaseBase(unittest.TestCase): ...@@ -171,7 +172,7 @@ class TestCaseBase(unittest.TestCase):
self.parse_error(configparser.ParsingError, self.parse_error(configparser.ParsingError,
"[Foo]\n=value-without-option-name\n") "[Foo]\n=value-without-option-name\n")
e = self.parse_error(configparser.MissingSectionHeaderError, e = self.parse_error(configparser.MissingSectionHeaderError,
"No Section!\n") "No Section!\n")
self.assertEqual(e.args, ('<???>', 1, "No Section!\n")) self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
def parse_error(self, exc, src): def parse_error(self, exc, src):
...@@ -185,7 +186,8 @@ class TestCaseBase(unittest.TestCase): ...@@ -185,7 +186,8 @@ class TestCaseBase(unittest.TestCase):
self.assertEqual(cf.sections(), [], self.assertEqual(cf.sections(), [],
"new ConfigParser should have no defined sections") "new ConfigParser should have no defined sections")
self.assertFalse(cf.has_section("Foo"), self.assertFalse(cf.has_section("Foo"),
"new ConfigParser should have no acknowledged sections") "new ConfigParser should have no acknowledged "
"sections")
with self.assertRaises(configparser.NoSectionError) as cm: with self.assertRaises(configparser.NoSectionError) as cm:
cf.options("Foo") cf.options("Foo")
with self.assertRaises(configparser.NoSectionError) as cm: with self.assertRaises(configparser.NoSectionError) as cm:
...@@ -355,8 +357,8 @@ class ConfigParserTestCase(TestCaseBase): ...@@ -355,8 +357,8 @@ class ConfigParserTestCase(TestCaseBase):
def test_interpolation(self): def test_interpolation(self):
rawval = { rawval = {
configparser.ConfigParser: "something %(with11)s "\ configparser.ConfigParser: ("something %(with11)s "
"lots of interpolation (11 steps)", "lots of interpolation (11 steps)"),
configparser.SafeConfigParser: "%(with1)s", configparser.SafeConfigParser: "%(with1)s",
} }
cf = self.get_interpolation_config() cf = self.get_interpolation_config()
...@@ -412,6 +414,33 @@ class ConfigParserTestCase(TestCaseBase): ...@@ -412,6 +414,33 @@ class ConfigParserTestCase(TestCaseBase):
self.assertRaises(ValueError, cf.get, 'non-string', self.assertRaises(ValueError, cf.get, 'non-string',
'string_with_interpolation', raw=False) '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(support.TESTFN, 'w') as f:
cf.write(f)
def tearDown(self):
os.unlink(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(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): class RawConfigParserTestCase(TestCaseBase):
config_class = configparser.RawConfigParser config_class = configparser.RawConfigParser
...@@ -530,10 +559,11 @@ class SortedTestCase(RawConfigParserTestCase): ...@@ -530,10 +559,11 @@ class SortedTestCase(RawConfigParserTestCase):
def test_main(): def test_main():
support.run_unittest( support.run_unittest(
ConfigParserTestCase, ConfigParserTestCase,
MultilineValuesTestCase,
RawConfigParserTestCase, RawConfigParserTestCase,
SafeConfigParserTestCase, SafeConfigParserTestCase,
SortedTestCase,
SafeConfigParserTestCaseNoValue, SafeConfigParserTestCaseNoValue,
SortedTestCase,
) )
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment