Added processing of format errors. Patch format was fixed #69

master
Иванов Денис 3 years ago
parent 24bd7b3e63
commit 2009c654ab

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

@ -29,13 +29,13 @@ class PatchFormat(Format):
self._cwd_path = path.dirname(self._cwd_path) self._cwd_path = path.dirname(self._cwd_path)
if not path.exists(self._cwd_path): if not path.exists(self._cwd_path):
raise FormatError('root path does not exist') raise FormatError('root path does not exist', executable=True)
if self._patch_text: if self._patch_text:
self._patch_document() self._patch_document()
return self.changed_files return self.changed_files
else: else:
raise FormatError('empty patch file') raise FormatError('empty patch file', executable=True)
def _patch_document(self): def _patch_document(self):
'''Метод, производящий наложение патча путем запуска процесса patch.''' '''Метод, производящий наложение патча путем запуска процесса patch.'''
@ -57,7 +57,7 @@ class PatchFormat(Format):
if patch_dry_run.success(): if patch_dry_run.success():
return '' return ''
else: else:
raise FormatError('correction failed') raise FormatError('correction failed', executable=True)
self._last_level = level self._last_level = level
patch_run = Process('patch', '-p{}'.format(level), patch_run = Process('patch', '-p{}'.format(level),
@ -78,3 +78,6 @@ class PatchFormat(Format):
return patch_run.read() return patch_run.read()
else: else:
return '' return ''
def __bool__(self):
return bool(self._patch_text)

@ -45,7 +45,7 @@ from typing import (
Callable Callable
) )
from calculate.variables.loader import Datavars from calculate.variables.loader import Datavars
from .format.base_format import Format from .format.base_format import Format, FormatError
from ..utils.io_module import IOModule from ..utils.io_module import IOModule
from collections import OrderedDict, abc from collections import OrderedDict, abc
from contextlib import contextmanager from contextlib import contextmanager
@ -2780,6 +2780,13 @@ class DirectoryProcessor:
format(str(error), format(str(error),
template_path)) template_path))
return False return False
except FormatError as error:
if error.executable:
msg = 'Format execution error: {} Template: {}'
else:
msg = 'Format joining error: {} Template: {}'
self.output.set_error(msg.format(str(error), template_path))
return False
if template_type == DIR: if template_type == DIR:
self.output.set_success('Processed directory: {}'. self.output.set_success('Processed directory: {}'.

@ -87,12 +87,14 @@ datavars = Variables({'install': install,
'custom': Variables()}) 'custom': Variables()})
def show_tree(dir_path, indent=0): def show_tree(dir_path: str, indent: int = 0,
files_only: bool = False) -> None:
print('{}{}/'.format(' ' * 4 * indent, os.path.basename(dir_path))) print('{}{}/'.format(' ' * 4 * indent, os.path.basename(dir_path)))
indent += 1 indent += 1
for node in os.scandir(dir_path): for node in os.scandir(dir_path):
if node.is_dir(): if node.is_dir():
show_tree(node.path, indent=indent) if not files_only:
show_tree(node.path, indent=indent)
else: else:
print('{}{}'.format(' ' * 4 * indent, node.name)) print('{}{}'.format(' ' * 4 * indent, node.name))
@ -1546,6 +1548,28 @@ class TestDirectoryProcessor:
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_73/file_0')) assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_73/file_0'))
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_74/file_0')) assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_74/file_0'))
def test_to_solve_bug_with_patch_template(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_53')
io = IOModule(save_messages=True)
directory_processor = DirectoryProcessor('install',
datavars_module=datavars,
output_module=io)
directory_processor.process_template_directories()
with open(join_paths(CHROOT_PATH, '/etc/file_18'), 'r') as output:
output_text = output.read()
assert output_text == 'A veeeeeeeeeeery interesting content\n'
# print("MESSAGES:")
# for message in io.messages:
# print(f"{message[0]} -> {message[1]}")
assert io.messages[-1] ==\
("error",
"Format execution error: correction failed Template:"
f" {join_paths(CHROOT_PATH, 'templates_53/install/patch')}")
def test_view_tree(self): def test_view_tree(self):
list_path = join_paths(CHROOT_PATH, '/etc') list_path = join_paths(CHROOT_PATH, '/etc')
show_tree(list_path) show_tree(list_path)

@ -1,9 +0,0 @@
#-------------------------------------------------------------------------------
# Modified by Calculate Utilities 4.0
# Processed template files:
# /var/calculate/development/calculate-utils-4-lib/tests/templates/testfiles/test_dir_processor_root/templates_10/root/etc/dir_7/file_0
#-------------------------------------------------------------------------------
options {
parameter-1 value_1;
parameter-2 value_2;
};

@ -1,9 +0,0 @@
#-------------------------------------------------------------------------------
# Modified by Calculate Utilities 4.0
# Processed template files:
# /var/calculate/development/calculate-utils-4-lib/tests/templates/testfiles/test_dir_processor_root/templates_10/root/etc/file_4
#-------------------------------------------------------------------------------
options {
parameter-1 value_1;
parameter-2 value_2;
};

@ -1,9 +0,0 @@
#-------------------------------------------------------------------------------
# Modified by Calculate Utilities 4.0
# Processed template files:
# /var/calculate/development/calculate-utils-4-lib/tests/templates/testfiles/test_dir_processor_root/templates_10/root/etc/file_5
#-------------------------------------------------------------------------------
options {
parameter-1 value_1;
parameter-2 value_2;
};

@ -0,0 +1,3 @@
{% calculate format = "patch" %}
<reg>test</reg>
<text>zxcv</text>
Loading…
Cancel
Save