mirror of
				https://github.com/python/cpython.git
				synced 2025-11-01 06:01:29 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									688b9e384e
								
							
						
					
					
						commit
						9a27b0cd19
					
				
					 2 changed files with 60 additions and 21 deletions
				
			
		|  | @ -405,12 +405,11 @@ def write(self, fp): | |||
|         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): | ||||
|  | @ -471,10 +470,10 @@ def _read(self, fp, fpname): | |||
|         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: | ||||
|  | @ -490,7 +489,7 @@ def _read(self, fp, fpname): | |||
|             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? | ||||
|  | @ -515,6 +514,7 @@ def _read(self, fp, fpname): | |||
|                     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: | ||||
|  | @ -525,11 +525,13 @@ def _read(self, fp, fpname): | |||
|                                 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 | ||||
|  | @ -542,6 +544,13 @@ def _read(self, fp, fpname): | |||
|         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): | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,8 @@ | |||
| import collections | ||||
| import configparser | ||||
| import io | ||||
| import os | ||||
| import unittest | ||||
| import collections | ||||
| 
 | ||||
| from test import support | ||||
| 
 | ||||
|  | @ -162,7 +163,7 @@ def test_default_case_sensitivity(self): | |||
|     def test_parse_errors(self): | ||||
|         self.newconfig() | ||||
|         e = self.parse_error(configparser.ParsingError, | ||||
|                          "[Foo]\n  extra-spaces: splat\n") | ||||
|                              "[Foo]\n  extra-spaces: splat\n") | ||||
|         self.assertEqual(e.args, ('<???>',)) | ||||
|         self.parse_error(configparser.ParsingError, | ||||
|                          "[Foo]\n  extra-spaces= splat\n") | ||||
|  | @ -171,7 +172,7 @@ def test_parse_errors(self): | |||
|         self.parse_error(configparser.ParsingError, | ||||
|                          "[Foo]\n=value-without-option-name\n") | ||||
|         e = self.parse_error(configparser.MissingSectionHeaderError, | ||||
|                          "No Section!\n") | ||||
|                              "No Section!\n") | ||||
|         self.assertEqual(e.args, ('<???>', 1, "No Section!\n")) | ||||
| 
 | ||||
|     def parse_error(self, exc, src): | ||||
|  | @ -185,7 +186,8 @@ def test_query_errors(self): | |||
|         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") | ||||
|         with self.assertRaises(configparser.NoSectionError) as cm: | ||||
|             cf.options("Foo") | ||||
|         with self.assertRaises(configparser.NoSectionError) as cm: | ||||
|  | @ -355,8 +357,8 @@ class ConfigParserTestCase(TestCaseBase): | |||
| 
 | ||||
|     def test_interpolation(self): | ||||
|         rawval = { | ||||
|             configparser.ConfigParser: "something %(with11)s "\ | ||||
|                                            "lots of interpolation (11 steps)", | ||||
|             configparser.ConfigParser: ("something %(with11)s " | ||||
|                                         "lots of interpolation (11 steps)"), | ||||
|             configparser.SafeConfigParser: "%(with1)s", | ||||
|         } | ||||
|         cf = self.get_interpolation_config() | ||||
|  | @ -412,6 +414,33 @@ def test_set_nonstring_types(self): | |||
|         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(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): | ||||
|     config_class = configparser.RawConfigParser | ||||
|  | @ -530,10 +559,11 @@ def test_sorted(self): | |||
| def test_main(): | ||||
|     support.run_unittest( | ||||
|         ConfigParserTestCase, | ||||
|         MultilineValuesTestCase, | ||||
|         RawConfigParserTestCase, | ||||
|         SafeConfigParserTestCase, | ||||
|         SortedTestCase, | ||||
|         SafeConfigParserTestCaseNoValue, | ||||
|         SortedTestCase, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Brian Curtin
						Brian Curtin