Added backgrounds format. fixed #75

master
Иванов Денис 3 years ago
parent d6c944890c
commit e59a4511d6

@ -0,0 +1,318 @@
# vim: fileencoding=utf-8
#
from ..template_engine import ParametersContainer, Variables
from ...variables.datavars import NamespaceNode, VariableNotFoundError
from ...variables.loader import Datavars
from ...utils.images import ImageMagick
from .base_format import Format, FormatError
from typing import Union, List, Tuple, NoReturn
import hashlib
import re
import os
class BackgroundsFormat(Format):
FORMAT = 'backgrounds'
EXECUTABLE = True
FORMAT_PARAMETERS = {'convert', 'stretch'}
def __init__(self, template_text: str,
template_path: str,
parameters: ParametersContainer,
datavars: Union[Datavars, NamespaceNode, Variables],
chroot_path: str = "/"):
self._lines: List[str] = [line for line
in template_text.strip().split('\n') if line]
self._datavars: Union[Datavars, NamespaceNode, Variables] = datavars
if parameters.source:
self._source = parameters.source
else:
raise FormatError("source parameter is not set for template with"
"with 'backgrounds' format.")
self._mirror: bool = parameters.mirror
self._stretch: bool = parameters.stretch
self._convert: Union[bool, str] = parameters.convert
self._empty_name: bool = (parameters.name == '')
# Измененные файлы.
self.changed_files: dict = dict()
# Список предупреждений.
self._warnings: list = []
# Флаг для тестов.
self._fake_chroot: bool = False
try:
self._fake_chroot = datavars.main.fake_chroot
except Exception:
pass
def execute_format(self, target_path: str, chroot_path: str = '/') -> dict:
'''Метод для запуска работы формата.'''
if not self._check_source(self._source, target_path, self._mirror):
return self.changed_files
print(f"FAKE_CHROOT: {self._fake_chroot}")
self._magician = ImageMagick(
chroot=chroot_path if not self._fake_chroot else "/")
source_resolution = self._magician.get_image_resolution(self._source)
resolutions_from_var = False
resolutions = self._get_resolutions_from_lines(self._lines,
source_resolution)
if not resolutions:
resolutions = self._get_resolutions_from_var(self._datavars)
resolutions_from_var = True
action_md5sum = self._get_action_md5sum(self._source, resolutions)
if not self._check_target_directory(target_path, self._mirror,
resolutions, action_md5sum):
return {}
images_format = self._get_format_value(self._convert,
self._datavars)
if self._convert and self._convert == "GFXBOOT":
converter = self._magician.convert_resize_gfxboot
else:
converter = self._magician.convert_resize_crop_center
output_paths = self._make_output_paths(self._lines, images_format,
target_path, resolutions,
resolutions_from_var)
# Выполняем преобразование изображений в требуемый размер и формат.
for resolution, output_path in output_paths.items():
if not self._check_stretch(source_resolution,
resolution,
self._stretch):
continue
width, height = resolution
converter(self._source, output_path, height, width,
image_format=images_format)
if output_path in self.changed_files:
self.changed_files[output_path] = 'M'
else:
self.changed_files[output_path] = 'N'
self._create_md5sum_file(target_path, action_md5sum)
return self.changed_files
def _get_format_value(self, convert: Union[bool, str],
datavars: Union[Datavars, NamespaceNode, Variables]
) -> str:
"""Метод для получения значения формата."""
if convert:
if convert == "JPG" or convert == "GFXBOOT":
images_format = "JPEG"
else:
images_format = convert
else:
images_format = self._magician.get_image_format(self._source)
return images_format
def _make_output_paths(self, lines: List[str],
images_format: str,
target_path: str,
resolutions: List[Tuple[int, int]],
resolutions_from_var: bool
) -> List[str]:
"""Метод для получения списка путей, по которым будут создаваться
изображения."""
if not self._empty_name:
path, name = os.path.split(target_path)
else:
path = os.path.dirname(target_path)
name = ''
paths = {}
if not resolutions_from_var and len(resolutions) == 1:
if self._empty_name:
raise FormatError("'name' parameter is empty in 'backgrounds'"
" format template with single file output.")
paths = {next(iter(resolutions)): target_path}
return paths
for width, height in resolutions:
paths[(width, height)] = (f"{path}/{name}{width}x{height}"
f".{images_format.lower()}")
return paths
def _get_resolutions_from_lines(self, lines: List[str],
source_resolution: Tuple[int, int]
) -> List[Tuple[int, int]]:
"""Метод для получения списка кортежей с разрешениями выходных
изображений из текста шаблона."""
resolutions = []
if lines:
for line in lines:
if (line.strip() == "original"
and source_resolution not in resolutions):
resolutions.append(source_resolution)
else:
try:
width, height = line.lower().strip().split("x")
resolution = (int(width), int(height))
except Exception:
raise FormatError("can not parse line from template"
" with 'backgrounds' format:"
f" '{line}'")
if resolution not in resolutions:
resolutions.append(resolution)
return resolutions
def _get_resolutions_from_var(self, datavars: Union[Datavars,
NamespaceNode,
Variables]
) -> List[Tuple[int, int]]:
"""Метод для получения списка кортежей с разрешениями выходных
изображений из переменной."""
try:
resolutions = []
for resolution in self._datavars.main.cl_resolutions:
resolution = tuple(resolution.strip().split("x"))
resolutions.append(resolution)
return resolutions
except VariableNotFoundError:
raise FormatError("resolutions values was not found.")
except Exception:
raise FormatError("can not use resolution values from variable:"
" 'main.cl_resolutions'")
def _check_target_directory(self, target_path: str, mirror: bool,
resolutions: List[Tuple[int, int]],
action_md5sum: str
) -> Union[bool, str]:
"""Метод для проверки содержимого целевой директории, удаления
изображений и md5-сумм, подлежащих удалению, а также сравнения
имеющейся в директории md5-суммы с суммой, полученной для действия
текущего шаблона."""
if not self._empty_name:
path, name = os.path.split(target_path)
else:
path = os.path.dirname(target_path)
name = ''
name_pattern = re.compile(rf"^{name}\d+x\d+")
md5sum = None
images = []
# Проверяем содержимое целевой директории.
for node in os.scandir(path):
if node.is_file():
if (node.name ==
f"{name.strip('-_.')}{'.' if name else ''}md5sum"):
md5sum = node.name
elif (node.name == name
or (name_pattern.search(node.name) is not None)):
images.append(node.name)
if not images and md5sum is None:
# Если нет файла суммы и нет изображений -- продолжаем выполнение
# шаблона.
return True
elif not images and md5sum is not None:
# Если есть файл суммы, но нет изображений -- удаляем файл суммы.
md5sum_path = os.path.join(path, md5sum)
os.unlink(md5sum_path)
self.changed_files[md5sum_path] = 'D'
return True
elif images and md5sum is None:
# Если есть файлы изображений, но нет суммы -- удаляем файлы
# изображений.
for image in images:
image_path = os.path.join(path, image)
os.unlink(image_path)
self.changed_files[image_path] = 'D'
return True
else:
# Сравниваем суммы из md5sum и для текущего действия если они
# сходятся, то делать ничего не надо, если нет -- удаляем
# имеющиеся изображения и md5-сумму.
with open(os.path.join(path, md5sum), "r") as md5sum_file:
current_md5sum = md5sum_file.read().strip()
if current_md5sum != action_md5sum:
for image in images:
image_path = os.path.join(path, image)
os.unlink(image_path)
self.changed_files[image_path] = 'D'
md5sum_path = os.path.join(path, md5sum)
os.unlink(md5sum_path)
self.changed_files[md5sum_path] = 'D'
return True
else:
return False
def _check_source(self, source: str, target_path: str, mirror: bool
) -> bool:
"""Метод для проверки исходного изображения."""
if not self._empty_name:
path, name = os.path.split(target_path)
else:
path = os.path.dirname(target_path)
name = ''
name_pattern = re.compile(rf"^{name}\d+x\d+")
if not os.path.exists(source):
if mirror:
for node in os.scandir(path):
if (node.name == name
or name_pattern.search(node.name) is not None
or node.name == (f"{node.name.strip('_-.')}"
f"{ '.' if name else '' }md5sum")):
os.unlink(node.path)
self.changed_files[node.path] = "D"
return False
else:
raise FormatError("image from 'source' parameter does not"
f" exist: {source}.")
return True
def _get_action_md5sum(self, source: str,
resolutions: List[Tuple[int, int]]) -> bool:
"""Метод для получения md5-суммы текущего действия шаблона,
рассчитываемой из последовательности байтов изображения и списка
разрешений, в которые данный файл должен быть конвертирован."""
print("RESOLUTIONS:", resolutions)
with open(source, "rb") as source_file:
md5_object = hashlib.md5(source_file.read())
for width, height in resolutions:
resolution = f"{width}x{height}"
md5_object.update(resolution.encode())
return md5_object.hexdigest()
def _create_md5sum_file(self, target_path: str, action_md5sum: str
) -> NoReturn:
"""Метод для создания файла с md5-суммой действия, выполненного
данным шаблоном."""
if not self._empty_name:
path, name = os.path.split(target_path)
else:
path = os.path.dirname(target_path)
name = ''
md5sum_path = (f"{path}/{name.strip('_-.')}"
f"{ '.' if name else '' }md5sum")
with open(md5sum_path, "w") as md5sum_file:
md5sum_file.write(action_md5sum)
if md5sum_path in self.changed_files:
self.changed_files[md5sum_path] = 'M'
else:
self.changed_files[md5sum_path] = 'N'
def _check_stretch(self, source_resolution: Tuple[int, int],
resolution: Tuple[int, int],
stretch: bool) -> bool:
"""Метод определяющий необходимость растягивания исходного изображения
и делающий вывод о том, возможно ли создание изображения заданного
разрешения исходя из значения параметра stretch."""
return (stretch
or (source_resolution[0] >= resolution[0]
and source_resolution[1] >= resolution[1]))
@property
def warnings(self):
return self._warnings

@ -2,11 +2,15 @@
#
import os
from .base_format import Format, FormatError
from calculate.utils.files import join_paths
from pyparsing import Literal, Regex, SkipTo, LineEnd, lineno, LineStart
from calculate.utils.package import PackageAtomParser, Package, PackageNotFound
from calculate.utils.files import join_paths
from glob import iglob
from ..template_engine import ParametersContainer, Variables
from ...variables.datavars import NamespaceNode
from ...variables.loader import Datavars
from fnmatch import fnmatch
from typing import Union
from glob import iglob
ADD, REMOVE, MOVE = range(0, 3)
@ -25,7 +29,8 @@ class ContentsFormat(Format):
def __init__(self, template_text: str,
template_path: str,
ignore_comments=None):
parameters: ParametersContainer,
datavars: Union[Datavars, NamespaceNode, Variables]):
self._command_methods = {ADD: self._add_command,
REMOVE: self._remove_command,
MOVE: self._move_command}
@ -39,7 +44,10 @@ class ContentsFormat(Format):
self._packages = dict()
self._atom_parser = None
def execute_format(self, target_path, chroot_path='/'):
# Предупреждения.
self._warnings: list = []
def execute_format(self, target_path: str, chroot_path='/') -> dict:
'''Метод для запуска работы формата.'''
self._package = dict()
self._atom_parser = PackageAtomParser(chroot_path=chroot_path)
@ -215,3 +223,7 @@ class ContentsFormat(Format):
if not result:
return [None]
return {'error': result, 'lineno': lineno(location, string)}
@property
def warnings(self):
return self._warnings

