55import copy
66from configparser import ConfigParser , DuplicateOptionError , Error , MissingSectionHeaderError , ParsingError
77from io import StringIO
8- from typing import TYPE_CHECKING , Any , ClassVar , Iterator
8+ from typing import TYPE_CHECKING , Any , ClassVar , Iterable , Iterator
99
1010import dictdiffer
1111from configupdater import ConfigUpdater , Space
@@ -67,9 +67,17 @@ def post_init(self):
6767 self .updater = ConfigUpdater ()
6868 self .comma_separated_values = set (self .comma_separated_values_dict .get (self .filename , []))
6969
70- if not self .needs_top_section :
71- return
7270 if all (isinstance (v , dict ) for v in self .expected_config .values ()):
71+ for section in self .expected_config :
72+ for key , value in self .expected_config [section ].items ():
73+ if self ._is_multiline_value (section , key , value ):
74+ # Convert the value to a string that's compatible with ConfigUpdater
75+ # Remove the indent to be diff-able later with values in self.updater,
76+ # which was read from existing config
77+ lines = [line .strip () for line in value .strip ().split ("\n " )]
78+ self .expected_config [section ][key ] = self ._get_configupdater_values (lines , indent = "" )
79+ return
80+ if not self .needs_top_section :
7381 return
7482
7583 new_config = {TOP_SECTION : {}}
@@ -134,6 +142,12 @@ def get_missing_output(self) -> str:
134142 parser = ConfigParser ()
135143 for section in sorted (missing , key = lambda s : "0" if s == TOP_SECTION else f"1{ s } " ):
136144 expected_config : dict = self .expected_config [section ]
145+ for k , v in expected_config .items (): # pylint: disable=invalid-name
146+ if self ._is_multiline_value (section , k , v ):
147+ # Convert the value to a string that's compatible with ConfigUpdater
148+ lines = [line .strip () for line in v .strip ().split ("\n " )]
149+ expected_config [k ] = self ._get_configupdater_values (lines )
150+
137151 if self .autofix :
138152 if self .updater .last_block :
139153 self .updater .last_block .add_after .space (1 )
@@ -143,6 +157,22 @@ def get_missing_output(self) -> str:
143157 parser [section ] = expected_config
144158 return self .contents_without_top_section (self .get_example_cfg (parser ))
145159
160+ def _is_multiline_value (self , section : str , key : str , value : int | str ) -> bool :
161+ """Check if the value is a multiline value."""
162+ return f"{ section } .{ key } " not in self .comma_separated_values and "\n " in str (value )
163+
164+ @staticmethod
165+ def _get_configupdater_values (values : Iterable [str ], separator = "\n " , indent = 4 * " " ):
166+ """Convert a list of values to a string compatible with ConfigUpdater.
167+
168+ This is similar to the ConfigUpdater's set_values() method
169+ """
170+ values = list (values ).copy ()
171+ if "\n " in separator :
172+ values = ["" , * values ]
173+ separator = separator + indent
174+ return separator .join (values )
175+
146176 # TODO: refactor: convert the contents to dict (with IniConfig().sections?) and mimic other plugins doing dict diffs
147177 def enforce_rules (self ) -> Iterator [Fuss ]:
148178 """Enforce rules on missing sections and missing key/value pairs in an INI file."""
@@ -323,13 +353,28 @@ def compare_different_keys(self, section, key, raw_actual: Any, raw_expected: An
323353 actual = str (raw_actual ).lower ()
324354 expected = str (raw_expected ).lower ()
325355 else :
356+ if self ._is_multiline_value (section , key , raw_expected ):
357+ # Find missing lines that should be in the expected value
358+ actual_lines = [line .strip () for line in raw_actual .strip ().split ("\n " )]
359+ expected_lines = [line .strip () for line in raw_expected .strip ().split ("\n " )]
360+ missing_lines = set (expected_lines ) - set (actual_lines )
361+ # expected value should be the actual lines plus the missing lines
362+ raw_expected = raw_actual
363+ for line in missing_lines :
364+ raw_expected += "\n " + line
365+
326366 actual = raw_actual
327367 expected = raw_expected
328368 if actual == expected :
329369 return
330370
331371 if self .autofix :
332- self .updater [section ][key ].value = expected
372+ if self ._is_multiline_value (section , key , expected ):
373+ # Handle multiline config using ConfigUpdater's set_values()
374+ expected_lines = [line .strip () for line in expected .strip ().split ("\n " )]
375+ self .updater [section ][key ].set_values (expected_lines )
376+ else :
377+ self .updater [section ][key ].value = expected
333378 self .dirty = True
334379 if section == TOP_SECTION :
335380 yield self .reporter .make_fuss (
0 commit comments