class LineParserTest(unittest.TestCase): def setUp(self): self.uut = LineParser(comment_separators=('#', ';')) def test_empty_line(self): self.check_data_set("") self.check_data_set("\n \n \n") def test_comment_parsing(self): self.check_data_set("# comment only$§\n", output_comment="# comment only$§") self.check_data_set(" ; comment only \n", output_comment="; comment only") self.check_data_set(" ; \\comment only \n", output_comment="; comment only") self.check_data_set("#", output_comment="#") def test_section_override(self): self.check_data_set(r"a.b, \a\.\b\ c=", output_keys=[("a", "b"), ("", r"\a.\b c")]) def test_escaping(self): self.check_data_set("hello = world\ # yes here's a space", output_keys=[('', 'hello')], output_value='world\\ ', output_comment="# yes here's a space") def test_multi_value_parsing(self): self.check_data_set( "a, b\\ \\=, section.c= = :()&/ \\\\#heres a comment \n", output_section='', output_keys=[("", 'a'), ("", 'b ='), ("section", 'c')], output_value='= :()&/ \\\\', output_comment='#heres a comment') def test_multi_line_parsing(self): self.check_data_set(" a,b,d another value ", output_value="a,b,d another value") self.check_data_set(" a,b,d\\= another value ", output_value="a,b,d\\= another value") def test_section_name_parsing(self): self.check_data_set(" [ a section name ] # with comment \n", 'a section name', output_comment="# with comment") self.check_data_set(" [ a section name] ] \n", 'a section name]') self.check_data_set(" [ a section name\\] ] \n", 'a section name]') self.check_data_set(" [ a section name\\; ] \n", 'a section name;') self.uut.section_name_surroundings["Section:"] = '' self.check_data_set("[ sec]; thats a normal section", output_section="sec", output_comment="; thats a normal section") self.check_data_set(" Section: sEc]\\\\; thats a new section", output_section="sEc]\\", output_comment="; thats a new section") self.check_data_set(" Section: sec]\\\\\\\\; thats a new section", output_section="sec]\\\\", output_comment="; thats a new section") self.check_data_set(" Section: sec]\\\\\\; thats a new section", output_section="sec]\\; thats a new section") def check_data_set(self, line, output_section="", output_keys=None, output_value='', output_comment=''): output_keys = output_keys or [] section_name, keys, value, comment = self.uut.parse(line) self.assertEqual(section_name, output_section) self.assertEqual(keys, output_keys) self.assertEqual(value, output_value) self.assertEqual(comment, output_comment)
class ConfParser: def __init__(self, key_value_delimiters=('=',), comment_seperators=('#',), key_delimiters=(',', ' '), section_name_surroundings=None, remove_empty_iter_elements=True): section_name_surroundings = section_name_surroundings or {"[": "]"} self.line_parser = LineParser(key_value_delimiters, comment_seperators, key_delimiters, section_name_surroundings) self.__remove_empty_iter_elements = remove_empty_iter_elements # Declare it self.sections = None self.__rand_helper = None self.__init_sections() def parse(self, input_data, overwrite=False): """ Parses the input and adds the new data to the existing. :param input_data: The filename to parse from. :param overwrite: If True, wipes all existing Settings inside this instance and adds only the newly parsed ones. If False, adds the newly parsed data to the existing one (and overwrites already existing keys with the newly parsed values). :return: A dictionary with (lowercase) section names as keys and their Setting objects as values. """ if os.path.isdir(input_data): input_data = os.path.join(input_data, Constants.default_coafile) with open(input_data, "r", encoding='utf-8') as _file: lines = _file.readlines() if overwrite: self.__init_sections() self.__parse_lines(lines, input_data) return self.sections def get_section(self, name, create_if_not_exists=False): key = self.__refine_key(name) sec = self.sections.get(key, None) if sec is not None: return sec if not create_if_not_exists: raise IndexError retval = self.sections[key] = Section(str(name), self.sections["default"]) return retval @staticmethod def __refine_key(key): return str(key).lower().strip() def __add_comment(self, section, comment, origin): key = "comment" + str(self.__rand_helper) self.__rand_helper += 1 section.append(Setting( key, comment, origin, remove_empty_iter_elements=self.__remove_empty_iter_elements)) def __parse_lines(self, lines, origin): current_section_name = "default" current_section = self.get_section(current_section_name) current_keys = [] for line in lines: section_name, keys, value, comment = self.line_parser.parse(line) if comment != "": self.__add_comment(current_section, comment, origin) if section_name != "": current_section_name = section_name current_section = self.get_section(current_section_name, True) current_keys = [] continue if comment == "" and keys == [] and value == "": self.__add_comment(current_section, "", origin) continue if keys != []: current_keys = keys for section_override, key in current_keys: if key == "": continue if section_override == "": current_section.add_or_create_setting( Setting(key, value, origin, # Ignore PEP8Bear, it fails to format that remove_empty_iter_elements= self.__remove_empty_iter_elements), allow_appending=(keys == [])) else: self.get_section( section_override, True).add_or_create_setting( Setting(key, value, origin, # Ignore PEP8Bear, it fails to format that remove_empty_iter_elements= self.__remove_empty_iter_elements), allow_appending=(keys == [])) def __init_sections(self): self.sections = OrderedDict() self.sections["default"] = Section("Default") self.__rand_helper = 0
class ConfParser: def __init__(self, key_value_delimiters=('=', ), comment_seperators=('#', ), key_delimiters=(',', ' '), section_name_surroundings=MappingProxyType({"[": "]"}), remove_empty_iter_elements=True): self.line_parser = LineParser(key_value_delimiters, comment_seperators, key_delimiters, section_name_surroundings) self.__remove_empty_iter_elements = remove_empty_iter_elements # Declare it self.sections = None self.__rand_helper = None self.__init_sections() def parse(self, input_data, overwrite=False): """ Parses the input and adds the new data to the existing. :param input_data: The filename to parse from. :param overwrite: If True, wipes all existing Settings inside this instance and adds only the newly parsed ones. If False, adds the newly parsed data to the existing one (and overwrites already existing keys with the newly parsed values). :return: A dictionary with (lowercase) section names as keys and their Setting objects as values. """ if os.path.isdir(input_data): input_data = os.path.join(input_data, Constants.default_coafile) with open(input_data, "r", encoding='utf-8') as _file: lines = _file.readlines() if overwrite: self.__init_sections() self.__parse_lines(lines, input_data) return self.sections def get_section(self, name, create_if_not_exists=False): key = self.__refine_key(name) sec = self.sections.get(key, None) if sec is not None: return sec if not create_if_not_exists: raise IndexError retval = self.sections[key] = Section(str(name), self.sections["default"]) return retval @staticmethod def __refine_key(key): return str(key).lower().strip() def __add_comment(self, section, comment, origin): key = "comment" + str(self.__rand_helper) self.__rand_helper += 1 section.append( Setting( key, comment, origin, remove_empty_iter_elements=self.__remove_empty_iter_elements)) def __parse_lines(self, lines, origin): current_section_name = "default" current_section = self.get_section(current_section_name) current_keys = [] for line in lines: section_name, keys, value, comment = self.line_parser.parse(line) if comment != "": self.__add_comment(current_section, comment, origin) if section_name != "": current_section_name = section_name current_section = self.get_section(current_section_name, True) current_keys = [] continue if comment == "" and keys == [] and value == "": self.__add_comment(current_section, "", origin) continue if keys != []: current_keys = keys for section_override, key in current_keys: if key == "": continue if section_override == "": current_section.add_or_create_setting( Setting( key, value, origin, # Ignore PEP8Bear, it fails to format that remove_empty_iter_elements=self. __remove_empty_iter_elements), allow_appending=(keys == [])) else: self.get_section( section_override, True).add_or_create_setting( Setting( key, value, origin, # Ignore PEP8Bear, it fails to format that remove_empty_iter_elements=self. __remove_empty_iter_elements), allow_appending=(keys == [])) def __init_sections(self): self.sections = OrderedDict() self.sections["default"] = Section("Default") self.__rand_helper = 0
class ConfParser(SectionParser): def __init__(self, key_value_delimiters=['=', ':'], comment_seperators=['#', ';', '//'], key_delimiters=[',', ' '], section_name_surroundings={'[': "]"}): SectionParser.__init__(self) self.line_parser = LineParser(key_value_delimiters, comment_seperators, key_delimiters, section_name_surroundings) # Declare it self.sections = None self.__rand_helper = None self.__init_sections() if sys.version_info < (3, 3): # pragma: no cover self.FileNotFoundError = IOError else: self.FileNotFoundError = FileNotFoundError def parse(self, input_data, overwrite=False): """ Parses the input and adds the new data to the existing. If you want to catch the FileNotFoundError please take the FileNotFoundError member of this object for catching for backwards compatability to python 3.2. :param input_data: filename :param overwrite: behaves like reparse if this is True :return: the settings dictionary """ if os.path.isdir(input_data): input_data = os.path.join(input_data, ".coafile") with open(input_data, "r", encoding='utf-8') as f: lines = f.readlines() if overwrite: self.__init_sections() self.__parse_lines(lines, input_data) return self.export_to_settings() def reparse(self, input_data): """ Parses the input and overwrites all existent data :param input_data: filename :return: the settings dictionary """ return self.parse(input_data, overwrite=True) def export_to_settings(self): """ :return a dict of Settings objects representing the current parsed things """ return self.sections def get_section(self, name, create_if_not_exists=False): key = self.__refine_key(name) sec = self.sections.get(key, None) if sec is not None: return sec if not create_if_not_exists: raise IndexError retval = self.sections[key] = Section(str(name), self.sections["default"]) return retval @staticmethod def __refine_key(key): return str(key).lower().strip() def __add_comment(self, section, comment, origin): key = "comment" + str(self.__rand_helper) self.__rand_helper += 1 section.append(Setting(key, comment, origin)) def __parse_lines(self, lines, origin): current_section_name = "default" current_section = self.get_section(current_section_name) current_keys = [] for line in lines: section_name, keys, value, comment = self.line_parser.parse(line) if comment != "": self.__add_comment(current_section, comment, origin) if section_name != "": current_section_name = section_name current_section = self.get_section(current_section_name, True) current_keys = [] continue if comment == "" and keys == [] and value == "": self.__add_comment(current_section, "", origin) continue if keys != []: current_keys = keys for section_override, key in current_keys: if key == "": continue if section_override == "": current_section.add_or_create_setting( Setting(key, value, origin), allow_appending=(keys == [])) else: self.get_section(section_override, True).add_or_create_setting( Setting(key, value, origin), allow_appending=(keys == [])) def __init_sections(self): self.sections = OrderedDict() self.sections["default"] = Section("Default") self.__rand_helper = 0
class ConfParser: if sys.version_info < (3, 3): # pragma: no cover FileNotFoundError = IOError else: FileNotFoundError = FileNotFoundError def __init__(self, key_value_delimiters=('=',), comment_seperators=('#', ';', '//'), key_delimiters=(',', ' '), section_name_surroundings=None): section_name_surroundings = section_name_surroundings or {"[": "]"} self.line_parser = LineParser(key_value_delimiters, comment_seperators, key_delimiters, section_name_surroundings) # Declare it self.sections = None self.__rand_helper = None self.__init_sections() def parse(self, input_data, overwrite=False): """ Parses the input and adds the new data to the existing. If you want to catch the FileNotFoundError please take the FileNotFoundError member of this object for catching for backwards compatability to python 3.2. :param input_data: filename :param overwrite: behaves like reparse if this is True :return: the settings dictionary """ if os.path.isdir(input_data): input_data = os.path.join(input_data, ".coafile") with open(input_data, "r", encoding='utf-8') as _file: lines = _file.readlines() if overwrite: self.__init_sections() self.__parse_lines(lines, input_data) return self.sections def get_section(self, name, create_if_not_exists=False): key = self.__refine_key(name) sec = self.sections.get(key, None) if sec is not None: return sec if not create_if_not_exists: raise IndexError retval = self.sections[key] = Section(str(name), self.sections["default"]) return retval @staticmethod def __refine_key(key): return str(key).lower().strip() def __add_comment(self, section, comment, origin): key = "comment" + str(self.__rand_helper) self.__rand_helper += 1 section.append(Setting(key, comment, origin)) def __parse_lines(self, lines, origin): current_section_name = "default" current_section = self.get_section(current_section_name) current_keys = [] for line in lines: section_name, keys, value, comment = self.line_parser.parse(line) if comment != "": self.__add_comment(current_section, comment, origin) if section_name != "": current_section_name = section_name current_section = self.get_section(current_section_name, True) current_keys = [] continue if comment == "" and keys == [] and value == "": self.__add_comment(current_section, "", origin) continue if keys != []: current_keys = keys for section_override, key in current_keys: if key == "": continue if section_override == "": current_section.add_or_create_setting( Setting(key, value, origin), allow_appending=(keys == [])) else: self.get_section( section_override, True).add_or_create_setting( Setting(key, value, origin), allow_appending=(keys == [])) def __init_sections(self): self.sections = OrderedDict() self.sections["default"] = Section("Default") self.__rand_helper = 0
class LineParserTest(unittest.TestCase): def setUp(self): self.uut = LineParser(comment_separators=('#', ';')) def test_empty_line(self): self.check_data_set('') self.check_data_set('\n \n \n') def test_comment_parsing(self): self.check_data_set('# comment only$§\n', output_comment='# comment only$§') self.check_data_set(' ; comment only \n', output_comment='; comment only') self.check_data_set(' ; \\comment only \n', output_comment='; comment only') self.check_data_set('#', output_comment='#') def test_section_override(self): self.check_data_set(r'a.b, \a\.\b\ c=', output_keys=[('a', 'b'), ('', r'\a.\b c')]) def test_escaping(self): self.check_data_set("hello = world\ # yes here's a space", output_keys=[('', 'hello')], output_value='world\\ ', output_comment="# yes here's a space") def test_multi_value_parsing(self): self.check_data_set( 'a, b\\ \\=, section.c= = :()&/ \\\\#heres a comment \n', output_section='', output_keys=[('', 'a'), ('', 'b ='), ('section', 'c')], output_value='= :()&/ \\\\', output_comment='#heres a comment') def test_multi_line_parsing(self): self.check_data_set(' a,b,d another value ', output_value='a,b,d another value') self.check_data_set(' a,b,d\\= another value ', output_value='a,b,d\\= another value') def test_section_name_parsing(self): self.check_data_set(' [ a section name ] # with comment \n', 'a section name', output_comment='# with comment') self.check_data_set(' [ a section name] ] \n', 'a section name]') self.check_data_set(' [ a section name\\] ] \n', 'a section name]') self.check_data_set(' [ a section name\\; ] \n', 'a section name;') self.uut.section_name_surroundings['Section:'] = '' self.check_data_set('[ sec]; thats a normal section', output_section='sec', output_comment='; thats a normal section') self.check_data_set(' Section: sEc]\\\\; thats a new section', output_section='sEc]\\', output_comment='; thats a new section') self.check_data_set(' Section: sec]\\\\\\\\; thats a new section', output_section='sec]\\\\', output_comment='; thats a new section') self.check_data_set(' Section: sec]\\\\\\; thats a new section', output_section='sec]\\; thats a new section') def test_append_value_parsing(self): self.check_data_set('a += b', output_keys=[('', 'a')], output_value='b', output_append=True) self.check_data_set('a = b', output_keys=[('', 'a')], output_value='b') self.check_data_set('a \\+\\= b', output_value='a \\+\\= b') def check_data_set(self, line, output_section='', output_keys=None, output_value='', output_append=False, output_comment=''): output_keys = output_keys or [] section_name, keys, value, append, comment = self.uut._parse(line) self.assertEqual(section_name, output_section) self.assertEqual(keys, output_keys) self.assertEqual(value, output_value) self.assertEqual(append, output_append) self.assertEqual(comment, output_comment) def test_deprecation(self): logger = logging.getLogger() with self.assertLogs(logger, 'WARNING') as cm: self.uut.parse('') self.assertRegex(cm.output[0], 'WARNING:root:The parse method of ' 'LineParser is deprecated\.*')
class CliParser(SectionParser): def __init__(self, arg_parser=default_arg_parser, key_value_delimiters=['=', ':'], comment_seperators=[], key_delimiters=[','], section_override_delimiters=["."]): """ CliParser parses arguments from the command line or a custom list of items that my look like this: ['-a', '-b', 'b1', 'b2', 'setting=value', 'section.setting=other_value', 'key1,section.key2=value2'] :param arg_parser: Instance of ArgParser() that is used to parse none-setting arguments :param key_value_delimiters: delimiters to separate key and value in setting arguments :param comment_seperators: allowed prefixes for comments :param key_delimiters: delimiter to separate multiple keys of a setting argument :param section_override_delimiters: The delimiter to delimit the section from the key name (e.g. the '.' in section.key = value) """ if not isinstance(arg_parser, argparse.ArgumentParser): raise TypeError("arg_parser must be an ArgumentParser") SectionParser.__init__(self) self._arg_parser = arg_parser self._line_parser = LineParser(key_value_delimiters, comment_seperators, key_delimiters, {}, section_override_delimiters) self.__reset_sections() def __reset_sections(self): self.sections = OrderedDict(default=Section('Default')) def _update_sections(self, section_name, key, value, origin): if key == '' or value is None: return if section_name == "" or section_name is None: section_name = "default" if not section_name.lower() in self.sections: self.sections[section_name.lower()] = Section(section_name) self.sections[section_name.lower()].append(Setting(key, str(value), origin, from_cli=True)) def parse(self, arg_list=sys.argv[1:], origin=os.getcwd()): """ parses the input and adds the new data to the existing :param arg_list: list of arguments. :param origin: directory used to interpret relative paths given as argument :return: the settings dictionary """ origin += os.path.sep for arg_key, arg_value in sorted(vars(self._arg_parser.parse_args(arg_list)).items()): if arg_key == 'settings' and arg_value is not None: self._parse_custom_settings(arg_value, origin) else: if isinstance(arg_value, list): arg_value = ",".join([str(val) for val in arg_value]) # [1,2,3] -> "1,2,3" self._update_sections("default", arg_key, arg_value, origin) return self.sections def _parse_custom_settings(self, custom_settings_list, origin): for setting_definition in custom_settings_list: section_stub, key_touples, value, comment_stub = self._line_parser.parse(setting_definition) for key_touple in key_touples: self._update_sections(section_name=key_touple[0], key=key_touple[1], value=value, origin=origin) def reparse(self, arg_list=sys.argv[1:], origin=os.getcwd()): self.__reset_sections() return self.parse(arg_list, origin) def export_to_settings(self): return self.sections
class LineParserTest(unittest.TestCase): def setUp(self): self.uut = LineParser(comment_seperators=("#", ";")) def test_empty_line(self): self.check_data_set("") self.check_data_set("\n \n \n") def test_comment_parsing(self): self.check_data_set("# comment only$§\n", output_comment="# comment only$§") self.check_data_set(" ; comment only \n", output_comment="; comment only") self.check_data_set(" ; \\comment only \n", output_comment="; comment only") self.check_data_set("#", output_comment="#") def test_section_override(self): self.check_data_set("a.b, \\a\\.\\b\\ c=", output_keys=[("a", "b"), ("", "a.b c")]) def test_multi_value_parsing(self): self.check_data_set( "a, b\\ \\=, section.c= = :()&/ \\\\#heres a comment \n", output_section="", output_keys=[("", "a"), ("", "b ="), ("section", "c")], output_value="= :()&/ \\\\", output_comment="#heres a comment", ) def test_multi_line_parsing(self): self.check_data_set(" a,b,d another value ", output_value="a,b,d another value") self.check_data_set(" a,b,d\\= another value ", output_value="a,b,d\\= another value") def test_section_name_parsing(self): self.check_data_set( " [ a section name ] # with comment \n", "a section name", output_comment="# with comment" ) self.check_data_set(" [ a section name] ] \n", "a section name]") self.check_data_set(" [ a section name\\] ] \n", "a section name]") self.check_data_set(" [ a section name\\; ] \n", "a section name;") self.uut.section_name_surroundings["Section:"] = "" self.check_data_set( "[ sec]; thats a normal section", output_section="sec", output_comment="; thats a normal section" ) self.check_data_set( " Section: sEc]\\\\; thats a new section", output_section="sEc]\\", output_comment="; thats a new section" ) self.check_data_set( " Section: sec]\\\\\\\\; thats a new section", output_section="sec]\\\\", output_comment="; thats a new section", ) self.check_data_set(" Section: sec]\\\\\\; thats a new section", output_section="sec]\\; thats a new section") def check_data_set(self, line, output_section="", output_keys=None, output_value="", output_comment=""): output_keys = output_keys or [] section_name, keys, value, comment = self.uut.parse(line) self.assertEqual(section_name, output_section) self.assertEqual(keys, output_keys) self.assertEqual(value, output_value) self.assertEqual(comment, output_comment)
class LineParserTest(unittest.TestCase): def setUp(self): self.uut = LineParser(comment_separators=('#', ';')) def test_empty_line(self): self.check_data_set('') self.check_data_set('\n \n \n') def test_comment_parsing(self): self.check_data_set('# comment only$§\n', output_comment='# comment only$§') self.check_data_set(' ; comment only \n', output_comment='; comment only') self.check_data_set(' ; \\comment only \n', output_comment='; comment only') self.check_data_set('#', output_comment='#') def test_section_override(self): self.check_data_set(r'a.b, \a\.\b\ c=', output_keys=[('a', 'b'), ('', r'\a.\b c')]) def test_escaping(self): self.check_data_set("hello = world\ # yes here's a space", output_keys=[('', 'hello')], output_value='world\\ ', output_comment="# yes here's a space") def test_multi_value_parsing(self): self.check_data_set( 'a, b\\ \\=, section.c= = :()&/ \\\\#heres a comment \n', output_section='', output_keys=[('', 'a'), ('', 'b ='), ('section', 'c')], output_value='= :()&/ \\\\', output_comment='#heres a comment') def test_multi_line_parsing(self): self.check_data_set(' a,b,d another value ', output_value='a,b,d another value') self.check_data_set(' a,b,d\\= another value ', output_value='a,b,d\\= another value') def test_section_name_parsing(self): self.check_data_set(' [ a section name ] # with comment \n', 'a section name', output_comment='# with comment') self.check_data_set(' [ a section name] ] \n', 'a section name]') self.check_data_set(' [ a section name\\] ] \n', 'a section name]') self.check_data_set(' [ a section name\\; ] \n', 'a section name;') self.uut.section_name_surroundings['Section:'] = '' self.check_data_set('[ sec]; thats a normal section', output_section='sec', output_comment='; thats a normal section') self.check_data_set(' Section: sEc]\\\\; thats a new section', output_section='sEc]\\', output_comment='; thats a new section') self.check_data_set(' Section: sec]\\\\\\\\; thats a new section', output_section='sec]\\\\', output_comment='; thats a new section') self.check_data_set(' Section: sec]\\\\\\; thats a new section', output_section='sec]\\; thats a new section') def check_data_set(self, line, output_section='', output_keys=None, output_value='', output_comment=''): output_keys = output_keys or [] section_name, keys, value, comment = self.uut.parse(line) self.assertEqual(section_name, output_section) self.assertEqual(keys, output_keys) self.assertEqual(value, output_value) self.assertEqual(comment, output_comment)
class CliParser(SectionParser): def __init__(self, arg_parser=default_arg_parser, key_value_delimiters=['=', ':'], comment_seperators=[], key_delimiters=[','], section_override_delimiters=["."]): """ CliParser parses arguments from the command line or a custom list of items that my look like this: ['-a', '-b', 'b1', 'b2', 'setting=value', 'section.setting=other_value', 'key1,section.key2=value2'] :param arg_parser: Instance of ArgParser() that is used to parse none-setting arguments :param key_value_delimiters: delimiters to separate key and value in setting arguments :param comment_seperators: allowed prefixes for comments :param key_delimiters: delimiter to separate multiple keys of a setting argument :param section_override_delimiters: The delimiter to delimit the section from the key name (e.g. the '.' in section.key = value) """ if not isinstance(arg_parser, argparse.ArgumentParser): raise TypeError("arg_parser must be an ArgumentParser") SectionParser.__init__(self) self._arg_parser = arg_parser self._line_parser = LineParser(key_value_delimiters, comment_seperators, key_delimiters, {}, section_override_delimiters) self.__reset_sections() def __reset_sections(self): self.sections = OrderedDict(default=Section('Default')) def _update_sections(self, section_name, key, value, origin): if key == '' or value is None: return if section_name == "" or section_name is None: section_name = "default" if not section_name in self.sections: self.sections[section_name] = Section(section_name) self.sections[section_name].append(Setting(key, str(value), origin, from_cli=True)) def parse(self, arg_list=sys.argv[1:], origin=os.getcwd()): """ parses the input and adds the new data to the existing :param arg_list: list of arguments. :param origin: directory used to interpret relative paths given as argument :return: the settings dictionary """ origin += os.path.sep for arg_key, arg_value in sorted(vars(self._arg_parser.parse_args(arg_list)).items()): if arg_key == 'settings' and arg_value is not None: self._parse_custom_settings(arg_value, origin) else: if isinstance(arg_value, list): arg_value = ",".join([str(val) for val in arg_value]) # [1,2,3] -> "1,2,3" self._update_sections("default", arg_key, arg_value, origin) return self.sections def _parse_custom_settings(self, custom_settings_list, origin): for setting_definition in custom_settings_list: section_stub, key_touples, value, comment_stub = self._line_parser.parse(setting_definition) for key_touple in key_touples: self._update_sections(section_name=key_touple[0], key=key_touple[1], value=value, origin=origin) def reparse(self, arg_list=sys.argv[1:], origin=os.getcwd()): self.__reset_sections() return self.parse(arg_list, origin) def export_to_settings(self): return self.sections
class LineParserTest(unittest.TestCase): def setUp(self): self.uut = LineParser(comment_separators=('#', ';')) def test_empty_line(self): with mock.patch('re.match', false_mock): import re self.check_data_set('') self.check_data_set('\n \n \n') def test_comment_parsing(self): logger = logging.getLogger() with mock.patch('re.match', false_mock): import re self.check_data_set('# comment only$§\n', output_comment='# comment only$§') self.check_data_set(' ; comment only \n', output_comment='; comment only') self.check_data_set(' ; \\comment only \n', output_comment='; comment only') self.assertEqual(re.match.called, True) self.check_data_set('#', output_comment='#') with self.assertLogs(logger, 'WARNING') as warn: self.check_data_set('##\n', output_comment='##') self.assertEqual(len(warn.output), 1) self.assertEqual( warn.output[0], 'WARNING:root:This comment does ' + 'not have whitespace before or ' + 'after # in: ' + repr('##') + '. If you didn\'t mean to make ' + 'a comment, use a backslash for ' + 'escaping.') with mock.patch('re.match', true_mock): with self.assertLogs(logger, 'WARNING') as warn: self.check_data_set('#A\n', output_comment='#A') self.assertEqual( warn.output[0], 'WARNING:root:This comment does ' + 'not have whitespace before or ' + 'after # in: ' + repr('#A') + '. If you didn\'t mean to make ' + 'a comment, use a backslash for ' + 'escaping.') def test_section_override(self): self.check_data_set(r'a.b, \a\.\b\ c=', output_keys=[('a', 'b'), ('', r'\a.\b c')]) def test_escaping(self): self.check_data_set("hello = world\ # yes here's a space", output_keys=[('', 'hello')], output_value='world\\ ', output_comment="# yes here's a space") def test_multi_value_parsing(self): self.check_data_set( 'a, b\\ \\=, section.c= = :()&/ \\\\#heres a comment \n', output_section='', output_keys=[('', 'a'), ('', 'b ='), ('section', 'c')], output_value='= :()&/ \\\\', output_comment='#heres a comment') def test_multi_line_parsing(self): self.check_data_set(' a,b,d another value ', output_value='a,b,d another value') self.check_data_set(' a,b,d\\= another value ', output_value='a,b,d\\= another value') def test_section_name_parsing(self): self.check_data_set(' [ a section name ] # with comment \n', 'a section name', output_comment='# with comment') self.check_data_set(' [ a section name] ] \n', 'a section name]') self.check_data_set(' [ a section name\\] ] \n', 'a section name]') self.check_data_set(' [ a section name\\; ] \n', 'a section name;') self.uut.section_name_surroundings['Section:'] = '' self.check_data_set('[ sec]; thats a normal section', output_section='sec', output_comment='; thats a normal section') self.check_data_set(' Section: sEc]\\\\; thats a new section', output_section='sEc]\\', output_comment='; thats a new section') self.check_data_set(' Section: sec]\\\\\\\\; thats a new section', output_section='sec]\\\\', output_comment='; thats a new section') self.check_data_set(' Section: sec]\\\\\\; thats a new section', output_section='sec]\\; thats a new section') def test_append_value_parsing(self): self.check_data_set('a += b', output_keys=[('', 'a')], output_value='b', output_append=True) self.check_data_set('a = b', output_keys=[('', 'a')], output_value='b') self.check_data_set('a \\+\\= b', output_value='a \\+\\= b') def check_data_set(self, line, output_section='', output_keys=None, output_value='', output_append=False, output_comment=''): output_keys = output_keys or [] section_name, keys, value, append, comment = self.uut._parse(line) self.assertEqual(section_name, output_section) self.assertEqual(keys, output_keys) self.assertEqual(value, output_value) self.assertEqual(append, output_append) self.assertEqual(comment, output_comment) def test_deprecation(self): logger = logging.getLogger() with self.assertLogs(logger, 'WARNING') as cm: self.uut.parse('') self.assertRegex( cm.output[0], 'WARNING:root:The parse method of ' 'LineParser is deprecated\.*')