@ -3,6 +3,10 @@
from .base_format import Format
from calculate.utils.files import Process
from calculate.templates.format.base_format import FormatError
from ..template_engine import ParametersContainer, Variables
from ...variables.datavars import NamespaceNode
from ...variables.loader import Datavars
from typing import Union
from os import path
@ -12,7 +16,8 @@ class PatchFormat(Format):
def __init__(self, patch_text: str,
template_path: str,
ignore_comments=None):
parameters: ParametersContainer,
datavars: Union[Datavars, NamespaceNode, Variables]):
self._patch_text = patch_text
self._cwd_path = '/'
self._last_level = 0
@ -20,6 +25,9 @@ class PatchFormat(Format):
# Измененные файлы.
self.changed_files = dict()
# Предупреждения.
self._warnings: list = []
def execute_format(self, target_path, chroot_path='/'):
'''Метод для запуска работы формата.'''
self._cwd_path = target_path
@ -81,3 +89,7 @@ class PatchFormat(Format):
def __bool__(self):
return bool(self._patch_text)
@property
def warnings(self):
return self._warnings

@ -1,12 +1,15 @@
# vim: fileencoding=utf-8
#
from jinja2.ext import Extension
from jinja2.lexer import Token
from jinja2.parser import Parser
from jinja2 import (
Environment,
FileSystemLoader,
TemplateSyntaxError,
nodes,
contextfunction
contextfunction,
Template,
)
from jinja2.utils import missing
from jinja2.runtime import Context, Undefined
@ -22,7 +25,10 @@ from typing import (
Union,
Any,
List,
Tuple
Tuple,
NoReturn,
Optional,
Iterator,
)
from ..utils.package import (
PackageAtomName,
@ -39,6 +45,7 @@ from ..utils.files import (
FilesError
)
from calculate.variables.datavars import (
VariableNotFoundError,
HashType,
NamespaceNode,
VariableNode,
@ -55,7 +62,6 @@ import calculate.templates.template_filters as template_filters
# Типы шаблона: директория или файл.
DIR, FILE, LINK = range(3)
# Словарь, в котором можно регистрировать фильтры.
CALCULATE_FILTERS = {"cut": template_filters.cut}
@ -79,19 +85,23 @@ class ConditionFailed(TemplateSyntaxError):
class Variables(MutableMapping):
'''Класс-заглушка вместо модуля переменных для тестов.'''
def __init__(self, *args, **kwargs):
self.__attrs = dict(*args, **kwargs)
self.__attrs: dict = dict(*args, **kwargs)
self.__iter: Union[Iterator, None] = None
def __next__(self):
iterator = iter(self.__attrs)
return next(iterator)
def __next__(self) -> Any:
if self._iter is None:
self._iter = iter(self.__attrs)
return next(self._iter)
def __getattribute__(self, name: str):
def __getattribute__(self, name: str) -> Any:
if name == '_Variables__attrs':
return super().__getattribute__(name)
if name == 'available_packages':
return super().__getattribute__(name)
if name == '_variables':
return self.__attrs
if name == '_iter':
return self._iter
try:
return self.__attrs[name]
except KeyError:
@ -103,16 +113,16 @@ class Variables(MutableMapping):
packages.update({'custom'})
return packages
def __getitem__(self, name: str):
def __getitem__(self, name: str) -> Any:
return self.__attrs[name]
def __setitem__(self, name: str, value) -> None:
def __setitem__(self, name: str, value: Any) -> NoReturn:
self.__attrs[name] = value
def __delitem__(self, name: str) -> None:
def __delitem__(self, name: str) -> NoReturn:
del self.__attrs[name]
def __iter__(self):
def __iter__(self) -> Iterator:
return iter(self.__attrs)
def __len__(self) -> int:
@ -124,7 +134,7 @@ class Variables(MutableMapping):
def __contains__(self, name: str) -> bool:
return name in self.__attrs
def __hash__(self):
def __hash__(self) -> int:
return hash(id(self))
@ -135,7 +145,8 @@ class ParametersProcessor:
'format', 'unbound', 'mirror', 'run', 'exec',
'env', 'package', 'merge', 'postmerge',
'action', 'rebuild', 'restart', 'stop',
'start', 'handler', 'notify', 'group'}
'start', 'handler', 'notify', 'group',
'convert', 'stretch'}
inheritable_parameters: set = {'chmod', 'chown', 'autoupdate', 'env',
'package', 'action', 'handler', 'group'}
@ -158,21 +169,24 @@ class ParametersProcessor:
r'([r-][w-][Xx-])([r-][w-][Xx-])([r-][w-][Xx-])')
def __init__(self,
parameters_container: Union["ParametersContainer",
None] = None,
parameters_container: Optional["ParametersContainer"] = None,
chroot_path: str = '/',
datavars_module: Union[Datavars, Variables] = Variables(),
for_package: Union[Package, None] = None):
datavars_module: Union[Datavars,
NamespaceNode,
Variables] = Variables(),
for_package: Optional[Package] = None):
self.chroot_path: str = chroot_path
self.template_type: int = DIR
self.datavars_module: Union[Datavars, Variables] = datavars_module
self.datavars_module: Union[Datavars,
NamespaceNode,
Variables] = datavars_module
self._parameters_container: ParametersContainer = parameters_container
self.package_atom_parser: PackageAtomParser =\
PackageAtomParser(chroot_path=chroot_path)
self.package_atom_parser: PackageAtomParser = PackageAtomParser(
chroot_path=chroot_path)
self._groups: dict = {}
try:
@ -212,7 +226,9 @@ class ParametersProcessor:
'merge': self.check_merge_parameter,
'format': self.check_format_parameter,
'handler': self.check_handler_parameter,
'notify': self.check_notify_parameter
'notify': self.check_notify_parameter,
'convert': self.check_convert_parameter,
'stretch': self.check_stretch_parameter,
})
# Если добавляемый параметр должен быть проверен после того, как
@ -225,7 +241,9 @@ class ParametersProcessor:
'autoupdate': self.check_postparse_autoupdate,
'run': self.check_postparse_run,
'exec': self.check_postparse_exec,
'handler': self.check_postparse_handler
'handler': self.check_postparse_handler,
'convert': self.check_postparse_convert,
'stretch': self.check_postparse_stretch,
})
# Если параметр является наследуемым только при некоторых условиях --
@ -234,7 +252,7 @@ class ParametersProcessor:
def set_parameters_container(self,
parameters_container: "ParametersContainer"
) -> None:
) -> NoReturn:
'''Метод для установки текущего контейнера параметров.'''
self._parameters_container = parameters_container
self._added_parameters = set()
@ -244,7 +262,7 @@ class ParametersProcessor:
return self._for_package
@for_package.setter
def for_package(self, package: Package):
def for_package(self, package: Package) -> NoReturn:
self._for_package = package
def __getattr__(self, parameter_name: str) -> Any:
@ -258,7 +276,7 @@ class ParametersProcessor:
def check_template_parameter(self, parameter_name: str,
parameter_value: Any,
template_type: int, lineno: int) -> None:
template_type: int, lineno: int) -> NoReturn:
'''Метод, проверяющий указанный параметр.'''
self.lineno = lineno
self.template_type = template_type
@ -293,7 +311,7 @@ class ParametersProcessor:
self._parameters_container.set_parameter({parameter_name:
checked_value})
def check_postparse_parameters(self) -> None:
def check_postparse_parameters(self) -> NoReturn:
'''Метод, запускающий проверку параметров после их разбора.'''
for parameter, parameter_checker in\
self.postparse_checkers_list.items():
@ -306,7 +324,7 @@ class ParametersProcessor:
result)
def check_template_parameters(self, parameters: dict,
template_type: int, lineno: int) -> None:
template_type: int, lineno: int) -> NoReturn:
'''Метод, запускающий проверку указанных параметров.'''
self.template_type = template_type
self.lineno = lineno
@ -339,7 +357,6 @@ class ParametersProcessor:
parameter_name=checked_value)
# Методы для проверки параметров во время разбора шаблона.
def check_package_parameter(self, parameter_value: Any) -> str:
if not isinstance(parameter_value, str):
raise IncorrectParameter("'package' parameter must have value of"
@ -402,17 +419,17 @@ class ParametersProcessor:
raise IncorrectParameter(
"'restart' parameter value is not correct")
def check_stop_parameter(self, parameter_value: Any):
def check_stop_parameter(self, parameter_value: Any) -> str:
if not parameter_value and isinstance(parameter_value, bool):
raise IncorrectParameter("'stop' parameter value is empty")
return parameter_value
def check_start_parameter(self, parameter_value: Any):
def check_start_parameter(self, parameter_value: Any) -> str:
if not parameter_value and isinstance(parameter_value, bool):
raise IncorrectParameter("'start' parameter value is empty")
return parameter_value
def check_run_parameter(self, parameter_value: Any):
def check_run_parameter(self, parameter_value: Any) -> str:
if self.template_type == DIR:
raise IncorrectParameter("'run' parameter is not available in"
" directory templates")
@ -425,7 +442,7 @@ class ParametersProcessor:
" found")
return interpreter_path
def check_exec_parameter(self, parameter_value: Any):
def check_exec_parameter(self, parameter_value: Any) -> str:
if self.template_type == DIR:
raise IncorrectParameter("'exec' parameter is not available in"
" directory templates")
@ -438,7 +455,7 @@ class ParametersProcessor:
" found")
return interpreter_path
def check_chown_parameter(self, parameter_value: Any):
def check_chown_parameter(self, parameter_value: Any) -> dict:
if not parameter_value or isinstance(parameter_value, bool):
raise IncorrectParameter("'chown' parameter value is empty.")
parameter_value = self.get_chown_values(parameter_value)
@ -478,7 +495,8 @@ class ParametersProcessor:
x_mask = x_mask + "0"
return (int(chmod, 2), int(x_mask, 2))
def check_source_parameter(self, parameter_value: Any):
def check_source_parameter(self, parameter_value: Any
) -> Union[str, Tuple[bool, str]]:
if not parameter_value or isinstance(parameter_value, bool):
raise IncorrectParameter("'source' parameter value is empty")
@ -510,7 +528,7 @@ class ParametersProcessor:
return os.path.normpath(real_path)
def check_env_parameter(self, parameter_value: Any):
def check_env_parameter(self, parameter_value: Any) -> Union[None, set]:
env_set = set()
for env_value in parameter_value.split(','):
@ -538,20 +556,20 @@ class ParametersProcessor:
return env_set
def check_force_parameter(self, parameter_value: Any):
def check_force_parameter(self, parameter_value: Any) -> bool:
if isinstance(parameter_value, bool):
return parameter_value
else:
raise IncorrectParameter("'force' parameter value is not bool")
def check_autoupdate_parameter(self, parameter_value: Any):
def check_autoupdate_parameter(self, parameter_value: Any) -> bool:
if isinstance(parameter_value, bool):
return parameter_value
else:
raise IncorrectParameter(
"'autoupdate' parameter value is not bool.")
def check_format_parameter(self, parameter_value: Any):
def check_format_parameter(self, parameter_value: Any) -> str:
if self.template_type == DIR:
raise IncorrectParameter("'format' parameter is redundant for"
" directory templates.")
@ -564,13 +582,13 @@ class ParametersProcessor:
raise IncorrectParameter("'format' parameter must be string value not"
f" {type(parameter_value)}.")
def check_handler_parameter(self, parameter_value: Any):
def check_handler_parameter(self, parameter_value: Any) -> str:
if not isinstance(parameter_value, str):
raise IncorrectParameter("'handler' parameter must be string"
f" value not {type(parameter_value)}.")
return parameter_value
def check_notify_parameter(self, parameter_value: Any):
def check_notify_parameter(self, parameter_value: Any) -> List[str]:
if isinstance(parameter_value, list):
return parameter_value
elif isinstance(parameter_value, str):
@ -579,9 +597,33 @@ class ParametersProcessor:
raise IncorrectParameter("'notify' parameter must be string or list"
f" value not {type(parameter_value)}.")
# Методы для проверки параметров после разбора всего шаблона.
def check_convert_parameter(self, parameter_value: Any) -> str:
if not isinstance(parameter_value, str):
raise IncorrectParameter("'convert' parameter value must be string"
f" not '{type(parameter_value)}'.")
parameter_value = parameter_value.strip().upper()
try:
available_image_formats =\
self.datavars_module.main.cl_image_formats
except VariableNotFoundError:
# TODO возможно стоит кидать ошибку.
available_image_formats = ["JPEG", "PNG", "GIF", "JPG"]
if parameter_value not in available_image_formats:
raise IncorrectParameter(f"'{parameter_value}' image format is "
"not available. Available image formats: "
f"'{', '.join(available_image_formats)}.'"
)
return parameter_value
def check_stretch_parameter(self, parameter_value: Any) -> bool:
if not isinstance(parameter_value, bool):
raise IncorrectParameter("'stretch' parameter value should be bool"
f" value not '{type(parameter_value)}'")
return parameter_value
def check_postparse_append(self, parameter_value):
# Методы для проверки параметров после разбора всего шаблона.
def check_postparse_append(self, parameter_value: str) -> NoReturn:
if parameter_value == 'link':
if 'source' not in self._parameters_container:
raise IncorrectParameter("append = 'link' without source "
@ -595,7 +637,7 @@ class ParametersProcessor:
raise IncorrectParameter("'append' parameter is not 'compatible' "
"with the 'exec' parameter")
def check_postparse_run(self, parameter_value):
def check_postparse_run(self, parameter_value: str) -> NoReturn:
if self._parameters_container.append:
raise IncorrectParameter("'run' parameter is not 'compatible' "
"with the 'append' parameter")
@ -604,7 +646,7 @@ class ParametersProcessor:
raise IncorrectParameter("'run' parameter is not 'compatible' "
"with the 'exec' parameter")
def check_postparse_exec(self, parameter_value):
def check_postparse_exec(self, parameter_value: str) -> NoReturn:
if self._parameters_container.append:
raise IncorrectParameter("'exec' parameter is not 'compatible' "
"with the 'append' parameter")
@ -613,13 +655,16 @@ class ParametersProcessor:
raise IncorrectParameter("'exec' parameter is not 'compatible' "
"with the 'run' parameter")
def check_postparse_source(self, parameter_value):
def check_postparse_source(self,
parameter_value: Union[str, Tuple[bool, str]]
) -> NoReturn:
# Если файл по пути source не существует, но присутствует параметр
# mirror -- пропускаем шаблон для того, чтобы целевой файл мог быть
# удален в исполнительном модуле.
if isinstance(parameter_value, tuple):
if (self._parameters_container.append == "link" and
self._parameters_container.force):
if ((self._parameters_container.append == "link" and
self._parameters_container.force)
or self._parameters_container.format == "backgrounds"):
self._parameters_container['source'] = parameter_value[1]
elif not self._parameters_container.mirror:
raise IncorrectParameter(
@ -632,12 +677,12 @@ class ParametersProcessor:
"append = 'link' for directory template")
)
def check_postparse_autoupdate(self, parameter_value):
def check_postparse_autoupdate(self, parameter_value: bool) -> NoReturn:
if self._parameters_container.unbound:
raise IncorrectParameter("'unbound' parameter is incompatible"
" with 'autoupdate' parameter")
def check_postparse_handler(self, parameter_value):
def check_postparse_handler(self, parameter_value: bool) -> NoReturn:
if self._parameters_container.merge:
raise IncorrectParameter("'merge' parameter is not available"
" in handler templates")
@ -646,7 +691,7 @@ class ParametersProcessor:
raise IncorrectParameter("'package' parameter is not available"
" in handler templates")
def check_postparse_package(self, parameter_value):
def check_postparse_package(self, parameter_value: str) -> NoReturn:
groups = []
package_atom = PackageAtomParser.parse_atom_name(parameter_value)
@ -679,7 +724,8 @@ class ParametersProcessor:
" does not match the template condition",
self.lineno if hasattr(self, 'lineno') else 0)
def _check_package_group(self, package: dict, group_packages: list):
def _check_package_group(self, package: dict, group_packages: list
) -> bool:
'''Метод для проверки соответствия описания пакета, заданного словарем,
какому-либо описанию пакета, заданного в переменных groups.'''
for group_package in group_packages:
@ -699,16 +745,28 @@ class ParametersProcessor:
return True
return False
def check_postparse_convert(self, parameter_value: str) -> NoReturn:
template_format = self._parameters_container.format
if not template_format or template_format != "backgrounds":
raise IncorrectParameter("'convert' parameter available for"
" 'backgrounds' format only.")
def check_postparse_stretch(self, parameter_value: str) -> NoReturn:
template_format = self._parameters_container.format
if not template_format or template_format != "backgrounds":
raise IncorrectParameter("'stretch' parameter available for"
" 'backgrounds' format only.")
# Методы для проверки того, являются ли параметры наследуемыми.
def is_chmod_inheritable(self, parameter_value):
def is_chmod_inheritable(self, parameter_value: str) -> bool:
chmod_regex = re.compile(r'\d+')
if chmod_regex.search(parameter_value):
return False
return True
def get_chown_values(self, chown: str):
def get_chown_values(self, chown: str) -> dict:
"""Получить значения uid и gid из параметра chown."""
if chown and ':' in chown:
user_name, group_name = chown.split(':')
@ -746,7 +804,7 @@ class ParametersProcessor:
raise IncorrectParameter("'chown' value '{0}' is not correct".
format(chown))
def get_uid_from_passwd(self, user_name: str):
def get_uid_from_passwd(self, user_name: str) -> int:
"""Функция для получения uid из chroot passwd файла."""
passwd_file_path = os.path.join(self.chroot_path, 'etc/passwd')
passwd_dictionary = dict()
@ -771,7 +829,7 @@ class ParametersProcessor:
raise FilesError("passwd file was not found in {}".
format(passwd_file_path))
def get_gid_from_group(self, group_name: str):
def get_gid_from_group(self, group_name: str) -> int:
"""Функция для получения gid из chroot group файла."""
group_file_path = os.path.join(self.chroot_path, 'etc/group')
group_dictionary = dict()
@ -797,7 +855,7 @@ class ParametersProcessor:
format(group_file_path))
@classmethod
def _inspect_formats_package(cls):
def _inspect_formats_package(cls) -> NoReturn:
'''Метод для определения множества доступных форматов и
предоставляемых ими параметров.'''
if cls.format_is_inspected:
@ -845,9 +903,13 @@ class ParametersProcessor:
cls.formats_inspected = True
def resolve_or_missing(context, key, missing=missing, env={}):
def resolve_or_missing(context: "CalculateContext",
key: str, missing=missing,
env: Optional[set] = None) -> Any:
'''Переопределение функции из для поиска значений переменных из jinja2.
Ищет переменные в datavars.'''
if env is None:
env = {}
datavars = context.parent['__datavars__']
if key in context.vars:
@ -871,7 +933,7 @@ class CalculateContext(Context):
сохранять их.'''
_env_set = set()
def resolve(self, key):
def resolve(self, key: str) -> Any:
if self._legacy_resolve_mode:
rv = resolve_or_missing(self, key,
env=self._env_set)
@ -881,7 +943,7 @@ class CalculateContext(Context):
return self.environment.undefined(name=key)
return rv
def resolve_or_missing(self, key):
def resolve_or_missing(self, key: str) -> Any:
if self._legacy_resolve_mode:
rv = self.resolve(key)
if isinstance(rv, Undefined):
@ -894,56 +956,56 @@ class CalculateContext(Context):
class ParametersContainer(MutableMapping):
'''Класс для хранения параметров, взятых из шаблона, и передачи
их шаблонизатору.'''
def __init__(self, parameters_dictionary=None):
def __init__(self, parameters_dictionary: Optional[dict] = None):
# Слой ненаследуемых параметров.
self.__parameters = {}
self.__parameters: dict = {}
# Слой наследуемых параметров.
if parameters_dictionary is not None:
self.__inheritable = parameters_dictionary
self.__inheritable: dict = parameters_dictionary
else:
self.__inheritable = {}
self.__inheritable: dict = {}
def set_parameter(self, item_to_add: dict):
def set_parameter(self, item_to_add: dict) -> NoReturn:
self.__parameters.update(item_to_add)
def set_inheritable(self, item_to_add: dict):
def set_inheritable(self, item_to_add: dict) -> NoReturn:
self.__inheritable.update(item_to_add)
def get_inheritables(self):
def get_inheritables(self) -> "ParametersContainer":
return ParametersContainer(copy.deepcopy(self.__inheritable))
def remove_not_inheritable(self):
def remove_not_inheritable(self) -> NoReturn:
self.__parameters.clear()
def print_parameters_for_debug(self):
def print_parameters_for_debug(self) -> NoReturn:
print('Parameters:')
pprint(self.__parameters)
print('Inherited:')
pprint(self.__inheritable)
def is_inherited(self, parameter_name):
def is_inherited(self, parameter_name: str) -> bool:
return (parameter_name not in self.__parameters
and parameter_name in self.__inheritable)
def remove_parameter(self, parameter_name):
def remove_parameter(self, parameter_name: str) -> NoReturn:
if parameter_name in self.__parameters:
self.__parameters.pop(parameter_name)
elif parameter_name in self.__inheritable:
self.__inheritable.pop(parameter_name)
def change_parameter(self, parameter, value):
def change_parameter(self, parameter: str, value: Any) -> NoReturn:
if parameter in self.__parameters:
self.__parameters.update({parameter: value})
elif parameter in self.__inheritable:
self.__inheritable.update({parameter: value})
def _clear_container(self):
def _clear_container(self) -> NoReturn:
self.__parameters.clear()
self.__inheritable.clear()
def __getattr__(self, parameter_name):
def __getattr__(self, parameter_name: str) -> Any:
if (parameter_name not in
ParametersProcessor.available_parameters):
raise IncorrectParameter("Unknown parameter: '{}'".
@ -956,7 +1018,7 @@ class ParametersContainer(MutableMapping):
else:
return False
def __getitem__(self, name):
def __getitem__(self, name: str) -> Any:
if name in self.__parameters:
return self.__parameters[name]
elif name in self.__inheritable:
@ -964,31 +1026,31 @@ class ParametersContainer(MutableMapping):
else:
return False
def __setitem__(self, name, value):
def __setitem__(self, name: str, value: Any) -> NoReturn:
self.__parameters[name] = value
def __delitem__(self, name):
def __delitem__(self, name: str) -> NoReturn:
if name in self.__parameters:
del self.__parameters[name]
if name in self.__inheritable:
del self.__inheritable[name]
def __iter__(self):
def __iter__(self) -> Iterator[str]:
return iter(set(self.__parameters).union(self.__inheritable))
def __len__(self):
def __len__(self) -> int:
return len(set(self.__parameters).union(self.__inheritable))
def __repr__(self):
def __repr__(self) -> str:
return '<ParametersContainer: parameters={0}, inheritables={1}>'.\
format(self.__parameters, self.__inheritable)
def __contains__(self, name):
def __contains__(self, name: str) -> bool:
return name in self.__parameters or name in self.__inheritable
@property
def parameters(self):
def parameters(self) -> dict:
return self.__parameters
@ -999,10 +1061,14 @@ class CalculateExtension(Extension):
# Виды операций в теге save.
ASSIGN, APPEND, REMOVE = range(3)
def __init__(self, environment, parameters_processor: ParametersProcessor,
datavars_module=Variables(), chroot_path="/"):
def __init__(self, environment: Environment,
parameters_processor: ParametersProcessor,
datavars_module: Union[Datavars,
NamespaceNode,
Variables] = Variables(),
chroot_path: str = "/"):
super().__init__(environment)
self.environment = environment
self.environment: Environment = environment
self.package_atom_parser = PackageAtomParser(chroot_path=chroot_path)
self.environment.globals.update({'pkg': self.pkg})
@ -1011,11 +1077,11 @@ class CalculateExtension(Extension):
self._datavars = datavars_module
self.parameters_processor = parameters_processor
self.template_type = DIR
self.template_type: int = DIR
# Флаг, указывающий, что тег calculate уже был разобран. Нужен для
# того, чтобы проверять единственность тега calculate.
self.calculate_parsed = False
self.calculate_parsed: bool = False
self.tags = {'calculate', 'save', 'set_var'}
self.CONDITION_TOKENS_TYPES = {'eq', 'ne', 'lt', 'gt', 'lteq', 'gteq'}
@ -1031,24 +1097,25 @@ class CalculateExtension(Extension):
self.parse_methods = {'calculate': self.parse_calculate,
'save': self.parse_save}
def __call__(self, env):
def __call__(self, env: Environment) -> "CalculateExtension":
# Необходимо для обеспечения возможности передать готовый объект
# расширения, а не его класс.
return self
def parse(self, parser):
def parse(self, parser: Parser) -> List[nodes.Output]:
self.parser = parser
self.stream = parser.stream
tag_token = self.stream.current.value
return [self.parse_methods[tag_token]()]
def parse_save(self):
def parse_save(self) -> nodes.Output:
'''Метод для разбора тега save, сохраняющего значение указанной
переменной datavars.'''
lineno = next(self.stream).lineno
target_file = nodes.Const('', lineno=lineno)
# Получаем имя целевого файла.
if self.stream.skip_if('dot'):
target_file_name = self.stream.expect('name').value
if target_file_name in self.TARGET_FILES_SET:
@ -1084,7 +1151,7 @@ class CalculateExtension(Extension):
raise TemplateSyntaxError("'=' is expected in 'save' tag",
lineno=lineno)
def parse_calculate(self):
def parse_calculate(self) -> nodes.Output:
'''Метод для разбора тега calculate, содержащего значения параметров и
условия выполнения шаблона.'''
lineno = next(self.stream).lineno
@ -1155,7 +1222,7 @@ class CalculateExtension(Extension):
self.calculate_parsed = True
return nodes.Output([nodes.Const('')], lineno=lineno)
def _is_variable_name(self, token):
def _is_variable_name(self, token: Token) -> bool:
'''Метод для проверки токена на предмет того, что он является частью
имени переменной.'''
if not token.type == 'name':
@ -1168,7 +1235,8 @@ class CalculateExtension(Extension):
return True
return False
def check_parameter(self, parameter_name, parameter_value, context):
def check_parameter(self, parameter_name: str, parameter_value: Any,
context: CalculateContext) -> str:
self.parameters_processor.check_template_parameter(
parameter_name,
parameter_value,
@ -1176,7 +1244,7 @@ class CalculateExtension(Extension):
self.stream.current.lineno)
return ''
def parse_condition(self):
def parse_condition(self) -> Template:
try:
condition_node = self.parser.parse_expression(with_condexpr=True)
condition_node = self.call_method(
@ -1196,7 +1264,7 @@ class CalculateExtension(Extension):
.format(str(error)),
lineno=self.stream.current.lineno)
def check_conditions(self, conditions: list):
def check_conditions(self, conditions: List[Template]) -> NoReturn:
for condition in conditions:
self.condition_result = False
try:
@ -1210,7 +1278,7 @@ class CalculateExtension(Extension):
lineno=self.stream.current.lineno)
# DEPRECATED
def get_condition_result(self):
def get_condition_result(self) -> bool:
'''Метод для разбора условий из тега calculate.'''
self.condition_result = False
@ -1233,13 +1301,14 @@ class CalculateExtension(Extension):
return self.condition_result
def set_condition_result(self, condition_result):
def set_condition_result(self, condition_result: Any) -> str:
'''Метод для сохранения результата вычисления условия.'''
self.condition_result = condition_result
return ''
def _make_save_node(self, variable_name_node, target_file_node, optype,
lineno):
def _make_save_node(self, variable_name_node: nodes.List,
target_file_node: nodes.Const, optype: int,
lineno: int) -> nodes.Output:
'''Метод для создания ноды, сохраняющей переменные.'''
right_value = self.parser.parse_expression(with_condexpr=True)
optype_node = nodes.Const(optype, lineno=lineno)
@ -1252,8 +1321,9 @@ class CalculateExtension(Extension):
lineno=lineno)
return nodes.Output([save_variable_node], lineno=lineno)
def save_variable(self, variable, right_value, target_file,
optype, context):
def save_variable(self, variable: List[str], right_value: Any,
target_file: str, optype: int,
context: CalculateContext) -> str:
'''Метод для сохранения значений переменных указанных в теге save.'''
datavars = context.parent['__datavars__']
if variable[0] not in datavars:
@ -1272,7 +1342,7 @@ class CalculateExtension(Extension):
# Теперь меняем знaчение переменной.
if isinstance(value_container, NamespaceNode):
self._modify_variables(variable, value_container, right_value,
optype, target=target_file,
optype, target_file=target_file,
modify_only=modify_only)
elif isinstance(value_container, VariableNode):
hash_value = value_container.get_value().get_hash()
@ -1296,7 +1366,7 @@ class CalculateExtension(Extension):
def _find_value_container(self, variable: List[str],
vars_package: NamespaceNode,
modify_only: bool = True
) -> Union[NamespaceNode]:
) -> Union[NamespaceNode, VariableNode]:
'''Метод для поиска контейнера, путь к которому указан в аргументе.
Этим контейнером может быть пространство имен или хэш.'''
current_container = vars_package
@ -1329,8 +1399,10 @@ class CalculateExtension(Extension):
current_container.get_fullname()))
return current_container
def _modify_variables(self, variable, namespace, new_value, optype,
target='', modify_only=True):
def _modify_variables(self, variable: List[str], namespace: NamespaceNode,
new_value: Any, optype: int,
target_file: Optional[str] = None,
modify_only: bool = True) -> NoReturn:
'''Метод для модификации значения переменной.'''
variable_name = variable[-1]
@ -1353,18 +1425,20 @@ class CalculateExtension(Extension):
raise SaveError("can not create variable '{}' in not 'custom'"
" namespace".format('.'.join(variable)))
if target:
if target_file:
if namespace._variables[variable_name].variable_type is HashType:
for key, value in new_value.items():
self._save_to_target(variable, key, value, target)
self._save_to_target(variable, key, value, target_file)
else:
self._save_to_target(variable[:-1], variable_name,
new_value, target)
new_value, target_file)
def _modify_hash(self, variable, hash_variable, new_value, optype,
target=''):
# DEPRECATED
def _modify_hash(self, variable_name: List[str],
hash_variable: VariableNode, new_value, optype,
target_file: Optional[str] = None) -> NoReturn:
'''Метод для модификации значения в переменной-хэше.'''
value_name = variable[-1]
value_name = variable_name[-1]
hash_value = hash_variable.get_value().get_hash()
if value_name in hash_value:
@ -1376,22 +1450,25 @@ class CalculateExtension(Extension):
hash_value.update({value_name: new_value})
hash_variable.set(hash_value)
if target:
self._save_to_target(variable[:-1], value_name,
new_value, target)
if target_file:
self._save_to_target(variable_name[:-1], value_name,
new_value, target_file)
def _save_to_target(self, namespace_name, variable_name, value, target):
def _save_to_target(self, namespace_name: List[str],
variable_name: str, value: Any, target_file: str
) -> NoReturn:
'''Метод для добавления переменной в список переменных, значение
которых было установлено через тег save и при этом должно быть
сохранено в указанном файле: save.target_file.'''
namespace_name = tuple(namespace_name)
target_file_dict = self._datavars.variables_to_save[target]
target_file_dict = self._datavars.variables_to_save[target_file]
if namespace_name not in target_file_dict:
target_file_dict.update({namespace_name: dict()})
target_file_dict[namespace_name].update(
{variable_name: ('=', str(value))})
def _append_variable_value(self, variable, value):
def _append_variable_value(self, variable: VariableNode,
value: Any) -> Any:
'''Метод описывающий операцию += в теге save.'''
variable_value = variable.get_value()
if (variable.variable_type is IntegerType or
@ -1429,7 +1506,8 @@ class CalculateExtension(Extension):
# значение.
return variable_value
def _remove_variable_value(self, variable, value):
def _remove_variable_value(self, variable: VariableNode, value: Any
) -> Any:
'''Метод описывающий операцию -= в теге save.'''
variable_value = variable.get_value()
@ -1469,7 +1547,7 @@ class CalculateExtension(Extension):
# значение.
return variable_value
def _get_parameter(self):
def _get_parameter(self) -> Tuple[nodes.Const, nodes.Node]:
'''Метод для разбора параметров, содержащихся в теге calculate.'''
lineno = self.stream.current.lineno
@ -1491,13 +1569,14 @@ class CalculateExtension(Extension):
return (parameter_name_node, parameter_rvalue)
def save_parameters(cls, parameters_dictionary, context):
def save_parameters(cls, parameters_dictionary: dict,
context: CalculateContext) -> str:
'''Метод для сохранения значений параметров.'''
context.parent['__parameters__'].set_parameter(parameters_dictionary)
return ''
@contextfunction
def pkg(self, context, *args) -> Version:
def pkg(self, context: CalculateContext, *args: dict) -> Version:
'''Метод, реализующий функцию pkg() шаблонов. Функция предназначена для
получения версии пакета, к которому уже привязан шаблон, если
аргументов нет, или версию пакета в аргументе функции. Если аргументов
@ -1518,7 +1597,7 @@ class CalculateExtension(Extension):
return Version()
return package.version
def get_full_filepath(self, fname):
def get_full_filepath(self, fname: str) -> str:
# TODO: добавить получение домашней директории пользователя
# if fname[0] == "~":
# # Получаем директорию пользователя
@ -1531,10 +1610,9 @@ class CalculateExtension(Extension):
return fname
@contextfunction
def grep(self, context, fname, regpattern) -> str:
'''
Метод реализующий функцию grep
'''
def grep(self, context: CalculateContext, fname: str,
regpattern: str) -> str:
'''Метод реализующий функцию grep.'''
fname = self.get_full_filepath(fname)
try:
reg = re.compile(regpattern, re.MULTILINE)
@ -1554,10 +1632,8 @@ class CalculateExtension(Extension):
return ""
@contextfunction
def exists(self, context, fname) -> str:
'''
Метод реализующий функцию exists
'''
def exists(self, context: CalculateContext, fname: str) -> str:
'''Метод реализующий функцию exists.'''
fname = self.get_full_filepath(fname)
try:
@ -1627,19 +1703,20 @@ class TemplateEngine:
self.environment.context_class = CalculateContext
@property
def for_package(self):
def for_package(self) -> Package:
return self.parameters_processor.for_package
@for_package.setter
def for_package(self, package):
def for_package(self, package: Package) -> NoReturn:
self.parameters_processor.for_package = package
def change_directory(self, directory_path):
def change_directory(self, directory_path: str) -> NoReturn:
'''Метод для смены директории в загрузчике.'''
self.environment.loader = FileSystemLoader(directory_path)
def process_template(self, template_path, template_type,
parameters=None):
def process_template(self, template_path: str, template_type: str,
parameters: Optional[ParametersContainer] = None
) -> NoReturn:
'''Метод для обработки файла шаблона, расположенного по указанному
пути.'''
if parameters is not None:
@ -1666,8 +1743,10 @@ class TemplateEngine:
Version=Version
)
def process_template_from_string(self, string, template_type,
parameters=None):
def process_template_from_string(
self, string: str, template_type: int,
parameters: Optional[ParametersContainer] = None
) -> NoReturn:
'''Метод для обработки текста шаблона.'''
if parameters is not None:
self._parameters_object = parameters
@ -1694,10 +1773,10 @@ class TemplateEngine:
)
@property
def parameters(self):
def parameters(self) -> ParametersContainer:
return self._parameters_object
@property
def template_text(self):
def template_text(self) -> str:
text, self._template_text = self._template_text, ''
return text

