From 803b9c5ae893fe2b594894cca7c8dd46a4995f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=81?= Date: Thu, 21 May 2020 18:10:47 +0300 Subject: [PATCH] Comments and exceptions are added for all format modules. --- calculate/templates/format/base_format.py | 46 ++++++++--- calculate/templates/format/bind_format.py | 30 ++++++++ calculate/templates/format/compiz_format.py | 49 ++++++------ calculate/templates/format/contents_format.py | 18 +++++ calculate/templates/format/diff_format.py | 14 ++-- calculate/templates/format/dovecot_format.py | 47 ++++++------ calculate/templates/format/json_format.py | 6 +- calculate/templates/format/kde_format.py | 21 ++++- calculate/templates/format/kernel_format.py | 18 ++++- calculate/templates/format/ldap_format.py | 56 ++++++-------- calculate/templates/format/openrc_format.py | 16 +++- calculate/templates/format/patch_format.py | 76 +++++++------------ calculate/templates/format/postfix_format.py | 16 +++- calculate/templates/format/procmail_format.py | 13 +++- calculate/templates/format/proftpd_format.py | 40 ++++++++-- calculate/templates/format/samba_format.py | 18 ++++- .../templates/format/xml_gconf_format.py | 27 ++++++- calculate/templates/format/xml_xfce_format.py | 27 +++++-- tests/templates/format/test_compiz.py | 39 ---------- 19 files changed, 356 insertions(+), 221 deletions(-) diff --git a/calculate/templates/format/base_format.py b/calculate/templates/format/base_format.py index 4fe7ca2..a813c5e 100644 --- a/calculate/templates/format/base_format.py +++ b/calculate/templates/format/base_format.py @@ -38,17 +38,20 @@ class BaseFormat(): self._line_timer = 0 def _lines_to_dictionary(self, document_lines): + '''Основной метод для парсинга документа. Принимает список строк, + к каждой строке применяет парсеры, определенные для некоторого формата. + Первый парсер, которому удается разобрать строку используется для + формирования словаря.''' # print('Lines processing...') for line in document_lines: # print(self._line_timer, '\t', line) for processing_method in self._processing_methods: - processing_method(line) - - if self._fatal_error_flag: - # Действия если файл невозможно разобрать. + try: + processing_method(line) + except FormatError as error: self._document_dictionary = OrderedDict() - raise FormatError('line {} is not correct.'. - format(self._line_timer)) + raise FormatError("can not parse line: {}, reason: {}". + format(line, str(error))) if self._is_match(): if self._is_ready_to_update(): @@ -57,27 +60,34 @@ class BaseFormat(): else: # Действия если не удалось разобрать строку. self._document_dictionary = OrderedDict() - raise FormatError('line {} is not correct.'. - format(self._line_timer)) - - self._line_timer += 1 + raise FormatError('can not parse line: {}'. + format(line)) if self._need_finish: self._finish_method() def _parse_xml_to_dictionary(self, xml_document_text): + '''Метод для парсинга xml файлов. + Файлы xml предварительно не разбиваются на строки, а разбираются с + помощью модуля lxml. Перевод в словарь осуществляется методами формата, + рекурсивно вызывающимися в зависимости от типа тега.''' root = fromstring(xml_document_text) self._document_dictionary = self._processing_methods[root.tag](root) def print_dictionary(self): + '''Метод для отладки.''' pprint(self._document_dictionary) def join_template(self, template): + '''Метод запускающий наложение шаблона.''' self._join(self._document_dictionary, template._document_dictionary, self._join_before) def _get_list_of_logic_lines(self, text): + '''Метод разбивающий документ на список логических строк -- то есть + учитывающий при разбиении возможность разбиение одной строки на + несколько с помощью бэкслеша. В некоторых форматах переопределен.''' list_of_lines = [] lines_to_join = [] for line in text.splitlines(): @@ -94,6 +104,8 @@ class BaseFormat(): return list_of_lines def _join(self, original, template, join_before): + '''Основной метод для наложения шаблонов путем объединения их словарей + выполняемого рекурсивно.''' if template == OrderedDict(): return if join_before: @@ -205,6 +217,8 @@ class BaseFormat(): original.move_to_end(key_value, last=False) def make_template(self, template): + '''Метод для запуска генерации шаблонов путем сравнения пары исходных + файлов.''' full_diff, set_to_check = self.compare_dictionaries( self._document_dictionary, template._document_dictionary @@ -214,6 +228,8 @@ class BaseFormat(): return template_object def compare_dictionaries(self, dict_1, dict_2): + '''Основной метод для генерации шаблонов путем сравнения пары исходных + файлов. Работает рекурсивно.''' to_remove_dictionary = OrderedDict() to_add_dictionary = OrderedDict() to_replace_dictionary = OrderedDict() @@ -289,6 +305,8 @@ class BaseFormat(): @property def document_text(self): + '''Метод для получения текста документа. Использует jinja2 для + рендеринга документа.''' file_loader = PackageLoader('calculate.templates.format', self.TEMPLATES_DIRECTORY) formats_environment = Environment(loader=file_loader, @@ -303,12 +321,20 @@ class BaseFormat(): return document_text def _finish_method(self): + '''Метод для выполнения заключительных действий парсинга. + Переопределяется в форматах. Вызывается при self._need_finish = True''' pass def _is_ready_to_update(self): + '''Метод для проверки флага self._ready_to_update, указывающего, что + сформированная форматом секция документа, находящаяся в + self._item_to_add, может быть добавлена в словарь документа.''' is_ready, self._ready_to_update = self._ready_to_update, False return is_ready def _is_match(self): + '''Метод для проверки флага self._is_match, указывающего что текущий + парсер, использованный форматом, смог распарсить строку и использовать + другие парсеры не нужно.''' is_match, self._match = self._match, False return is_match diff --git a/calculate/templates/format/bind_format.py b/calculate/templates/format/bind_format.py index fb0fb21..9e8a2ba 100644 --- a/calculate/templates/format/bind_format.py +++ b/calculate/templates/format/bind_format.py @@ -11,6 +11,9 @@ from pyparsing import originalTextFor, OneOrMore, Word, alphanums, Literal,\ class BINDFormat(BaseFormat): + '''Класс формата BIND. В отличие от большинства других форматов обрабатывает + документ не построчно, а напрямую применяя парсер ко всему тексту + документа.''' FORMAT = 'bind' EXECUTABLE = False @@ -36,6 +39,9 @@ class BINDFormat(BaseFormat): self._parse_text(document_text) def _initialize_parser(self): + '''Метод для инициализации парсеров. Для данного формата парсеры + инициализируется при создании экземпляра формата, поскольку настройка + парсеров зависит от того, включен ли флаг ignore_comments.''' left_brace = Literal('{') right_brace = Literal('}') semicolon = Literal(';') @@ -211,13 +217,20 @@ class BINDFormat(BaseFormat): self._document_parser = OneOrMore(block_item) def _parse_text(self, text): + '''Метод для запуска разбора документа.''' parsing_result = self._document_parser.parseString(text, parseAll=True) list_of_elements = parsing_result.asList() + + # На выходе парсера получаем список словарей секций, который затем + # преобразуем в словарь документа. for part in list_of_elements: self._join_dictionary(self._document_dictionary, part) def _join_dictionary(self, out_dictionary, dictionary_to_add): + '''Метод для преобразования результата парсинга в итоговый словарь. + Работает рекурсивно. Умеет объединять секции с одинаковыми названиями. + ''' for key in dictionary_to_add: if dictionary_to_add == OrderedDict(): return @@ -230,6 +243,8 @@ class BINDFormat(BaseFormat): out_dictionary[key] = dictionary_to_add[key] def _add_plain_line(self, current_parse): + '''Метод используемый в парсере простых строк, состоящих только из + имени параметра и опционально из его значения.''' name = tuple(current_parse.name.asList()) if not current_parse.parameter == '': value = current_parse.parameter @@ -241,14 +256,17 @@ class BINDFormat(BaseFormat): return OrderedDict({name: [value]}) def _add_include_line(self, current_parse): + '''Метод используемый в парсере директивы include.''' name = current_parse.asList() return OrderedDict({tuple(name): ['']}) def _add_ipline(self, current_parse): + '''Метод используемый в парсере ip адресов.''' ip_value = current_parse.IP return OrderedDict({tuple(ip_value): ['']}) def _add_inet_specline(self, current_parse): + '''Метод используемый в парсере директивы inet_spec.''' # Удаляем пробельные символы из второго параметра директивы. current_parse.name.parameter = current_parse.name.parameter.strip() block_name = tuple(current_parse.name.asList()) @@ -261,6 +279,9 @@ class BINDFormat(BaseFormat): return OrderedDict({block_name: content}) def _add_param_block(self, current_parse): + '''Метод используемый в парсере блоков параметров. Использует + рекурсивный метод self._join_dictionary для построения словаря блока + параметров. Учитывает возможность наличия вложенных блоков.''' block_name = tuple(current_parse.name.asList()) block_content = current_parse.content.asList() content = OrderedDict({'#': []}) @@ -270,6 +291,9 @@ class BINDFormat(BaseFormat): return OrderedDict({block_name: content}) def _add_comments_to_paramline(self, current_parse): + '''Метод используемый в парсере директив вместе с относящимися к ним + комментариями. Закрепляет комментарии за данным параметром в итоговом + словаре.''' [parameter] = current_parse.value.asList() comments = current_parse.comments if parameter == OrderedDict(): @@ -288,6 +312,9 @@ class BINDFormat(BaseFormat): return parameter def _add_comments_to_block(self, current_parse): + '''Метод используемый в парсере блоков директив вместе с относящимися + к ним комментариями. Закрепляет комментарии за данным параметром в + итоговом словаре.''' [value] = current_parse.value [block_name] = value block = value[block_name] @@ -301,6 +328,9 @@ class BINDFormat(BaseFormat): return value def _create_comment_list(self, current_parse): + '''Метод используемый в парсере комментариев. Формирует из обнаруженных + комментариев список, который затем закрепляется за блоком или + параметром.''' comments_list = [] comments = current_parse.asList() for comment in comments: diff --git a/calculate/templates/format/compiz_format.py b/calculate/templates/format/compiz_format.py index 19806af..30529fc 100644 --- a/calculate/templates/format/compiz_format.py +++ b/calculate/templates/format/compiz_format.py @@ -12,10 +12,15 @@ class CompizFormat(BaseFormat): EXECUTABLE = False _initialized = False - _comment_symbol = '' - def __init__(self, document_text: str, ignore_comments=False, - join_before=False, comment_symbol=''): + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + + def __init__(self, document_text: str, + ignore_comments=False, + join_before=False): processing_methods = [self._parse_comment_line, self._parse_section_line, self._parse_parameter_line, @@ -31,8 +36,6 @@ class CompizFormat(BaseFormat): self._current_section_name = '' self._last_comments_list = [] - if not self._initialized or comment_symbol != self._comment_symbol: - self._initialize_parser(comment_symbol) if document_text == '': self._document_dictionary = OrderedDict() @@ -41,7 +44,8 @@ class CompizFormat(BaseFormat): self._lines_to_dictionary(document_lines) @classmethod - def _initialize_parser(cls, comment_symbol): + def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' section_name = originalTextFor( OneOrMore(Word(alphanums+'_')) ) @@ -54,8 +58,8 @@ class CompizFormat(BaseFormat): + Literal(']').suppress())('section_name') parameter_name = originalTextFor( - OneOrMore(Word(printables, - excludeChars='=')) + OneOrMore(Word(printables, + excludeChars='=')) ) parameter_value = Word(printables) @@ -71,24 +75,16 @@ class CompizFormat(BaseFormat): + parameter_name('name') + restOfLine.suppress())('parameter_name') - if not comment_symbol: - cls._comment_line = originalTextFor( - Literal('#') - + ZeroOrMore(Word( - printables - + pyparsing_unicode.alphanums)) - )('comment') - else: - cls._comment_line = originalTextFor( - (Literal('#') | Literal(comment_symbol)) - + ZeroOrMore(Word( - printables - + pyparsing_unicode.alphanums)) - )('comment') - + cls._comment_line = originalTextFor( + Literal('#') + + ZeroOrMore(Word( + printables + + pyparsing_unicode.alphanums)) + )('comment') cls._initialized = True def _parse_section_line(self, line): + '''Метод для парсинга строк содержащих имя секции.''' try: self._item_to_add = OrderedDict() parsing_result = self._section_line.parseString(line) @@ -117,6 +113,7 @@ class CompizFormat(BaseFormat): return def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих параметр.''' try: parsing_result = self._parameter_line.parseString(line) self._match = True @@ -132,6 +129,9 @@ class CompizFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: parsing_result = self._parameter_to_delete.parseString(line) self._match = True @@ -148,6 +148,7 @@ class CompizFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: parsing_result = self._comment_line.parseString(line) self._match = True @@ -158,6 +159,8 @@ class CompizFormat(BaseFormat): return def _finish_method(self): + '''Метод для завершения парсинга. В данном случае добавляет в итоговый + словарь последнюю разобранную секцию.''' if self._current_section_name in self._document_dictionary.keys(): self._document_dictionary[self._current_section_name].update( self._current_section diff --git a/calculate/templates/format/contents_format.py b/calculate/templates/format/contents_format.py index 19ef044..548c177 100644 --- a/calculate/templates/format/contents_format.py +++ b/calculate/templates/format/contents_format.py @@ -13,6 +13,12 @@ class ContentsFormat(BaseFormat): _initialized = False + def __new__(cls, *args, **kwargs): + '''Метод для инициализации парсеров.''' + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, join_before=False, @@ -25,6 +31,11 @@ class ContentsFormat(BaseFormat): self._ignore_comments = ignore_comments self._join_before = join_before + + # флаг для указания режима, в котором работает формат. + # Если True => формат работает для наложения шаблонов; + # Если False => формат работает для получения словаря, используемого + # модулем package. self._template_parser_flag = template_parser if not self._initialized: @@ -67,6 +78,7 @@ class ContentsFormat(BaseFormat): cls._initialized = True def _parse_sym_line(self, line): + '''Метод для разбора строк типа sym.''' try: parsing_result = self.sym_line.parseString(line) self._match = True @@ -88,6 +100,7 @@ class ContentsFormat(BaseFormat): return def _parse_dir_line(self, line): + '''Метод для разбора строк типа dir.''' try: parsing_result = self.dir_line.parseString(line) self._match = True @@ -105,6 +118,7 @@ class ContentsFormat(BaseFormat): return def _parse_obj_line(self, line): + '''Метод для разбора строк типа obj.''' try: parsing_result = self.obj_line.parseString(line) self._match = True @@ -127,6 +141,10 @@ class ContentsFormat(BaseFormat): @property def document_text(self): + '''Метод для получения текста результирующего документа. + Представляет собой переопределение соответствующего метода базового + класса, посколько в данном случае нужно учитывать режим работы формата. + ''' file_loader = PackageLoader('calculate.templates.format', self.TEMPLATES_DIRECTORY) formats_environment = Environment(loader=file_loader, diff --git a/calculate/templates/format/diff_format.py b/calculate/templates/format/diff_format.py index c329705..e2baa32 100644 --- a/calculate/templates/format/diff_format.py +++ b/calculate/templates/format/diff_format.py @@ -10,7 +10,7 @@ class DiffFormat(BaseFormat): FORMAT = 'diff' EXECUTABLE = True - def __init__(self, document_text: str, comment_symbol=''): + def __init__(self, document_text: str): self._patch_text = document_text self._root_path = '' self._last_level = 0 @@ -19,18 +19,19 @@ class DiffFormat(BaseFormat): self._changed_files_list = [] def execute_format(self, root_path): - print + '''Метод для запуска работы формата.''' if path.exists(root_path): self._root_path = root_path else: - raise FormatError('Root path does not exist.') + raise FormatError('root path does not exist') if self._patch_text: return self._patch_document() else: - raise FormatError('Empty patch file.') + raise FormatError('empty patch file') def _patch_document(self): + '''Метод, производящий наложение патча путем запуска процесса patch.''' for level in range(0, 4): patch_dry_run = Process('patch', '--dry-run', '-p{}'.format(level), cwd=self._root_path) @@ -44,19 +45,16 @@ class DiffFormat(BaseFormat): if patch_dry_run.success(): return '' else: - raise FormatError('Correction failed.') + raise FormatError('correction failed') self._last_level = level patch_run = Process('patch', '-p{}'.format(level), cwd=self._root_path) patch_run.write(self._patch_text) if patch_run.success(): - print('patch run is successful...') for line in patch_run: if line.startswith('patching file'): self._changed_files_list.append(line[13:].strip()) return patch_run.read() else: - print('patch run is no successful...') - print(patch_run.read_error()) return '' diff --git a/calculate/templates/format/dovecot_format.py b/calculate/templates/format/dovecot_format.py index 94e8f36..6441049 100644 --- a/calculate/templates/format/dovecot_format.py +++ b/calculate/templates/format/dovecot_format.py @@ -16,8 +16,14 @@ class DovecotFormat(BaseFormat): _initialized = False _comment_symbol = '' - def __init__(self, document_text: str, ignore_comments=False, - join_before=False, comment_symbol=''): + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + + def __init__(self, document_text: str, + ignore_comments=False, + join_before=False): processing_methods = [self._parse_comment_line, self._parse_section_start_line, self._parse_include_line, @@ -35,8 +41,6 @@ class DovecotFormat(BaseFormat): self._current_section_name = '' self._last_comments_list = [] - if not self._initialized or comment_symbol != self._comment_symbol: - self._initialize_parser(comment_symbol) if document_text == '': self._document_dictionary = OrderedDict() @@ -45,28 +49,19 @@ class DovecotFormat(BaseFormat): self._lines_to_dictionary(document_lines) @classmethod - def _initialize_parser(cls, comment_symbol=''): + def _initialize_parser(cls): # Знаки пунктуации и действий. left_brace = Literal('{') right_brace = Literal('}') action_symbols = (Literal('!') | Literal('-')) - if not comment_symbol: - cls._comment_line_parser = originalTextFor( - Literal('#') - + ZeroOrMore(Word( - printables - + pyparsing_unicode.alphanums) - ) - )('comment') - else: - cls._comment_line_parser = originalTextFor( - (Literal('#') | Literal(comment_symbol)) - + ZeroOrMore(Word( - printables - + pyparsing_unicode.alphanums) - ) - )('comment') + cls._comment_line_parser = originalTextFor( + Literal('#') + + ZeroOrMore(Word( + printables + + pyparsing_unicode.alphanums) + ) + )('comment') # Для парсинга строк с началом секций. section = Word(alphas, alphanums+'-_', excludeChars='{}') @@ -96,9 +91,10 @@ class DovecotFormat(BaseFormat): )('value')) # Для парсинга строк с параметрами, подлежащими удалению. - cls._parameter_to_delete_parser = (action_symbols('action') - + parameter_name - + Optional(Literal('=')).suppress() + cls._parameter_to_delete_parser = ( + action_symbols('action') + + parameter_name + + Optional(Literal('=')).suppress() ) # Для парсинга строк, содержащих директиву !include. @@ -107,7 +103,8 @@ class DovecotFormat(BaseFormat): include_line_plain = (Optional(~action_symbols, default='')('action') + include('keyword') + Word(printables)('value')) - include_line_to_delete = (action_symbols('action') + include('keyword') + include_line_to_delete = (action_symbols('action') + + include('keyword') + Word(printables)('value')) cls._include_line_parser = (include_line_plain | diff --git a/calculate/templates/format/json_format.py b/calculate/templates/format/json_format.py index 89cca40..6baed99 100644 --- a/calculate/templates/format/json_format.py +++ b/calculate/templates/format/json_format.py @@ -9,7 +9,7 @@ class JSONFormat(BaseFormat): FORMAT = 'json' def __init__(self, document_text: str, ignore_comments=False, - join_before=False, comment_symbol=''): + join_before=False): processing_methods = [] super().__init__(processing_methods) self._ignore_comments = ignore_comments @@ -22,10 +22,14 @@ class JSONFormat(BaseFormat): self._text_to_dictionary(document_text) def _text_to_dictionary(self, json_file_text): + '''Метод для получения словаря документа, переопределяющий метод + базового класса.''' self._document_dictionary = json.loads(json_file_text, object_pairs_hook=OrderedDict) @property def document_text(self): + '''Метод для получения текста документа, переопределяющий метод + базового класса.''' json_file_text = json.dumps(self._document_dictionary, indent=4) return json_file_text diff --git a/calculate/templates/format/kde_format.py b/calculate/templates/format/kde_format.py index 9771fb4..6a2c93f 100644 --- a/calculate/templates/format/kde_format.py +++ b/calculate/templates/format/kde_format.py @@ -12,8 +12,14 @@ class KDEFormat(BaseFormat): _initialized = False - def __init__(self, document_text: str, ignore_comments=False, - join_before=False, comment_symbol=''): + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + + def __init__(self, document_text: str, + ignore_comments=False, + join_before=False): processing_methods = [self._parse_comment_line, self._parse_section_line, self._parse_parameter_line, @@ -29,8 +35,6 @@ class KDEFormat(BaseFormat): self._current_section_name = '' self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -40,6 +44,7 @@ class KDEFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' action_symbols = (Literal('!') | Literal('-')) section_name_part_content = originalTextFor((OneOrMore( Word(alphanums+':')))) @@ -79,6 +84,7 @@ class KDEFormat(BaseFormat): cls._initialized = True def _parse_section_line(self, line): + '''Метод для парсинга строк содержащих название секции.''' try: self._item_to_add = OrderedDict() parsing_result = self._section_line.parseString(line) @@ -107,6 +113,7 @@ class KDEFormat(BaseFormat): return def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих обычный параметр.''' try: parsing_result = self._parameter_line.parseString(line) self._match = True @@ -122,6 +129,9 @@ class KDEFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: parsing_result = self._parameter_to_delete.parseString(line) self._match = True @@ -138,6 +148,7 @@ class KDEFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: parsing_result = self._comment_line.parseString(line) self._match = True @@ -148,6 +159,8 @@ class KDEFormat(BaseFormat): return def _finish_method(self): + '''Метод для завершения парсинга. В данном случае добавляет в итоговый + словарь последнюю разобранную секцию.''' if self._current_section_name in self._document_dictionary.keys(): self._document_dictionary[self._current_section_name].update( self._current_section diff --git a/calculate/templates/format/kernel_format.py b/calculate/templates/format/kernel_format.py index fbf1f67..9a7ba71 100644 --- a/calculate/templates/format/kernel_format.py +++ b/calculate/templates/format/kernel_format.py @@ -12,8 +12,14 @@ class KernelFormat(BaseFormat): _initialized = False - def __init__(self, document_text: str, ignore_comments=False, - join_before=False, comment_symbol=''): + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + + def __init__(self, document_text: str, + ignore_comments=False, + join_before=False): processing_methods = [self._parse_comment_line, self._parse_parameter_line, self._parse_to_delete_line] @@ -24,8 +30,6 @@ class KernelFormat(BaseFormat): self._comments_processing = True self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -35,6 +39,7 @@ class KernelFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' parameter_name = Word(alphanums+'_')('parameter_name') parameter_value = originalTextFor( @@ -63,6 +68,7 @@ class KernelFormat(BaseFormat): cls._initialized = True def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих параметр.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_line.parseString(line) @@ -80,6 +86,9 @@ class KernelFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_to_delete.parseString(line) @@ -99,6 +108,7 @@ class KernelFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: result = self._comment_line.parseString(line) self._match = True diff --git a/calculate/templates/format/ldap_format.py b/calculate/templates/format/ldap_format.py index 2e2f497..f8badf8 100644 --- a/calculate/templates/format/ldap_format.py +++ b/calculate/templates/format/ldap_format.py @@ -13,10 +13,14 @@ class LDAPFormat(BaseFormat): _initialized = False + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, - join_before=False, - comment_symbol=''): + join_before=False): processing_methods = [self._parse_comment_line, self._parse_type_line, self._parse_access_line, @@ -43,8 +47,6 @@ class LDAPFormat(BaseFormat): self._current_type = ('', 'global') self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -54,6 +56,7 @@ class LDAPFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' cls._comment_line = originalTextFor( Literal('#') + ZeroOrMore(Word(printables @@ -215,6 +218,9 @@ class LDAPFormat(BaseFormat): cls._initialized = True def _get_list_of_logic_lines(self, text): + '''Метод для разбиения исходного документа на список логических строк, + то есть с учетом того, что строка ldap файла начинающаяся с отступа + является продолжением предыдущей.''' list_of_lines = [] lines_to_join = [] for line in text.splitlines(): @@ -239,6 +245,8 @@ class LDAPFormat(BaseFormat): return list_of_lines def _parse_type_line(self, line): + '''Метод для парсинга строк с объявлением областей backend или database + ''' try: self._item_to_add = OrderedDict() parsing_result = self._type_line.parseString(line) @@ -273,8 +281,7 @@ class LDAPFormat(BaseFormat): def _parse_notunique_line(self, line): '''Метод для парсинга строк c директивами неуникальными для секции. - Аргументы: line -- строка, которую нужно распарсить. - ''' + Их приходится парсить полностью как ключ словаря.''' try: self._item_to_add = OrderedDict() parsing_result = self._not_unique_parser.parseString(line) @@ -293,9 +300,7 @@ class LDAPFormat(BaseFormat): def _parse_access_line(self, line): '''Метод для парсинга строк содержащих конструкцию - access to by ||. - Аргументы: line -- строка, которую нужно распарсить. - ''' + access to by ||.''' try: parsing_result = self._access_line_parser.parseString(line) self._match = True @@ -335,9 +340,7 @@ class LDAPFormat(BaseFormat): def _parse_access_line_to_delete(self, line): '''Метод для парсинга строк, предписывающих удаление конструкций - access to, если указано только ее название и значение What. - Аргументы: line -- строка, которую нужно распарсить. - ''' + access to, если указано только ее название и значение What.''' try: parsing_result = self._access_line_to_delete_parser.parseString( line @@ -357,9 +360,7 @@ class LDAPFormat(BaseFormat): def _parse_syncrepl_line(self, line): '''Метод для парсинга строк содержащих конструкцию syncrepl - rep=. - Аргументы: line -- строка, которую нужно распарсить. - ''' + rep=.''' try: parsing_result = self._syncrepl_line_parser.parseString(line) self._match = True @@ -398,9 +399,7 @@ class LDAPFormat(BaseFormat): def _parse_syncrepl_line_to_delete(self, line): '''Метод для парсинга строк, предписывающих удаление конструкций syncrepl rid=, если указано только ее название и значение - ReplicaID. - Аргументы: line -- строка, которую нужно распарсить. - ''' + ReplicaID.''' try: parsing_result = self._syncrepl_line_to_delete_parser.parseString( line @@ -419,9 +418,7 @@ class LDAPFormat(BaseFormat): return def _parse_index_line(self, line): - '''Метод для парсинга строк с директивами index. - Аргументы: line -- строка, которую нужно распарсить. - ''' + '''Метод для парсинга строк с директивами index.''' try: parsing_result = self._index_line_parser.parseString(line) self._match = True @@ -438,9 +435,7 @@ class LDAPFormat(BaseFormat): def _parse_index_line_to_delete(self, line): '''Метод для парсинга строк, предписывающих удаление директив index, - если указано только из имя, но отсутвует значение. - Аргументы: line -- строка, которую нужно распарсить. - ''' + если указано только из имя, но отсутвует значение.''' try: parsing_result = self._index_line_to_delete_parser.parseString( line @@ -460,9 +455,7 @@ class LDAPFormat(BaseFormat): def _parse_plain_directive_line(self, line): '''Метод для парсинга строк с простыми уникальными для секции - директивами. - Аргументы: line -- строка, которую нужно распарсить. - ''' + директивами.''' try: parsing_result = self._directive_line_parser.parseString(line) self._match = True @@ -478,9 +471,7 @@ class LDAPFormat(BaseFormat): def _parse_plain_directive_line_to_delete(self, line): '''Метод для парсинга строк, предписывающих удаление простых уникальных - директив, если указано только их имя, но отсутствует значение. - Аргументы: line -- строка, которую нужно распарсить. - ''' + директив, если указано только их имя, но отсутствует значение.''' try: parsing_result = self._directive_line_to_delete_parser.parseString( line @@ -501,8 +492,7 @@ class LDAPFormat(BaseFormat): def _parse_comment_line(self, line): '''Метод для парсинга строк с комментариями и добавления их в список комментариев _last_comments_list, предназначенный для сбора - комментариев и последующего их присваивания параметрам и секциям. - Аргументы: line -- строка, которую нужно распарсить.''' + комментариев и последующего их присваивания параметрам и секциям.''' try: parsing_result = self._comment_line.parseString(line) self._match = True @@ -522,6 +512,8 @@ class LDAPFormat(BaseFormat): return def _finish_method(self): + '''Метод для завершения парсинга. В данном случае добавляет в итоговый + словарь последнюю разобранную область.''' self._item_to_add = OrderedDict() if self._current_type in self._document_dictionary.keys(): self._document_dictionary[self._current_type].update( diff --git a/calculate/templates/format/openrc_format.py b/calculate/templates/format/openrc_format.py index 55adb5f..b8f85dd 100644 --- a/calculate/templates/format/openrc_format.py +++ b/calculate/templates/format/openrc_format.py @@ -12,10 +12,14 @@ class OpenRCFormat(BaseFormat): _initialized = False + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, - join_before=False, - comment_symbol=''): + join_before=False): processing_methods = [self._parse_comment_line, self._parse_parameter_line, self._parse_to_delete_line] @@ -25,8 +29,6 @@ class OpenRCFormat(BaseFormat): self._comments_processing = True self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -36,6 +38,7 @@ class OpenRCFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' parameter_name = Word(printables, excludeChars='=') parameter_value = originalTextFor(OneOrMore(Word(printables))) @@ -61,6 +64,7 @@ class OpenRCFormat(BaseFormat): cls._initialized = True def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих параметр.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_line.parseString(line) @@ -79,6 +83,9 @@ class OpenRCFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_to_delete.parseString(line) @@ -98,6 +105,7 @@ class OpenRCFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: result = self._comment_line.parseString(line) self._match = True diff --git a/calculate/templates/format/patch_format.py b/calculate/templates/format/patch_format.py index 046fd1d..db28650 100644 --- a/calculate/templates/format/patch_format.py +++ b/calculate/templates/format/patch_format.py @@ -1,6 +1,6 @@ # vim: fileencoding=utf-8 # -from .base_format import BaseFormat +from .base_format import BaseFormat, FormatError from collections import OrderedDict import re try: @@ -33,21 +33,19 @@ class PatchFormat(BaseFormat): self._XML_ROOT_LINE = '\ {0}' - if not self._parse_patch(document_text): - # Какая-то обработка ошибки. - print('Error: Can not parse patch document.') + self._parse_patch(document_text) def _parse_patch(self, patch_text): + '''Метод, составляющий из текста шаблона xml документ и разбирающий его + с помощью lxml.''' xml_patch = self._XML_ROOT_LINE.format(patch_text.strip()) try: self._parsed_patch = fromstring(xml_patch) - return True except Exception: - # Какая-то обработка ошибки. - print('Error: Incorrect text of the template.') - return False + raise FormatError('can not parse patch document') def execute_format(self, document_to_patch): + '''Метод для запуска наложения патча.''' if not document_to_patch.strip() == '': self._document_to_patch = document_to_patch @@ -55,24 +53,23 @@ class PatchFormat(BaseFormat): return False else: if not self._patch_document(document_to_patch): - error_message = 'Error: Can not run patch.' - print(error_message) - return False - + raise FormatError('Error: Can not run patch.') else: after_patch = self._document_to_patch self._document_to_patch = '' + # Пока что возвращает результат наложения шаблона, это временно return after_patch def _patch_document(self, document_to_patch): + '''Метод, обходящий теги шаблона и использующий указанные в нем + регулярные выражения.''' patch_iterator = self._parsed_patch.getiterator() PATCH_DOCUMENT_TAGS = ('reg', 'text') patch_element = next(patch_iterator, False) if not patch_element or not patch_element.tag == 'patch': - print('Error: Incorrect text of the template.') - return False + raise FormatError('incorrect text of the template') while True: for patch_tag in PATCH_DOCUMENT_TAGS: @@ -80,9 +77,8 @@ class PatchFormat(BaseFormat): if patch_element is None: if patch_tag == 'text': - print('Error: Last Text ' - 'object is missed.') - return False + raise FormatError('last Text ' + 'object is missed.') else: break @@ -90,19 +86,16 @@ class PatchFormat(BaseFormat): if patch_element.text is not None: element_text = patch_element.text.strip() if element_text == '': - error_message = 'Error: Incorrect text of the \ - template: <{0}>%s'.format( + raise FormatError( + ("Error: Incorrect text of the " + "template: <{0}>%s").format( patch_tag - ) - print(error_message) - return False + )) else: - error_message = 'Error: Incorrect text of the \ - template: <{0}>'.format( + raise FormatError("Error: Incorrect text of the " + "template: <{0}>").format( patch_tag - ) - print(error_message) - return False + ) if patch_tag == 'reg': dotall = patch_element.attrib.get('dotall', False) @@ -111,10 +104,7 @@ class PatchFormat(BaseFormat): if 'multiline' in patch_element.attrib: multiline = patch_element.attrib['multiline'] if multiline not in self._FLAG_VALUES: - error_message = ('Error: Invalid multiline ' - 'value.') - print(error_message) - return False + raise FormatError('invalid multiline value') else: multiline = self._FLAG_VALUES[multiline] @@ -133,9 +123,7 @@ class PatchFormat(BaseFormat): if 'dotall' in patch_element.attrib: dotall = patch_element.attrib['dotall'] if dotall not in self._FLAG_VALUES: - error_message = 'Error: Invalid dotall value.' - print(error_message) - return False + raise FormatError('invalid dotall value') else: dotall = self._FLAG_VALUES[dotall] @@ -157,21 +145,15 @@ class PatchFormat(BaseFormat): text_for_replace = element_text else: if patch_element.tag in PATCH_DOCUMENT_TAGS: - error_message = 'Error: <{0}> is expected, \ - <{1}> instead.'.format( - patch_tag, - patch_element.tag - ) - print(error_message) + error_message = '<{0}> is expected, <{1}> instead.'.\ + format(patch_tag, + patch_element.tag) else: - error_message = 'Error: Unknown tag: {0}'.format( + error_message = 'unknown tag: {0}'.format( patch_element.tag - ) - print(error_message) - # Какая-то обработка ошибки. - error_message = 'Error: Incorrect text of the template.' - print(error_message) - return False + ) + raise ("incorrect text of the template: {}".format( + error_message)) else: self._document_to_patch = re.sub(regex_expression, text_for_replace, diff --git a/calculate/templates/format/postfix_format.py b/calculate/templates/format/postfix_format.py index 51f3fa6..0cc88cc 100644 --- a/calculate/templates/format/postfix_format.py +++ b/calculate/templates/format/postfix_format.py @@ -12,10 +12,14 @@ class PostfixFormat(BaseFormat): _initialized = False + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, - join_before=False, - comment_symbol=''): + join_before=False): processing_methods = [self._parse_comment_line, self._parse_parameter_line, self._parse_to_delete_line] @@ -25,8 +29,6 @@ class PostfixFormat(BaseFormat): self._comments_processing = True self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -36,6 +38,7 @@ class PostfixFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' parameter_name = Word(alphanums+'_') parameter_value = originalTextFor(OneOrMore(Word(printables))) @@ -61,6 +64,7 @@ class PostfixFormat(BaseFormat): )('comment') def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих параметр.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_line.parseString(line) @@ -78,6 +82,9 @@ class PostfixFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_to_delete.parseString(line) @@ -96,6 +103,7 @@ class PostfixFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: parsing_result = self._comment_line.parseString(line) self._match = True diff --git a/calculate/templates/format/procmail_format.py b/calculate/templates/format/procmail_format.py index 2ffb5a3..b8b0181 100644 --- a/calculate/templates/format/procmail_format.py +++ b/calculate/templates/format/procmail_format.py @@ -12,6 +12,11 @@ class ProcmailFormat(BaseFormat): _initialized = False + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, join_before=True, @@ -25,8 +30,6 @@ class ProcmailFormat(BaseFormat): self._comments_processing = True self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -36,6 +39,7 @@ class ProcmailFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' parameter_name = Word(alphanums+'_.') parameter_value = originalTextFor(OneOrMore(Word(printables))) @@ -64,6 +68,7 @@ class ProcmailFormat(BaseFormat): cls._initialized = True def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих параметр.''' try: parsing_result = self._parameter_line.parseString(line) self._match = True @@ -80,6 +85,9 @@ class ProcmailFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: self._item_to_add = OrderedDict() parsing_result = self._parameter_to_delete.parseString(line) @@ -98,6 +106,7 @@ class ProcmailFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: parsing_result = self._comment_line.parseString(line) self._match = True diff --git a/calculate/templates/format/proftpd_format.py b/calculate/templates/format/proftpd_format.py index 249600b..2af4688 100644 --- a/calculate/templates/format/proftpd_format.py +++ b/calculate/templates/format/proftpd_format.py @@ -1,6 +1,6 @@ # vim: fileencoding=utf-8 # -from .base_format import BaseFormat +from .base_format import BaseFormat, FormatError from jinja2 import Environment, PackageLoader from collections import OrderedDict from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ @@ -9,10 +9,21 @@ from pyparsing import originalTextFor, Literal, ZeroOrMore, Word, printables,\ class ProFTPDFormat(BaseFormat): + '''Класс формата ProFTPD. В отличие от других форматов, при разборе + вложенных на несколько уровней блоков, в итоговый словарь добавляются не + вложенные на несколько уровней словари, а отдельные объекты этих блоков, + содержащие в названии последовательность имен тегов, к которым они + относятся. + ''' FORMAT = 'proftpd' _initialized = False + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, join_before=False, @@ -36,8 +47,6 @@ class ProFTPDFormat(BaseFormat): self._actions_stack = [] self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -47,6 +56,7 @@ class ProFTPDFormat(BaseFormat): @classmethod def _initialize_parser(cls): + '''Метод для инициализации парсеров.''' left_angle_bracket = Literal('<') right_angle_bracket = Literal('>') slash = Literal('/') @@ -200,6 +210,7 @@ class ProFTPDFormat(BaseFormat): cls._initialized = True def _parse_section_start_line(self, line): + '''Метод для разбора тега открывающего секцию.''' try: parsing_result = self._section_start_parser.parseString(line) self._match = True @@ -212,6 +223,7 @@ class ProFTPDFormat(BaseFormat): return def _parse_section_end_line(self, line): + '''Метод для разбора тега закрывающего секцию.''' try: parsing_result = self._section_end_parser.parseString(line) self._match = True @@ -221,14 +233,14 @@ class ProFTPDFormat(BaseFormat): directive = tuple(parsing_result.directive) if not current_section[1] != directive: - # Здесь будет кидаться исключение. - self._fatal_error_flag = True - return - + raise FormatError("incorrect end tag , expected ". + format(directive, current_section)) except ParseException: return def _parse_plain_directive_line(self, line): + '''Метод для разбора строк состоящих из имени параметра и его значения. + ''' try: parsing_result = self._plain_directive_parser.parseString(line) self._match = True @@ -258,6 +270,8 @@ class ProFTPDFormat(BaseFormat): return def _parse_to_delete_plain_directive_line(self, line): + '''Метод для разбора строк с параметрами, подлежащими удалению, то есть + содержащих имя параметра с символом ! и, опционально, его значение.''' try: parsing_result = self._delete_plain_directive_parser.parseString( line @@ -283,6 +297,9 @@ class ProFTPDFormat(BaseFormat): return def _parse_single_key_directive_line(self, line): + '''Метод для разбора строк с директивами, состоящих лишь из одного + имени директивы. + ''' try: parsing_result = self._single_key_directive_parser.parseString( line @@ -314,6 +331,9 @@ class ProFTPDFormat(BaseFormat): return def _parse_double_key_directive_line(self, line): + '''Метод для разбора директив, добавление в словарь которых возможно + посредством формирования ключа из названия директивы и последующего за + ней значения.''' try: parsing_result = self._double_key_directive_parser.parseString( line @@ -345,6 +365,8 @@ class ProFTPDFormat(BaseFormat): return def _parse_to_delete_double_key_directive_line(self, line): + '''Метод для разбора директив, подлежащих удалению, ключи которых + формируются из названия директивы и последующего за ней значения.''' try: parsing_result = self._delete_double_key_directive_parser.\ parseString(line) @@ -369,6 +391,9 @@ class ProFTPDFormat(BaseFormat): return def _parse_full_key_directive_line(self, line): + '''Метод для разбора строк с директивами, из имен которых невозможно + составить уникальный ключ для итогового словаря. Такие директивы + разбираем полностью как ключ для пустого значения в словаре.''' try: parsing_result = self._full_key_directive_parser.parseString(line) self._match = True @@ -398,6 +423,7 @@ class ProFTPDFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для разбора строк, содержащих комментарии.''' try: result = self._comment_line.parseString(line) self._match = True diff --git a/calculate/templates/format/samba_format.py b/calculate/templates/format/samba_format.py index 13820af..77ed35c 100644 --- a/calculate/templates/format/samba_format.py +++ b/calculate/templates/format/samba_format.py @@ -10,10 +10,14 @@ class SambaFormat(BaseFormat): FORMAT = 'samba' _initialized = False + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False, - join_before=False, - comment_symbol=''): + join_before=False): processing_methods = [self._parse_comment_line, self._parse_section_line, self._parse_parameter_line, @@ -30,8 +34,6 @@ class SambaFormat(BaseFormat): self._join_before = join_before self._last_comments_list = [] - if not self._initialized: - self._initialize_parser() if document_text == '': self._document_dictionary = OrderedDict() @@ -80,6 +82,7 @@ class SambaFormat(BaseFormat): cls._initialized = True def _parse_section_line(self, line): + '''Метод для парсинга строк содержащих имя секции.''' try: self._item_to_add = OrderedDict() parsing_result = self._section_line.parseString(line) @@ -107,6 +110,7 @@ class SambaFormat(BaseFormat): return def _parse_parameter_line(self, line): + '''Метод для парсинга строк содержащих параметр.''' try: parsing_result = self._parameter_line.parseString(line) self._match = True @@ -123,6 +127,9 @@ class SambaFormat(BaseFormat): return def _parse_to_delete_line(self, line): + '''Метод для парсинга строк, подлежащих удалению, т.е. для которых + указано имя параметра со знаком !, и опционально присутствует значение + параметра.''' try: parsing_result = self._parameter_to_delete.parseString(line) self._match = True @@ -140,6 +147,7 @@ class SambaFormat(BaseFormat): return def _parse_comment_line(self, line): + '''Метод для парсинга строк содержащих комментарий.''' try: parsing_result = self._comment_line.parseString(line) self._match = True @@ -150,6 +158,8 @@ class SambaFormat(BaseFormat): return def _finish_method(self): + '''Метод для завершения парсинга. В данном случае добавляет в итоговый + словарь последнюю разобранную секцию.''' self._item_to_add = OrderedDict() if self._current_section_name in self._document_dictionary.keys(): self._document_dictionary[self._current_section_name].update( diff --git a/calculate/templates/format/xml_gconf_format.py b/calculate/templates/format/xml_gconf_format.py index 9fc81d2..dfd467b 100644 --- a/calculate/templates/format/xml_gconf_format.py +++ b/calculate/templates/format/xml_gconf_format.py @@ -14,6 +14,13 @@ except ImportError: class XMLGConfFormat(BaseFormat): FORMAT = 'xml_gconf' + _initialized = False + + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str): processing_methods = OrderedDict({'gconf': self._gconf, 'entry': self._entry, @@ -30,14 +37,18 @@ class XMLGConfFormat(BaseFormat): self._parse_xml_to_dictionary(document_text) - def _initialize_parser(self): + @classmethod + def _initialize_parser(cls): + '''Метод для инициализации парсерa.''' action_symbols = (Literal('!') | Literal('-')) name = originalTextFor(OneOrMore(Word(printables))) - self._node_name = Optional(action_symbols)('action') + name('name') + cls._node_name = Optional(action_symbols)('action') + name('name') + cls._initialized = True def _entry(self, xml_element): + '''Метод для парсинга тега entry.''' try: element_items = OrderedDict(xml_element.attrib) @@ -71,6 +82,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict() def _gconf(self, xml_element): + '''Метод для парсинга тега gconf.''' output_dictionary = OrderedDict() element_name = ('', xml_element.tag) for child in xml_element: @@ -81,6 +93,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict({element_name: output_dictionary}) def _dir(self, xml_element): + '''Метод для парсинга тега dir.''' output_dictionary = OrderedDict() try: parsing_result = self._node_name.parseString( @@ -101,6 +114,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict({element_name: output_dictionary}) def _longdesc(self, xml_element): + '''Метод для парсинга тега longdesc.''' element_name = ('', 'longdesc') description = xml_element.text @@ -112,6 +126,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict({element_name: ''}) def _local_schema(self, xml_element): + '''Метод для парсинга тега local_schema.''' output_dictionary = OrderedDict() try: element_name = ('', xml_element.tag, @@ -129,6 +144,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict({element_name: output_dictionary}) def _stringvalue(self, xml_element): + '''Метод для парсинга тега stringvalue.''' element_name = ('', 'stringvalue') value = xml_element.text @@ -140,6 +156,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict({element_name: ''}) def _default(self, xml_element): + '''Уже не акутальный метод, вместо него теперь _unknown''' output_dictionary = OrderedDict() element_name = ('', xml_element.tag, *xml_element.items()) @@ -151,6 +168,7 @@ class XMLGConfFormat(BaseFormat): return OrderedDict({element_name: output_dictionary}) def _li(self, xml_element): + '''Метод для разбора элементов с тегом li.''' child = next(iter(xml_element)) list_element = self._processing_methods.get(child.tag, @@ -166,6 +184,8 @@ class XMLGConfFormat(BaseFormat): @property def document_text(self): + '''Метод для получения исходного текста документа. Использует + рекурсивный метод _build_section.''' gconf_header = next(iter(self._document_dictionary)) root = Element('gconf') @@ -180,6 +200,9 @@ class XMLGConfFormat(BaseFormat): return xml_document def _build_section(self, current_element, dictionary): + '''Метод для перевода словаря xml-документа обратно в текст документа. + Для этого рекурсивно строит дерево xml-документа пригодное работы lxml. + ''' for dict_element in dictionary.keys(): element_tag = dict_element[1] element_attributes = OrderedDict({key: value for key, value in diff --git a/calculate/templates/format/xml_xfce_format.py b/calculate/templates/format/xml_xfce_format.py index f066009..90e4890 100644 --- a/calculate/templates/format/xml_xfce_format.py +++ b/calculate/templates/format/xml_xfce_format.py @@ -14,6 +14,13 @@ except ImportError: class XMLXfceFormat(BaseFormat): FORMAT = 'xml_xfce' + _initialized = False + + def __new__(cls, *args, **kwargs): + if not cls._initialized: + cls._initialize_parser() + return super().__new__(cls) + def __init__(self, document_text: str, ignore_comments=False): processing_methods = OrderedDict({'channel': self._channel, 'property': self._property, @@ -27,19 +34,22 @@ class XMLXfceFormat(BaseFormat): else: self._parse_xml_to_dictionary(document_text) - def _initialize_parser(self): + @classmethod + def _initialize_parser(cls): + '''Метод для инициализации парсерa.''' action_symbols = (Literal('!') | Literal('-')) name = originalTextFor(OneOrMore(Word(printables))) - self._node_name = Optional(action_symbols)('action') + name('name') + cls._node_name = Optional(action_symbols)('action') + name('name') # Кортежи с названиями атрибутов различных элементов. - self._ELEMENT_ATTRIBUTES = ('tag', 'name', 'type', 'value') - self._CHANNEL_ATTRIBUTES = ('tag', 'name', 'version') - self._VALUE_ATTRIBUTES = ('tag', 'type', 'value') + cls._ELEMENT_ATTRIBUTES = ('tag', 'name', 'type', 'value') + cls._CHANNEL_ATTRIBUTES = ('tag', 'name', 'version') + cls._VALUE_ATTRIBUTES = ('tag', 'type', 'value') def _property(self, xml_element): + '''Метод для парсинга тега property.''' try: parsing_result = self._node_name.parseString( xml_element.attrib['name'] @@ -77,6 +87,7 @@ class XMLXfceFormat(BaseFormat): return OrderedDict({element_name: output}) def _value(self, xml_element): + '''Метод для парсинга тега value.''' try: value = (xml_element.tag, xml_element.attrib['type'], @@ -87,6 +98,7 @@ class XMLXfceFormat(BaseFormat): return value def _channel(self, xml_element): + '''Метод для парсинга тега channel.''' output_dictionary = OrderedDict() try: parsing_result = self._node_name.parseString( @@ -114,6 +126,8 @@ class XMLXfceFormat(BaseFormat): @property def document_text(self): + '''Метод для получения исходного текста документа. Использует + рекурсивный метод _build_section.''' channel = next(iter(self._document_dictionary.keys())) channel_head = OrderedDict( {key: value for key, value in @@ -132,6 +146,9 @@ class XMLXfceFormat(BaseFormat): return xml_document def _build_section(self, current_element, dictionary): + '''Метод для перевода словаря xml-документа обратно в текст документа. + Для этого рекурсивно строит дерево xml-документа пригодное работы lxml. + ''' for dict_element in dictionary.keys(): element_head = OrderedDict({key: value for key, value in zip(self._ELEMENT_ATTRIBUTES, diff --git a/tests/templates/format/test_compiz.py b/tests/templates/format/test_compiz.py index 75c98b4..1319dde 100644 --- a/tests/templates/format/test_compiz.py +++ b/tests/templates/format/test_compiz.py @@ -139,45 +139,6 @@ class TestParsingMethods: compiz_object = CompizFormat(document_text) assert compiz_object._document_dictionary == result - def test_if_comment_parameter_is_set_for_template__format_object_will_parse_comments_with_comment_symbol_from_this_parameter(self): - document_text = ''' - # Comment - [Added Associations] - @ Comment1 - application/illustrator=zzz-gimp.desktop - application/pdf=evince.desktop; - - @ Comment2 - # Comment3 - - application/rtf=libreoffice-writer.desktop; - - [Other Section] - @Comment - !application/vnd.oasis.opendocument.spreadsheet=calculate-calc.desktop; - ''' - - section_1 = OrderedDict({'#': ['# Comment'], - ('', 'application/illustrator'): - ['@ Comment1', - 'zzz-gimp.desktop'], - ('', 'application/pdf'): - ['evince.desktop;'], - ('', 'application/rtf'): - ['@ Comment2', - '# Comment3', - 'libreoffice-writer.desktop;']}) - - section_2 = OrderedDict({('!', 'application/vnd.oasis.opendocument.spreadsheet'): - ['@Comment', - "calculate-calc.desktop;"]}) - - result = OrderedDict({('', 'Added Associations'): section_1, - ('', 'Other Section'): section_2}) - - compiz_object = CompizFormat(document_text, comment_symbol='@') - assert compiz_object._document_dictionary == result - def test_if_the_ignore_comments_flag_is_set__the_parser_ignores_all_comments(self): document_text = ''' # Comment