|
|
|
@ -2,6 +2,7 @@
|
|
|
|
|
#
|
|
|
|
|
from collections import OrderedDict
|
|
|
|
|
from jinja2 import Environment, PackageLoader
|
|
|
|
|
from typing import Callable, List, Tuple
|
|
|
|
|
from pprint import pprint
|
|
|
|
|
from copy import copy
|
|
|
|
|
import re
|
|
|
|
@ -12,14 +13,16 @@ except ImportError:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FormatError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
def __init__(self, message: str, executable: bool = False):
|
|
|
|
|
super().__init__(message)
|
|
|
|
|
self.executable: bool = executable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Format():
|
|
|
|
|
class Format:
|
|
|
|
|
FORMAT = 'none'
|
|
|
|
|
CALCULATE_VERSION = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, processing_methods):
|
|
|
|
|
def __init__(self, processing_methods: List[Callable]):
|
|
|
|
|
self._processing_methods = processing_methods
|
|
|
|
|
self._document_dictionary = OrderedDict()
|
|
|
|
|
self._item_to_add = OrderedDict()
|
|
|
|
@ -39,7 +42,7 @@ class Format():
|
|
|
|
|
# для отладки.
|
|
|
|
|
self._line_timer = 0
|
|
|
|
|
|
|
|
|
|
def _lines_to_dictionary(self, document_lines):
|
|
|
|
|
def _lines_to_dictionary(self, document_lines: List[str]) -> None:
|
|
|
|
|
'''Основной метод для парсинга документа. Принимает список строк,
|
|
|
|
|
к каждой строке применяет парсеры, определенные для некоторого формата.
|
|
|
|
|
Первый парсер, которому удается разобрать строку используется для
|
|
|
|
@ -68,7 +71,7 @@ class Format():
|
|
|
|
|
if self._need_finish:
|
|
|
|
|
self._finish_method()
|
|
|
|
|
|
|
|
|
|
def _parse_xml_to_dictionary(self, xml_document_text):
|
|
|
|
|
def _parse_xml_to_dictionary(self, xml_document_text: str) -> None:
|
|
|
|
|
'''Метод для парсинга xml файлов.
|
|
|
|
|
Файлы xml предварительно не разбиваются на строки, а разбираются с
|
|
|
|
|
помощью модуля lxml. Перевод в словарь осуществляется методами формата,
|
|
|
|
@ -76,17 +79,17 @@ class Format():
|
|
|
|
|
root = fromstring(xml_document_text)
|
|
|
|
|
self._document_dictionary = self._processing_methods[root.tag](root)
|
|
|
|
|
|
|
|
|
|
def print_dictionary(self):
|
|
|
|
|
def print_dictionary(self) -> None:
|
|
|
|
|
'''Метод для отладки.'''
|
|
|
|
|
pprint(self._document_dictionary)
|
|
|
|
|
|
|
|
|
|
def join_template(self, template):
|
|
|
|
|
def join_template(self, template: "Format"):
|
|
|
|
|
'''Метод запускающий наложение шаблона.'''
|
|
|
|
|
self._join(self._document_dictionary,
|
|
|
|
|
template._document_dictionary,
|
|
|
|
|
self._join_before)
|
|
|
|
|
|
|
|
|
|
def _get_list_of_logic_lines(self, text):
|
|
|
|
|
def _get_list_of_logic_lines(self, text: str) -> List[str]:
|
|
|
|
|
'''Метод разбивающий документ на список логических строк -- то есть
|
|
|
|
|
учитывающий при разбиении возможность разбиение одной строки на
|
|
|
|
|
несколько с помощью бэкслеша. В некоторых форматах переопределен.'''
|
|
|
|
@ -105,7 +108,8 @@ class Format():
|
|
|
|
|
lines_to_join.append(line[:-1])
|
|
|
|
|
return list_of_lines
|
|
|
|
|
|
|
|
|
|
def _join(self, original, template, join_before):
|
|
|
|
|
def _join(self, original: OrderedDict,
|
|
|
|
|
template: OrderedDict, join_before: bool):
|
|
|
|
|
'''Основной метод для наложения шаблонов путем объединения их словарей
|
|
|
|
|
выполняемого рекурсивно.'''
|
|
|
|
|
if template == OrderedDict():
|
|
|
|
@ -226,7 +230,7 @@ class Format():
|
|
|
|
|
original[key_value] = forwarded_items[key_value]
|
|
|
|
|
original.move_to_end(key_value, last=False)
|
|
|
|
|
|
|
|
|
|
def make_template(self, template):
|
|
|
|
|
def make_template(self, template: "Format") -> "Format":
|
|
|
|
|
'''Метод для запуска генерации шаблонов путем сравнения пары исходных
|
|
|
|
|
файлов.'''
|
|
|
|
|
full_diff, set_to_check = self.compare_dictionaries(
|
|
|
|
@ -237,7 +241,9 @@ class Format():
|
|
|
|
|
template_object._document_dictionary = full_diff
|
|
|
|
|
return template_object
|
|
|
|
|
|
|
|
|
|
def compare_dictionaries(self, dict_1, dict_2):
|
|
|
|
|
def compare_dictionaries(self, dict_1: OrderedDict,
|
|
|
|
|
dict_2: OrderedDict
|
|
|
|
|
) -> Tuple[OrderedDict, set]:
|
|
|
|
|
'''Основной метод для генерации шаблонов путем сравнения пары исходных
|
|
|
|
|
файлов. Работает рекурсивно.'''
|
|
|
|
|
to_remove_dictionary = OrderedDict()
|
|
|
|
@ -314,7 +320,7 @@ class Format():
|
|
|
|
|
return full_diff, unchanged_set
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def document_text(self):
|
|
|
|
|
def document_text(self) -> str:
|
|
|
|
|
'''Метод для получения текста документа. Использует jinja2 для
|
|
|
|
|
рендеринга документа.'''
|
|
|
|
|
file_loader = PackageLoader('calculate.templates.format',
|
|
|
|
@ -335,23 +341,24 @@ class Format():
|
|
|
|
|
Переопределяется в форматах. Вызывается при self._need_finish = True'''
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def _is_ready_to_update(self):
|
|
|
|
|
def _is_ready_to_update(self) -> bool:
|
|
|
|
|
'''Метод для проверки флага 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):
|
|
|
|
|
def _is_match(self) -> bool:
|
|
|
|
|
'''Метод для проверки флага self._is_match, указывающего что текущий
|
|
|
|
|
парсер, использованный форматом, смог распарсить строку и использовать
|
|
|
|
|
другие парсеры не нужно.'''
|
|
|
|
|
is_match, self._match = self._match, False
|
|
|
|
|
return is_match
|
|
|
|
|
|
|
|
|
|
def _get_header_and_document_text(self, input_text,
|
|
|
|
|
template_path,
|
|
|
|
|
already_changed=False):
|
|
|
|
|
def _get_header_and_document_text(self, input_text: str,
|
|
|
|
|
template_path: str,
|
|
|
|
|
already_changed: bool = False
|
|
|
|
|
) -> Tuple[str, str]:
|
|
|
|
|
'''Метод для создания заголовка измененного файла и удаления его из
|
|
|
|
|
текста исходного файла.'''
|
|
|
|
|
header_pattern = self._get_header_pattern()
|
|
|
|
@ -369,8 +376,7 @@ class Format():
|
|
|
|
|
document_text = re.sub(header_pattern, '', input_text)
|
|
|
|
|
return header, document_text
|
|
|
|
|
|
|
|
|
|
def _make_header(self, template_paths: list):
|
|
|
|
|
print('making header...')
|
|
|
|
|
def _make_header(self, template_paths: list) -> str:
|
|
|
|
|
if not self.comment_symbol:
|
|
|
|
|
return ""
|
|
|
|
|
elif self.comment_symbol in ("xml", "XML"):
|
|
|
|
@ -388,7 +394,7 @@ class Format():
|
|
|
|
|
'{0}' + '-' * 79 + '\n').format(self.comment_symbol,
|
|
|
|
|
self.CALCULATE_VERSION)
|
|
|
|
|
|
|
|
|
|
def _get_header_pattern(self):
|
|
|
|
|
def _get_header_pattern(self) -> str:
|
|
|
|
|
if self.comment_symbol in {"xml", "XML"}:
|
|
|
|
|
return (r'<!--' + r'-' * 76 + r'\n' +
|
|
|
|
|
r'\s*Modified by Calculate Utilities [\d\w\.]*\n' +
|
|
|
|
@ -402,5 +408,5 @@ class Format():
|
|
|
|
|
r'(?P<template_paths>({0}\s*[/\w\d\-_\.]*\n)+)' +
|
|
|
|
|
r'{0}' + r'-' * 79 + r'\n?').format(self.comment_symbol)
|
|
|
|
|
|
|
|
|
|
def __bool__(self):
|
|
|
|
|
def __bool__(self) -> bool:
|
|
|
|
|
return bool(self._document_dictionary)
|
|
|
|
|