@ -275,7 +275,8 @@ class TemplateWrapper:
# Если установлен параметр mirror и есть параметр source,
# содержащий несуществующий путь -- удаляем целевой файл.
if self.parameters.source is True and self.parameters.mirror:
if (self.parameters.source is True and self.parameters.mirror and
not self.format_class.EXECUTABLE):
self.remove_original = True
else:
self.target_type = None
@ -283,13 +284,10 @@ class TemplateWrapper:
if self.format_class is not None and self.format_class.EXECUTABLE:
# Если формат исполняемый -- проверяем, существует ли директория,
# из которой будет выполняться шаблон.
if not os.path.exists(self.target_path):
if self.target_type is None:
# Если не существует -- создаем ее.
os.makedirs(self.target_path)
elif os.path.isfile(self.target_path):
# Если вместо директории файл -- определяем по файлу
# директорию.
self.target_path = os.path.dirname(self.target_path)
if not os.path.exists(os.path.dirname(self.target_path)):
os.makedirs(os.path.dirname(self.target_path))
# Если есть параметр package, определяем по нему пакет.
if self.parameters.package:
@ -656,11 +654,12 @@ class TemplateWrapper:
def update_contents_from_list(self, changed_list: dict) -> NoReturn:
'''Метод для изменения CONTENTS по списку измененных файлов.'''
print("UPDATE CONTENTS FROM LIST")
if self.target_package is None:
return
for file_path, mode in changed_list.items():
if mode == "M":
if mode in {"M", "N"}:
if os.path.islink(file_path):
self.target_package.add_sym(file_path)
@ -790,7 +789,7 @@ class TemplateExecutor:
self.executor_output = {'target_path': None,
'stdout': None,
'stderr': None,
'warning': None}
'warnings': []}
if parameters.append == 'skip':
return self.executor_output
@ -859,7 +858,7 @@ class TemplateExecutor:
template_object.target_path
if template_object.ca_is_missed:
self.executor_output['warning'] = (
self.executor_output['warnings'].append(
"archive file is missed,"
" target file was used instead")
@ -1027,10 +1026,8 @@ class TemplateExecutor:
# Действия, если целевой файл не имеет пользовательских изменений
# или если он исполнительный.
# Парсим текст шаблона используя его формат.
parsed_template = template_format(template_object.template_text,
template_object.template_path,
ignore_comments=True)
if (not parsed_template and template_object.parameters.source
if (not template_object.template_text.strip()
and template_object.parameters.source
and template_object.parameters.append == "replace"
and template_object.parameters.format == "raw"):
# Если шаблон пуст, параметром source задан входной файл,
@ -1042,6 +1039,10 @@ class TemplateExecutor:
chown=chown,
chmod=chmod)
elif not template_object.format_class.EXECUTABLE:
parsed_template = template_format(
template_object.template_text,
template_object.template_path,
ignore_comments=True)
# Действия для шаблонов не являющихся исполнительными.
output_paths = [output_path]
@ -1076,7 +1077,7 @@ class TemplateExecutor:
output_file_md5 = hashlib.md5(output_text.encode()).hexdigest()
if input_text:
input_file_md5 = hashlib.md5(
input_text.encode()).hexdigest()
input_text.encode()).hexdigest()
for save_path in output_paths:
if not os.path.exists(os.path.dirname(save_path)):
@ -1090,10 +1091,14 @@ class TemplateExecutor:
# если это необходимо.
if chown:
self._chown_file(save_path, chown)
if chmod:
self._chmod_file(save_path, chmod)
elif template_object.format_class.EXECUTABLE:
parsed_template = template_format(
template_object.template_text,
template_object.template_path,
template_object.parameters,
self.datavars_module)
changed_files = parsed_template.execute_format(
template_object.target_path,
chroot_path=self.chroot_path)
@ -1102,7 +1107,6 @@ class TemplateExecutor:
# Если исполняемый формат выдал список измененных файлов для
# изменения CONTENTS и при этом задан пакет -- обновляем
# CONTENTS.
for file_path, status in changed_files.items():
if status == 'M':
if file_path in self.changed_files:
@ -1119,6 +1123,12 @@ class TemplateExecutor:
else:
self.changed_files[file_path] = 'D'
# Если в ходе работы формат выкидывал предупреждения --
# добавляем их в список предупреждений.
if parsed_template.warnings:
self.executor_output['warnings'].extend(
parsed_template.warnings)
if (self.dbpkg and changed_files and
template_object.target_package and
template_object.format_class.FORMAT != 'contents'):
@ -2853,8 +2863,8 @@ class DirectoryProcessor:
parameters: ParametersContainer) -> str:
'''Метод для получения пути к целевому файлу с учетом наличия
параметров name, path и append = skip.'''
# Если есть параметр name -- меняем имя шаблона.
if parameters.name:
# Если есть непустой параметр name -- меняем имя шаблона.
if parameters.name is not False and parameters.name != '':
template_name = parameters.name
# Если для шаблона задан путь -- меняем путь к директории шаблона.
@ -2924,9 +2934,12 @@ class DirectoryProcessor:
if output['target_path'] is not None:
target_path = output['target_path']
if output['warning'] is not None:
self.output.set_warning(f"{output['warning']}."
f" Template: {template_path}")
if output['warnings']:
if not isinstance(output['warnings'], list):
output['warnings'] = [output['warnings']]
for warning in output['warnings']:
self.output.set_warning(f"{warning}."
f" Template: {template_path}")
# Если есть вывод от параметра run -- выводим как info.
if output['stdout'] is not None:

