From 708ce1d94c38dff29cc3126f79b5935e5e7601e9 Mon Sep 17 00:00:00 2001 From: Victor Wheeler Date: Sat, 1 Mar 2025 22:42:35 -0700 Subject: [PATCH 1/2] fix(configParser.py + test): incorporate PR #11 in main repo Fixes 2 issues with 1st line of multi-line option: - was allowing whitespace at end of 1st line value of multi-line option, - was not removing quotes around 1st line value of multi-line option. Also, enhanced test to detect these problems in the future. --- doxygen/configParser.py | 5 ++-- tests/test_doxygenConfigParser.py | 49 +++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/doxygen/configParser.py b/doxygen/configParser.py index da536bb..82bd998 100644 --- a/doxygen/configParser.py +++ b/doxygen/configParser.py @@ -11,7 +11,7 @@ class ConfigParser: def __init__(self): self.__single_line_option_regex = re.compile("^\s*(\w+)\s*=\s*([^\\\\]*)\s*$") - self.__first_line_of_multine_option_regex = re.compile("^\s*(\w+)\s*=\s*(.*)\\\\$") + self.__first_line_of_multine_option_regex = re.compile("^\s*(\w+)\s*=\s*(|.*[^\s])\s*\\\\$") def load_configuration(self, doxyfile: str) -> dict: """ @@ -45,7 +45,8 @@ def load_configuration(self, doxyfile: str) -> dict: if not line.endswith('\\'): in_multiline_option = False option_value = line.rstrip('\\').strip() - configuration[current_multiline_option_name].append(option_value) + unquoted_option_value = self.__remove_double_quote_if_required(option_value) + configuration[current_multiline_option_name].append(unquoted_option_value) elif self.__is_first_line_of_multiline_option(line): current_multiline_option_name, option_value = self.__extract_multiline_option_name_and_first_value(line) diff --git a/tests/test_doxygenConfigParser.py b/tests/test_doxygenConfigParser.py index 54968c4..3fab9d5 100644 --- a/tests/test_doxygenConfigParser.py +++ b/tests/test_doxygenConfigParser.py @@ -1,7 +1,7 @@ import logging -import unittest - import os +import unittest +from typing import List from doxygen import ConfigParser @@ -13,6 +13,7 @@ def __init__(self, *args, **kwargs): logging.disable(logging.CRITICAL) self.doxyfile_original = os.path.join(os.path.dirname(__file__), "assets/Doxyfile") self.doxyfile_working = "./Doxyfile.tmp" + self.doxyfile_clone = "./Cloned_Doxyfile.tmp" def test_try_use_not_existing_doxyfile(self): config_parser = ConfigParser() @@ -22,6 +23,9 @@ def tearDown(self): if os.path.exists(self.doxyfile_working): os.remove(self.doxyfile_working) + if os.path.exists(self.doxyfile_clone): + os.remove(self.doxyfile_clone) + def test_load_configuration(self): config_parser = ConfigParser() configuration = config_parser.load_configuration(self.doxyfile_original) @@ -51,6 +55,47 @@ def test_update_configuration(self): self.assertEqual(configuration_updated['PROJECT_NUMBER'], '1.2.3.4') self.assertTrue("*.dtc" in configuration_updated['FILE_PATTERNS']) + def test_multiline_option_with_empty_lines(self): + configuration = self.get_configuration_from_lines([ + 'MULTILINE_OPTION = \\', + ' \\', + ' line3', + ]) + + self.assertEqual(['', '', 'line3'], configuration['MULTILINE_OPTION']) + + def test_multiline_option(self): + configuration = self.get_configuration_from_lines([ + 'MULTILINE_OPTION = line1 \\', + ' line2 \\', + ' line3', + ]) + + self.assertEqual(['line1', 'line2', 'line3'], configuration['MULTILINE_OPTION']) + + def test_quoted_multiline_option(self): + configuration = self.get_configuration_from_lines([ + 'MULTILINE_OPTION = "line 1" \\', + ' "line 2" \\', + ' "line 3"', + ]) + + self.assertEqual(['line 1', 'line 2', 'line 3'], configuration['MULTILINE_OPTION']) + + def get_configuration_from_lines(self, lines: List[str]) -> dict: + """Writes lines into a Doxyfile and reads it. This will also write and load the configuration again to check if + the config_parser changes it + """ + with open(self.doxyfile_working, 'w') as io: + io.write('\n'.join(lines)) + + parser = ConfigParser() + configuration = parser.load_configuration(self.doxyfile_working) + parser.store_configuration(configuration, self.doxyfile_clone) + other_configuration = parser.load_configuration(self.doxyfile_clone) + self.assertEqual(configuration, other_configuration) + return configuration + if __name__ == '__main__': unittest.main() From 8bb077ac7c75fc2ae63e7034f05e901df7c0a6a7 Mon Sep 17 00:00:00 2001 From: Victor Wheeler Date: Sat, 1 Mar 2025 22:48:06 -0700 Subject: [PATCH 2/2] feat(configParser.py): 3 enhancements on regexes... - Switch to raw string literal to make regexes more readable. - Simplify "not space character" expression to just `\S`. - Correct mis-spelling of 2nd regex variable name. --- doxygen/configParser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doxygen/configParser.py b/doxygen/configParser.py index 82bd998..01af3be 100644 --- a/doxygen/configParser.py +++ b/doxygen/configParser.py @@ -10,8 +10,8 @@ class ConfigParser: """ def __init__(self): - self.__single_line_option_regex = re.compile("^\s*(\w+)\s*=\s*([^\\\\]*)\s*$") - self.__first_line_of_multine_option_regex = re.compile("^\s*(\w+)\s*=\s*(|.*[^\s])\s*\\\\$") + self.__single_line_option_regex = re.compile(r"^\s*(\w+)\s*=\s*([^\\]*)\s*$") + self.__first_line_of_multiline_option_regex = re.compile(r"^\s*(\w+)\s*=\s*(|.*\S)\s*\\$") def load_configuration(self, doxyfile: str) -> dict: """ @@ -90,7 +90,7 @@ def __extract_multiline_option_name_and_first_value(self, line) -> (str, str): :raise ParseException: When process fail to extract data """ - matches = self.__first_line_of_multine_option_regex.search(line) + matches = self.__first_line_of_multiline_option_regex.search(line) if matches is None or len(matches.groups()) != 2: logging.error("Impossible to extract first value off multi line option from: {}" % line) raise ParseException("Impossible to extract first value off multi line option from: {}" % line) @@ -121,7 +121,7 @@ def __is_comment_line(self, line: str) -> bool: return line.startswith("#") def __is_first_line_of_multiline_option(self, line) -> bool: - return self.__first_line_of_multine_option_regex.match(line) is not None + return self.__first_line_of_multiline_option_regex.match(line) is not None @staticmethod def __remove_double_quote_if_required(option_value: str) -> str: