mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Add an option to ini.set_option
Adds an new option named ``no_spaces`` to the ini.set_option function that allows the user to deteremine whether the separator (``=``) should be wrapped with spaces or not.
This commit is contained in:
parent
3ae69b9691
commit
7faee48ef3
3 changed files with 108 additions and 59 deletions
3
changelog/33669.added.md
Normal file
3
changelog/33669.added.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Issue #33669: Fixes an issue with the ``ini_managed`` execution module
|
||||||
|
where it would always wrap the separator with spaces. Adds a new parameter
|
||||||
|
named ``no_spaces`` that will not warp the separator with spaces.
|
|
@ -37,7 +37,7 @@ COM_REGX = re.compile(r"^\s*(#|;)\s*(.*)")
|
||||||
INDENTED_REGX = re.compile(r"(\s+)(.*)")
|
INDENTED_REGX = re.compile(r"(\s+)(.*)")
|
||||||
|
|
||||||
|
|
||||||
def set_option(file_name, sections=None, separator="=", encoding=None):
|
def set_option(file_name, sections=None, separator="=", encoding=None, no_spaces=False):
|
||||||
"""
|
"""
|
||||||
Edit an ini file, replacing one or more sections. Returns a dictionary
|
Edit an ini file, replacing one or more sections. Returns a dictionary
|
||||||
containing the changes made.
|
containing the changes made.
|
||||||
|
@ -66,6 +66,14 @@ def set_option(file_name, sections=None, separator="=", encoding=None):
|
||||||
|
|
||||||
.. versionadded:: 3006.6
|
.. versionadded:: 3006.6
|
||||||
|
|
||||||
|
no_spaces (bool):
|
||||||
|
A bool value that specifies if the separator will be wrapped with
|
||||||
|
spaces. This parameter was added to have the ability to not wrap the
|
||||||
|
separator with spaces. Default is ``False``, which maintains
|
||||||
|
backwards compatibility.
|
||||||
|
|
||||||
|
.. versionadded:: 3006.10
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary representing the changes made to the ini file
|
dict: A dictionary representing the changes made to the ini file
|
||||||
|
|
||||||
|
@ -88,7 +96,9 @@ def set_option(file_name, sections=None, separator="=", encoding=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sections = sections or {}
|
sections = sections or {}
|
||||||
inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding)
|
inifile = _Ini.get_ini_file(
|
||||||
|
file_name, separator=separator, encoding=encoding, no_spaces=no_spaces
|
||||||
|
)
|
||||||
changes = inifile.update(sections)
|
changes = inifile.update(sections)
|
||||||
inifile.flush()
|
inifile.flush()
|
||||||
return changes
|
return changes
|
||||||
|
@ -388,20 +398,19 @@ def get_ini(file_name, separator="=", encoding=None):
|
||||||
|
|
||||||
|
|
||||||
class _Section(OrderedDict):
|
class _Section(OrderedDict):
|
||||||
def __init__(self, name, inicontents="", separator="=", commenter="#", no_spaces=False):
|
def __init__(
|
||||||
|
self, name, inicontents="", separator="=", commenter="#", no_spaces=False
|
||||||
|
):
|
||||||
super().__init__(self)
|
super().__init__(self)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.inicontents = inicontents
|
self.inicontents = inicontents
|
||||||
self.sep = separator
|
self.sep = separator
|
||||||
self.com = commenter
|
self.com = commenter
|
||||||
if not no_spaces =
|
self.no_spaces = no_spaces
|
||||||
self.sep = ' ' + self.sep + ' '
|
|
||||||
|
|
||||||
opt_regx_prefix = r"(\s*)(.+?)\s*"
|
opt_regx_prefix = r"(\s*)(.+?)\s*"
|
||||||
opt_regx_suffix = r"\s*(.*)\s*"
|
opt_regx_suffix = r"\s*(.*)\s*"
|
||||||
self.opt_regx_str = r"{}(\{}){}".format(
|
self.opt_regx_str = rf"{opt_regx_prefix}(\{self.sep}){opt_regx_suffix}"
|
||||||
opt_regx_prefix, self.sep, opt_regx_suffix
|
|
||||||
)
|
|
||||||
self.opt_regx = re.compile(self.opt_regx_str)
|
self.opt_regx = re.compile(self.opt_regx_str)
|
||||||
|
|
||||||
def refresh(self, inicontents=None):
|
def refresh(self, inicontents=None):
|
||||||
|
@ -477,7 +486,11 @@ class _Section(OrderedDict):
|
||||||
# Ensure the value is either a _Section or a string
|
# Ensure the value is either a _Section or a string
|
||||||
if isinstance(value, (dict, OrderedDict)):
|
if isinstance(value, (dict, OrderedDict)):
|
||||||
sect = _Section(
|
sect = _Section(
|
||||||
name=key, inicontents="", separator=self.sep, commenter=self.com
|
name=key,
|
||||||
|
inicontents="",
|
||||||
|
separator=self.sep,
|
||||||
|
commenter=self.com,
|
||||||
|
no_spaces=self.no_spaces,
|
||||||
)
|
)
|
||||||
sect.update(value)
|
sect.update(value)
|
||||||
value = sect
|
value = sect
|
||||||
|
@ -509,7 +522,7 @@ class _Section(OrderedDict):
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
def gen_ini(self):
|
def gen_ini(self):
|
||||||
yield "{0}[{1}]{0}".format(os.linesep, self.name)
|
yield f"{os.linesep}[{self.name}]{os.linesep}"
|
||||||
sections_dict = OrderedDict()
|
sections_dict = OrderedDict()
|
||||||
for name, value in self.items():
|
for name, value in self.items():
|
||||||
# Handle Comment Lines
|
# Handle Comment Lines
|
||||||
|
@ -520,12 +533,19 @@ class _Section(OrderedDict):
|
||||||
sections_dict.update({name: value})
|
sections_dict.update({name: value})
|
||||||
# Key / Value pairs
|
# Key / Value pairs
|
||||||
else:
|
else:
|
||||||
yield "{}{}{}{}".format(
|
# multiple spaces will be a single space
|
||||||
name,
|
if all(c == " " for c in self.sep):
|
||||||
self.sep,
|
self.sep = " "
|
||||||
value,
|
# Default is to add spaces
|
||||||
os.linesep,
|
if self.no_spaces:
|
||||||
)
|
if self.sep != " ":
|
||||||
|
# We only strip whitespace if the delimiter is not a space
|
||||||
|
self.sep = self.sep.strip()
|
||||||
|
else:
|
||||||
|
if self.sep != " ":
|
||||||
|
# We only add spaces if the delimiter itself is not a space
|
||||||
|
self.sep = f" {self.sep.strip()} "
|
||||||
|
yield f"{name}{self.sep}{value}{os.linesep}"
|
||||||
for name, value in sections_dict.items():
|
for name, value in sections_dict.items():
|
||||||
yield from value.gen_ini()
|
yield from value.gen_ini()
|
||||||
|
|
||||||
|
@ -558,15 +578,26 @@ class _Section(OrderedDict):
|
||||||
|
|
||||||
class _Ini(_Section):
|
class _Ini(_Section):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name, inicontents="", separator="=", commenter="#", encoding=None
|
self,
|
||||||
|
name,
|
||||||
|
inicontents="",
|
||||||
|
separator="=",
|
||||||
|
commenter="#",
|
||||||
|
encoding=None,
|
||||||
|
no_spaces=False,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
self, inicontents=inicontents, separator=separator, commenter=commenter
|
self,
|
||||||
|
inicontents=inicontents,
|
||||||
|
separator=separator,
|
||||||
|
commenter=commenter,
|
||||||
|
no_spaces=no_spaces,
|
||||||
)
|
)
|
||||||
self.name = name
|
self.name = name
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
encoding = __salt_system_encoding__
|
encoding = __salt_system_encoding__
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
self.no_spaces = no_spaces
|
||||||
|
|
||||||
def refresh(self, inicontents=None):
|
def refresh(self, inicontents=None):
|
||||||
if inicontents is None:
|
if inicontents is None:
|
||||||
|
@ -613,7 +644,7 @@ class _Ini(_Section):
|
||||||
self.name, "w", encoding=self.encoding
|
self.name, "w", encoding=self.encoding
|
||||||
) as outfile:
|
) as outfile:
|
||||||
ini_gen = self.gen_ini()
|
ini_gen = self.gen_ini()
|
||||||
next(ini_gen)
|
next(ini_gen) # Next to skip the file name
|
||||||
ini_gen_list = list(ini_gen)
|
ini_gen_list = list(ini_gen)
|
||||||
# Avoid writing an initial line separator.
|
# Avoid writing an initial line separator.
|
||||||
if ini_gen_list:
|
if ini_gen_list:
|
||||||
|
@ -625,8 +656,10 @@ class _Ini(_Section):
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_ini_file(file_name, separator="=", encoding=None):
|
def get_ini_file(file_name, separator="=", encoding=None, no_spaces=False):
|
||||||
inifile = _Ini(file_name, separator=separator, encoding=encoding)
|
inifile = _Ini(
|
||||||
|
file_name, separator=separator, encoding=encoding, no_spaces=no_spaces
|
||||||
|
)
|
||||||
inifile.refresh()
|
inifile.refresh()
|
||||||
return inifile
|
return inifile
|
||||||
|
|
||||||
|
|
|
@ -94,24 +94,22 @@ def test_get_option(encoding, linesep, ini_file, ini_content):
|
||||||
)
|
)
|
||||||
ini_file.write_bytes(content)
|
ini_file.write_bytes(content)
|
||||||
|
|
||||||
assert (
|
option = ini.get_option(str(ini_file), "main", "test1", encoding=encoding)
|
||||||
ini.get_option(str(ini_file), "main", "test1", encoding=encoding) == "value 1"
|
assert option == "value 1"
|
||||||
)
|
|
||||||
assert (
|
option = ini.get_option(str(ini_file), "main", "test2", encoding=encoding)
|
||||||
ini.get_option(str(ini_file), "main", "test2", encoding=encoding) == "value 2"
|
assert option == "value 2"
|
||||||
)
|
|
||||||
assert (
|
option = ini.get_option(str(ini_file), "SectionB", "test1", encoding=encoding)
|
||||||
ini.get_option(str(ini_file), "SectionB", "test1", encoding=encoding)
|
assert option == "value 1B"
|
||||||
== "value 1B"
|
|
||||||
)
|
option = ini.get_option(str(ini_file), "SectionB", "test3", encoding=encoding)
|
||||||
assert (
|
assert option == "value 3B"
|
||||||
ini.get_option(str(ini_file), "SectionB", "test3", encoding=encoding)
|
|
||||||
== "value 3B"
|
option = ini.get_option(
|
||||||
)
|
str(ini_file), "SectionC", "empty_option", encoding=encoding
|
||||||
assert (
|
|
||||||
ini.get_option(str(ini_file), "SectionC", "empty_option", encoding=encoding)
|
|
||||||
== ""
|
|
||||||
)
|
)
|
||||||
|
assert option == ""
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
||||||
|
@ -249,11 +247,12 @@ def test_set_option(encoding, linesep, ini_file, ini_content):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("no_spaces", [True, False])
|
||||||
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"encoding", [None, "cp1252" if sys.platform == "win32" else "ISO-2022-JP"]
|
"encoding", [None, "cp1252" if sys.platform == "win32" else "ISO-2022-JP"]
|
||||||
)
|
)
|
||||||
def test_empty_value(encoding, linesep, ini_file, ini_content):
|
def test_empty_value(encoding, linesep, no_spaces, ini_file, ini_content):
|
||||||
"""
|
"""
|
||||||
Test empty value preserved after edit
|
Test empty value preserved after edit
|
||||||
"""
|
"""
|
||||||
|
@ -263,19 +262,23 @@ def test_empty_value(encoding, linesep, ini_file, ini_content):
|
||||||
ini_file.write_bytes(content)
|
ini_file.write_bytes(content)
|
||||||
|
|
||||||
ini.set_option(
|
ini.set_option(
|
||||||
str(ini_file), {"SectionB": {"test3": "new value 3B"}}, encoding=encoding
|
str(ini_file),
|
||||||
|
{"SectionB": {"test3": "new value 3B"}},
|
||||||
|
encoding=encoding,
|
||||||
|
no_spaces=no_spaces,
|
||||||
)
|
)
|
||||||
with salt.utils.files.fopen(str(ini_file), "r") as fp_:
|
with salt.utils.files.fopen(str(ini_file), "r") as fp_:
|
||||||
file_content = salt.utils.stringutils.to_unicode(fp_.read(), encoding=encoding)
|
file_content = salt.utils.stringutils.to_unicode(fp_.read(), encoding=encoding)
|
||||||
expected = "{0}{1}{0}".format(os.linesep, "empty_option = ")
|
expected = f"{os.linesep}empty_option{'=' if no_spaces else ' = '}{os.linesep}"
|
||||||
assert expected in file_content, "empty_option was not preserved"
|
assert expected in file_content, "empty_option was not preserved"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("no_spaces", [True, False])
|
||||||
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"encoding", [None, "cp1252" if sys.platform == "win32" else "ISO-2022-JP"]
|
"encoding", [None, "cp1252" if sys.platform == "win32" else "ISO-2022-JP"]
|
||||||
)
|
)
|
||||||
def test_empty_lines(encoding, linesep, ini_file, ini_content):
|
def test_empty_lines(encoding, linesep, no_spaces, ini_file, ini_content):
|
||||||
"""
|
"""
|
||||||
Test empty lines preserved after edit
|
Test empty lines preserved after edit
|
||||||
"""
|
"""
|
||||||
|
@ -289,42 +292,48 @@ def test_empty_lines(encoding, linesep, ini_file, ini_content):
|
||||||
"# Comment on the first line",
|
"# Comment on the first line",
|
||||||
"",
|
"",
|
||||||
"# First main option",
|
"# First main option",
|
||||||
"option1 = main1",
|
f"option1{'=' if no_spaces else ' = '}main1",
|
||||||
"",
|
"",
|
||||||
"# Second main option",
|
"# Second main option",
|
||||||
"option2 = main2",
|
f"option2{'=' if no_spaces else ' = '}main2",
|
||||||
"",
|
"",
|
||||||
"[main]",
|
"[main]",
|
||||||
"# Another comment",
|
"# Another comment",
|
||||||
"test1 = value 1",
|
f"test1{'=' if no_spaces else ' = '}value 1",
|
||||||
"",
|
"",
|
||||||
"test2 = value 2",
|
f"test2{'=' if no_spaces else ' = '}value 2",
|
||||||
"",
|
"",
|
||||||
"[SectionB]",
|
"[SectionB]",
|
||||||
"test1 = value 1B",
|
f"test1{'=' if no_spaces else ' = '}value 1B",
|
||||||
"",
|
"",
|
||||||
"# Blank line should be above",
|
"# Blank line should be above",
|
||||||
"test3 = new value 3B",
|
f"test3{'=' if no_spaces else ' = '}new value 3B",
|
||||||
"",
|
"",
|
||||||
"[SectionC]",
|
"[SectionC]",
|
||||||
"# The following option is empty",
|
"# The following option is empty",
|
||||||
"empty_option = ",
|
f"empty_option{'=' if no_spaces else ' = '}",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
ini.set_option(
|
ini.set_option(
|
||||||
str(ini_file), {"SectionB": {"test3": "new value 3B"}}, encoding=encoding
|
str(ini_file),
|
||||||
|
{"SectionB": {"test3": "new value 3B"}},
|
||||||
|
encoding=encoding,
|
||||||
|
no_spaces=no_spaces,
|
||||||
)
|
)
|
||||||
with salt.utils.files.fopen(str(ini_file), "r") as fp_:
|
with salt.utils.files.fopen(str(ini_file), "r") as fp_:
|
||||||
file_content = fp_.read()
|
file_content = fp_.read()
|
||||||
assert expected == file_content
|
assert expected == file_content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("no_spaces", [True, False])
|
||||||
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
@pytest.mark.parametrize("linesep", ["\r", "\n", "\r\n"])
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"encoding", [None, "cp1252" if sys.platform == "win32" else "ISO-2022-JP"]
|
"encoding", [None, "cp1252" if sys.platform == "win32" else "ISO-2022-JP"]
|
||||||
)
|
)
|
||||||
def test_empty_lines_multiple_edits(encoding, linesep, ini_file, ini_content):
|
def test_empty_lines_multiple_edits(
|
||||||
|
encoding, linesep, no_spaces, ini_file, ini_content
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Test empty lines preserved after multiple edits
|
Test empty lines preserved after multiple edits
|
||||||
"""
|
"""
|
||||||
|
@ -337,6 +346,7 @@ def test_empty_lines_multiple_edits(encoding, linesep, ini_file, ini_content):
|
||||||
str(ini_file),
|
str(ini_file),
|
||||||
{"SectionB": {"test3": "this value will be edited two times"}},
|
{"SectionB": {"test3": "this value will be edited two times"}},
|
||||||
encoding=encoding,
|
encoding=encoding,
|
||||||
|
no_spaces=no_spaces,
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = os.linesep.join(
|
expected = os.linesep.join(
|
||||||
|
@ -344,31 +354,34 @@ def test_empty_lines_multiple_edits(encoding, linesep, ini_file, ini_content):
|
||||||
"# Comment on the first line",
|
"# Comment on the first line",
|
||||||
"",
|
"",
|
||||||
"# First main option",
|
"# First main option",
|
||||||
"option1 = main1",
|
f"option1{'=' if no_spaces else ' = '}main1",
|
||||||
"",
|
"",
|
||||||
"# Second main option",
|
"# Second main option",
|
||||||
"option2 = main2",
|
f"option2{'=' if no_spaces else ' = '}main2",
|
||||||
"",
|
"",
|
||||||
"[main]",
|
"[main]",
|
||||||
"# Another comment",
|
"# Another comment",
|
||||||
"test1 = value 1",
|
f"test1{'=' if no_spaces else ' = '}value 1",
|
||||||
"",
|
"",
|
||||||
"test2 = value 2",
|
f"test2{'=' if no_spaces else ' = '}value 2",
|
||||||
"",
|
"",
|
||||||
"[SectionB]",
|
"[SectionB]",
|
||||||
"test1 = value 1B",
|
f"test1{'=' if no_spaces else ' = '}value 1B",
|
||||||
"",
|
"",
|
||||||
"# Blank line should be above",
|
"# Blank line should be above",
|
||||||
"test3 = new value 3B",
|
f"test3{'=' if no_spaces else ' = '}new value 3B",
|
||||||
"",
|
"",
|
||||||
"[SectionC]",
|
"[SectionC]",
|
||||||
"# The following option is empty",
|
"# The following option is empty",
|
||||||
"empty_option = ",
|
f"empty_option{'=' if no_spaces else ' = '}",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
ini.set_option(
|
ini.set_option(
|
||||||
str(ini_file), {"SectionB": {"test3": "new value 3B"}}, encoding=encoding
|
str(ini_file),
|
||||||
|
{"SectionB": {"test3": "new value 3B"}},
|
||||||
|
encoding=encoding,
|
||||||
|
no_spaces=no_spaces,
|
||||||
)
|
)
|
||||||
with salt.utils.files.fopen(str(ini_file), "r") as fp_:
|
with salt.utils.files.fopen(str(ini_file), "r") as fp_:
|
||||||
file_content = fp_.read()
|
file_content = fp_.read()
|
||||||
|
|
Loading…
Add table
Reference in a new issue