@ -1,6 +1,7 @@
# vim: fileencoding=utf-8
#
from subprocess import Popen, PIPE, STDOUT
from typing import Union, List
from io import TextIOWrapper
from os import path
from .tools import GenericFS, get_traceback_caller
@ -248,9 +249,9 @@ class ProgramPathCache:
'''Класс, для поиска и кэширования путей к исполнительным файлам различных
команд.'''
def __init__(self):
self._cache = {}
self._cache: dict = {}
def __call__(self, program_name, prefix='/'):
def __call__(self, program_name: str, prefix: str = '/'):
program_base_name = path.basename(program_name)
PATH = os.environ['PATH']
PATH = PATH.split(':')
@ -274,7 +275,7 @@ class ProgramPathCache:
get_program_path = ProgramPathCache()
def check_command(*utils):
def check_command(*utils: List[str]):
'''Функция для проверки наличия той или иной команды системе.'''
output = []
for util in utils:
@ -289,7 +290,7 @@ def check_command(*utils):
return output
def join_paths(*paths):
def join_paths(*paths: List[str]) -> str:
'''Функция для объединения путей. Объединяет также абсолютные пути.'''
if len(paths) == 1:
return next(iter(paths))
@ -307,7 +308,7 @@ def join_paths(*paths):
return output_path
def read_link(file_path):
def read_link(file_path: str) -> str:
'''Функция для получения целевого пути символьной ссылки.'''
try:
if path.exists(file_path):
@ -321,7 +322,8 @@ def read_link(file_path):
format(str(error), mod, lineno))
def get_target_from_link(link_path, link_source, chroot_path='/'):
def get_target_from_link(link_path: str, link_source: str,
chroot_path: str = '/') -> str:
'''Метод для получения целевого пути из целевого пути символьной ссылки
с учетом того, что целевой путь символьной ссылки может быть
относительным.'''
@ -342,11 +344,11 @@ def get_target_from_link(link_path, link_source, chroot_path='/'):
return '/'.join(link_dir)
def read_file(file_path):
def read_file(file_path: str, binary: bool = False) -> Union[str, bytes]:
'''Функция для чтения файлов, возвращает текст файла.'''
try:
if path.exists(file_path):
with open(file_path, 'r') as opened_file:
with open(file_path, f'r{"b" if binary else ""}') as opened_file:
return opened_file.read()
except (OSError, IOError) as error:
mod, lineno = get_traceback_caller(*sys.exc_info())

