Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9a27b0c

Browse files
committed
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.
1 parent 688b9e3 commit 9a27b0c

2 files changed

Lines changed: 60 additions & 21 deletions

File tree

Lib/configparser.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,11 @@ def write(self, fp):
405405
for section in self._sections:
406406
fp.write("[%s]\n" % section)
407407
for (key, value) in self._sections[section].items():
408-
if key != "__name__":
409-
if value is None:
410-
fp.write("%s\n" % (key))
411-
else:
412-
fp.write("%s = %s\n" %
413-
(key, str(value).replace('\n', '\n\t')))
408+
if key == "__name__":
409+
continue
410+
if value is not None:
411+
key = " = ".join((key, str(value).replace('\n', '\n\t')))
412+
fp.write("%s\n" % (key))
414413
fp.write("\n")
415414

416415
def remove_option(self, section, option):
@@ -471,10 +470,10 @@ def _read(self, fp, fpname):
471470
leading whitespace. Blank lines, lines beginning with a '#',
472471
and just about everything else are ignored.
473472
"""
474-
cursect = None # None, or a dictionary
473+
cursect = None # None, or a dictionary
475474
optname = None
476475
lineno = 0
477-
e = None # None, or an exception
476+
e = None # None, or an exception
478477
while True:
479478
line = fp.readline()
480479
if not line:
@@ -490,7 +489,7 @@ def _read(self, fp, fpname):
490489
if line[0].isspace() and cursect is not None and optname:
491490
value = line.strip()
492491
if value:
493-
cursect[optname] = "%s\n%s" % (cursect[optname], value)
492+
cursect[optname].append(value)
494493
# a section header or option header?
495494
else:
496495
# is it a section header?
@@ -515,6 +514,7 @@ def _read(self, fp, fpname):
515514
mo = self._optcre.match(line)
516515
if mo:
517516
optname, vi, optval = mo.group('option', 'vi', 'value')
517+
optname = self.optionxform(optname.rstrip())
518518
# This check is fine because the OPTCRE cannot
519519
# match if it would set optval to None
520520
if optval is not None:
@@ -525,11 +525,13 @@ def _read(self, fp, fpname):
525525
if pos != -1 and optval[pos-1].isspace():
526526
optval = optval[:pos]
527527
optval = optval.strip()
528-
# allow empty values
529-
if optval == '""':
530-
optval = ''
531-
optname = self.optionxform(optname.rstrip())
532-
cursect[optname] = optval
528+
# allow empty values
529+
if optval == '""':
530+
optval = ''
531+
cursect[optname] = [optval]
532+
else:
533+
# valueless option handling
534+
cursect[optname] = optval
533535
else:
534536
# a non-fatal parsing error occurred. set up the
535537
# exception but keep going. the exception will be
@@ -542,6 +544,13 @@ def _read(self, fp, fpname):
542544
if e:
543545
raise e
544546

547+
# join the multi-line values collected while reading
548+
all_sections = [self._defaults]
549+
all_sections.extend(self._sections.values())
550+
for options in all_sections:
551+
for name, val in options.items():
552+
if isinstance(val, list):
553+
options[name] = '\n'.join(val)
545554

546555
class ConfigParser(RawConfigParser):
547556

Lib/test/test_cfgparser.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import collections
12
import configparser
23
import io
4+
import os
35
import unittest
4-
import collections
56

67
from test import support
78

@@ -162,7 +163,7 @@ def test_default_case_sensitivity(self):
162163
def test_parse_errors(self):
163164
self.newconfig()
164165
e = self.parse_error(configparser.ParsingError,
165-
"[Foo]\n extra-spaces: splat\n")
166+
"[Foo]\n extra-spaces: splat\n")
166167
self.assertEqual(e.args, ('<???>',))
167168
self.parse_error(configparser.ParsingError,
168169
"[Foo]\n extra-spaces= splat\n")
@@ -171,7 +172,7 @@ def test_parse_errors(self):
171172
self.parse_error(configparser.ParsingError,
172173
"[Foo]\n=value-without-option-name\n")
173174
e = self.parse_error(configparser.MissingSectionHeaderError,
174-
"No Section!\n")
175+
"No Section!\n")
175176
self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
176177

177178
def parse_error(self, exc, src):
@@ -185,7 +186,8 @@ def test_query_errors(self):
185186
self.assertEqual(cf.sections(), [],
186187
"new ConfigParser should have no defined sections")
187188
self.assertFalse(cf.has_section("Foo"),
188-
"new ConfigParser should have no acknowledged sections")
189+
"new ConfigParser should have no acknowledged "
190+
"sections")
189191
with self.assertRaises(configparser.NoSectionError) as cm:
190192
cf.options("Foo")
191193
with self.assertRaises(configparser.NoSectionError) as cm:
@@ -355,8 +357,8 @@ class ConfigParserTestCase(TestCaseBase):
355357

356358
def test_interpolation(self):
357359
rawval = {
358-
configparser.ConfigParser: "something %(with11)s "\
359-
"lots of interpolation (11 steps)",
360+
configparser.ConfigParser: ("something %(with11)s "
361+
"lots of interpolation (11 steps)"),
360362
configparser.SafeConfigParser: "%(with1)s",
361363
}
362364
cf = self.get_interpolation_config()
@@ -412,6 +414,33 @@ def test_set_nonstring_types(self):
412414
self.assertRaises(ValueError, cf.get, 'non-string',
413415
'string_with_interpolation', raw=False)
414416

417+
class MultilineValuesTestCase(TestCaseBase):
418+
config_class = configparser.ConfigParser
419+
wonderful_spam = ("I'm having spam spam spam spam "
420+
"spam spam spam beaked beans spam "
421+
"spam spam and spam!").replace(' ', '\t\n')
422+
423+
def setUp(self):
424+
cf = self.newconfig()
425+
for i in range(100):
426+
s = 'section{}'.format(i)
427+
cf.add_section(s)
428+
for j in range(10):
429+
cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam)
430+
with open(support.TESTFN, 'w') as f:
431+
cf.write(f)
432+
433+
def tearDown(self):
434+
os.unlink(support.TESTFN)
435+
436+
def test_dominating_multiline_values(self):
437+
# We're reading from file because this is where the code changed
438+
# during performance updates in Python 3.2
439+
cf_from_file = self.newconfig()
440+
with open(support.TESTFN) as f:
441+
cf_from_file.readfp(f)
442+
self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
443+
self.wonderful_spam.replace('\t\n', '\n'))
415444

416445
class RawConfigParserTestCase(TestCaseBase):
417446
config_class = configparser.RawConfigParser
@@ -530,10 +559,11 @@ def test_sorted(self):
530559
def test_main():
531560
support.run_unittest(
532561
ConfigParserTestCase,
562+
MultilineValuesTestCase,
533563
RawConfigParserTestCase,
534564
SafeConfigParserTestCase,
535-
SortedTestCase,
536565
SafeConfigParserTestCaseNoValue,
566+
SortedTestCase,
537567
)
538568

539569

0 commit comments

Comments
 (0)