From d522e469bb1c2336a65f62c21145ac43c117577d Mon Sep 17 00:00:00 2001 From: TitanSnow Date: Fri, 23 Feb 2018 17:07:21 +0800 Subject: [PATCH] bpo-32917: make ConfigParser blank line writing controllable Add two arguments *trim_final_blankline* and *blankline_around_sections* to control the behavior of :meth:`ConfigParser.write` in writing blank lines. --- Doc/library/configparser.rst | 13 ++++++++- Lib/configparser.py | 34 +++++++++++++++++----- Lib/test/test_configparser.py | 65 ++++++++++++++++++++++++------------------- 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index dd82577..01c12d5 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -1132,13 +1132,24 @@ ConfigParser Objects strings; if not, :exc:`TypeError` is raised. - .. method:: write(fileobject, space_around_delimiters=True) + .. method:: write(fileobject, space_around_delimiters=True, trim_final_blankline=False, blankline_around_sections=True) Write a representation of the configuration to the specified :term:`file object`, which must be opened in text mode (accepting strings). This representation can be parsed by a future :meth:`read` call. If *space_around_delimiters* is true, delimiters between keys and values are surrounded by spaces. + If *blankline_around_sections* is true, a blank line + will be inserted after each section. Set it to false for + some rudimentary programs which do not allow blank lines in INI file. + if *trim_final_blankline* is true, the final blank line + after the last section will not be inserted. + + .. note:: + + *trim_final_blankline* does *not* trim the final + newline character (EOL) that makes the final line + has no EOL, which is common on Windows. .. method:: remove_option(section, option) diff --git a/Lib/configparser.py b/Lib/configparser.py index 33dc9b9..1d42f5a 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -132,10 +132,16 @@ ConfigParser -- responsible for parsing a list of set(section, option, value) Set the given option. - write(fp, space_around_delimiters=True) + write(fp, space_around_delimiters=True, + trim_final_blankline=False, + blankline_around_sections=True) Write the configuration state in .ini format. If `space_around_delimiters' is True (the default), delimiters between keys and values are surrounded by spaces. + If `trim_final_blankline' is True (not default), the final + blank line will be trimmed. If `blankline_around_sections' + is True (the default), a blank line will be inserted + between sections. """ from collections.abc import MutableMapping @@ -900,11 +906,18 @@ class RawConfigParser(MutableMapping): raise NoSectionError(section) from None sectdict[self.optionxform(option)] = value - def write(self, fp, space_around_delimiters=True): + def write(self, fp, space_around_delimiters=True, + trim_final_blankline=False, blankline_around_sections=True): """Write an .ini-format representation of the configuration state. If `space_around_delimiters' is True (the default), delimiters between keys and values are surrounded by spaces. + + If `trim_final_blankline' is True (not default), the final + blank line will be trimmed. + + If `blankline_around_sections' is True (the default), a blank + line will be inserted between sections. """ if space_around_delimiters: d = " {} ".format(self._delimiters[0]) @@ -912,12 +925,19 @@ class RawConfigParser(MutableMapping): d = self._delimiters[0] if self._defaults: self._write_section(fp, self.default_section, - self._defaults.items(), d) - for section in self._sections: + self._defaults.items(), d, + (blankline_around_sections and + (not trim_final_blankline or + self._sections))) + for i, section in enumerate(self._sections.keys()): self._write_section(fp, section, - self._sections[section].items(), d) + self._sections[section].items(), d, + (blankline_around_sections and + (not trim_final_blankline or + i + 1 != len(self._sections)))) - def _write_section(self, fp, section_name, section_items, delimiter): + def _write_section(self, fp, section_name, section_items, delimiter, + final_blankline=True): """Write a single section to the specified `fp'.""" fp.write("[{}]\n".format(section_name)) for key, value in section_items: @@ -928,7 +948,7 @@ class RawConfigParser(MutableMapping): else: value = "" fp.write("{}{}\n".format(key, value)) - fp.write("\n") + if final_blankline: fp.write("\n") def remove_option(self, section, option): """Remove an option.""" diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 4d07203..883c0ed 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -663,35 +663,42 @@ boolean {0[0]} NO cf = self.fromstring(config_string) for space_around_delimiters in (True, False): - output = io.StringIO() - cf.write(output, space_around_delimiters=space_around_delimiters) - delimiter = self.delimiters[0] - if space_around_delimiters: - delimiter = " {} ".format(delimiter) - expect_string = ( - "[{default_section}]\n" - "foo{equals}another very\n" - "\tlong line\n" - "\n" - "[Long Line]\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=delimiter, - default_section=self.default_section) - ) - if self.allow_no_value: - expect_string += ( - "[Valueless]\n" - "option-without-value\n" - "\n" - ) - self.assertEqual(output.getvalue(), expect_string) + for trim_final_blankline in (True, False): + for blankline_around_sections in (True, False): + output = io.StringIO() + cf.write(output, + space_around_delimiters=space_around_delimiters, + trim_final_blankline=trim_final_blankline, + blankline_around_sections=blankline_around_sections) + delimiter = self.delimiters[0] + if space_around_delimiters: + delimiter = " {} ".format(delimiter) + expect_string = ( + "[{default_section}]\n" + "foo{equals}another very\n" + "\tlong line\n" + + ("\n" if blankline_around_sections else "") + + "[Long Line]\n" + "foo{equals}this line is much, much longer than my editor\n" + "\tlikes it.\n" + + ("\n" if blankline_around_sections else "") + + "[Long Line - With Comments!]\n" + "test{equals}we\n" + "\talso\n" + "\tcomments\n" + "\tmultiline\n" + + ('\n' if blankline_around_sections else "") + ).format(equals=delimiter, + default_section=self.default_section) + if self.allow_no_value: + expect_string += ( + "[Valueless]\n" + "option-without-value\n" + + ('\n' if blankline_around_sections else "") + ) + if trim_final_blankline and blankline_around_sections: + expect_string = expect_string[:-1] + self.assertEqual(output.getvalue(), expect_string) def test_set_string_types(self): cf = self.fromstring("[sect]\n" -- 2.11.0