@ -1,92 +1,135 @@
import os
import hashlib
from calculate.utils.files import Process, write_file, read_file
# import hashlib
from calculate.utils.files import Process, join_paths
from calculate.templates.format.base_format import FormatError
from typing import NoReturn, Union, List, Tuple, Optional
class ImageMagickError(Exception):
class ImageMagickError(FormatError):
pass
class ImageMagick:
def __init__(self, prefix='/'):
self.prefix = prefix
self.init_commands(prefix)
self.default_opts = []
def __init__(self, chroot: str = '/'):
self._chroot_path: str = chroot
self.init_commands(chroot)
self.default_opts: List[str] = []
@property
def available(self):
return self.convert_cmd and self.identify_cmd
def available(self) -> bool:
return bool(self.convert_cmd and self.identify_cmd)
@property
def chroot(self):
return self.prefix != '/'
def init_commands(self, prefix):
self.convert_cmd = "/usr/bin/convert"
self.identify_cmd = "/usr/bin/identify"
self.chroot_cmd = "/bin/chroot"
self.bash_cmd = "/bin/bash"
if not os.path.exists(os.path.join(prefix, self.convert_cmd[1:])):
def chrooted(self) -> bool:
return self._chroot_path != '/'
def init_commands(self, chroot: str) -> NoReturn:
self.convert_cmd: str = "/usr/bin/convert"
self.identify_cmd: str = "/usr/bin/identify"
self.chroot_cmd: str = "/bin/chroot"
self.bash_cmd: str = "/bin/bash"
if not os.path.exists(join_paths(chroot, self.convert_cmd)):
self.convert_cmd = None
if not os.path.exists(os.path.join(prefix, self.identify_cmd[1:])):
if not os.path.exists(join_paths(chroot, self.identify_cmd)):
self.identify_cmd = None
def trim_prefix_path(self, filename):
retpath = "/%s" % os.path.relpath(filename, self.prefix)
def trim_prefix_path(self, filename: str) -> Union[str, None]:
retpath = "/{}".format(os.path.relpath(filename, self._chroot_path))
if retpath.startswith("/.."):
return None
return retpath
def get_image_resolution(self, source):
if self.chroot:
identify = Process(self.chroot_cmd, self.prefix,
def get_image_resolution(self, source: str
) -> Union[None, Tuple[int, int]]:
'''Метод для получения разрешения указанного изображения,
с помощью команды 'identify -format %w %h <path_to_image>'.'''
print(f"SOURCE: {source}")
if self.chrooted:
print(f"CHROOT_PATH: {self._chroot_path}")
identify = Process(self.chroot_cmd, self._chroot_path,
self.bash_cmd, "-c",
" ".join([self.identify_cmd,
"-format '%w %h'", source]))
else:
identify = Process(self.identify_cmd, "-format", "%w %h", source)
if identify.success():
swidth, _sep, sheight = identify.read().strip().partition(" ")
result = identify.read()
swidth, sheight = result.split(" ")
if swidth.isdigit() and sheight.isdigit():
return int(swidth), int(sheight)
return None
else:
raise ImageMagickError(f"ERROR: can not parse: '{result}'")
else:
raise ImageMagickError(f"ERROR: {identify.read_error()}")
def convert(self, source, target, *opts):
def convert(self, source: str, target: str, *opts: List[str],
image_format: Optional[str] = None) -> bool:
command = [self.convert_cmd, "-quality", "95",
source]
command.extend(self.default_opts)
command.extend(opts)
command.append(target)
if self.chroot:
convert = Process(self.chroot_cmd, self.prefix,
if image_format is not None:
command.append(f"{image_format}:{target}")
else:
command.append(target)
if self.chrooted:
convert = Process(self.chroot_cmd, self._chroot_path,
self.bash_cmd, "-c",
" ".join(command))
else:
print("OPTIONS:")
print(command)
convert = Process(*command)
if convert.success():
print("CREATED: {}".format(target))
return True
else:
print(convert.read_error())
print("ERROR:", convert.read_error())
return False
def convert_resize_crop_center(self, source, target, height, width):
#if ((width == self.source_width and height == self.source_height) and
# (source.rpartition('.')[2] == target.rpartition('.')[2])):
# with write_file(target) as sf:
# sf.write(read_file(source))
# return True
res = "%dx%d" % (width, height)
def convert_resize_crop_center(self, source: str, target: str,
height: int, width: int,
image_format: Optional[str] = None) -> bool:
# if ((width == self.source_width and height == self.source_height) and
# (source.rpartition('.')[2] == target.rpartition('.')[2])):
# with write_file(target) as sf:
# sf.write(read_file(source))
# return True
res = f"{width}x{height}"
return self.convert(source, target, "-quality", "95",
"-resize", f"{res}^",
"-strip", "-gravity", "center",
"-crop", f"{res}+0+0",
image_format=image_format)
def convert_resize_gfxboot(self, source: str, target: str,
height: int, width: int,
image_format: Optional[str] = None) -> bool:
res = f"{width}x{height}"
return self.convert(source, target, "-quality", "95",
"-resize", "%s^" % res,
"-strip", "-gravity", "center",
"-crop", "%s+0+0" % res)
"-resize", f"{res}^",
"-strip", "-gravity", "center",
"-crop", f"{res}+0+0",
"-sampling-factor", "2x2",
"-interlace", "none",
"-set", "units", "PixelsPerSecond",
image_format=image_format)
def convert_resize_gfxboot(self, source, target, height, width):
res = "%dx%d" % (width, height)
def get_image_format(self, source: str) -> str:
"""Метод для получения формата указанного файла."""
if self.chrooted:
identify = Process(self.chroot_cmd, self._chroot_path,
self.bash_cmd, "-c",
" ".join([self.identify_cmd,
"-format '%m'", source]))
else:
identify = Process(self.identify_cmd, "-format", "%m", source)
return self.convert(source, target, "-quality", "95",
"-resize", "%s^" % res,
"-strip", "-gravity", "center",
"-crop", "%s+0+0" % res,
"-sampling-factor", "2x2",
"-interlace", "none",
"-set", "units", "PixelsPerSecond")
if identify.success():
image_format = identify.read()
return image_format
else:
raise ImageMagickError(f"ERROR: {identify.read_error()}")

@ -1032,7 +1032,7 @@ class Package(metaclass=PackageCreator):
if file_md5 is None:
try:
file_text = read_file(real_path).encode()
file_text = read_file(real_path, binary=True)
except FilesError as error:
raise PackageError(str(error))
file_md5 = hashlib.md5(file_text).hexdigest()

@ -1,6 +1,14 @@
from calculate.variables.datavars import (Variable, Namespace, Dependence,
StringType, BooleanType, HashType,
ListType, Calculate, Copy)
from calculate.variables.datavars import (
Variable,
Namespace,
Dependence,
StringType,
BooleanType,
HashType,
ListType,
Calculate,
Copy
)
from calculate.vars.main.os.func import get_arch_gentoo
from calculate.vars.install.os.func import (get_audio_selected,
get_available_audio_system)
@ -10,6 +18,7 @@ def import_variables():
with Namespace("arch"):
Variable("machine", type=StringType,
source=Copy("main.os.arch.machine"))
Variable("gentoo", type=StringType,
source=Calculate(get_arch_gentoo, ".machine"))
@ -17,6 +26,7 @@ def import_variables():
Variable("available", type=ListType,
source=Calculate(
get_available_audio_system))
Variable("selected", type=StringType,
source=Calculate(
get_audio_selected,
@ -26,12 +36,24 @@ def import_variables():
Variable("subsystem", type=StringType, source=Copy("main.os.subsystem"))
with Namespace("container"):
Variable("type", type=StringType, source=Copy("main.os.container.type"))
Variable("type", type=StringType,
source=Copy("main.os.container.type"))
with Namespace("linux"):
Variable("shortname", type=StringType, source=Copy("main.os.linux.shortname"))
Variable("name", type=StringType, source=Copy("main.os.linux.name"))
Variable("subname", type=StringType, source=Copy("main.os.linux.subname"))
Variable("system", type=StringType, source=Copy("main.os.linux.system"))
Variable("ver", type=StringType, source=Copy("main.os.linux.ver"))
Variable("build", type=StringType, source=Copy("main.os.linux.build"))
Variable("shortname", type=StringType,
source=Copy("main.os.linux.shortname"))
Variable("name", type=StringType,
source=Copy("main.os.linux.name"))
Variable("subname", type=StringType,
source=Copy("main.os.linux.subname"))
Variable("system", type=StringType,
source=Copy("main.os.linux.system"))
Variable("ver", type=StringType,
source=Copy("main.os.linux.ver"))
Variable("build", type=StringType,
source=Copy("main.os.linux.build"))

@ -1,5 +1,6 @@
from calculate.utils.package import PackageAtomParser
def get_available_audio_system():
audio_systems = (
('alsa', None),
@ -12,6 +13,7 @@ def get_available_audio_system():
if pkg is None or package_db.is_package_exists(pkg)
]
def get_audio_selected(available_systems, cmdline_audio):
available_systems = available_systems.value

@ -26,3 +26,12 @@ Variable('cl_config_archive', type=StringType.readonly,
Variable('cl_exec_dir_path', type=StringType.readonly,
source='/var/lib/calculate/.execute')
Variable('cl_resolutions', type=ListType.readonly,
source=["1680x1050", "1024x768"])
Variable('cl_resolutions', type=ListType.readonly,
source=["1680x1050", "1024x768"])
Variable('cl_image_formats', type=ListType.readonly,
source=["GIF", "JPG", "JPEG", "PNG", "GFXBOOT"])

@ -9,6 +9,7 @@ from calculate.variables.datavars import (
Calculate
)
def get_ebuild_phase():
return os.environ.get("EBUILD_PHASE", "")
@ -105,9 +106,9 @@ def get_isoscan_fullpath(base_path, filename):
def import_variables():
Variable("chroot_path", type=StringType,
source=Calculate(lambda x:x.value, "main.cl_chroot_path"))
source=Calculate(lambda x: x.value, "main.cl_chroot_path"))
Variable("root_path", type=StringType,
source=Calculate(lambda x:x.value, "main.cl_root_path"))
source=Calculate(lambda x: x.value, "main.cl_root_path"))
Variable("ebuild_phase", type=StringType,
source=Calculate(get_ebuild_phase))

@ -1,4 +1,3 @@
import os
from calculate.utils.fs import readFile
from calculate.variables.datavars import Variable, Namespace, Dependence, \
@ -6,6 +5,7 @@ from calculate.variables.datavars import Variable, Namespace, Dependence, \
from calculate.utils.files import Process, FilesError
import re
def get_resolution_by_xdpyinfo():
"""
Get resolution by xdpyinfo utility
@ -23,13 +23,17 @@ def get_resolution_by_xdpyinfo():
return "%sx%s" % (searchRes.group(1), searchRes.group(2))
return ""
def get_resolution_by_xorg():
pass
def get_standart_resolution():
# TODO: заглушка
return "1024x768"
with Namespace('resolution'):
Variable("standard", type=StringType,
source=Calculate(get_standart_resolution))
def import_variables():
with Namespace('resolution'):
Variable("standard", type=StringType,
source=Calculate(get_standart_resolution))

@ -7,13 +7,16 @@ from calculate.variables.datavars import (
TableType
)
def get_repository_table():
return [
{'name':'gentoo',
'url': 'git://git.calculate-linux.org/calculate/gentoo-overlay.git'},
{'name':'calculate',
'url': 'git://git.calculate-linux.org/calculate/calculate-overlay.git'},
]
return [{'name': 'gentoo',
'url':
'git://git.calculate-linux.org/calculate/gentoo-overlay.git'},
{'name': 'calculate',
'url':
'git://git.calculate-linux.org/calculate/calculate-overlay.git'},
]
def import_variables():
Variable('repositories', type=TableType,

@ -163,18 +163,46 @@ def DictionariesWithoutSections():
return (OriginalDictionary, TemplateDictionary, ResultDictionary)
import pytest
def pytest_addoption(parser):
parser.addoption(
"--chroot-test", action="store_true", default=False, help="run chroot tests"
"--chroot-test",
action="store_true",
default=False,
help="run chroot tests"
)
parser.addoption(
"--backgrounds-slow",
action="store_true",
default=False,
help="run slow backgrounds tests."
)
parser.addoption(
"--from-root",
action="store_true",
default=False,
help="run tests that needs root rights."
)
def pytest_collection_modifyitems(config, items):
if config.getoption("--chroot-test"):
return
skip_chroot = pytest.mark.skip(reason="need --chroot option to run")
for item in items:
if "chroot" in item.keywords:
item.add_marker(skip_chroot)
if not config.getoption("--chroot-test"):
skip_chroot = pytest.mark.skip(
reason="need --chroot-test option to run")
for item in items:
if "chroot" in item.keywords:
item.add_marker(skip_chroot)
if not config.getoption("--backgrounds-slow"):
skip_backgrounds = pytest.mark.skip(
reason="need --backgrounds-slow option to run")
for item in items:
if "backgrounds_slow" in item.keywords:
item.add_marker(skip_backgrounds)
if not config.getoption("--from-root"):
skip_from_root = pytest.mark.skip(
reason="need --from-root option to run")
for item in items:
if "needs_root" in item.keywords:
item.add_marker(skip_from_root)

@ -3,6 +3,8 @@
[pytest]
markers =
base: marker for running tests for base format class.
backgrounds: marker for running tests for backgrounds format.
backgrounds_slow: marker for running slow tests for backgrounds format.
bind: marker for running tests for bind format.
compiz: marker for running tests for compiz format.
contents: marker for running tests for contents format.
@ -44,3 +46,4 @@ markers =
server: marker for testing of the server.
chroot: marker for testing running by chroot
needs_root: marker for tests that needs root rights.

@ -0,0 +1,331 @@
import os
import pytest
import shutil
from calculate.templates.format.base_format import FormatError
from calculate.templates.format.backgrounds_format import BackgroundsFormat
from calculate.templates.template_engine import ParametersContainer
from calculate.variables.datavars import NamespaceNode, VariableNode, ListType
datavars = NamespaceNode("<root>")
datavars.add_namespace(NamespaceNode("main"))
VariableNode("cl_resolutions", datavars.main, ListType,
source=["1024x768"])
TMP_BACKUP_DIR = 'tests/templates/format/testfiles/backgrounds/tmp.backup'
TMP_DIR = 'tests/templates/format/testfiles/backgrounds/tmp'
@pytest.mark.backgrounds
def test_make_tmp_dir():
if not os.path.exists(TMP_DIR):
shutil.copytree(TMP_BACKUP_DIR, TMP_DIR)
@pytest.mark.backgrounds
@pytest.mark.parametrize('case', [
{
"id": "PNG -> PNG",
"source":
'tests/templates/format/testfiles/backgrounds/picture_0.png',
"convert": False,
"stretch": True,
"target": 'result_0-',
"template_text": ("original\n1024x768"),
"result": ("PNG", ["32x16", "1024x768"]),
},
{
"id": "JPEG -> JPEG",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": False,
"stretch": True,
"target": 'result_1-',
"template_text": ("original\n1024x768"),
"result": ("JPEG", ["320x180", "1024x768"]),
},
{
"id": "PNG -> JPEG",
"source":
'tests/templates/format/testfiles/backgrounds/picture_0.png',
"convert": "JPEG",
"stretch": True,
"target": "result_2-",
"template_text": ("320x160\n1024x768"),
"result": ("JPEG", ["320x160", "1024x768"]),
},
{
"id": "PNG -> GIF",
"source":
'tests/templates/format/testfiles/backgrounds/picture_0.png',
"convert": "GIF",
"stretch": True,
"target": "result_3-",
"template_text": ("320x160\n1024x768"),
"result": ("GIF", ["320x160", "1024x768"]),
},
pytest.param(
{
"id": "JPEG -> PNG",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": "PNG",
"stretch": True,
"target": "result_4-",
"template_text": ("640x360\n1024x768"),
"result": ("PNG", ["640x360", "1024x768"]),
},
marks=pytest.mark.backgrounds_slow
),
{
"id": "JPEG -> GIF",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": "GIF",
"stretch": True,
"target": "result_5-",
"template_text": ("original\n1024x768"),
"result": ("GIF", ["320x180", "1024x768"]),
},
{
"id": "JPEG -> GFXBOOT(JPEG)",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": "GFXBOOT",
"stretch": False,
"target": "result_6-",
"template_text": ("original\n200x100"),
"result": ("JPEG", ["320x180", "200x100"]),
},
{
"id": "PNG -> JPEG (single from template)",
"source":
'tests/templates/format/testfiles/backgrounds/picture_0.png',
"convert": "JPEG",
"stretch": True,
"target": "result_7",
"template_text": ("320x160"),
"result": ("JPEG", ["320x160"]),
},
{
"id": "JPEG -> JPEG (single from variable)",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": False,
"stretch": True,
"target": "result_8-",
"template_text": "",
"result": ("JPEG", ["1024x768"]),
},
],
ids=lambda x: x["id"])
def test_resize_and_convert_of_different_formats(case):
target_path = os.path.join(TMP_DIR, case["target"])
parameters = ParametersContainer({'source': case["source"],
'convert': case["convert"],
'stretch': case["stretch"]})
backgrounds_object = BackgroundsFormat(case["template_text"],
"path/to/template",
parameters, datavars)
changed_files = backgrounds_object.execute_format(target_path)
images_format, resolutions = case["result"]
output_paths = []
if len(resolutions) > 1 or not case['template_text']:
for resolution in resolutions:
output_paths.append("{}{}.{}".format(target_path, resolution,
images_format.lower()))
elif len(resolutions) == 1:
resolution = next(iter(resolutions))
output_paths.append(target_path)
for outpath in output_paths:
assert os.path.exists(outpath)
assert backgrounds_object._magician.get_image_format(outpath
) == images_format
# print("CHANGED FILES:")
# for path, status in changed_files.items():
# print(f"{path} -> {status}")
# assert False
@pytest.mark.backgrounds
@pytest.mark.parametrize('case', [
{
"id": "stretch off",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": False,
"stretch": False,
"target": 'result_8-',
"template_text": ("400x200\n30x10"),
"result": {"400x200": False, "30x10": True},
},
{
"id": "stretch on",
"source":
'tests/templates/format/testfiles/backgrounds/picture_1.jpg',
"convert": False,
"stretch": True,
"target": 'result_9-',
"template_text": ("400x200\n30x10"),
"result": {"400x200": True, "30x10": True},
},
],
ids=lambda x: x["id"])
def test_scretch_parameter(case):
target_path = os.path.join(TMP_DIR, case["target"])
parameters = ParametersContainer({'source': case["source"],
'convert': case["convert"],
'stretch': case["stretch"]})
backgrounds_object = BackgroundsFormat(case["template_text"],
"path/to/template",
parameters, datavars)
changed_files = backgrounds_object.execute_format(target_path)
for resolution, result in case["result"].items():
image_path = "{}{}.jpeg".format(target_path, resolution)
assert os.path.exists(image_path) is result
if result:
width, height = resolution.split("x")
assert backgrounds_object._magician.get_image_resolution(
image_path) == (
int(width),
int(height))
# @pytest.mark.backgrounds
# def test_to_create():
# target_path = os.path.join(TMP_BACKUP_DIR, "result_13-")
# source_path = 'tests/templates/format/testfiles/backgrounds/picture_2.png'
# parameters = ParametersContainer({'source': source_path,
# 'convert': False,
# 'stretch': False})
# backgrounds_object = BackgroundsFormat("1650x1050\n1024x768",
# "path/to/template",
# parameters, datavars)
# backgrounds_object.execute_format(target_path)
@pytest.mark.backgrounds
def test_if_template_has_been_already_used__it_will_not_be_reused():
target_path = os.path.join(TMP_DIR, "result_10-")
source_path = 'tests/templates/format/testfiles/backgrounds/picture_2.png'
parameters = ParametersContainer({'source': source_path,
'convert': False,
'stretch': False})
backgrounds_object = BackgroundsFormat("1650x1050\n1024x768",
"path/to/template",
parameters, datavars)
changed_files = backgrounds_object.execute_format(target_path)
assert not changed_files
@pytest.mark.backgrounds_slow
@pytest.mark.backgrounds
def test_if_template_is_using_in_directory_but_other_template_has_been_already_used_for_the_same_image_name__latest_template_will_remove_old_images_and_md5sum_file_and_create_new_ones():
target_path = os.path.join(TMP_DIR, "result_11-")
source_path = 'tests/templates/format/testfiles/backgrounds/picture_2.png'
parameters = ParametersContainer({'source': source_path,
'convert': False,
'stretch': False})
backgrounds_object = BackgroundsFormat("1440x1080\n1280x960",
"path/to/template",
parameters, datavars)
changed_files = backgrounds_object.execute_format(target_path)
assert not os.path.exists(f"{target_path}1650x1050.png")
assert os.path.exists(f"{target_path}1440x1080.png")
assert os.path.exists(f"{target_path}1280x960.png")
assert not os.path.exists(f"{target_path}1024x768.png")
assert changed_files == {f"{target_path}1650x1050.png": "D",
f"{target_path}1024x768.png": "D",
f"{target_path.strip('_-.')}.md5sum": "M",
f"{target_path}1440x1080.png": "N",
f"{target_path}1280x960.png": "N"}
@pytest.mark.backgrounds_slow
@pytest.mark.backgrounds
def test_if_template_is_using_in_directory_which_contains_md5sum_file_without_any_images__the_template_will_remove_md5sum_file_and_create_new_one_and_images():
target_path = os.path.join(TMP_DIR, "result_12-")
source_path = 'tests/templates/format/testfiles/backgrounds/picture_2.png'
parameters = ParametersContainer({'source': source_path,
'convert': False,
'stretch': False})
backgrounds_object = BackgroundsFormat("1440x1080\n1280x960",
"path/to/template",
parameters, datavars)
changed_files = backgrounds_object.execute_format(target_path)
assert os.path.exists(f"{target_path}1440x1080.png")
assert os.path.exists(f"{target_path}1280x960.png")
assert changed_files == {f"{target_path.strip('_-.')}.md5sum": "M",
f"{target_path}1440x1080.png": "N",
f"{target_path}1280x960.png": "N"}
@pytest.mark.backgrounds_slow
@pytest.mark.backgrounds
def test_if_template_is_using_in_directory_which_contains_some_image_files_without_md5sum_file__the_template_will_remove_all_images_and_create_new_ones_and_new_md5sum_file():
target_path = os.path.join(TMP_DIR, "result_13-")
source_path = 'tests/templates/format/testfiles/backgrounds/picture_2.png'
parameters = ParametersContainer({'source': source_path,
'convert': False,
'stretch': False})
backgrounds_object = BackgroundsFormat("1440x1080\n1280x960",
"path/to/template",
parameters, datavars)
changed_files = backgrounds_object.execute_format(target_path)
assert not os.path.exists(f"{target_path}1650x1050.png")
assert os.path.exists(f"{target_path}1440x1080.png")
assert os.path.exists(f"{target_path}1280x960.png")
assert not os.path.exists(f"{target_path}1024x768.png")
assert changed_files == {f"{target_path}1650x1050.png": "D",
f"{target_path}1024x768.png": "D",
f"{target_path.strip('_-.')}.md5sum": "N",
f"{target_path}1440x1080.png": "N",
f"{target_path}1280x960.png": "N"}
@pytest.mark.backgrounds
def test_if_template_s_name_parameter_is_the_empty_line_and_its_text_contents_two_or_more_resolution_values__the_output_files_name_will_contains_its_resolutions_only():
target_path = os.path.join(TMP_DIR, "result_14-")
source_path = 'tests/templates/format/testfiles/backgrounds/picture_1.jpg'
parameters = ParametersContainer({'source': source_path,
'convert': False,
'stretch': False,
'name': ''})
backgrounds_object = BackgroundsFormat("100x100\n60x60",
"path/to/template",
parameters, datavars)
backgrounds_object.execute_format(target_path)
assert os.path.exists(os.path.join(TMP_DIR, "100x100.jpeg"))
assert os.path.exists(os.path.join(TMP_DIR, "60x60.jpeg"))
assert os.path.exists(os.path.join(TMP_DIR, "md5sum"))
@pytest.mark.backgrounds
def test_if_template_s_name_parameter_is_the_empty_line_and_its_text_contents_one_resolution_value__the_format_will_raise_FormatError_exception():
target_path = os.path.join(TMP_DIR, "result_15-")
source_path = 'tests/templates/format/testfiles/backgrounds/picture_1.jpg'
parameters = ParametersContainer({'source': source_path,
'convert': False,
'stretch': False,
'name': ''})
backgrounds_object = BackgroundsFormat("100x100",
"path/to/template",
parameters, datavars)
with pytest.raises(FormatError):
backgrounds_object.execute_format(target_path)
@pytest.mark.backgrounds
def test_remove_tmp():
if os.path.exists(TMP_DIR):
shutil.rmtree(TMP_DIR)

@ -3,6 +3,8 @@ import pytest
import shutil
from calculate.templates.format.contents_format import ContentsFormat
from calculate.utils.package import PackageAtomName, Version, Package
from calculate.templates.template_engine import ParametersContainer
from calculate.variables.datavars import NamespaceNode
from calculate.utils.files import join_paths
@ -54,7 +56,9 @@ sym /etc/dir_3/link_0 -> ../dir_1/file_0 {2}
int(os.lstat(join_paths(chroot_path,
'/etc/dir_3/link_0')).st_mtime))
contents_format = ContentsFormat(template_text, '/path/to/template')
contents_format = ContentsFormat(template_text, '/path/to/template',
ParametersContainer(),
NamespaceNode())
contents_format.execute_format('target/path', chroot_path=chroot_path)
test_package = Package(test_package_name,
@ -101,7 +105,9 @@ dir /etc/dir_1
dir /etc/dir_3
'''
contents_format = ContentsFormat(template_text, '/path/to/template')
contents_format = ContentsFormat(template_text, '/path/to/template',
ParametersContainer(),
NamespaceNode())
contents_format.execute_format('target/path', chroot_path=chroot_path)
test_package = Package(test_package_name,
@ -177,7 +183,9 @@ dir /etc/dir_5
sym /etc/dir_5/link_0 -> ../dir_1/file_0 1601991426
'''
contents_format = ContentsFormat(template_text, '/path/to/template')
contents_format = ContentsFormat(template_text, '/path/to/template',
ParametersContainer(),
NamespaceNode())
contents_format.execute_format('target/path', chroot_path=chroot_path)
test_package = Package(test_package_name,

@ -1,6 +1,8 @@
import pytest
import shutil
from calculate.templates.format.patch_format import PatchFormat
from calculate.templates.template_engine import ParametersContainer
from calculate.variables.datavars import NamespaceNode
from calculate.utils.files import Process
from os import path
import os
@ -31,7 +33,9 @@ class TestExecuteMethods:
with open(path.join(cwd_path, 'diff_1.patch')) as patch_file:
patch_text = patch_file.read()
diff_patch = PatchFormat(patch_text, '/template/path')
diff_patch = PatchFormat(patch_text, '/template/path',
ParametersContainer(),
NamespaceNode("<root>"))
output = diff_patch.execute_format(target_path=cwd_path)
if output:
for changed_file, change_type in diff_patch.changed_files.items():
@ -62,7 +66,9 @@ class TestExecuteMethods:
with open(path.join(patch_path)) as patch_file:
patch_text = patch_file.read()
diff_patch = PatchFormat(patch_text, '/template/path')
diff_patch = PatchFormat(patch_text, '/template/path',
ParametersContainer(),
NamespaceNode("<root>"))
diff_patch.execute_format(target_path=cwd_path)
for changed_file, change_type in diff_patch.changed_files.items():

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,2 @@
{% calculate append = "skip", action = "install",
package = "test-category/test-package" %}

@ -0,0 +1,3 @@
{% calculate format = "backgrounds", source = "/etc/picture.jpg", name = "image-" %}
original
300x100

@ -0,0 +1,2 @@
{% calculate append = "skip", action = "install",
package = "test-category/test-package" %}

@ -0,0 +1,4 @@
{% calculate format = "backgrounds", source = "/etc/picture.jpg", name = "image-",
stretch %}
666x333
220x130

@ -0,0 +1,2 @@
{% calculate append = "skip", action = "install",
package = "test-category/test-package" %}

@ -0,0 +1,3 @@
{% calculate format = "backgrounds", source = "/etc/picture.jpg", name = "image.png",
stretch, convert = "png" %}
300x200

@ -0,0 +1,2 @@
{% calculate append = "skip", action = "install",
package = "test-category/test-package" %}

@ -0,0 +1,4 @@
{% calculate format = "backgrounds", source = "/etc/picture.jpg", name = "image-",
stretch %}
original
300x100

@ -3,20 +3,20 @@ from calculate.utils.images import ImageMagick, ImageMagickError
import os
from calculate.utils.files import Process
@pytest.mark.images_utils
def test_imagemagick_initialization():
im = ImageMagick()
assert im.available
assert not im.chroot
assert not im.chrooted
im = ImageMagick(prefix='/mnt/somepath')
im = ImageMagick(chroot='/mnt/somepath')
assert not im.available
assert im.chroot
assert im.chrooted
@pytest.mark.images_utils
@pytest.mark.parametrize('case',
[
@pytest.mark.parametrize('case', [
{
"name": "simple path",
"chroot": "/mnt/install",
@ -42,23 +42,23 @@ def test_imagemagick_initialization():
"result": None,
},
],
ids=lambda x:x["name"])
ids=lambda x: x["name"])
def test_imagemagick_trim_prefix_path(case):
im = ImageMagick(case['chroot'])
assert im.trim_prefix_path(case["source"]) == case["result"]
@pytest.mark.images_utils
@pytest.mark.parametrize('case',
[
@pytest.mark.parametrize('case', [
{
"name": "PNG file",
"image": "tests/utils/testfiles/file.png",
"result": (48,48),
"result": (48, 48),
},
{
"name": "JPEG file",
"image": "tests/utils/testfiles/file.jpg",
"result": (320,180),
"result": (320, 180),
},
{
"name": "No file",
@ -71,10 +71,48 @@ def test_imagemagick_trim_prefix_path(case):
"result": None,
},
],
ids=lambda x:x["name"])
ids=lambda x: x["name"])
def test_imagemagick_get_resolutions(case):
im = ImageMagick()
assert im.get_image_resolution(case["image"]) == case["result"]
if case["result"] is None:
with pytest.raises(ImageMagickError):
im.get_image_resolution(case["image"])
else:
assert im.get_image_resolution(case["image"]) == case["result"]
@pytest.mark.images_utils
@pytest.mark.parametrize('case', [
{
"name": "PNG file",
"image": "tests/utils/testfiles/file.png",
"result": "PNG",
},
{
"name": "JPEG file",
"image": "tests/utils/testfiles/file.jpg",
"result": "JPEG",
},
{
"name": "No file",
"image": "tests/utils/testfiles/file2.jpg",
"result": None,
},
{
"name": "Wrong file",
"image": "tests/utils/testfiles/wrong.jpg",
"result": None,
},
],
ids=lambda x: x["name"])
def test_imagemagick_get_image_format(case):
im = ImageMagick()
if case["result"] is None:
with pytest.raises(ImageMagickError):
im.get_image_format(case["image"])
else:
assert im.get_image_format(case["image"]) == case["result"]
@pytest.fixture
def chroot_test():
@ -88,29 +126,37 @@ def chroot_test():
finally:
os.unlink(chrootpath)
@pytest.mark.chroot
@pytest.mark.images_utils
def test_chroot_imagemagick_get_resolution(chroot_test):
im = ImageMagick(chroot_test)
curpath = os.getcwd()
image_path = os.path.join(curpath, "tests/utils/testfiles/file.png")
assert im.get_image_resolution(image_path) == (48,48)
assert im.get_image_resolution(image_path) == (48, 48)
def get_histogram(image, remap_image):
p = Process("/usr/bin/convert", image, "-remap", remap_image, "-format", "%c", "histogram:info:-")
p = Process("/usr/bin/convert", image, "-remap",
remap_image, "-format", "%c", "histogram:info:-")
return p.read()
def get_verbose_image_info(image):
p = Process("/usr/bin/identify", "-verbose", image)
return p.read()
def discard_space(x: str) -> str:
return x.replace(" ", "").replace("\n", "")
@pytest.mark.images_utils
@pytest.mark.parametrize('case',
[
@pytest.mark.parametrize('case', [
{
# проверка, пропорционального уменьшения
"name": "Origin test",
"resize": (16,32),
"resize": (16, 32),
"result": """192: (0,0,0) #000000 black
64: (0,255,0) #00FF00 lime
256: (255,255,255) #FFFFFF white"""
@ -120,22 +166,23 @@ def get_verbose_image_info(image):
# удаляются только части изображения справа и слева
# в исходном изображении на этих частях находится белый фон
"name": "Shrink horizontal",
"resize": (16,16),
"resize": (16, 16),
"result": """192: (0,0,0) #000000 black
64: (0,255,0) #00FF00 lime"""
},
{
# проверка, что при уменьшении изображения первоначально оно сдавливается
# по вертикали а затем обрезаются части слева и справа
# проверка, что при уменьшении изображения первоначально оно
# сдавливается # по вертикали а затем обрезаются части слева и
# справа
"name": "Shrink all",
"resize": (8,8),
"resize": (8, 8),
"result": """48: (0,0,0) #000000 black
16: (0,255,0) #00FF00 lime"""
},
{
# проверка, пропорционального уменьшения
"name": "Shrink proportionately",
"resize": (8,16),
"resize": (8, 16),
"result": """48: (0,0,0) #000000 black
16: (0,255,0) #00FF00 lime
64: (255,255,255) #FFFFFF white"""
@ -143,7 +190,7 @@ def get_verbose_image_info(image):
{
# проверка, пропорционального увеличения
"name": "Increase size proportionately",
"resize": (32,64),
"resize": (32, 64),
"result": """768: (0,0,0) #000000 black
256: (0,255,0) #00FF00 lime
1024: (255,255,255) #FFFFFF white"""
@ -151,7 +198,7 @@ def get_verbose_image_info(image):
{
# проверка увеличения и обрезки по горизонтали
"name": "Increase size and cut",
"resize": (32,32),
"resize": (32, 32),
"result": """768: (0,0,0) #000000 black
256: (0,255,0) #00FF00 lime"""
},
@ -160,24 +207,27 @@ def get_verbose_image_info(image):
# в этом случае будет отрезан верх и низ
# поэтому на выходе нет зелёного цвета
"name": "Increase horizontal size",
"resize": (16,48),
"resize": (16, 48),
"result": """384: (0,0,0) #000000 gray(0)
384: (255,255,255) #FFFFFF gray(255)"""
},
],
ids=lambda x:x["name"])
],
ids=lambda x: x["name"])
def test_imagemagick_convert(case):
image_path = "tests/utils/testfiles/origin.png"
output_file = "tests/utils/testfiles/test_output5.png"
if os.path.exists(output_file):
os.unlink(output_file)
im = ImageMagick()
im.default_opts = ["-filter","box"]
assert im.convert_resize_crop_center(image_path, output_file, *case["resize"])
im.default_opts = ["-filter", "box"]
assert im.convert_resize_crop_center(image_path, output_file,
*case["resize"])
histogram = get_histogram(output_file, image_path)
discard_space = lambda x: x.replace(" ","").replace("\n","")
assert discard_space(histogram) == discard_space(case["result"])
@pytest.mark.chroot
@pytest.mark.images_utils
def test_chroot_imagemagick_convert_center(chroot_test):
@ -191,12 +241,15 @@ def test_chroot_imagemagick_convert_center(chroot_test):
if os.path.exists(output_file):
os.unlink(output_file)
im = ImageMagick(chroot_test)
im.default_opts = ["-filter","box"]
im.default_opts = ["-filter", "box"]
assert im.convert_resize_crop_center(image_path, output_file, 8, 8)
histogram = get_histogram(output_file, image_path)
discard_space = lambda x: x.replace(" ","").replace("\n","")
assert discard_space(histogram) == discard_space(result)
@pytest.mark.images_utils
def test_imagemagick_convert_gfxboot():
output_file = "tests/utils/testfiles/test_output.jpg"
@ -205,11 +258,12 @@ def test_imagemagick_convert_gfxboot():
im.convert_resize_gfxboot(image_path, output_file, 32, 32)
assert "sampling-factor: 2x2" in get_verbose_image_info(output_file)
@pytest.mark.images_utils
def test_clear_imagemagick_convert():
for output_file in (
"tests/utils/testfiles/test_output.png",
"tests/utils/testfiles/test_output.jpg"
):
):
if os.path.exists(output_file):
os.unlink(output_file)

@ -74,8 +74,8 @@ class TestDatavars:
assert namespace_1._namespaces == dict()
assert namespace_1._variables == {'var_1': variable_1,
'var_2': variable_2,
'name': variable_3}
'var_2': variable_2,
'name': variable_3}
assert namespace_1.get_fullname() == 'namespace_1'
assert namespace_1.var_1 == 'value_1'

Loading…
Cancel
Save