Now template can be run without install package value.

master
Иванов Денис 3 年前
父节点 74e9ef8b9f
当前提交 c480831c59

@ -46,18 +46,18 @@ class Script:
в себе. Создает экземпляры лаунчеров.'''
def __init__(self, script_id: str,
args: List[Any] = [],
success_message: str = None,
failed_message: str = None,
interrupt_message: str = None):
success_message: Union[str, None] = None,
failed_message: Union[str, None] = None,
interrupt_message: Union[str, None] = None):
self._id: str = script_id
self.__items: List[Union['Task', 'Block', 'Handler', 'Run']] = None
self.__tasks_is_set: bool = False
self.__args: list = args
self.__success_message: str = success_message
self.__failed_message: str = failed_message
self.__interrupt_message: str = interrupt_message
self.__success_message: Union[str, None] = success_message
self.__failed_message: Union[str, None] = failed_message
self.__interrupt_message: Union[str, None] = interrupt_message
@property
def id(self) -> str:

@ -18,10 +18,13 @@ import copy
import re
import os
import stat
from typing import Union, Any, List
from ..utils.package import (
PackageAtomName,
PackageAtomParser,
PackageAtomError,
Package,
NOTEXIST,
Version
)
@ -123,48 +126,51 @@ class Variables(MutableMapping):
class ParametersProcessor:
'''Класс для проверки и разбора параметров шаблона.'''
available_parameters = {'name', 'path', 'append', 'chmod', 'chown',
'autoupdate', 'env', 'force', 'source', 'format',
'unbound', 'mirror', 'run', 'exec', 'env',
'package', 'merge', 'postmerge', 'action',
'rebuild', 'restart', 'stop', 'start', 'handler',
'notify', 'group'}
available_parameters: set = {'name', 'path', 'append', 'chmod', 'chown',
'autoupdate', 'env', 'force', 'source',
'format', 'unbound', 'mirror', 'run', 'exec',
'env', 'package', 'merge', 'postmerge',
'action', 'rebuild', 'restart', 'stop',
'start', 'handler', 'notify', 'group'}
inheritable_parameters = {'chmod', 'chown', 'autoupdate', 'env',
'package', 'action', 'handler', 'group'}
inheritable_parameters: set = {'chmod', 'chown', 'autoupdate', 'env',
'package', 'action', 'handler', 'group'}
# Параметры по умолчанию для файлов --
# будут заполняться из __datavars__
file_default_parameters = {}
file_default_parameters: dict = {}
# Параметры по умолчанию для директорий --
# будут заполняться из __datavars__
directory_default_parameters = {}
directory_default_parameters: dict = {}
available_appends = set()
available_appends: set = set()
available_formats = dict()
available_formats: dict = dict()
format_is_inspected = False
format_is_inspected: bool = False
chmod_value_regular = re.compile(
r'([r-][w-][x-])([r-][w-][x-])([r-][w-][x-])')
def __init__(self, parameters_container=None,
chroot_path='/',
datavars_module=Variables(),
for_package=None):
self.chroot_path = chroot_path
def __init__(self,
parameters_container: Union["ParametersContainer",
None] = None,
chroot_path: str = '/',
datavars_module: Union[Datavars, Variables] = Variables(),
for_package: Union[Package, None] = None):
self.chroot_path: str = chroot_path
self.template_type = DIR
self.template_type: int = DIR
self.datavars_module = datavars_module
self.datavars_module: Union[Datavars, Variables] = datavars_module
self._parameters_container = parameters_container
self._parameters_container: ParametersContainer = parameters_container
self.package_atom_parser = PackageAtomParser(chroot_path=chroot_path)
self.package_atom_parser: PackageAtomParser =\
PackageAtomParser(chroot_path=chroot_path)
self._groups = {}
self._groups: dict = {}
try:
groups = list(datavars_module.main.cl.groups._variables.keys())
for group in groups:
@ -179,7 +185,7 @@ class ParametersProcessor:
self._inspect_formats_package()
self._for_package = None
self._for_package: Union[Package, None] = for_package
# Если добавляемый параметр нуждается в проверке -- добавляем сюда
# метод для проверки.
@ -222,20 +228,22 @@ class ParametersProcessor:
# указываем здесь эти условия.
self.inherit_conditions = {'chmod': self.is_chmod_inheritable}
def set_parameters_container(self, parameters_container):
def set_parameters_container(self,
parameters_container: "ParametersContainer"
) -> None:
'''Метод для установки текущего контейнера параметров.'''
self._parameters_container = parameters_container
self._added_parameters = set()
@property
def for_package(self):
def for_package(self) -> Union[Package, None]:
return self._for_package
@for_package.setter
def for_package(self, package):
def for_package(self, package: Package):
self._for_package = package
def __getattr__(self, parameter_name):
def __getattr__(self, parameter_name: str) -> Any:
if parameter_name not in self.available_parameters:
raise IncorrectParameter("Unknown parameter: '{}'".
format(parameter_name))
@ -244,8 +252,9 @@ class ParametersProcessor:
else:
return self._parameters_container[parameter_name]
def check_template_parameter(self, parameter_name, parameter_value,
template_type, lineno):
def check_template_parameter(self, parameter_name: str,
parameter_value: Any,
template_type: int, lineno: int) -> None:
'''Метод, проверяющий указанный параметр.'''
self.lineno = lineno
self.template_type = template_type
@ -280,7 +289,7 @@ class ParametersProcessor:
self._parameters_container.set_parameter(
{parameter_name: checked_value})
def check_postparse_parameters(self):
def check_postparse_parameters(self) -> None:
'''Метод, запускающий проверку параметров после их разбора.'''
for parameter, parameter_checker in\
self.postparse_checkers_list.items():
@ -292,7 +301,8 @@ class ParametersProcessor:
self._parameters_container.change_parameter(parameter,
result)
def check_template_parameters(self, parameters, template_type, lineno):
def check_template_parameters(self, parameters: dict,
template_type: int, lineno: int) -> None:
'''Метод, запускающий проверку указанных параметров.'''
self.template_type = template_type
self.lineno = lineno
@ -326,14 +336,14 @@ class ParametersProcessor:
# Методы для проверки параметров во время разбора шаблона.
def check_package_parameter(self, parameter_value):
def check_package_parameter(self, parameter_value: Any) -> str:
if not isinstance(parameter_value, str):
raise IncorrectParameter("'package' parameter must have value of"
" the 'str' type, not"
f" {type(parameter_value)}")
return parameter_value
def check_group_parameter(self, parameter_value):
def check_group_parameter(self, parameter_value: Any) -> List[str]:
if isinstance(parameter_value, str):
result = [group.strip() for group in parameter_value.split(',')]
elif isinstance(parameter_value, (list, tuple)):
@ -350,13 +360,14 @@ class ParametersProcessor:
f" {', '.join(self._groups.keys())}")
return result
def check_append_parameter(self, parameter_value):
def check_append_parameter(self, parameter_value: Any) -> str:
if parameter_value not in self.available_appends:
raise IncorrectParameter("Unacceptable value '{}' of parameter"
" 'append'".format(parameter_value))
return parameter_value
def check_merge_parameter(self, parameter_value):
def check_merge_parameter(self, parameter_value: Any
) -> List[PackageAtomName]:
packages_list = []
packages_names = parameter_value.split(',')
@ -369,15 +380,15 @@ class ParametersProcessor:
return packages_list
def check_rebuild_parameter(self, parameter_value):
if isinstance(parameter_value, bool):
def check_rebuild_parameter(self, parameter_value: Any) -> bool:
if not isinstance(parameter_value, bool):
raise IncorrectParameter("'rebuild' parameter value is not bool")
elif 'package' not in self._parameters_container:
raise IncorrectParameter(("'rebuild' parameter is set without "
"'package' parameter"))
return parameter_value
def check_restart_parameter(self, parameter_value):
def check_restart_parameter(self, parameter_value: Any) -> str:
if parameter_value and isinstance(parameter_value, str):
return parameter_value
else:
@ -614,6 +625,8 @@ class ParametersProcessor:
" in handler templates.")
def check_postparse_package(self, parameter_value):
print('check package value:', parameter_value)
print('groups:', self._groups)
groups = []
package_atom = PackageAtomParser.parse_atom_name(parameter_value)
@ -637,18 +650,24 @@ class ParametersProcessor:
raise IncorrectParameter(error.message)
elif self._check_package_group(package_atom,
self._groups[group]):
self._parameters_container.remove_parameter('package')
print('successfully checked')
if (self._parameters_container is not None
and self._parameters_container.package):
self._parameters_container.remove_parameter('package')
return
raise ConditionFailed(f"package '{parameter_value}'"
" does not match the template condition",
self.lineno)
self.lineno if hasattr(self, 'lineno') else 0)
def _check_package_group(self, package: dict, group_packages: list):
'''Метод для проверки соответствия описания пакета, заданного словарем,
какому-либо описанию пакета, заданного в переменных groups.'''
print(f'CHECK\n{package}')
for group_package in group_packages:
print(f'WITH GROUP PACKAGE {group_package}')
for parameter in ['category', 'name', 'version', 'slot']:
print(f"checkin' {parameter}")
if package[parameter] is not None:
if (group_package[parameter] is None
or group_package[parameter] != package[parameter]):

@ -36,6 +36,14 @@ from calculate.variables.datavars import (
TableType,
VariableNotFoundError
)
from typing import (
Union,
Dict,
List,
Tuple,
Iterator,
Callable
)
from calculate.variables.loader import Datavars
from .format.base_format import Format
from ..utils.io_module import IOModule
@ -70,15 +78,15 @@ class TemplateCollisionError(Exception):
class CalculateConfigFile:
'''Класс для работы с файлом /var/lib/calculate/config.'''
def __init__(self, cl_config_path='/var/lib/calculate/config',
cl_chroot_path='/'):
self.chroot_path = cl_chroot_path
def __init__(self, cl_config_path: str = '/var/lib/calculate/config',
cl_chroot_path: str = '/'):
self.chroot_path: str = cl_chroot_path
self.cl_config_path = cl_config_path
self.cl_config_path: str = cl_config_path
self._config_dictionary = self._get_cl_config_dictionary()
self._config_dictionary: OrderedDict = self._get_cl_config_dictionary()
self._unsaved_changes = False
self._unsaved_changes: bool = False
def __contains__(self, file_path: str) -> bool:
file_path = self._remove_chroot(file_path)
@ -158,12 +166,13 @@ class TemplateWrapper:
'''Класс связывающий шаблон с целевым файлом и определяющий параметры
наложения шаблона, обусловленные состоянием целевого файла.'''
type_checks = {DIR: os.path.isdir,
FILE: os.path.isfile}
type_checks: Dict[int,
Callable[[str], bool]] = {DIR: os.path.isdir,
FILE: os.path.isfile}
_protected_is_set = False
_protected_set = set()
_unprotected_set = set()
_protected_is_set: bool = False
_protected_set: set = set()
_unprotected_set: set = set()
def __new__(cls, *args, **kwargs):
if not cls._protected_is_set:
@ -175,63 +184,64 @@ class TemplateWrapper:
cls._set_protected(chroot_path)
return super().__new__(cls)
def __init__(self, target_file_path,
parameters,
template_type,
template_path,
template_text='',
target_package=None,
chroot_path='/',
config_archive_path='/var/lib/calculate/config-archive',
dbpkg=True):
self.target_path = target_file_path
self.template_path = template_path
self.chroot_path = chroot_path
self.config_archive_path = config_archive_path
self.target_package_name = None
self.package_atom_parser = PackageAtomParser(
def __init__(
self, target_file_path: str,
parameters: ParametersContainer,
template_type: int,
template_path: str,
template_text: str = '',
target_package: Union[Package, None] = None,
chroot_path: str = '/',
config_archive_path: str = '/var/lib/calculate/config-archive',
dbpkg: bool = True):
self.target_path: str = target_file_path
self.template_path: str = template_path
self.chroot_path: str = chroot_path
self.config_archive_path: str = config_archive_path
self.target_package_name: Union[PackageAtomName, None] = None
self.package_atom_parser: PackageAtomParser = PackageAtomParser(
chroot_path=self.chroot_path)
# Вспомогательный флаг, включается, если по целевому пути лежит файл,
# для которого не определился никакой пакет.
self.target_without_package = False
self.target_without_package: bool = False
self.parameters = parameters
self.parameters: ParametersContainer = parameters
self.output_path = self.target_path
self.input_path = None
self.output_path: str = self.target_path
self.input_path: Union[str, None] = None
self.template_type = template_type
self.template_text = template_text
self.template_type: int = template_type
self.template_text: str = template_text
self.contents_matching = True
self.ca_is_missed = False
self.contents_matching: bool = True
self.ca_is_missed: bool = False
# Флаг, указывающий, что нужно удалить файл из target_path перед
# применением шаблона.
self.remove_original = False
self.remove_original: bool = False
# Флаг, указывающий, что целевой путь был изменен.
self.target_path_is_changed = False
self.target_path_is_changed: bool = False
# Флаг, указывающий, что файл по целевому пути является ссылкой.
self.target_is_link = False
self.target_is_link: bool = False
# Пакет, к которому относится файл.
self.target_package = target_package
self.target_package: Package = target_package
# Флаг, разрешающий работу с CONTENTS. Если False, то выключает
# protected для всех файлов блокирует все операции с CONTENTS и ._cfg.
self.dbpkg = dbpkg
self.dbpkg: bool = dbpkg
# Флаг, указывающий, что файл является PROTECTED.
self.protected = False
self.protected: bool = False
# Временный флаг для определения того, является ли шаблон userspace.
self.is_userspace = False
self.is_userspace: bool = False
self.format_class = None
self.format_class: Union[Format, None] = None
if self.parameters.run or self.parameters.exec:
# Если есть параметр run или exec, то кроме текста шаблона ничего
@ -410,8 +420,8 @@ class TemplateWrapper:
self.target_package_name = parameter_package
elif file_package != parameter_package and self.template_type != DIR:
target_name = self._check_packages_slots(parameter_package,
file_package)
target_name = self._compare_packages(parameter_package,
file_package)
if (target_name is not None and self.target_package is not None
and self.target_package.package_name == target_name):
target_name = self.target_package.package_name
@ -453,7 +463,10 @@ class TemplateWrapper:
self.target_package = Package(self.target_package_name,
chroot_path=self.chroot_path)
def _check_packages_slots(self, lpackage, rpackage):
def _compare_packages(self, lpackage: PackageAtomName,
rpackage: PackageAtomName
) -> Union[None, PackageAtomName]:
'''Метод, сравнивающий пакеты по их именам, возвращает старший.'''
if lpackage.category != rpackage.category:
return None
@ -606,7 +619,7 @@ class TemplateWrapper:
if self.template_type == DIR and self.target_package is not None:
self.target_package.clear_dir(self.target_path)
def add_to_contents(self, file_md5=None) -> None:
def add_to_contents(self, file_md5: Union[str, None] = None) -> None:
'''Метод для добавления целевого файла в CONTENTS.'''
if self.target_package is None:
return
@ -684,60 +697,65 @@ class TemplateWrapper:
class TemplateExecutor:
'''Класс исполнительного модуля.'''
def __init__(self, datavars_module=Variables(), chroot_path='/',
cl_config_archive='/var/lib/calculate/config-archive',
cl_config_path='/var/lib/calculate/config',
execute_archive_path='/var/lib/calculate/.execute/',
dbpkg=True):
def __init__(self,
datavars_module: Union[Datavars, Variables] = Variables(),
chroot_path: str = '/',
cl_config_archive: str = '/var/lib/calculate/config-archive',
cl_config_path: str = '/var/lib/calculate/config',
execute_archive_path: str = '/var/lib/calculate/.execute/',
dbpkg: bool = True):
# TODO добавить список измененных файлов.
self.datavars_module = datavars_module
self.datavars_module: Union[Datavars, Variables] = datavars_module
self.chroot_path = chroot_path
self.chroot_path: str = chroot_path
# Объект для проверки файловых систем. Пока не инициализируем.
self.mounts = None
self.mounts: Union[Mounts, None] = None
# Директория для хранения полученных при обработке exec скриптов.
self.execute_archive_path = execute_archive_path
self.execute_files = OrderedDict()
self.execute_archive_path: str = execute_archive_path
self.execute_files: OrderedDict = OrderedDict()
self.dbpkg = dbpkg
self.dbpkg: bool = dbpkg
# Словарь с измененными файлами и статусами их изменений.
self.changed_files = {}
self.changed_files: dict = {}
# Список целевых путей измененных файлов. Нужен для корректного
# формирования calculate-заголовка.
self.processed_targets = []
self.processed_targets: list = []
self.directory_default_parameters =\
# TODO разобраться с этим.
# Значения параметров по умолчанию, пока не используются.
self.directory_default_parameters: dict =\
ParametersProcessor.directory_default_parameters
self.file_default_parameters =\
self.file_default_parameters: dict =\
ParametersProcessor.file_default_parameters
# Отображение имен действий для директорий на методы их реализующие.
self.directory_appends = {'join': self._append_join_directory,
'remove': self._append_remove_directory,
'skip': self._append_skip_directory,
'clear': self._append_clear_directory,
'link': self._append_link_directory,
'replace': self._append_replace_directory}
self.directory_appends: dict = {
'join': self._append_join_directory,
'remove': self._append_remove_directory,
'skip': self._append_skip_directory,
'clear': self._append_clear_directory,
'link': self._append_link_directory,
'replace': self._append_replace_directory}
# Отображение имен действий для файлов на методы их реализующие.
self.file_appends = {'join': self._append_join_file,
'after': self._append_after_file,
'before': self._append_before_file,
'replace': self._append_replace_file,
'remove': self._append_remove_file,
'skip': self._append_skip_file,
'clear': self._append_clear_file,
'link': self._append_link_file}
self.formats_classes = ParametersProcessor.available_formats
self.calculate_config_file = CalculateConfigFile(
self.file_appends: dict = {'join': self._append_join_file,
'after': self._append_after_file,
'before': self._append_before_file,
'replace': self._append_replace_file,
'remove': self._append_remove_file,
'skip': self._append_skip_file,
'clear': self._append_clear_file,
'link': self._append_link_file}
self.formats_classes: set = ParametersProcessor.available_formats
self.calculate_config_file: CalculateConfigFile = CalculateConfigFile(
cl_config_path=cl_config_path,
cl_chroot_path=chroot_path)
self.cl_config_archive_path = cl_config_archive
self.cl_config_archive_path: str = cl_config_archive
Format.CALCULATE_VERSION = CALCULATE_VERSION
@property
@ -750,8 +768,9 @@ class TemplateExecutor:
def execute_template(self, target_path: str,
parameters: ParametersContainer, template_type: int,
template_path: str, template_text='',
save_changes=True, target_package=None) -> dict:
template_path: str, template_text: str = '',
save_changes: bool = True,
target_package: Union[Package, None] = None) -> dict:
'''Метод для запуска выполнения шаблонов.'''
# Словарь с данными о результате работы исполнительного метода.
self.executor_output = {'target_path': None,
@ -831,7 +850,7 @@ class TemplateExecutor:
return self.executor_output
def save_changes(self):
def save_changes(self) -> None:
'''Метод для сохранения чего-нибудь после выполнения всех шаблонов.'''
# Пока сохраняем только получившееся содержимое config-файла.
self.calculate_config_file.save_changes()
@ -851,8 +870,8 @@ class TemplateExecutor:
if self.dbpkg:
template_object.add_to_contents()
def _append_remove_directory(self,
template_object: TemplateWrapper) -> None:
def _append_remove_directory(self, template_object: TemplateWrapper
) -> None:
'''Метод описывающий действия для append = "remove", если шаблон --
директория. Удаляет директорию со всем содержимым, если она есть.'''
if template_object.target_type is not None:
@ -964,7 +983,8 @@ class TemplateExecutor:
template_object.clear_dir_contents()
def _append_join_file(self, template_object: TemplateWrapper,
join_before=False, replace=False) -> None:
join_before: bool = False, replace: bool = False
) -> None:
'''Метод описывающий действия при append = "join", если шаблон -- файл.
Объединяет шаблон с целевым файлом.'''
output_path = template_object.output_path
@ -1178,7 +1198,8 @@ class TemplateExecutor:
self.calculate_config_file.remove_file(template_object.target_path)
def _copy_from_source(self, template_object: TemplateWrapper,
chown=None, chmod=None) -> str:
chown: Union[dict, None] = None,
chmod: Union[int, None] = None) -> str:
'''Метод для копирования файла, указанного в source.'''
output_path = template_object.output_path
source_path = template_object.input_path
@ -1361,7 +1382,7 @@ class TemplateExecutor:
return hashlib.md5(source_path.encode()).hexdigest()
def _create_directory(self, template_object: TemplateWrapper,
path_to_create=None) -> None:
path_to_create: Union[str, None] = None) -> None:
'''Метод для создания директории и, при необходимости, изменения
владельца и доступа все директорий на пути к целевой.'''
if path_to_create is None:
@ -1817,7 +1838,7 @@ class TemplateExecutor:
return hasattr(error, 'errno') and error.errno == errno.EACCES and\
'var/calculate/remote' in path_to_check
def _is_vfat(self, path_to_check):
def _is_vfat(self, path_to_check: str):
'''Метод, проверяющий является ли файловая система vfat. Нужно для того,
чтобы знать о возможности применения chown, chmod и т.д.'''
# Инициализируем объект для проверки примонтированных файловых систем.
@ -1833,7 +1854,7 @@ class TemplateExecutor:
class DirectoryTree:
'''Класс реализующий дерево каталогов для пакета.'''
def __init__(self, base_directory):
def __init__(self, base_directory: str):
self.base_directory = base_directory
self._tree = {}
@ -1856,7 +1877,7 @@ class DirectoryTree:
def show_tree(self) -> None:
pprint(self._tree)
def get_directory_tree(self, directory: str):
def get_directory_tree(self, directory: str) -> "DirectoryTree":
'''Метод для получения нового дерева из ветви данного дерева,
соответствующей некоторому каталогу, содержащемуся в корне данного
дерева.'''
@ -1872,10 +1893,10 @@ class DirectoryTree:
else:
return None
def __setitem__(self, name: str, value):
def __setitem__(self, name: str, value: Union[None, dict]) -> None:
self._tree[name] = value
def __iter__(self):
def __iter__(self) -> Iterator[str]:
if self._tree is not None:
return iter(self._tree.keys())
else:
@ -1890,35 +1911,40 @@ class DirectoryTree:
class DirectoryProcessor:
'''Класс обработчика директорий шаблонов.'''
def __init__(self, action: str, datavars_module=Variables(), package='',
output_module=IOModule(), dbpkg=True,
namespace: NamespaceNode = None, **groups):
def __init__(self, action: str,
datavars_module: Union[Datavars, Variables] = Variables(),
install: Union[str, PackageAtomName] = '',
output_module: IOModule = IOModule(), dbpkg: bool = True,
namespace: NamespaceNode = None, **groups: dict):
if isinstance(action, list):
self.action = action
else:
self.action = [action]
self.output = output_module
self.datavars_module = datavars_module
self._namespace = namespace
self.output: IOModule = output_module
self.datavars_module: Variables = datavars_module
self._namespace: NamespaceNode = namespace
# Корневая директория.
self.cl_chroot_path: str
if 'cl_chroot_path' in datavars_module.main:
self.cl_chroot_path = datavars_module.main.cl_chroot_path
else:
self.cl_chroot_path = '/'
# Корневая директория.
# Корневой путь шаблонов.
self.templates_root: str
if 'cl_root_path' in datavars_module.main:
self.templates_root = join_paths(self.cl_chroot_path,
datavars_module.main.cl_root_path)
else:
self.templates_root = self.cl_chroot_path
self.cl_ignore_files = self._get_cl_ignore_files()
self.cl_ignore_files: List[str] = self._get_cl_ignore_files()
# Путь к файлу config с хэш-суммами файлов, для которых уже
# предлагались изменения.
self.cl_config_path: str
if 'cl_config_path' in datavars_module.main:
self.cl_config_path = self._add_chroot_path(
self.datavars_module.main.cl_config_path)
@ -1928,6 +1954,7 @@ class DirectoryProcessor:
# Путь к директории config-archive для хранения оригинальной ветки
# конфигурационных файлов.
self.cl_config_archive: str
if 'cl_config_archive' in datavars_module.main:
self.cl_config_archive = self._add_chroot_path(
self.datavars_module.main.cl_config_archive)
@ -1937,6 +1964,7 @@ class DirectoryProcessor:
# Путь к директории .execute для хранения хранения файлов скриптов,
# полученных из шаблонов с параметром exec.
self.cl_exec_dir_path: str
if 'cl_exec_dir_path' in datavars_module.main:
self.cl_exec_dir_path = self._add_chroot_path(
self.datavars_module.main.cl_exec_dir_path)
@ -1945,7 +1973,7 @@ class DirectoryProcessor:
'/var/lib/calculate/.execute/')
# Инициализируем исполнительный модуль.
self.template_executor = TemplateExecutor(
self.template_executor: TemplateExecutor = TemplateExecutor(
datavars_module=self.datavars_module,
chroot_path=self.cl_chroot_path,
cl_config_archive=self.cl_config_archive,
@ -1966,22 +1994,23 @@ class DirectoryProcessor:
self._make_current_template_var()
# Инициализируем шаблонизатор.
self.template_engine = TemplateEngine(
self.template_engine: TemplateEngine = TemplateEngine(
datavars_module=self.datavars_module,
chroot_path=self.cl_chroot_path,
appends_set=self.template_executor.available_appends)
# Разбираем atom имя пакета, для которого накладываем шаблоны.
self.for_package = False
if package:
if isinstance(package, PackageAtomName):
self.for_package = package
elif isinstance(package, str):
self.for_package: Union[PackageAtomName, None] = None
if install:
if isinstance(install, PackageAtomName):
self.for_package = install
elif isinstance(install, str):
try:
self.for_package = self.template_engine.\
parameters_processor.check_postparse_package(package)
parameters_processor.check_postparse_package(
install)
except ConditionFailed as error:
# ConfitionFailed потому что для проверки значения пакета,
# ConditionFailed потому что для проверки значения пакета,
# используется тот же метод, что проверяет параметр package
# в шаблонах, а в них этот параметр играет роль условия.
self.output.set_error(str(error))
@ -1990,6 +2019,7 @@ class DirectoryProcessor:
# Получаем список директорий шаблонов.
# TODO переменная список.
self.template_paths: List[str]
if isinstance(self.datavars_module, (Datavars, NamespaceNode)):
var_type = self.datavars_module.main[
'cl_template_path'].variable_type
@ -2012,11 +2042,11 @@ class DirectoryProcessor:
self.packages_file_trees = OrderedDict()
# Список обработчиков.
self._handlers = {}
self._handlers_queue = []
self._handling = None
self._handlers: Dict[tuple] = {}
self._handlers_queue: List[str] = []
self._handling: Union[str, None] = None
def _get_cl_ignore_files(self) -> list:
def _get_cl_ignore_files(self) -> List[str]:
'''Метод для получения из соответствующей переменной списка паттернов
для обнаружения игнорируемых в ходе обработки шаблонов файлов.'''
if 'cl_ignore_files' in self.datavars_module.main:
@ -2050,7 +2080,7 @@ class DirectoryProcessor:
package_atom: str) -> None:
try:
groups_namespace = self.datavars_module.main.cl.groups
except VariableNotFoundError:
except (VariableNotFoundError, AttributeError):
namespaces = ['cl', 'groups']
groups_namespace = self.datavars_module.main
for namespace in namespaces:
@ -2065,6 +2095,9 @@ class DirectoryProcessor:
groups_namespace[namespace] = Variables()
groups_namespace = groups_namespace[namespace]
print('GROUPS NAMESPACE:')
print(groups_namespace)
atom_dict = PackageAtomParser.parse_atom_name(package_atom)
if isinstance(self.datavars_module, (Datavars, NamespaceNode)):
if group_name not in groups_namespace:
@ -2074,24 +2107,59 @@ class DirectoryProcessor:
else:
group_var = groups_namespace[group_name]
group_table = group_var.get_value().get_table()
if not self.check_existance_in_group(atom_dict, group_table):
check_result = self.check_existance_in_group(atom_dict,
group_table)
if check_result == -2:
group_table.append(atom_dict)
elif check_result >= 0:
group_table[check_result] = atom_dict
if check_result != -1:
group_var.source = group_table
else:
if group_name not in groups_namespace:
groups_namespace[group_name] = [atom_dict]
else:
group_table = groups_namespace[group_name]
if not self.check_existance_in_group(atom_dict, group_table):
check_result = self.check_existance_in_group(atom_dict,
group_table)
if check_result == -2:
group_table.append(atom_dict)
elif check_result >= 0:
group_table[check_result] = atom_dict
if check_result != -1:
groups_namespace[group_name] = group_table
def check_existance_in_group(self, atom_dict: dict, group: list):
def check_existance_in_group(self, atom_dict: dict, group: list) -> bool:
'''Метод для проверки наличия в таблице групп указанного пакета, а
также для сравнения данных о пакете из таблицы и из атом словаря.
Возвращает:
-2 -- если пакета нет;
-1 -- если присутствует и полностью совпадает;
>=0 -- если имеющиеся в таблице данные о пакете должены быть заменены
на указанные. В этом случае возвращается индекс старых данных в
таблице. '''
index = 0
for group_atom in group:
for field in ['category', 'name', 'version', 'slot']:
if atom_dict[field] != group_atom[field]:
continue
return True
if (atom_dict['category'] != group_atom['category'] and
atom_dict['name'] != group_atom['name']):
index += 1
continue
if (atom_dict['slot'] is not None and
group_atom['slot'] is not None):
if atom_dict['slot'] != group_atom['slot']:
return -2
if atom_dict['version'] is not None:
if (group_atom['version'] is None or
atom_dict['version'] != group_atom['version']):
return index
return -1
return -2
def _make_current_template_var(self) -> None:
var_path = ['main', 'cl']
@ -2120,6 +2188,7 @@ class DirectoryProcessor:
# Режим заполнения очередей директорий пакетов, необходимых для более
# быстрой обработки параметра merge.
self.fill_trees = bool(self.for_package)
if self.for_package:
if self.for_package is NonePackage:
package = self.for_package
@ -2129,6 +2198,7 @@ class DirectoryProcessor:
else:
package = None
self.base_directory: str
for directory_path in self.template_paths:
self.base_directory = directory_path.strip()
entries = os.scandir(self.base_directory)
@ -2163,11 +2233,12 @@ class DirectoryProcessor:
self.template_executor.save_changes()
return self.template_executor.changed_files
def _execute_handlers(self):
def _execute_handlers(self) -> None:
'''Метод для запуска обработчиков добавленных в очередь обработчиков
с помощью параметра notify.'''
self.output.set_info('Processing handlers...')
index = 0
while index < len(self._handlers_queue):
handler = self._handlers_queue[index]
index += 1
@ -2219,7 +2290,7 @@ class DirectoryProcessor:
FILE, handler_path,
template_text=handler_text)
def _merge_packages(self):
def _merge_packages(self) -> None:
'''Метод для выполнения шаблонов относящихся к пакетам, указанным во
всех встреченных значениях параметра merge.'''
not_merged_packages = []
@ -2283,7 +2354,7 @@ class DirectoryProcessor:
else:
self.output.set_success('All packages are merged.')
def _run_exec_files(self):
def _run_exec_files(self) -> None:
'''Метод для выполнения скриптов, полученных в результате обработки
шаблонов с параметром exec.'''
for exec_file_path, exec_info in\
@ -2306,28 +2377,11 @@ class DirectoryProcessor:
except TemplateExecutorError as error:
self.output.set_error(str(error))
def _get_directories_queue(self, path: str) -> tuple:
'''Уже не актуальный метод для построение очередей из путей к
шаблонам. Хотя возможно еще пригодится.'''
directories_queue = []
for base_dir in self.template_paths:
base_dir = base_dir.strip()
if path.startswith(base_dir):
base_directory = base_dir
break
while path != base_directory:
path, dir_name = os.path.split(path)
directories_queue.append(dir_name)
return base_directory, directories_queue
def _walk_directory_tree(self, current_directory_path: str,
current_target_path: str,
directory_parameters: ParametersContainer(),
directory_tree={},
package=None) -> None:
directory_parameters: ParametersContainer,
directory_tree: Union[dict, DirectoryTree] = {},
package: Union[Package, None] = None) -> None:
'''Метод для рекурсивного обхода директорий с шаблонами, а также, при
необходимости, заполнения деревьев директорий шаблонов, с помощью
которых далее выполняются шаблоны пакетов из merge.'''
@ -2389,6 +2443,7 @@ class DirectoryProcessor:
directory_tree = {}
return
# Хэндлеры, вложенные в другие хэндлеры не разрешены.
if self._handling is not None and directory_parameters.handler:
if directory_parameters.handler != self._handling:
self.output.set_error("'handler' parameter is not"
@ -2403,6 +2458,8 @@ class DirectoryProcessor:
if pkg not in self.processed_packages:
self.packages_to_merge.add(pkg)
# Если присутствует параметр notify, в котором указаны хэндлеры для
# последующего выполнения -- добавляем их в очередь.
if directory_parameters.notify:
for handler_id in directory_parameters.notify:
if handler_id not in self._handlers_queue:
@ -2425,6 +2482,7 @@ class DirectoryProcessor:
current_target_path = os.path.join(current_target_path,
directory_name)
# Для директорий по умолчанию append = join.
if not directory_parameters.append:
directory_parameters.set_parameter({'append': 'join'})
@ -2459,7 +2517,7 @@ class DirectoryProcessor:
template_path = os.path.join(current_directory_path,
template_name)
# Применяем к файлу шаблона шаблонизатор.
# Обрабатываем файл шаблона шаблонизатором.
template_text = self._parse_template(template_parameters,
template_name,
FILE, current_directory_path)
@ -2507,6 +2565,8 @@ class DirectoryProcessor:
if pkg not in self.processed_packages:
self.packages_to_merge.add(pkg)
# Если присутствует параметр notify, в котором указаны хэндлеры для
# последующего выполнения -- добавляем их в очередь.
if template_parameters.notify:
for handler_id in template_parameters.notify:
if handler_id not in self._handlers_queue:
@ -2572,7 +2632,8 @@ class DirectoryProcessor:
directory_tree = {}
return
def _scan_directory(self, directory_path: str) -> tuple:
def _scan_directory(self, directory_path: str
) -> Tuple[List[str], List[str]]:
'''Метод для получения и фильтрования списка файлов и директорий,
содержащихся в директории шаблонов.'''
template_files = []
@ -2604,9 +2665,11 @@ class DirectoryProcessor:
return False
return True
def _get_files_and_dirs_from_tree(self, template_files,
template_directories,
directory_tree: DirectoryTree):
def _get_files_and_dirs_from_tree(self, template_files: List[str],
template_directories: List[str],
directory_tree: DirectoryTree
) -> Tuple[List[str],
List[DirectoryTree]]:
'''Метод для получения списков файлов и директорий из дерева
директорий.'''
tree_files = []
@ -2622,8 +2685,8 @@ class DirectoryProcessor:
return tree_directories, tree_files
def _make_target_path(self, target_path, template_name,
parameters):
def _make_target_path(self, target_path: str, template_name: str,
parameters: ParametersContainer) -> str:
'''Метод для получения пути к целевому файлу с учетом наличия
параметров name, path и append = skip.'''
# Если есть параметр name -- меняем имя шаблона.
@ -2642,10 +2705,10 @@ class DirectoryProcessor:
template_name)
return target_path
def _parse_template(self, parameters,
template_name,
template_type,
template_directory):
def _parse_template(self, parameters: ParametersContainer,
template_name: str,
template_type: int,
template_directory: str) -> Union[str, bool]:
'''Метод для разбора шаблонов, получения значений их параметров и их
текста после отработки шаблонизитора.'''
if template_type == DIR:
@ -2657,6 +2720,7 @@ class DirectoryProcessor:
self.datavars_module.main.cl['current_template'].set(template_path)
else:
self.datavars_module.main.cl['current_template'] = template_path
try:
self.template_engine.process_template(template_name,
template_type,
@ -2673,12 +2737,13 @@ class DirectoryProcessor:
template_path))
return False
def _execute_template(self, target_path,
parameters,
template_type,
template_path,
template_text='',
package=None):
def _execute_template(self, target_path: str,
parameters: ParametersContainer,
template_type: int,
template_path: str,
template_text: str = '',
package: Union[Package, None] = None
) -> Union[bool, str]:
'''Метод для наложения шаблонов и обработки информации полученной после
наложения.'''
try:
@ -2727,7 +2792,8 @@ class DirectoryProcessor:
format(template_path))
return target_path
def _update_package_tree(self, package, current_level_tree):
def _update_package_tree(self, package: Package,
current_level_tree: Union[None, dict]) -> None:
'''Метод для обновления деревьев директорий пакетов, необходимых для
обработки шаблонов пакетов из значения параметра merge.'''
# Если текущему уровню соответствует заглушка None или он содержит
@ -2743,12 +2809,13 @@ class DirectoryProcessor:
# Если для данного пакета еще нет дерева --
# копируем для него текущее.
directory_tree = DirectoryTree(self.base_directory)
directory_tree.update_tree(
copy.deepcopy(self.directory_tree))
directory_tree.update_tree(copy.deepcopy(self.directory_tree))
self.packages_file_trees[package] = directory_tree
def _check_package_and_action(self, parameters, template_path,
directory_tree=None):
def _check_package_and_action(self, parameters: ParametersContainer,
template_path: str,
directory_tree: Union[dict, None] = None
) -> bool:
'''Метод для проверки параметров action и package во время обработки
каталогов с шаблонами. Если среди аргументов указано также
дерево каталогов, то в случае несовпадения значений package для файла
@ -2790,6 +2857,9 @@ class DirectoryProcessor:
if parameters.package != self.for_package:
if directory_tree is not None:
# Если есть дерево, которое собирается для текущих шаблонов
# и параметр package шаблона не совпадает с текущим,
# ставим заглушку, в качестве которой используется None.
template_name = os.path.basename(template_path)
directory_tree[template_name] = None
self.output.set_warning(
@ -2804,17 +2874,17 @@ class DirectoryProcessor:
return True
@contextmanager
def _start_handling(self, handler):
def _start_handling(self, handler_id: str):
'''Метод для перевода обработчика каталогов в режим обработки
хэндлеров.'''
try:
self._handling = handler
self._handling = handler_id
yield self
finally:
self._handling = None
@contextmanager
def _set_current_package(self, package):
def _set_current_package(self, package: Package):
'''Метод для указания в шаблонизаторе пакета, для которого в данный
момент проводим настройку. Пока не используется.'''
try:

@ -11,7 +11,7 @@ def main():
parser = argparse.ArgumentParser('Run templates.')
parser.add_argument('-a', '--action', action='append', type=str, nargs='+',
help="action parameter value.")
parser.add_argument('-p', '--package', type=str,
parser.add_argument('-p', '--install', type=str,
help="atom name of a target package.")
parser.add_argument('--dbpkg', action='store_true',
help=("flag for switching template engine's mode from"
@ -27,8 +27,10 @@ def main():
datavars = Datavars()
io_module = IOModule()
if args.package == 'None':
if args.install is None:
package = NonePackage
elif args.install.strip().casefold() == 'all':
package = None
else:
package = args.package

@ -663,7 +663,7 @@ class TestDirectoryProcessor:
'templates_17')
directory_processor = DirectoryProcessor('install',
datavars_module=datavars,
package=other_package_name)
install=other_package_name)
directory_processor.process_template_directories()
test_package = Package(test_package_name_1, chroot_path=CHROOT_PATH)
@ -689,7 +689,7 @@ class TestDirectoryProcessor:
'templates_18')
directory_processor = DirectoryProcessor('install',
datavars_module=datavars,
package=other_package_name)
install=other_package_name)
directory_processor.process_template_directories()
test_package = Package(test_package_name_1, chroot_path=CHROOT_PATH)
@ -777,7 +777,7 @@ class TestDirectoryProcessor:
'templates_21')
directory_processor = DirectoryProcessor('install',
datavars_module=datavars,
package=other_package_name)
install=other_package_name)
directory_processor.process_template_directories()
test_package = Package(test_package_name_1, chroot_path=CHROOT_PATH)
@ -820,7 +820,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/other-package')
install='test-category/other-package')
directory_processor.process_template_directories()
test_package = Package(test_package_name_1, chroot_path=CHROOT_PATH)
@ -863,7 +863,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/other-package')
install='test-category/other-package')
directory_processor.process_template_directories()
test_package = Package(test_package_name_1, chroot_path=CHROOT_PATH)
@ -1109,7 +1109,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
test_package = Package(test_package_name_1, chroot_path=CHROOT_PATH)
@ -1205,7 +1205,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
@ -1233,9 +1233,9 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package',
build='test-category/build-package-2.1:0[use_1,use_2]',
uninstall='test-category/unmerged-package-1.0.1:1[use_1,use_2]'
install='test-category/test-package',
build='test-category/build-package-2.1:0[use_1 use_2]',
uninstall='test-category/unmerged-package-1.0.1:1[use_1 use_2]'
)
assert 'build' in datavars.main.cl.groups
assert 'uninstall' in datavars.main.cl.groups
@ -1257,6 +1257,7 @@ class TestDirectoryProcessor:
'etc/dir_51'): 'N',
join_paths(CHROOT_PATH,
'etc/dir_51/file_0'): 'N'}
datavars.main.cl['groups'] = Variables({})
def test_group_parameter_with_merge_parameter(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
@ -1264,9 +1265,9 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package',
build='test-category/build-package-2.1:0[use_1,use_2]',
uninstall='test-category/unmerged-package-1.0.1:1[use_1,use_2]'
install='test-category/test-package',
build='test-category/build-package-2.1:0[use_1 use_2]',
uninstall='test-category/unmerged-package-1.0.1:1[use_1 use_2]'
)
assert 'build' in datavars.main.cl.groups
assert 'uninstall' in datavars.main.cl.groups
@ -1287,6 +1288,7 @@ class TestDirectoryProcessor:
'etc/dir_56'): 'N',
join_paths(CHROOT_PATH,
'etc/dir_56/file_0'): 'N'}
# datavars.main.cl['groups'] = Variables({})
def test_solving_collisions_for_the_same_packages_from_different_slots(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
@ -1294,7 +1296,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
header = ('#' + '-' * 79 + '\n' +
@ -1361,7 +1363,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package:1'
install='test-category/test-package:1'
)
output_text = "options {\n parameter-0 yes;\n};\n"
@ -1389,7 +1391,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/copy.gif'))
@ -1400,7 +1402,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_60/file_0'))
@ -1413,7 +1415,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_61/file_0'))
@ -1443,7 +1445,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_62/file_0'))
@ -1458,7 +1460,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_63/file_0'))
@ -1469,7 +1471,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'update',
datavars_module=datavars,
package='test-category/test-package'
install='test-category/test-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_65/file_0'))
@ -1481,7 +1483,7 @@ class TestDirectoryProcessor:
directory_processor = DirectoryProcessor(
'install',
datavars_module=datavars,
package='test-category/test-package',
install='test-category/test-package',
output_module=io
)
directory_processor.process_template_directories()
@ -1491,6 +1493,47 @@ class TestDirectoryProcessor:
"Warning: package 'test-category/new-package-0.1.1'"
" not found for action 'install'.")
def test_group_and_package_parameter_for_unexisting_package(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_49')
directory_processor = DirectoryProcessor(
'merge',
datavars_module=datavars,
uninstall='test-category/unmerged-package-1.0.1:1[use_1 use_2]'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_67/file_0'))
def test_group_and_some_templates_ignored(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_50')
directory_processor = DirectoryProcessor(
'unmerge',
datavars_module=datavars,
uninstall='test-category/unmerged-package-1.0.1:1[use_1 use_2]'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_68/file_0'))
assert not os.path.exists(join_paths(CHROOT_PATH,
'/etc/dir_69/file_0'))
def test_group_with_NonePackage(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_51')
directory_processor = DirectoryProcessor(
'unmerge',
datavars_module=datavars,
install=NonePackage,
uninstall='test-category/unmerged-package-1.0.1:1[use_1 use_2]',
build='test-category/build-package'
)
directory_processor.process_template_directories()
assert os.path.exists(join_paths(CHROOT_PATH, '/etc/dir_70/file_0'))
assert not os.path.exists(join_paths(CHROOT_PATH,
'/etc/dir_71/file_0'))
assert not os.path.exists(join_paths(CHROOT_PATH,
'/etc/dir_72/file_0'))
def test_view_tree(self):
list_path = join_paths(CHROOT_PATH, '/etc')
show_tree(list_path)

@ -1,9 +0,0 @@
#-------------------------------------------------------------------------------
# Modified by Calculate Utilities 4.0
# Processed template files:
# /path/to/template
#-------------------------------------------------------------------------------
section-name {
parameter-1 yes;
parameter-2 no;
};

@ -1,9 +0,0 @@
#-------------------------------------------------------------------------------
# Modified by Calculate Utilities 4.0
# Processed template files:
# /home/divanov/Home/development/calculate-lib/tests/templates/testfiles/test_dir_processor_root/templates_28/root/file_12
#-------------------------------------------------------------------------------
section-name {
parameter-1 yes;
parameter-3 10;
};

@ -1 +1 @@
{% calculate append = 'skip', path = '/etc', action = 'install' %}
{% calculate append = 'skip', action = 'install' %}

@ -0,0 +1,12 @@
{% calculate run = '/usr/bin/python' %}
import os
text = '''Please, take this and run
far away, far away from me
I am tainted
And happyness, and peace of mind
Was never meant for me.'''
os.mkdir('./dir_67')
with open('./dir_67/file_0', 'w') as output_file:
output_file.write(text)

@ -0,0 +1,2 @@
{% calculate append = "skip", group = "uninstall",
package = 'test-category/unmerged-package' %}

@ -0,0 +1,12 @@
{% calculate run = '/usr/bin/python' %}
import os
text = '''Please, take this and run
far away, far away from me
I am tainted
And happyness, and peace of mind
Was never meant for me.'''
os.mkdir('./dir_68')
with open('./dir_68/file_0', 'w') as output_file:
output_file.write(text)

@ -0,0 +1,2 @@
{% calculate append = "skip", group = "uninstall",
package = 'test-category/build-package'%}

@ -0,0 +1,12 @@
{% calculate run = '/usr/bin/python' %}
import os
text = '''Please, take this and run
far away, far away from me
I am tainted
And happyness, and peace of mind
Was never meant for me.'''
os.mkdir('./dir_69')
with open('./dir_69/file_0', 'w') as output_file:
output_file.write(text)

@ -0,0 +1,2 @@
{% calculate append = "skip", group = "uninstall",
package = 'test-category/unmerged-package' %}

@ -0,0 +1,12 @@
{% calculate run = '/usr/bin/python' %}
import os
text = '''Please, take this and run
far away, far away from me
I am tainted
And happyness, and peace of mind
Was never meant for me.'''
os.mkdir('./dir_70')
with open('./dir_70/file_0', 'w') as output_file:
output_file.write(text)

@ -0,0 +1,2 @@
{% calculate append = "skip", group = "uninstall",
package = 'test-category/build-package'%}

@ -0,0 +1,12 @@
{% calculate run = '/usr/bin/python' %}
import os
text = '''Please, take this and run
far away, far away from me
I am tainted
And happyness, and peace of mind
Was never meant for me.'''
os.mkdir('./dir_71')
with open('./dir_71/file_0', 'w') as output_file:
output_file.write(text)

@ -0,0 +1 @@
{% calculate append = 'join', package = 'test-category/test-package' %}
正在加载...
取消
保存