Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Lib/configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,9 @@ def __init__(self, full_prefixes, inline_prefixes):
)
self.pattern = re.compile('|'.join(itertools.chain(full_patterns, inline_patterns)))

def strip(self, text):
return self.pattern.sub('', text).rstrip()
def strip(self, text, *, rstrip=True):
text = self.pattern.sub('', text)
return text.rstrip() if rstrip else text

def wrap(self, text):
return _Line(text, self)
Expand Down Expand Up @@ -1156,7 +1157,13 @@ def _handle_option(self, st, line, fpname):
# an option line?
st.indent_level = st.cur_indent_level

mo = self._optcre.match(line.clean)
# `line.clean` strips trailing whitespace, but for a whitespace delimiter
# that whitespace is the separator before an (empty) value, so match
# against a form that preserves it. `optval` is stripped below, so
# ordinary values are unaffected.
match_target = self._comments.strip(str(line).strip('\r\n').lstrip(),
rstrip=False)
mo = self._optcre.match(match_target)
if not mo:
# a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,23 @@ def test_any_delimiter(self, delimiter, space_before, space_after):
self.assertEqual(cf.options('all'), ['foo'])
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')

def test_whitespace_delimiter_empty_value(self):
# gh-157456: an option with a whitespace-ending delimiter and an empty
# value must parse rather than raise ParsingError, and the parser must
# read back the output produced by write().
cf = self.newconfig(delimiters=(' ',))
cf.read_string("[all]\nkey \n")
# With allow_no_value a whitespace delimiter cannot distinguish an
# empty value from a valueless option, so the value is None there;
# otherwise it is the empty string.
expected = None if cf._allow_no_value else ''
self.assertEqual(cf.get('all', 'key'), expected)
output = io.StringIO()
cf.write(output)
cf2 = self.newconfig(delimiters=(' ',))
cf2.read_string(output.getvalue())
self.assertEqual(cf2.get('all', 'key'), expected)

def test_basic_from_dict(self):
config = {
"Foo Bar": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :mod:`configparser` failing to parse an option with an empty value when
the delimiter ends in whitespace (for example ``delimiters=(' ',)``). Such an
option, including the output produced by :meth:`~configparser.RawConfigParser.write`,
is now read back correctly.
Loading