Implemented commands and scripts modules and parameter's container too.

packages
Иванов Денис 4 years ago
parent 8f02dcd5e5
commit a36aa05bae

@ -0,0 +1,175 @@
from types import FunctionType
from typing import Union, Callable, List
from calculate.scripts.scripts import Script
from calculate.variables.parameters import Parameters
from calculate.variables.datavars import NamespaceNode, DependenceAPI,\
DependenceError, VariableNotFoundError
from calculate.variables.loader import Datavars
from calculate.utils.io_module import IOModule
class CommandCreationError(Exception):
'''Исключение кидаемое при наличии ошибок во время создания объекта
команды.'''
def __init__(self, message: str, command_id: str = '', title: str = ''):
self.message: str = message
self.command_id: str = command_id
self.command_title: str = title
def __str__(self) -> str:
if self.command_title:
return (f'can not create command {self.command_title}:'
f' {self.message}')
if self.command_id:
return f'can not create command {self.command_id}: {self.message}'
return f'can not create command: {self.message}'
class CommandInitializationError(Exception):
'''Исключение кидаемое при наличии ошибок во время инициализации.'''
def __init__(self, message: str, title: str):
self.message: str = message
self.command_title: str = title
def __str__(self) -> str:
if self.command_title:
return (f'can not initialize command {self.command_title}:'
f' {self.message}')
class Command:
'''Класс команды, предназначен для хранения информации о команде и привязки ее к модулю ввода/вывода'''
def __init__(self, command_id: str = '', category: str = '',
title: str = '',
script: Union[Callable, Script, None] = None,
parameters: Union[Parameters, None] = None,
namespace: Union[str, NamespaceNode, None] = None,
command: str = '', gui: bool = False,
icon: Union[str, List[str]] = '',
setvars: dict = {}, rights: List[str] = []):
self._datavars: Union[Datavars, NamespaceNode, None] = None
if not command_id:
raise CommandCreationError('command ID is not set')
self._id: str = command_id
# Если собственно команда не была указана, получаем ее из ID.
if command:
self._command: str = command
else:
self._command: str = f'cl_{self._id}'
if not title:
raise CommandCreationError("title is not set",
command_id=command_id)
self._title: str = title
if not category:
raise CommandCreationError("category is not set",
command_title=title)
self._category: str = category
# Пространство имен относительно которого, будет выполняться скрипт.
self._namespace: Union[str, NamespaceNode, None] = namespace
# Параметры, указываемые при вызове этой команды.
if parameters is None:
raise CommandCreationError("parameters is not set",
command_title=title)
self._parameters: Parameters = parameters
# Скрипт выполняемый при вызове этой команды.
if not script:
raise CommandCreationError("script is not set",
command_title=title)
elif isinstance(script, FunctionType):
# Поддержка способа описания скрипта в функции.
self._script: Script = script()
else:
self._script: Script = script
# Параметр указывающий, должена ли данная команда отображаться в
# графическом интерфейсе.
self._gui: bool = gui
# Устанавливаем название иконки.
if not icon and self._gui:
raise CommandCreationError("icon is not set",
command_title=title)
self._icon: Union[str, List[str]] = icon
# Словарь с переменными и значениями, которые нужно им присвоить.
self._setvars = setvars
# Права, необходимые данной команде.
if not rights:
raise CommandCreationError("rights is not set",
title=title)
self._rights = rights
@property
def script(self) -> Script:
return self._script
def initialize(self, datavars: Union[Datavars, NamespaceNode],
output: IOModule) -> 'Command':
'''Метод для инициализации всего, что нужно инициализировать в команде.
'''
self._datavars = datavars
self._output = output
# Сначала найдем пространство имен.
if self._namespace is not None:
if isinstance(self._namespace, str):
self._namespace = self._find_namespace(self._namespace,
datavars)
# Инициализируем параметры.
self._parameters.initialize(datavars)
# Инициализируем скрипт.
self._script.initialize(output, datavars, self._namespace)
# Устанавливаем переменные, если нужно.
if self._setvars:
self._set_variables(self._setvars, datavars, self._namespace)
return self
def _find_namespace(self, namespace_path: str,
datavars: Union[Datavars, NamespaceNode]
) -> NamespaceNode:
'''Метод для поиска пространств имен.'''
# TODO сделать где-нибудь единственный статический вариант.
parts = namespace_path.split('.')
namespace = datavars
for part in parts:
namespace = namespace[part]
return namespace
def _set_variables(self, setvars: dict,
datavars: Union[Datavars, NamespaceNode],
namespace: Union[NamespaceNode, None]) -> None:
'''Метод для установки указанных значений, указанным переменным.'''
force = False
for varname, value in setvars.items():
if varname.endswith('!'):
varname = varname[:-1]
force = True
else:
force = False
try:
variable = DependenceAPI.find_variable(
varname, datavars,
current_namespace=namespace)
except VariableNotFoundError:
self._output.set_error(f"Can not set variable '{varname}':"
" variable does not exist.")
if not variable.readonly or force:
variable.set(value)
else:
self._output.set_error("Can not set readonly variable "
f"'{variable.get_fullname}'.")

@ -0,0 +1,986 @@
# vim: fileencoding=utf-8
#
import re
import inspect
from typing import Callable, Any, Union, List, Generator
from calculate.variables.datavars import DependenceAPI, DependenceError,\
VariableNode, NamespaceNode,\
HashType, TableType
from calculate.variables.loader import Datavars
from calculate.utils.io_module import IOModule
from collections.abc import Iterable, Mapping
class TaskInitializationError(Exception):
pass
class ScriptError(Exception):
pass
class TaskError(Exception):
def __init__(self, message, handlers=set()):
super().__init__(message)
self.handlers = handlers
class ActionError(Exception):
'''Класс исключения для создания штатных исключений в функциях действий.'''
pass
class ConditionError(Exception):
pass
class Script:
'''Класс скрипта, собирает задачи, обработчики, блоки и прочее. Принимает
аргументы.'''
def __init__(self, script_id: str, args: List[Any] = [],
success_message: str = None,
failed_message: str = None,
interrupt_message: str = None):
self._id: str = script_id
self._items: List[Union['Task', 'Block', 'Handler', 'Run']] = None
self._args_names: list = args
self._args_vars: list = []
self._success_message: str = success_message
self._failed_message: str = failed_message
self._interrupt_message: str = interrupt_message
self._output: IOModule = None
self._datavars: Union[Datavars, NamespaceNode] = None
self._namespace: Union[NamespaceNode, None] = None
self._handlers: set = set()
self._initialized: bool = False
def tasks(self, *items: List[Union['Task', 'Block', 'Handler', 'Run']]
) -> 'Script':
'''Метод для указания задач и контейнеров, из которых состоит скрипт.
'''
self._items = items
return self
def initialize(self, output: IOModule,
datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode = None) -> 'Script':
'''Метод для инициализации, состоящей в установке модулей вывода,
модуля переменных, текущего пространства имен, а также создании
переменных скрипта.'''
self._output = output
self._datavars = datavars
self._namespace = namespace
if not self._initialized:
self._script_namespace, self._args_vars =\
self.make_script_variables(self._id,
self._args_names,
self._datavars)
self._initialized = True
return self
def run(self) -> None:
'''Метод для запуска задач, содержащихся в данном скрипте.'''
essential_error = None
handlers_to_run = []
for task_item in self._items:
if isinstance(task_item, Handler):
if task_item.id in self._handlers:
handlers_to_run.append(task_item)
elif essential_error is None:
try:
output = task_item.run(self._output,
self._datavars,
self._script_namespace,
self._namespace)
self._handlers.update(output)
except Exception as error:
if isinstance(error, TaskError):
self._handlers.update(error.handlers)
essential_error = error
for handler in handlers_to_run:
try:
task_item.run(self._output, self._datavars,
self._script_namespace, self._namespace)
except Exception as error:
# TODO Разобраться что делать с ошибками в обработчике.
self._output.set_error(f"error in handler '{task_item.id}':"
f" {str(error)}")
if essential_error is not None:
raise essential_error
@staticmethod
def make_script_variables(script_id,
args: List[Any],
datavars: Union[Datavars, NamespaceNode]
) -> NamespaceNode:
'''Метод для создания переменных скрипта. Создает пространство имен
скрипта и переменных аргументов.'''
if 'scripts' not in datavars:
scripts = Script.make_scripts_namespace(datavars)
else:
scripts = datavars.scripts
if script_id not in scripts:
current_script = NamespaceNode(script_id)
scripts.add_namespace(current_script)
else:
current_script = scripts[script_id]
current_script.clear()
args_vars = []
for arg in args:
args_vars.append(VariableNode(arg, current_script))
return current_script, args_vars
@staticmethod
def make_scripts_namespace(datavars: Union[Datavars, NamespaceNode]
) -> NamespaceNode:
'''Метод создающий пространства имен, необходимые для работы задач.'''
# Для тестирования скорее.
scripts = NamespaceNode('scripts')
datavars.add_namespace(scripts)
env = NamespaceNode('env')
scripts.add_namespace(env)
loop = NamespaceNode('loop')
env.add_namespace(loop)
return scripts
def __call__(self, *args):
'''Метод для запуска скрипта с аргументами.'''
if len(args) < len(self._args_names):
raise ScriptError((f"script '{self._id}' missing"
f" {len(self._args_names) - len(args)} required"
" arguments: '") +
"', '".join(self._args_names[len(args):]) + "'")
elif len(args) > len(self._args_names):
raise ScriptError(f"script '{self._id}' takes"
f" {len(self._args_names)} arguments, but"
f" {len(args)} were given")
args_values = self._get_args_values(args, self._datavars,
self._namespace)
for arg_variable, arg_value in zip(self._args_vars, args_values):
arg_variable.source = arg_value
self.run()
def _get_args_values(self, args: List[Any],
datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode):
'''Метод для получения значений аргументов.'''
args_values = []
for argument in args:
if isinstance(argument, str):
variable = DependenceAPI.find_variable(
argument, datavars,
current_namespace=namespace)
args_values.append(variable.get_value())
elif isinstance(argument, Static):
args_values.append(argument.value)
else:
args_values.append(argument)
return args_values
class Run:
'''Класс запускателя скриптов.'''
def __init__(self, script: Script, namespace: str = None,
args: List[Any] = [], when: 'Var' = None,
essential: bool = True):
self._script: Script = script
self._namespace: str = namespace
self._args: List[Any] = args
self._condition: Var = when
self._essential = essential
def run(self, output: IOModule, datavars: Union[Datavars, NamespaceNode],
parent_namespace: NamespaceNode, namespace: NamespaceNode) -> set:
'''Метод для запуска скрипта, указанного имеющегося в запускателе.'''
if self._condition is None or self._condition(datavars,
namespace,
parent_namespace):
# пространство имен или наследуем, либо берем по указанному пути.
if not self._namespace:
self._namespace = namespace
elif isinstance(self._namespace, str):
parts = self._namespace.split('.')
parts.reverse()
self._namespace = datavars[parts.pop()]
while parts:
self._namespace = self._namespace[parts.pop()]
try:
# if not self._script._initialized:
self._script.initialize(output, datavars, self._namespace)
self._script(*self._args)
except (ScriptError, TaskError) as error:
if self._essential:
raise ScriptError(f"essential script '{self._script._id}'"
f" is failed. Reason: {error}")
output.set_error(f"essential script '{self._script._id}'"
f" is failed. Reason: {error}")
return set()
class Static:
'''Класс, оборачивающий значение, которое не должно восприниматься как
переменная.'''
def __init__(self, value: Any):
self._value: Any = value
@property
def value(self) -> Any:
return self._value
class Task:
'''Класс реализующий задачи -- объекты, исполняющие указанные функции
действия action, и содержащий всю необходимую информацию для их работы.'''
def __init__(self, **kwargs):
if 'id' in kwargs:
self._id: str = kwargs['id']
else:
raise TaskInitializationError("id is not set for task")
# Имя не обязательно.
self._name: str = kwargs.get('name', None)
if 'action' in kwargs:
self._action = kwargs['action']
else:
raise TaskInitializationError("action is not set for task"
f" '{self._name or self._id}'")
# Список аргументов и их типы
self._args_sources: Union[tuple, list] = kwargs.get('args', [])
self._args_types: List[type] = []
self._arg_names: List[str] = []
self._args: Union[tuple, list] = []
# Переменные, которым через set будет присвоен результат работы action.
self._set: Union[list, tuple] = kwargs.get('set', None)
self._return_type: type = None
# Условие выполнения задачи.
self._condition: Var = kwargs.get('when', None)
# Существенность задачи. Если задача существенная, ошибка в ней
# приведет к остановке скрипта, если несущественная -- ошибка будет
# записана в переменную этой задачи, а скрипт будет выполняться дальше.
self._essential: bool = kwargs.get('essential', True)
# Параметр для указания обработчиков, которые нужно запустить в конце
# выполнения скрипта.
self._handlers = set()
handlers: set = kwargs.get('notify', set())
if handlers:
if isinstance(handlers, (tuple, list)):
self._handlers.update(handlers)
else:
self._handlers.add(handlers)
self._initialized: bool = False
self._namespace: NamespaceNode = None
self._script_namespace: NamespaceNode = None
self._datavars: Union[Datavars, NamespaceNode] = None
self._output: IOModule = None
@property
def essential(self) -> bool:
return self._essential
@property
def initialized(self) -> bool:
return self._initialized
def _initialize(self, output: IOModule,
datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> 'Task':
'''Метод для инициализации задач, привязывает задачу к указанному
модулю переменных, модулю вывода, формирует список аргументов,
определяет их типы и находит переменные, к которым в последствии
нужно будет присвоить значение, возвращенное action.'''
self._datavars = datavars
self._output = output
self._namespace = namespace
self._script_namespace = script_namespace
if not self._initialized:
self._arg_names,\
self._args_types,\
self._return_type = self._get_action_info(self._action)
self._args = self._update_arguments(self._args_sources)
if self._set is not None:
self._set = self._get_set_variables(self._set, datavars,
namespace)
self._initialized = True
else:
self._args = self._update_arguments(self._args_sources)
return self
def run(self, output: IOModule,
datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> set:
'''Метод для запуска данной задачи.'''
self._initialize(output, datavars, script_namespace, namespace)
if (self._condition is None or self._condition(
self._datavars,
self._namespace,
self._script_namespace)):
arguments_values = self._get_args_values(self._args,
self._args_types)
try:
result = self._action(*arguments_values)
self._create_task_var(result=result,
success=True)
if self._return_type:
self._check_return_type(result)
if self._set:
self._set_variables(result)
return self._handlers
except Exception as error:
if self._essential and not isinstance(error, ActionError):
raise TaskError(
f"essential task '{self._name or self._id}' is"
f" failed. Reason: {error}")
self._create_task_var(success=False,
error_message=str(error))
return set()
def _get_action_info(self, action: Callable):
'''Метод для получения информации о функции действия.'''
arguments = [arg.name for arg in inspect.signature(
action).parameters.values()]
annotation = action.__annotations__
action_types = []
for argument in arguments:
action_types.append(annotation.get(argument, None))
return arguments, action_types, annotation.get('return', None)
def _update_arguments(self, args_sources: List[Any]) -> tuple:
'''Метод для обновления и установки аргументов функции действия.'''
arguments = []
# Проверяем количество аргументов.
args_length = len(self._arg_names)
if 'output' in self._arg_names:
if (args_length - 1) != len(self._args_sources):
raise TaskInitializationError(f"action function takes"
f" {args_length - 1} arguments"
f" but {len(args_sources)}"
" is given")
elif args_length != len(self._args_sources):
raise TaskInitializationError(f"action function takes"
f" {args_length} arguments but"
f" {len(args_sources)} is given")
# Формируем список значений аргументов.
args_iterator = iter(args_sources)
for arg_name in self._arg_names:
if arg_name == 'output':
arguments.append(self._output)
else:
argument = next(args_iterator)
if isinstance(argument, str):
try:
variable = DependenceAPI.find_variable(
argument,
self._datavars,
current_namespace=self._namespace)
arguments.append(variable)
except DependenceError as error:
raise TaskInitializationError(str(error))
elif isinstance(argument, Static):
arguments.append(argument.value)
else:
arguments.append(argument)
return tuple(arguments)
def _get_args_values(self, args: List[Any],
args_types: List[type]) -> List[Any]:
'''Метод для получения значений из списка аргументов функции action.'''
arguments_values = []
for arg, arg_type in zip(args, args_types):
if isinstance(arg, VariableNode):
value = arg.get_value()
if arg.variable_type is HashType:
value = value.get_hash()
elif arg.variable_type is TableType:
value = value.get_table()
arguments_values.append(self._check_type(value, arg_type))
else:
arguments_values.append(self._check_type(arg, arg_type))
return arguments_values
def _check_type(self, value: Any, arg_type: type) -> Any:
'''Метод для проверки типа аргумента.'''
if arg_type is None or isinstance(value, arg_type):
return value
else:
raise TaskError(f"'{arg_type}' is expected, not {type(value)}")
def _create_task_var(self,
result: Any = None,
success: bool = True,
error_message: str = None) -> None:
'''Метод для создания переменной задачи, в которую отправляются
результаты вычислений, имеющихся в функции action.'''
VariableNode(self._id, self._script_namespace, variable_type=HashType,
source={'result': result,
'success': success,
'error_message': error_message})
def _get_set_variables(self, to_set: Union[str, list, tuple, dict],
datavars: Union[NamespaceNode, Datavars],
namespace: NamespaceNode) -> Union[str, list, dict]:
'''Метод для поиска переменных, в которые нужно положить значения,
полученные в результате вычислений в action.'''
if isinstance(to_set, str):
return DependenceAPI.find_variable(to_set, datavars,
current_namespace=namespace)
elif isinstance(to_set, (list, tuple)):
vars_to_set = []
for var in to_set:
vars_to_set.append(
DependenceAPI.find_variable(
var, datavars,
current_namespace=namespace))
return vars_to_set
elif isinstance(to_set, dict):
for key in to_set:
variable = DependenceAPI.find_variable(
to_set[key], datavars,
current_namespace=namespace)
to_set[key] = variable
return to_set
else:
raise TaskError(f"set value must be {dict}, {list}, {tuple} or "
f"{str}, not {type(to_set)}")
def _set_variables(self, result: Any) -> None:
'''Метод для присвоения переменным результатов работы функции action.
'''
self._check_set_vars_types(result)
if isinstance(self._set, dict):
if not isinstance(result, dict):
raise TaskError(f"set parameter value is '{dict}' but action"
f" result is '{type(result)}'")
for key in self._set:
if key in result:
self._set[key].set(result[key])
elif isinstance(self._set, (list, tuple)):
for var in self._set:
var.set(result)
else:
self._set.set(result)
def _check_set_vars_types(self, result: Any) -> None:
'''Метод для проверки соответствия типов значений, полученных из
action, и переменных, в которые нужно положить эти значения.'''
# Теперь проверяем соответствие типа переменных из set и типа значений.
if isinstance(self._set, dict):
for key, variable in self._set.items():
if (key in result and not
isinstance(result[key],
variable.variable_type.python_type)):
raise TaskError("can not set value of"
f" '{type(result[key])}' type to the"
" variable of"
f" '{variable.variable_type.name}'"
" type")
elif isinstance(self._set, (list, tuple)):
for variable in self._set:
if not isinstance(result, variable.variable_type.python_type):
raise TaskError(f"can not set value of '{type(result)}'"
" type to the variable of"
f" '{variable.variable_type.name}' type")
else:
if not isinstance(result, self._set.variable_type.python_type):
raise TaskError(f"can not set value of '{type(result)}'"
" type to the variable of"
f" '{self._set.variable_type.name}' type")
def _check_return_type(self, result: Any) -> None:
'''Метод для проверки типа значения, возвращенного функцией action.'''
if not isinstance(result, self._return_type):
raise TaskError(f"action returned value of '{type(result)}' "
f"type, but expected is '{self._return_type}'")
class Loop:
'''Базовый класс всех объектов цикла для блоков.'''
def initialize_loop(self, datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для инициализации объекта цикла.'''
pass
def get_looper(self) -> Generator[bool, bool, None]:
'''Генератор, предназначенный для управления циклом при запуске блока
задач.'''
yield True
yield False
class For(Loop):
'''Класс цикла блока, использующего указанное итерируемое значение.'''
def __init__(self, value: str, iter_arg: Union[str, Iterable]):
self._iter_arg = iter_arg
self._item_name = value
self._iterable_value = None
self._item_variable = None
self._initialized = False
def initialize_loop(self, datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для инициализации объекта цикла.'''
self._iterable_value = self._get_iterable_value(self._iter_arg,
datavars,
namespace)
self._item_variable = self._get_item_variable(self._item_name,
script_namespace)
self._initialized = True
def get_looper(self) -> Generator[bool, bool, None]:
'''Генератор, предназначенный для управления циклом при запуске блока
задач.'''
if not self._initialized:
yield False
else:
for item in self._iterable_value:
self._item_variable.source = item
yield True
yield False
def _get_iterable_value(self, iter_argument: Union[str, Iterable],
datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode) -> Iterable:
'''Метод для получения и проверки значения, которое будем далее
итерировать.'''
value = self._iter_arg
if isinstance(self._iter_arg, str):
variable = DependenceAPI.find_variable(self._iter_arg,
datavars,
current_namespace=namespace)
value = variable.get_value()
if variable.variable_type is HashType:
value = value.get_hash()
elif variable.variable_type is TableType:
value = value.table_hash()
if not isinstance(value, Iterable):
raise TaskError("For() argument '{value}' is not iterable.")
if isinstance(value, Mapping):
value = value.items()
return value
def _get_item_variable(self, item_name: str,
script_namespace: NamespaceNode) -> VariableNode:
'''Метод для получения или создания переменной, в которой будет
храниться текущее значение, взятое из итерируемого значения.'''
if item_name in script_namespace:
item_variable = script_namespace[item_name]
else:
item_variable = VariableNode(item_name, script_namespace)
return item_variable
class While(Loop):
'''Класс цикла блока, аналогичного while.'''
def __init__(self, condition: 'Var'):
self._condition: Var = condition
self._datavars: Union[Datavars, NamespaceNode] = None
self._namespace: NamespaceNode = None
def initialize_loop(self, datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для инициализации объекта цикла.'''
self._datavars: Union[Datavars, NamespaceNode] = datavars
self._namespace: Union[NamespaceNode, None] = namespace
self._script_namespace: NamespaceNode = script_namespace
def get_looper(self) -> Generator[bool, bool, None]:
'''Генератор, предназначенный для управления циклом при запуске блока
задач.'''
while True:
condition_result = self._condition(self._datavars,
self._namespace,
self._script_namespace)
yield condition_result
class Until(Loop):
'''Класс цикла блока, аналогичного конструкции do ... while.'''
def __init__(self, condition: 'Var'):
self._condition: Var = condition
self._datavars: Union[Datavars, NamespaceNode] = None
self._namespace: Datavars = None
def initialize_loop(self, datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для инициализации объекта цикла.'''
self._datavars: Union[Datavars, NamespaceNode] = datavars
self._namespace: Union[NamespaceNode, None] = namespace
self._script_namespace: NamespaceNode = script_namespace
def get_looper(self) -> Generator[bool, bool, None]:
'''Генератор, предназначенный для управления циклом при запуске блока
задач.'''
yield True
while True:
yield self._condition(self._datavars,
self._namespace,
self._script_namespace)
class Block:
'''Класс блока задач.'''
def __init__(self, *tasks: List[Task], when: Union['Var', None] = None,
loop: Loop = Loop()):
self._tasks: List[Task] = tasks
self._rescue_tasks: List[Task] = None
self._condition: 'Var' = when
if not isinstance(loop, Loop):
raise TaskError('loop block parameter must be Loop type')
self._loop: Loop = loop
def run(self, output: IOModule,
datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для запуска выполнения задач, содержащихся в блоке.'''
handlers = set()
if (self._condition is None or self._condition(datavars, namespace,
script_namespace)):
self._loop.initialize_loop(datavars, script_namespace, namespace)
looper = self._loop.get_looper()
while next(looper):
for task in self._tasks:
try:
output = task.run(output, datavars, script_namespace,
namespace)
handlers.update(output)
except Exception as error:
if self._rescue_tasks is not None:
self._run_rescue(output, datavars,
script_namespace, namespace)
if isinstance(error, TaskError):
error.handlers.update(handlers)
raise error
return handlers
def _run_rescue(self, output: IOModule,
datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для запуска задач, указанных в rescue. Эти задачи выполняются,
если возникает ошибка при выполнении важной задачи, содержащейся в
блоке.'''
for task in self._rescue_tasks:
try:
task.run(output, datavars, script_namespace, namespace)
except Exception:
# TODO разобраться с тем, что делать если ошибка закралась в
# задачи из rescue.
pass
def rescue(self, *tasks: List[Task]) -> 'Block':
'''Метод для задания задач, выполняемых, если некоторая важная задача,
содержащаяся в блоке выполняется с ошибкой.'''
self._rescue_tasks = tasks
return self
class Handler:
'''Класс обработчика -- объекта скрипта, имеющего идентификатор,
запускаемого в конце выполнения скрипта в том случае, если его
идентификатор присутствовал в параметре notify хотя бы у одной успешно
выполненной задачи.'''
def __init__(self, handler_id, *tasks: List[Task]):
self._id: str = handler_id
self._tasks: List[Task] = tasks
@property
def id(self) -> str:
return self._id
def run(self, output: IOModule, datavars: Union[Datavars, NamespaceNode],
script_namespace: NamespaceNode,
namespace: NamespaceNode) -> None:
'''Метод для запуска выполнения задач, содержащихся в обработчике.'''
handlers = set()
for task in self._tasks:
try:
handlers.update(task.run(output, datavars, script_namespace,
namespace))
except Exception as error:
output.set_error(f"error during execution handle '{self._id}'"
f" in task '{task._name or task._id}':"
f" {str(error)}")
return handlers
class ConditionValue:
'''Базовый класс для всех объектов, предназначенных для создания условий.
'''
def __init__(self, value: Any):
self.value: Any = value
def get(self, datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode,
script_namespace: NamespaceNode) -> Any:
return self.value
class Var(ConditionValue):
'''Класс для создания условий выполнения задач.'''
def __init__(self, *variable: List[VariableNode],
value: Any = None, other: Any = None,
tasks: Union[List[str], None] = None,
function: Union[Callable, None] = None):
if variable:
self.variable: Union[VariableNode, str, None] = variable[0]
else:
self.variable = None
self.value: Any = value
self.other: Any = other
self.function: Callable = function
self.tasks: List[str] = tasks
def get(self, datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode,
script_namespace: NamespaceNode) -> Any:
'''Метод для расчета итогового значения ноды условия выполнения задачи.
'''
if self.function is not None:
if self.tasks:
return self.function(self.tasks, script_namespace)
elif self.other is not None:
return self.function(self.get_value(datavars, namespace,
script_namespace),
self.get_other(datavars, namespace,
script_namespace))
else:
return self.function(self.get_value(datavars, namespace,
script_namespace))
return self.get_value(datavars, namespace, script_namespace)
def get_value(self, datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode,
script_namespace: NamespaceNode) -> Any:
'''Метод для получения собственного значения ноды.'''
if self.value is not None:
return self.value.get(datavars, namespace, script_namespace)
elif self.variable is not None:
if isinstance(self.variable, str):
self.variable = DependenceAPI.find_variable(
self.variable, datavars,
current_namespace=namespace)
return self.variable.get_value()
else:
raise ConditionError('Can not get value for condition')
def get_other(self, datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode,
script_namespace: NamespaceNode) -> Any:
'''Метод для получения значения ноды, необходимой для выполнения
бинарной операции.'''
if isinstance(self.other, ConditionValue):
return self.other.get(datavars, namespace, script_namespace)
else:
return self.other
def __call__(self, datavars: Union[Datavars, NamespaceNode],
namespace: NamespaceNode,
script_namespace: NamespaceNode) -> Any:
return self.get(datavars, namespace, script_namespace)
def __eq__(self, other: Any) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x == y)
def __ne__(self, other: Any) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x != y)
def __lt__(self, other: Any) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x < y)
def __gt__(self, other: Any) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x > y)
def __le__(self, other: Any) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x <= y)
def __ge__(self, other: Any) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x >= y)
def __and__(self, other: bool) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x and y)
def __or__(self, other: bool) -> 'Var':
return Var(value=self, other=other, function=lambda x, y: x or y)
def __invert__(self) -> 'Var':
return Var(value=self, function=lambda x: not x)
def __lshift__(self, other: Any) -> 'Var':
'''Метод для переопределения операции <<, которая теперь играет роль
in.'''
return Var(value=self, other=other, function=lambda x, y: y in x)
def has(self, other: Any) -> 'Var':
'''Метод аналогичный операции in.'''
return Var(value=self, other=other, function=lambda x, y: y in x)
def match(self, pattern: str) -> 'Var':
'''Метод для проверки с помощью регулярки, если в строке нечто,
соответствующее паттерну.'''
return Var(value=self, other=pattern,
function=lambda x, y: bool(re.search(y, x)))
def regex(self, pattern: str, repl: str) -> 'Var':
'''Метод для реализации sub'''
return Var(value=self, other=(pattern, repl),
function=lambda x, y: re.sub(*y, x))
def replace(self, original: str, repl: str) -> 'Var':
'''Метод для реализации replace.'''
return Var(value=self, other=(original, repl),
function=lambda x, y: x.replace(*y))
def __repr__(self) -> 'Var':
return ("<Condition value>")
def Done(*tasks: List[str]) -> Var:
'''Функция создающая объект Var, получающий информацию о том, все ли
указанные методы выполнены.'''
return Var(tasks=tasks,
function=_is_done)
def DoneAny(*tasks: List[str]) -> Var:
'''Функция создающая объект Var, получающий информацию о том, есть ли
среди указанных методов те, которые выполнены.'''
return Var(tasks=tasks,
function=_is_done_any)
def Success(*tasks: List[str]) -> Var:
'''Функция создающая объект Var, получающий информацию о том, все ли
указанные методы выполнены и притом выполнены успешно.'''
return Var(tasks=tasks,
function=_is_success)
def SuccessAny(*tasks: List[str]) -> Var:
'''Функция создающая объект Var, получающий информацию о том, есть ли
среди указанных методов те, которые выполнены успешно.'''
return Var(tasks=tasks,
function=_is_success_any)
def Failed(*tasks: List[str]) -> Var:
'''Функция создающая объект Var, получающий информацию о том, все ли
указанные методы не выполнены или выполнены с ошибкой.'''
return Var(tasks=tasks,
function=_is_failed)
def FailedAny(*tasks: List[str]) -> Var:
'''Функция создающая объект Var, получающий информацию о том, есть ли
среди указанных методов те, которые не выполнены или выполнены с ошибкой.
'''
return Var(tasks=tasks,
function=_is_failed_any)
def _is_done(tasks, script_namespace: NamespaceNode) -> bool:
'''Функция проверяющая по содержимому пространства имен tasks все ли методы
из указанных выполнены.'''
for task in tasks:
if task not in script_namespace:
return False
return True
def _is_done_any(tasks, script_namespace: NamespaceNode) -> bool:
'''Функция проверяющая по содержимому пространства имен tasks есть ли среди
указанных методов те, которые выполнены.'''
for task in tasks:
if task in script_namespace:
return True
return False
def _is_success(tasks, script_namespace: NamespaceNode) -> bool:
'''Функция проверяющая по содержимому пространства имен tasks все ли методы
из указанных выполнены успешно.'''
for task in tasks:
if (task not in script_namespace or
not script_namespace[task].get_value().get_hash()['success']):
return False
return True
def _is_success_any(tasks, script_namespace: NamespaceNode) -> bool:
'''Функция проверяющая по содержимому пространства имен tasks есть ли среди
указанных методов те, которые выполнены успешно.'''
for task in tasks:
if (task in script_namespace and
script_namespace[task].get_value().get_hash()['success']):
return True
return False
def _is_failed(tasks, script_namespace: NamespaceNode) -> bool:
'''Функция проверяющая по содержимому пространства имен tasks все ли методы
из указанных не выполнены или выполнены с ошибкой.'''
for task in tasks:
if (task in script_namespace and
script_namespace[task].get_value().get_hash()['success']):
return False
return True
def _is_failed_any(tasks, script_namespace: NamespaceNode) -> bool:
'''Функция проверяющая по содержимому пространства имен tasks есть ли среди
указанных методов те, которые не выполнены или выполнены с ошибкой.'''
for task in tasks:
if (task not in script_namespace or
not script_namespace[task].get_value().get_hash()['success']):
return True
return False

@ -244,15 +244,24 @@ class ParametersProcessor:
def check_package_parameter(self, parameter_value):
try:
atom_object = self.package_atom_parser.parse_package_parameter(
if isinstance(parameter_value, str):
result = self.package_atom_parser.parse_package_parameter(
parameter_value)
elif isinstance(parameter_value, list):
result = []
for atom in parameter_value:
result.append(
self.package_atom_parser.parse_package_parameter(
atom)
)
except PackageAtomError as error:
if error.errno == NOTEXIST:
raise ConditionFailed(error.message, self.lineno)
else:
raise IncorrectParameter(error.message)
return atom_object
return result
def check_append_parameter(self, parameter_value):
if parameter_value not in self.available_appends:
@ -754,18 +763,18 @@ class CalculateExtension(Extension):
'''Класс расширения для jinja2, поддерживающий теги calculate-шаблонов.'''
_parameters_set = set()
parameters_processor = None
# Виды операций в теге save.
ASSIGN, APPEND, REMOVE = range(3)
def __init__(self, environment, datavars_module=Variables()):
def __init__(self, environment, parameters_processor: ParametersProcessor,
datavars_module=Variables()):
super().__init__(environment)
self.environment = environment
self.environment.globals.update({'pkg': self.pkg})
self._datavars = datavars_module
self.parameters_processor = parameters_processor
self.template_type = DIR
self.tags = {'calculate', 'save', 'set_var'}
@ -782,6 +791,8 @@ class CalculateExtension(Extension):
'save': self.parse_save}
def __call__(self, env):
# Необходимо для обеспечения возможности передать готовый объект
# расширения, а не его класс.
return self
def parse(self, parser):
@ -1245,9 +1256,6 @@ class TemplateEngine:
CalculateExtension._parameters_set =\
ParametersProcessor.available_parameters
CalculateExtension._datavars = datavars_module
self.available_appends = appends_set
ParametersProcessor.available_appends = appends_set
@ -1259,8 +1267,6 @@ class TemplateEngine:
chroot_path=chroot_path,
datavars_module=datavars_module)
CalculateExtension.parameters_processor = self.parameters_processor
if directory_path is not None:
self.environment = Environment(
loader=FileSystemLoader(directory_path))
@ -1269,6 +1275,7 @@ class TemplateEngine:
self.calculate_extension = CalculateExtension(
self.environment,
self.parameters_processor,
datavars_module=datavars_module)
self.environment.add_extension(self.calculate_extension)
@ -1313,8 +1320,7 @@ class TemplateEngine:
if parameters is not None:
self._parameters_object = parameters
else:
self._parameters_object = ParametersContainer(
parameters_dictionary={})
self._parameters_object = ParametersContainer()
if self._parameters_object.env:
CalculateContext._env_set = self._parameters_object.env.copy()

@ -2,13 +2,15 @@
#
from pprint import pprint
from ..utils.package import PackageAtomParser, Package, PackageNotFound,\
PackageAtomName, Version
PackageAtomName, Version, NonePackage
from ..utils.files import join_paths, write_file, read_file_lines, FilesError,\
check_directory_link, read_link, Process,\
get_target_from_link
from .template_engine import TemplateEngine, Variables, ConditionFailed,\
ParametersProcessor, DIR, FILE,\
ParametersContainer
from calculate.variables.datavars import StringType, ListType, NamespaceNode
from calculate.variables.loader import Datavars
from .format.base_format import Format
from ..utils.io_module import IOModule
from collections import OrderedDict, abc
@ -153,7 +155,8 @@ class TemplateWrapper:
template_text='',
target_package=None,
chroot_path='/',
config_archive_path='/var/lib/calculate/config-archive'):
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
@ -188,6 +191,10 @@ class TemplateWrapper:
# Пакет, к которому относится файл.
self.target_package = target_package
# Флаг, разрешающий работу с CONTENTS. Если False, то выключает
# protected для всех файлов блокирует все операции с CONTENTS и ._cfg.
self.dbpkg = dbpkg
# Флаг, указывающий, что файл является PROTECTED.
self.protected = False
@ -349,10 +356,12 @@ class TemplateWrapper:
# Если для шаблона и целевого файла никаким образом не удается
# определить пакет и есть параметр append -- шаблон пропускаем.
if parameter_package is None and file_package is None:
if self.parameters.append:
if self.parameters.append and self.parameters.append != 'skip':
raise TemplateCollisionError(
"'package' parameter is not defined for"
" template with 'append' parameter.")
else:
return
elif parameter_package is None:
self.target_package_name = file_package
@ -383,16 +392,19 @@ class TemplateWrapper:
# Проверим, является ли файл защищенным.
# Сначала проверяем по переменной CONFIG_PROTECT.
for protected_path in self._protected_set:
if self.target_path.startswith(protected_path):
self.protected = True
break
if self.dbpkg:
for protected_path in self._protected_set:
if self.target_path.startswith(protected_path):
self.protected = True
break
# Затем по переменной CONFIG_PROTECT_MASK.
for unprotected_path in self._unprotected_set:
if self.target_path.startswith(unprotected_path):
self.protected = False
break
# Затем по переменной CONFIG_PROTECT_MASK.
for unprotected_path in self._unprotected_set:
if self.target_path.startswith(unprotected_path):
self.protected = False
break
else:
self.protected = False
# Собираем список имеющихся ._cfg файлов.
cfg_pattern = os.path.join(os.path.dirname(self.target_path),
@ -586,7 +598,9 @@ 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/'):
execute_archive_path='/var/lib/calculate/.execute/',
dbpkg=True):
# TODO добавить список измененных файлов.
self.datavars_module = datavars_module
self.chroot_path = chroot_path
@ -598,6 +612,8 @@ class TemplateExecutor:
self.execute_archive_path = execute_archive_path
self.execute_files = OrderedDict()
self.dbpkg = dbpkg
# Список целевых путей измененных файлов. Нужен для корректиного
# формирования calculate-заголовка.
self.processed_targets = []
@ -661,7 +677,8 @@ class TemplateExecutor:
template_text=template_text,
target_package=target_package,
chroot_path=self.chroot_path,
config_archive_path=self.cl_config_archive_path)
config_archive_path=self.cl_config_archive_path,
dbpkg=self.dbpkg)
except TemplateTypeConflict as error:
raise TemplateExecutorError("type conflict: {}".format(str(error)))
@ -677,7 +694,8 @@ class TemplateExecutor:
else:
self._remove_file(template_object.target_path)
template_object.remove_from_contents()
if self.dbpkg:
template_object.remove_from_contents()
template_object.target_type = None
# Если был включен mirror, то после удаления файла завершаем
@ -727,7 +745,8 @@ class TemplateExecutor:
if template_object.target_type is None:
self._create_directory(template_object)
template_object.add_to_contents()
if self.dbpkg:
template_object.add_to_contents()
def _append_remove_directory(self,
template_object: TemplateWrapper) -> None:
@ -736,7 +755,8 @@ class TemplateExecutor:
if template_object.target_type is not None:
self._remove_directory(template_object.target_path)
template_object.remove_from_contents()
if self.dbpkg:
template_object.remove_from_contents()
def _append_skip_directory(self,
template_object: TemplateWrapper) -> None:
@ -759,7 +779,8 @@ class TemplateExecutor:
self._chown_directory(template_object.target_path,
template_object.parameters.chown)
template_object.clear_dir_contents()
if self.dbpkg:
template_object.clear_dir_contents()
def _append_link_directory(self, template_object: TemplateWrapper) -> None:
'''Метод описывающий действия для append = "link", если шаблон --
@ -776,7 +797,8 @@ class TemplateExecutor:
self._chown_directory(template_object.parameters.source,
template_object.parameters.chown)
template_object.add_to_contents()
if self.dbpkg:
template_object.add_to_contents()
def _append_replace_directory(self,
template_object: TemplateWrapper) -> None:
@ -784,10 +806,12 @@ class TemplateExecutor:
директория. Очищает директорию или создает, если ее нет.'''
if template_object.target_type is None:
self._create_directory(template_object)
template_object.add_to_contents()
if self.dbpkg:
template_object.add_to_contents()
else:
self._clear_directory(template_object.target_path)
template_object.clear_dir_contents()
if self.dbpkg:
template_object.clear_dir_contents()
def _append_join_file(self, template_object: TemplateWrapper,
join_before=False, replace=False) -> None:
@ -879,21 +903,22 @@ class TemplateExecutor:
if chmod:
self._chmod_file(save_path, chmod)
# Убираем все ._cfg файлы.
if template_object.cfg_list:
for cfg_file_path in template_object.cfg_list:
self._remove_file(cfg_file_path)
if self.dbpkg:
# Убираем все ._cfg файлы.
if template_object.cfg_list:
for cfg_file_path in template_object.cfg_list:
self._remove_file(cfg_file_path)
# Убираем целевой файл из CL.
self.calculate_config_file.remove_file(
# Убираем целевой файл из CL.
self.calculate_config_file.remove_file(
template_object.target_path)
# Обновляем CONTENTS.
if template_object.protected:
if template_object.parameters.unbound:
template_object.remove_from_contents()
else:
template_object.add_to_contents(
# Обновляем CONTENTS.
if template_object.protected:
if template_object.parameters.unbound:
template_object.remove_from_contents()
else:
template_object.add_to_contents(
file_md5=output_text_md5)
else:
changed_files = parsed_template.execute_format(
@ -904,7 +929,8 @@ class TemplateExecutor:
# Если исполняемый формат выдал список измененных файлов для
# изменения CONTENTS и при этом задан пакет -- обновляем
# CONTENTS.
if changed_files and template_object.target_package:
if (self.dbpkg and changed_files and
template_object.target_package):
template_object.update_contents_from_list(changed_files)
else:
if template_object.target_type is not None and not replace:
@ -993,7 +1019,8 @@ class TemplateExecutor:
if template_object.target_type is not None:
self._remove_file(template_object.target_path)
template_object.remove_from_contents()
if self.dbpkg:
template_object.remove_from_contents()
def _append_clear_file(self, template_object: TemplateWrapper) -> None:
'''Метод описывающий действия при append = "clear", если шаблон --
@ -1010,7 +1037,8 @@ class TemplateExecutor:
self._chmod_file(template_object.target_path,
template_object.parameters.chmod)
template_object.add_to_contents()
if self.dbpkg:
template_object.add_to_contents()
def _append_link_file(self, template_object: TemplateWrapper) -> None:
'''Метод описывающий действия при append = "link", если шаблон --
@ -1027,7 +1055,8 @@ class TemplateExecutor:
self._chown_file(template_object.parameters.source,
template_object.parameters.chown)
template_object.add_to_contents()
if self.dbpkg:
template_object.add_to_contents()
def _create_directory(self, template_object: TemplateWrapper,
path_to_create=None) -> None:
@ -1560,8 +1589,11 @@ class DirectoryTree:
class DirectoryProcessor:
'''Класс обработчика директорий шаблонов.'''
def __init__(self, action: str, datavars_module=Variables(), package='',
output_module=IOModule()):
self.action = action
output_module=IOModule(), dbpkg=True):
if isinstance(action, list):
self.action = action
else:
self.action = [action]
self.output = output_module
self.datavars_module = datavars_module
@ -1603,12 +1635,12 @@ class DirectoryProcessor:
# Инициализируем исполнительный модуль.
self.template_executor = TemplateExecutor(
datavars_module=self.datavars_module,
chroot_path=self.cl_chroot_path,
cl_config_archive=self.cl_config_archive,
cl_config_path=self.cl_config_path,
execute_archive_path=self.cl_exec_dir_path
)
datavars_module=self.datavars_module,
chroot_path=self.cl_chroot_path,
cl_config_archive=self.cl_config_archive,
cl_config_path=self.cl_config_path,
execute_archive_path=self.cl_exec_dir_path,
dbpkg=dbpkg)
# Инициализируем шаблонизатор.
self.template_engine = TemplateEngine(
@ -1633,8 +1665,18 @@ class DirectoryProcessor:
return
# Получаем список директорий шаблонов.
self.template_paths = (self.datavars_module.
main.cl_template_path.split(','))
# TODO переменная список.
if isinstance(self.datavars_module, (Datavars, NamespaceNode)):
var_type = self.datavars_module.main[
'cl_template_path'].variable_type
else:
var_type = StringType
if var_type is StringType:
self.template_paths = (self.datavars_module.
main.cl_template_path.split(','))
elif var_type is ListType:
self.template_paths = self.datavars_module.main.cl_template_path
# Список обработанных пакетов.
self.processed_packages = []
@ -1649,11 +1691,19 @@ class DirectoryProcessor:
'''Метод для получения из соответствующей переменной списка паттернов
для обнаружения игнорируемых в ходе обработки шаблонов файлов.'''
if 'cl_ignore_files' in self.datavars_module.main:
if isinstance(self.datavars_module, (Datavars, NamespaceNode)):
var_type = self.datavars_module.main[
'cl_ignore_files'].variable_type
else:
var_type = StringType
cl_ignore_files = self.datavars_module.main.cl_ignore_files
cl_ignore_files_list = []
for pattern in cl_ignore_files.split(','):
cl_ignore_files_list.append(pattern.strip())
if var_type is StringType:
for pattern in cl_ignore_files.split(','):
cl_ignore_files_list.append(pattern.strip())
elif var_type is ListType:
cl_ignore_files_list = cl_ignore_files
return cl_ignore_files_list
else:
return []
@ -1674,8 +1724,11 @@ class DirectoryProcessor:
# быстрой обработки параметра merge.
self.fill_trees = bool(self.for_package)
if self.for_package:
package = Package(self.for_package,
chroot_path=self.cl_chroot_path)
if self.for_package is NonePackage:
package = self.for_package
else:
package = Package(self.for_package,
chroot_path=self.cl_chroot_path)
else:
package = None
@ -1714,9 +1767,10 @@ class DirectoryProcessor:
if self.for_package not in self.packages_file_trees:
self.output.set_error(
"Error: package '{0}' not found for action '{1}'.".
format(self.for_package, self.action)
)
"Error: package '{0}' not found for action{1} '{2}'.".
format(self.for_package,
's' if len(self.action) > 1 else '',
', '.join(self.action)))
not_merged_packages.append(self.for_package)
continue
@ -1739,7 +1793,7 @@ class DirectoryProcessor:
self.output.set_error('Packages {} is not merged.'.
format(','.join(self.packages_to_merge)))
else:
self.output.set_success('All packages are merged...')
self.output.set_success('All packages are merged.')
def _run_exec_files(self):
'''Метод для выполнения скриптов, полученных в результате обработки
@ -1810,7 +1864,7 @@ class DirectoryProcessor:
directory_tree = {}
return
directory_parameters.print_parameters_for_debug()
# directory_parameters.print_parameters_for_debug()
# Корректируем путь к целевой директории.
current_target_path = self._make_target_path(current_target_path,
@ -1909,7 +1963,7 @@ class DirectoryProcessor:
if template_text is False:
continue
template_parameters.print_parameters_for_debug()
# template_parameters.print_parameters_for_debug()
# Если находимся на стадии заполнения дерева директорий --
# проверяем параметры package и action с заполнением дерева.
@ -1949,8 +2003,8 @@ class DirectoryProcessor:
continue
# * * * ПРИДУМАТЬ ОПТИМИЗАЦИЮ * * *
# Потому что накладывать дерево каждый раз, когда обнаружены файлы
# какого-то пакета не рационально.
# TODO Потому что накладывать дерево каждый раз, когда обнаружены
# файлы какого-то пакета не рационально.
# Обновляем дерево директорий для данного пакета, если происходит
# его заполнение.
if self.fill_trees:
@ -2124,8 +2178,12 @@ class DirectoryProcessor:
template_path))
return False
self.output.set_success('Processing directory: {}'.
format(template_path))
if template_type == DIR:
self.output.set_success('Processed directory: {}'.
format(template_path))
else:
self.output.set_success('Processed template: {}'.
format(template_path))
return target_path
def _update_package_tree(self, package, current_level_tree):
@ -2160,29 +2218,28 @@ class DirectoryProcessor:
("Action parameter is not set for template:"
" {0}").format(template_path))
return False
elif parameters.action != self.action:
elif parameters.action not in self.action:
self.output.set_warning(
("Action parameter value '{0}' does not match its"
" current value '{1}'. Template: {2}").format(
parameters.action,
self.action,
template_path))
" current value{1} '{2}'. Template: {3}").format(
parameters.action,
's' if len(self.action) > 1
else '',
', '.join(self.action),
template_path))
return False
if self.for_package:
if not parameters.package:
self.output.set_warning(
"'package' parameter is not defined. Template: {}".
format(template_path))
# пока что считаем наличие параметра package необязательным
# return False
if self.for_package is not NonePackage:
self.output.set_warning(
"'package' parameter is not defined. Template: {}".
format(template_path))
elif parameters.package != self.for_package:
if directory_tree is not None:
template_name = os.path.basename(template_path)
directory_tree[template_name] = None
self.output.set_warning('DirectoryTree: {}'.
format(directory_tree))
self.output.set_warning(
("'package' parameter value '{0}' does not "

@ -38,7 +38,7 @@ class KeyboardInputProcess():
return ''
class Process():
class Process:
'''Класс-обертка для работы с процессами.'''
STDOUT = STDOUT
PIPE = PIPE

@ -1,5 +1,6 @@
import sys
import os
from contextlib import contextmanager
class ColorPrint:
@ -172,3 +173,7 @@ class IOModule:
if self.save_messages:
self.messages.append(('fail', message))
self.console_output.print_not_ok(message)
@contextmanager
def set_task(self, message):
pass

@ -212,22 +212,32 @@ class PackageAtomName:
@property
def name(self):
if self._package_directory is None:
return 'None'
return os.path.basename(self._package_directory)
@property
def category(self):
if self._package_directory is None:
return 'None'
return os.path.basename(os.path.dirname(self._package_directory))
@property
def atom(self):
if self._package_directory is None:
return 'None'
return "{}/{}".format(self.category, self.name)
@property
def version(self):
if self._package_directory is None:
return 'None'
return self._version
@property
def use_flags(self):
if self._package_directory is None:
return []
use_path = os.path.join(self._package_directory, 'USE')
try:
return read_file(use_path).strip('\n').split(' ')
@ -241,6 +251,8 @@ class PackageAtomName:
@property
def slot(self):
if self._package_directory is None:
return None
slot_path = os.path.join(self._package_directory, 'SLOT')
try:
return read_file(slot_path).strip('\n')
@ -263,9 +275,13 @@ class PackageAtomName:
return False
def __bool__(self):
if self._package_directory is None:
return True
return bool(self._package_directory)
def __repr__(self):
if self._package_directory is None:
return '<PackageAtomName: None>'
return '<PackageAtomName: {}/{}>'.format(self.category,
self.name)
@ -273,6 +289,9 @@ class PackageAtomName:
return hash(self._package_directory)
NonePackage = PackageAtomName({'pkg_path': None, 'version': None})
class PackageAtomParser:
'''Класс для парсинга параметра package, его проверки, а также определения
принадлежности файла пакету.'''

@ -1,8 +1,9 @@
# vim: fileencoding=utf-8
#
import re
import ast
import dis
from typing import List, Any
from typing import List, Any, Union
from contextlib import contextmanager
from inspect import signature, getsource
from types import FunctionType, LambdaType
@ -37,6 +38,7 @@ class CyclicVariableError(VariableError):
class VariableType:
'''Базовый класс для типов.'''
name = 'undefined'
python_type = None
@classmethod
def process_value(cls, value, variable):
@ -52,6 +54,7 @@ class IniType(VariableType):
'''Класс, соответствующий типу переменных созданных в calculate.ini файлах.
'''
name = 'ini'
python_type: type = str
@classmethod
def process_value(cls, value, variable):
@ -61,6 +64,7 @@ class IniType(VariableType):
class StringType(VariableType):
'''Класс, соответствующий типу переменных с строковым значением.'''
name = 'string'
python_type: type = str
@classmethod
def process_value(cls, value, variable) -> str:
@ -79,6 +83,7 @@ class StringType(VariableType):
class IntegerType(VariableType):
'''Класс, соответствующий типу переменных с целочисленным значением.'''
name = 'integer'
python_type: type = int
@classmethod
def process_value(cls, value, variable) -> int:
@ -97,6 +102,7 @@ class IntegerType(VariableType):
class FloatType(VariableType):
'''Класс, соответствующий типу переменных с вещественным значением.'''
name = 'float'
python_type: type = float
@classmethod
def process_value(cls, value, variable) -> float:
@ -115,6 +121,7 @@ class FloatType(VariableType):
class BooleanType(VariableType):
'''Класс, соответствующий типу переменных с булевым значением.'''
name = 'bool'
python_type: type = bool
true_values = {'True', 'true'}
false_values = {'False', 'false'}
@ -138,10 +145,10 @@ class BooleanType(VariableType):
class ListType(VariableType):
name = 'list'
python_type: type = list
@classmethod
def process_value(cls, value, variable) -> list:
# TODO нормально все сделать.
if isinstance(value, list):
return value
elif isinstance(value, str):
@ -184,6 +191,17 @@ class HashValue:
self = self.master_variable.get_value()[self.key]
return self.value
def set(self, value) -> None:
'''Метод для задания одного значения хэша без изменения источника
значения.'''
current_hash = self.master_variable.get_value().get_hash()
current_hash[self.key] = value
self.master_variable.set(current_hash)
def reset(self):
'''Метод для сброса значения, установленного поверх источника.'''
self.master_variable.reset()
class Hash:
'''Класс реализующий контейнер для хранения хэша в переменной
@ -251,6 +269,7 @@ class Hash:
class HashType(VariableType):
'''Класс, соответствующий типу переменных хэшей.'''
name = 'hash'
python_type: type = dict
@classmethod
def process_value(cls, values, variable) -> Hash:
@ -266,6 +285,8 @@ class HashType(VariableType):
@classmethod
def fixed(cls, variable_object) -> None:
'''Метод, который передается переменной вместо типа, если нужно задать
тип фиксированного хэша.'''
variable_object.variable_type = cls
variable_object.fixed = True
@ -292,13 +313,16 @@ class Table:
type(row)))
def get_table(self) -> List[dict]:
'''Метод для получения всего списка строк таблицы.'''
return self._rows
def add_row(self, row: dict):
'''Метод для добавления строк в таблицу.'''
self._check_columns(row)
self._rows.append(row)
def change_row(self, row: dict, index: int) -> None:
'''Метод для замены существующей строки.'''
self._check_columns(row)
self._rows[index] = row
@ -340,6 +364,8 @@ class Table:
class TableType(VariableType):
name = 'table'
# TODO Сомнительно.
python_type: type = list
@classmethod
def process_value(self, value: List[dict], variable) -> Table:
@ -353,6 +379,16 @@ class TableType(VariableType):
return Table(value, variable)
class Static:
'''Класс для указания в качестве аргументов зависимостей статичных
значений, а не только переменных.'''
def __init__(self, value):
self.value = value
def get_value(self):
return self.value
class VariableWrapper:
'''Класс обертки для переменных, с помощью которого отслеживается
применение переменной в образовании значения переменной от нее зависящей.
@ -365,7 +401,8 @@ class VariableWrapper:
def value(self):
'''Метод возвращающий значение переменной и при этом добавляющий его в
подписки.'''
self._subscriptions.add(self._variable)
if not isinstance(self._variable, Static):
self._subscriptions.add(self._variable)
value = self._variable.get_value()
if isinstance(value, Hash):
value = value.get_hash()
@ -429,17 +466,23 @@ class DependenceSource:
содержащего переменные и строки, в список аргументов состоящий только
из нод переменных и значений хэшей.'''
if not self._args_founded:
for index in range(0, len(self._args)):
if isinstance(self._args[index], str):
variable = Dependence._find_variable(
self._args[index],
current_namespace=namespace)
if variable is None:
raise DependenceError("variable '{}' not found".
format(variable))
self._args[index] = variable
self._args = self.find_variables(self._args, namespace)
self._args_founded = True
def find_variables(self, variables, namespace):
'''Метод для поиска переменных по заданным путям к ним.'''
output = []
for variable in variables:
if isinstance(variable, str):
variable = Dependence._get_variable(
variable,
current_namespace=namespace)
if variable is None:
raise DependenceError("variable '{}' not found".
format(variable))
output.append(variable)
return output
@property
def subscriptions(self) -> set:
return self._subscriptions
@ -460,12 +503,18 @@ class DependenceSource:
# Проверяем совпадение количества аргументов функции и заданных для
# функции переменных.
self.check_signature(function_to_check, self._args)
@staticmethod
def check_signature(function_to_check, arguments):
'''Метод для проверки соответствия сигнатуры функции и заданного для
нее набора аргументов.'''
function_signature = signature(function_to_check)
if not len(self._args) == len(function_signature.parameters):
raise VariableError("the depend function takes {} arguments,"
" while {} is given".format(
if not len(arguments) == len(function_signature.parameters):
raise DependenceError("the depend function takes {} arguments,"
" while {} is given".format(
len(function_signature.parameters),
len(self._args)))
len(arguments)))
def __ne__(self, other) -> bool:
if not isinstance(other, DependenceSource):
@ -528,6 +577,11 @@ class VariableNode:
self.value = None
self._invalidated = True
# Флаг имеющий значение только для переменных типа HashType.
# Предназначен для включения проверки соответствия полей хэша при
# установке значения.
self._fixed = False
# Источник значения переменной, может быть значением, а может быть
# зависимостью.
self._source = source
@ -539,10 +593,6 @@ class VariableNode:
self.set_by_user = False
self._readonly = False
# Флаг имеющий значение только для переменных типа HashType.
# Предназначен для включения проверки соответствия полей хэша при
# установке значения.
self._fixed = False
def update_value(self) -> None:
'''Метод для обновления значения переменной с помощью указанного
@ -579,7 +629,8 @@ class VariableNode:
self.value = self.variable_type.process_value(value, self)
self._invalidated = False
def set_variable_type(self, variable_type, readonly=None, fixed=None):
def set_variable_type(self, variable_type: VariableType,
readonly: str = None, fixed: str = None) -> None:
'''Метод для установки типа переменной.'''
if readonly is not None and isinstance(readonly, bool):
self._readonly = readonly
@ -604,9 +655,8 @@ class VariableNode:
self._invalidated = False
self.set_by_user = False
self._invalidate()
self.set_by_user = True
self.value = self.variable_type.process_value(value, self)
self._invalidate(set_by_user=True)
def reset(self):
'''Метод для сброса пользовательского значения.'''
@ -621,10 +671,6 @@ class VariableNode:
@source.setter
def source(self, source) -> None:
if self._readonly:
raise VariableError("can not change the variable '{}': read only".
format(self.get_fullname()))
# Если источники не совпадают или текущее значение переменной было
# установлено пользователем, то инвалидируем переменную и меняем
# источник.
@ -653,21 +699,22 @@ class VariableNode:
def fixed(self, value) -> bool:
self._fixed = value
def _invalidate(self) -> None:
def _invalidate(self, set_by_user=False) -> None:
'''Метод для инвалидации данной переменной и всех зависящих от нее
переменных.'''
# print('{} is invalidated'.format(self.get_fullname()))
if not self._invalidated and not self.set_by_user:
if self.variable_type is not HashType:
self.value = None
self._invalidated = True
self.set_by_user = set_by_user
for subscriber in self.subscribers:
subscriber._invalidate()
@contextmanager
def _start_calculate(self):
'''Менеджер контекста устанавливающий флаг, указывающий, что данная
переменная в состоянии расчета.'''
переменная в состоянии расчета. В данном случае необходима только для
того, чтобы при получении значения параметра внутри метода для расчета
не иницировалось ее обновление, которое может привести к рекурсии.'''
try:
self.calculating = True
yield self
@ -676,6 +723,8 @@ class VariableNode:
def get_value(self) -> Any:
'''Метод для получения значения переменной.'''
if self._invalidated and self._source is None and self._value is None:
return None
if self._invalidated and not self.set_by_user:
self.update_value()
return self.value
@ -747,6 +796,12 @@ class NamespaceNode:
else:
return self.name
def get_package_name(self) -> str:
if self.parent.name == '<root>':
return self.name
else:
return self.parent.get_package_name()
def __getattr__(self, name: str):
'''Метод возвращает ноду пространства имен или значение переменной.'''
if name in self.namespaces:
@ -790,41 +845,39 @@ class DependenceAPI(metaclass=Singleton):
def __call__(self, *variables, depend=None):
subscriptions = list()
for variable in variables:
# Поиск переменных теперь происходит при обновлении значения
# переменной.
# if isinstance(variable, str):
# variable = self._find_variable(variable)
# if variable is None:
# raise DependenceError("variable '{}' not found".format(
# variable))
# elif not isinstance(variable, VariableNode):
# raise DependenceError("dependence variables must be 'str' or"
# " 'VariableNode' not '{}'".format(
# type(variable)))
if not (isinstance(variable, str) or
isinstance(variable, VariableNode)):
raise DependenceError("dependence variables must be 'str' or"
" 'VariableNode' not '{}'".format(
type(variable)))
isinstance(variable, VariableNode) or
isinstance(variable, Static)):
variable = Static(variable)
subscriptions.append(variable)
return DependenceSource(subscriptions, depend=depend)
def _find_variable(self, variable_name, current_namespace=None):
def _get_variable(self, variable_name, current_namespace=None):
'''Метод для поиска переменной в пространствах имен.'''
if current_namespace is None:
current_namespace = self.current_namespace
return self.find_variable(variable_name, self.datavars_root,
current_namespace=current_namespace)
@staticmethod
def find_variable(variable_name, datavars_root, current_namespace=None):
'''Метод для поиска переменных по строковому пути от корня переменных
или от текущего пространства имен.'''
name_parts = variable_name.split('.')
if not name_parts[0]:
namespace = current_namespace
for index in range(1, len(name_parts)):
if not name_parts[index]:
namespace = namespace.parent
else:
name_parts = name_parts[index:]
break
if current_namespace is not None:
namespace = current_namespace
for index in range(1, len(name_parts)):
if not name_parts[index]:
namespace = namespace.parent
else:
name_parts = name_parts[index:]
break
else:
raise VariableNotFoundError(
f"variable '{variable_name}' is not found.")
else:
namespace = self.datavars_root
namespace = datavars_root
search_result = namespace
for part in name_parts:
@ -832,6 +885,44 @@ class DependenceAPI(metaclass=Singleton):
return search_result
class CopyAPI(metaclass=Singleton):
'''Класс для создания зависимостей представляющих собой простое копирование
значения переменной в зависимую переменную.'''
def __call__(self, variable: Union[str, VariableNode]):
return Dependence(variable)
class FormatAPI(metaclass=Singleton):
'''Класс для создания зависимостей представляющих собой форматируемую
строку с указанием переменных в тех местах, где их значения должны быть
подставлены в строку.'''
pattern = re.compile(
r'{\s*([a-zA-Z][a-zA-Z_0-9]+)?(.[a-zA-Z][a-zA-Z_0-9]+)+\s*}')
def __call__(self, string: str):
vars_list = []
def subfunc(matchobj):
vars_list.append(matchobj.group(0)[1:-1].strip())
return '{}'
format_string = self.pattern.sub(subfunc, string)
def depend_function(*args):
values = [arg.value for arg in args]
return format_string.format(*values)
return Dependence(*vars_list, depend=depend_function)
class CalculateAPI(metaclass=Singleton):
'''Метод для создания зависимостей, представляющих собой функцию для
вычисления значения зависимой переменной на основе значений указанных
переменных.'''
def __call__(self, *args):
depend_function = args[0]
return Dependence(*args[1:], depend=depend_function)
class VariableAPI(metaclass=Singleton):
'''Класс для создания переменных при задании их через
python-скрипты.'''
@ -943,5 +1034,9 @@ class NamespaceAPI(metaclass=Singleton):
Dependence = DependenceAPI()
Copy = CopyAPI()
Format = FormatAPI()
Calculate = CalculateAPI()
Variable = VariableAPI()
Namespace = NamespaceAPI(Variable, Dependence)

@ -8,7 +8,7 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\
ListType, IntegerType,\
FloatType, IniType, TableType,\
Namespace, HashType,\
VariableNotFoundError
VariableNotFoundError, VariableError
from calculate.utils.gentoo import ProfileWalker
from calculate.utils.files import read_file, FilesError
from calculate.utils.tools import Singleton
@ -197,7 +197,6 @@ class NamespaceIniFiller:
for section in sections:
if isinstance(self.current_namespace, Datavars):
if section not in self.current_namespace:
# TODO Поменять на логгирование.
self._set_error(lineno, 'VariableError',
"variables package '{}' is not found.".
format(section))
@ -216,7 +215,6 @@ class NamespaceIniFiller:
self.current_namespace.add_namespace(
NamespaceNode(section))
else:
# TODO Поменять на логгирование.
self._set_error(lineno, 'VariableError',
"can not create namespace '{}.{}' in"
" not 'custom' namespace.".format(
@ -243,7 +241,7 @@ class NamespaceIniFiller:
" package '{}' is not found.".format(
".".join(sections),
section))
return
return
elif isinstance(current_namespace, NamespaceNode):
if section in current_namespace.namespaces:
current_namespace = current_namespace[section]
@ -261,6 +259,7 @@ class NamespaceIniFiller:
" is not found.".format(
".".join(sections)))
return
if not self.modify_only:
current_namespace.clear()
else:
@ -325,21 +324,26 @@ class NamespaceIniFiller:
if key not in self.current_namespace:
self.define_variable(key, value, lineno)
else:
self.change_value(key, value)
self.change_value(key, value, lineno)
elif optype == Define.append:
if key not in self.current_namespace:
self.define_variable(key, value, lineno)
else:
self.append_value(key, value)
self.append_value(key, value, lineno)
elif optype == Define.remove:
if key not in self.current_namespace:
self.define_variable(key, value, lineno)
else:
self.remove_value(key, value)
self.remove_value(key, value, lineno)
def change_value(self, key: str, value: str) -> None:
def change_value(self, key: str, value: str, lineno) -> None:
'''Метод для изменения значения переменной.'''
variable = self.current_namespace[key]
if variable.readonly:
self._set_error(lineno, 'VariableError',
"can not change readonly variable "
f"'{self.current_namespace.get_fullname()}.{key}'")
return
variable.source = value
def define_variable(self, key: str, value: str, lineno) -> None:
@ -354,9 +358,14 @@ class NamespaceIniFiller:
self.current_namespace.get_fullname(),
key))
def append_value(self, key: str, value: str) -> None:
def append_value(self, key: str, value: str, lineno) -> None:
'''Метод выполняющий действия возложенные на оператор +=.'''
variable = self.current_namespace[key]
if variable.readonly:
self._set_error(lineno, 'VariableError',
"can not change readonly variable "
f"'{self.current_namespace.get_fullname()}.{key}'")
return
variable_value = variable.get_value()
if variable.variable_type is IniType:
@ -383,10 +392,16 @@ class NamespaceIniFiller:
variable.source = variable_value
def remove_value(self, key: str, value: str) -> None:
def remove_value(self, key: str, value: str, lineno) -> None:
'''Метод выполняющий действия возложенные на оператор -=.'''
variable = self.current_namespace[key]
if variable.readonly:
self._set_error(lineno, 'VariableError',
"can not change readonly variable "
f"'{self.current_namespace.get_fullname()}.{key}'")
return
variable_value = variable.get_value()
if variable.variable_type is IniType:
value_list = value.split(',')
variable_list = [item.strip() for item in
@ -413,6 +428,11 @@ class NamespaceIniFiller:
def update_hash(self, key: str, value: str, optype, lineno):
'''Метод для изменения переменных хэшей через calculate.ini.'''
if self.current_namespace.readonly:
self._set_error(lineno, 'VariableError',
"can not change readonly hash variable "
f"'{self.current_namespace.get_fullname()}'")
return
hash_to_update = self.current_namespace.get_value().get_hash()
if key not in hash_to_update:
# Если ключ отсутствует в хэше, то проверяем, является ли он
@ -515,6 +535,8 @@ class VariableLoader:
" variables.")
return
self.output.set_info("Load variables from profile: '{}'.".format(
profile_path))
self._fill_from_profile_ini(profile_path)
def load_user_variables(self):
@ -592,8 +614,6 @@ class VariableLoader:
def fill_from_custom_ini(self, file_path: str):
'''Метод для заполнения переменных из конкретного указанного файла.'''
self.output.set_info('Loading variables from file: {}'.format(
file_path))
if os.path.exists(file_path):
ini_file_text = read_file(file_path)
self.ini_filler.fill(self.datavars, ini_file_text)
@ -698,6 +718,8 @@ class Datavars:
self._loader.load_from_profile()
self._loader.load_user_variables()
# Создаем словарь переменных, которые нужно сохранить потом в
# ini-файлах.
try:
self.variables_to_save = {target: dict() for target in
self.system.env_order if target in
@ -725,6 +747,15 @@ class Datavars:
available_packages.update({file_name: file_path})
return available_packages
def _load_package(self, package_name):
'''Метод для загрузки переменных содержащихся в указанном пакете.'''
self.output.set_info("Loading package '{}'".format(package_name))
try:
self._loader.load_variables_package(package_name)
except Exception as error:
raise VariableError("Can not load variables package: {}".
format(error))
def __getattr__(self, package_name: str):
'''Метод возвращает ноду пространства имен, соответствующего искомому
пакету.'''
@ -738,7 +769,7 @@ class Datavars:
raise VariableNotFoundError("variables package '{}' is not found".
format(package_name))
else:
self._loader.load_variables_package(package_name)
self._load_package(package_name)
return self.root[package_name]
def __getitem__(self, package_name: str) -> None:
@ -750,11 +781,14 @@ class Datavars:
custom_namespace = NamespaceNode('custom')
self.root.add_namespace(custom_namespace)
return self.root[package_name]
elif package_name == 'tasks':
self.create_tasks()
return self.root[package_name]
elif package_name not in self._available_packages:
raise VariableNotFoundError("variables package '{}' is not found".
format(package_name))
else:
self._loader.load_variables_package(package_name)
self._load_package(package_name)
return self.root[package_name]
def __contains__(self, package_name):
@ -764,16 +798,30 @@ class Datavars:
custom_namespace = NamespaceNode('custom')
self.root.add_namespace(custom_namespace)
return True
elif package_name == 'tasks':
self.create_tasks()
return True
elif (package_name not in self._available_packages
and package_name != 'custom'):
return False
else:
self._loader.load_variables_package(package_name)
self._load_package(package_name)
return True
def add_namespace(self, namespace_node):
self.root.add_namespace(namespace_node)
def create_tasks(self):
'''Метод для создания всех необходимых пространств имен для работы
задач.'''
tasks = NamespaceNode('tasks')
self.add_namespace(tasks)
env = NamespaceNode('env')
tasks.add_namespace(env)
env.add_namespace('loop')
@property
def namespaces(self):
return self.root.namespaces

@ -1,575 +0,0 @@
# import site
# import os
import re
# import sys
import types
# import functools
from contextlib import contextmanager
_ = lambda x: x
class BaseClass:
BASE_CLASS = "BaseClass"
@classmethod
def is_implementation(cls, check_class):
'''Метод для проверки того, что класс является производным базового
класса, а не самим базовым. Используется в автозагрузке переменных из
модулей.'''
if isinstance(check_class, type) and issubclass(check_class, cls):
if check_class.BASE_CLASS == cls.BASE_CLASS and \
check_class.__name__ != cls.BASE_CLASS:
return True
return False
class VariableError(Exception):
pass
class VariableNotFoundError(VariableError):
pass
class CyclicVariableError(VariableError):
def __init__(self, *queue):
self.queue = queue
def __str__(self):
return _("Cyclic dependence in variables: {}").format(", ".join(
self.queue[:-1]))
class VariableType:
'''Базовый класс для объектов свойств.'''
def __init__(self, parent):
self.parent = parent
class StringVariable(VariableType):
'''Класс свойства, соответствующий переменным просто хранящим строки как
значения.'''
pass
class ListVariable(VariableType):
'''Класс свойства, соответствующий переменным хранящим списки значений.'''
def set_value(self, value, force=False):
if isinstance(value, (list, tuple)):
return value
elif isinstance(value, str):
return value.split(",")
else:
raise VariableError(
_("The value of variable '{varname}' must be list").format(
varname=self.parent.name))
def post_get(self, value):
return value
class ReadonlyVariable(VariableType):
'''Класс свойства, соответствующий переменным только для чтения.'''
def set_value(self, value, force=False):
if not force:
raise VariableError(
_("Attempting to rewrite readonly variable {}").format(
self.parent.name))
return value
class IntegerVariable(VariableType):
'''Класс свойства, соответствующий целочисленным переменным.'''
re_match = re.compile(r"^-?\d+$")
def check(self, value):
if value and not self.re_match.match(value):
raise VariableError(
_("The value of variable '{varname}' must be integer").format(
varname=self.parent.name))
def post_get(self, value):
return int(value)
class BooleanVariable(VariableType):
'''Класс свойства, соответствующий логическим переменным.'''
def post_get(self, value):
return value == "true"
class ChoiceVariable(VariableType):
'''Класс свойства, соответствующий переменным выбора.'''
def check(self, value):
choices = self.parent.choice()
if value and value not in choices:
raise VariableError(
_("The value for variable '{varname}' may be "
"{vartype}").format(varname=self.parent.name,
vartype=",".join(choices)))
def choice(self):
if self.parent.__class__.choice == Variable.choice and \
self.parent.__class__.choice_comment == Variable.choice_comment:
raise VariableError(_("Wrong choice variable {}").format(
self.parent.name))
return [x[0] for x in self.parent.choice_comment()]
def choice_comment(self):
return [(x, x) for x in self.parent.choice()]
class DefaultValue(VariableType):
'''Класс свойства, соответствующий логическим переменным.'''
def __init__(self, value):
self.value = value
self.parent = None
def get_value(self, subscribers=None):
if self.parent._value is None:
self.parent._unsubscribe_depends()
self.parent._value = self.value
class IniCreated(DefaultValue):
'''Класс свойства, соответствующий переменным, полученным из ini-файла.'''
pass
class Variable(BaseClass):
BASE_CLASS = "Variable"
On = "on"
Off = "off"
value = ""
class Type:
Bool = "bool"
List = "list"
String = "string"
Integer = "int"
Choice = "choice"
properties = (StringVariable,)
def __init__(self, name):
self.name = name
self._value = None
self.vars = None
self.subscribers = set()
# множество переменных, на которые подписана данная переменная.
self.subscriptions = set()
self.calculating = False
self._properties = [x(self) for x in self.properties]
# версия значения переменной, если переменная со времен получения
# значения успела обновиться, исключить сброс ее значения
@property
def fullname(self):
'''Метод для получения полного имени переменной.'''
return "{}.{}".format(self.vars.get_fullname(), self.name)
def add_property(self, prop):
'''Метод для подключения свойств к переменной.'''
prop.parent = self
self._properties.insert(0, prop)
def find_property(self, property_class):
'''Метод для проверки наличия заданного свойства у переменной.'''
for _property in self._properties:
if isinstance(_property, property_class):
return _property
else:
return None
def _emit_invalidate(self, subscribers):
print('subscribers = {}'.format(subscribers))
for f in subscribers:
print('f = {}'.format(f))
f()
def _unsubscribe_depends(self):
for subscription in self.subscriptions:
subscription.update_unsubscribe(self.invalidate)
self.subscriptions = set()
def update_subscribe(self, f_or_var):
if isinstance(f_or_var, Variable):
f_or_var.subscriptions.add(self)
self.subscribers.add(f_or_var.invalidate)
else:
self.subscribers.add(f_or_var)
def update_unsubscribe(self, f):
if f in self.subscribers:
self.subscribers.remove(f)
def invalidate(self):
'''Метод для инвалидации всех переменных-подписчиков.'''
print('invalidation method')
print('subscribers = {}'.format(self.subscribers))
print('subscriptions = {}'.format(self.subscriptions))
self._value = None
if self.subscribers:
subscribers = self.subscribers
self.subscribers = set()
self._emit_invalidate(subscribers)
def set_parent(self, namespace):
'''Метод для установки родительского пространства имен переменной.'''
self.vars = namespace
@contextmanager
def _start_calculate(self):
'''Менеджер контекста для запуска расчета переменных с учетом всех
взаимозависимостей.'''
try:
self.calculating = True
yield self
finally:
self.calculating = False
def get_value(self, subscription=None):
for get_method in self.call_properties("get_value"):
get_method(subscription)
if self.calculating:
raise CyclicVariableError(self.name)
if self._value is None:
with self._start_calculate():
try:
self._unsubscribe_depends()
self._value = self.get()
if isinstance(self._value, types.GeneratorType):
self._value = list(self._value)
except CyclicVariableError as error:
raise CyclicVariableError(self.name, *error.queue)
if subscription is not None:
self.update_subscribe(subscription)
return self.post_get(self._value)
def post_get(self, value):
for post_get_method in self.call_properties("post_get"):
result = post_get_method(value)
if result is not None:
return result
return value
def call_properties(self, method_name, *args):
'''Метод для вызова указанного метода у всех объектов, которыми владеет
переменная.'''
for _property in self._properties:
property_method = getattr(_property, method_name, None)
if property_method:
yield property_method
def set_value(self, value, force=False):
'''Метод для установки некоторого заданного значения всем объектам,
принадлежащим переменной.'''
for setter in self.call_properties("set_value"):
value = setter(value, force)
value = self.set(value)
self.check(value)
self.invalidate()
self._value = value
self._unsubscribe_depends()
def check(self, value):
'''Метод для проверки корректности устанавливаемого значения путем
вызова проверочных методов всех объектов, которыми владеет переменная.
'''
for checker in self.call_properties("check"):
checker(value)
def get(self):
'''Метод для заполнения переменной.'''
return self.value
def get_comment_value(self, subscription=None):
'''Этот метод вызывается внутри методов get.'''
val = self.get_comment()
if subscription is not None:
self.update_subscribe(subscription)
return val
def get_comment(self):
'''Метод для установки .'''
for get_method in self.call_properties("get_comment"):
return get_method()
return self.get_value()
def set(self, value):
'''Метод для модификации переменной.'''
return value
def choice(self):
'''Метод возвращет список доступных значений для переменной.'''
for f in self.call_properties("choice"):
return f()
return []
def choice_comment(self):
for f in self.call_properties("choice_comment"):
return f()
return []
class NamespaceError(Exception):
pass
class Namespace(BaseClass):
'''Класс пространства имен.'''
BASE_CLASS = "Namespace"
def __init__(self, name="", parent=None):
self._name = name
self.variables = {}
self.childs = {}
self.parent = parent or self
self.root = self
self._next_namespace = 0
def get_fullname(self):
'''Метод для получения полного имени пространства имен, включающего в
себя имена всех родительских пространств имен.'''
if self.parent is not self and self.parent.parent is not self.parent:
return "{}.{}".format(self.parent.get_fullname(), self._name)
else:
return self._name
def __getattr__(self, name):
if name in self.childs:
return self.childs[name]
elif name in self.variables:
return self.variables[name]
else:
raise VariableNotFoundError(
_("Variable or namespace {varname} not found").format(
varname="{}.{}".format(self.get_fullname(),
name)))
def clear_childs(self):
'''Метод для глубокой очистки пространства имен от всех дочерних
пространств имен.'''
for child in self.childs.values():
child.clear_childs()
self.childs = {}
def __getitem__(self, name):
return getattr(self, str(name))
def __setitem__(self, name, value):
return getattr(self, str(name)).set_value(value)
def __iter__(self):
# Сортировка: вначале числовые ключи потом прочие.
def sortkey(x):
k, v = x
if k.isdigit():
return (0, int(k), k)
else:
return (1, 0, k)
for k, v in sorted(self.childs.items(), key=sortkey):
yield v
def __contains__(self, name):
return name in self.childs or name in self.variables
def add_string_variable(self, varname: str, value: str):
'''Метод для добавления переменной с помощью строк.'''
var = Variable(varname)
var.value = value
var.set_parent(self)
self.variables[varname] = var
def add_variable(self, variable: Variable):
'''Метод для добавления переменной.'''
self.variables[variable.name] = variable
variable.set_parent(self)
def _get_next_namespace_name(self):
'''Метод для получения имени следующего по счету пространства имен.'''
name = str(self._next_namespace)
while name in self.childs:
self._next_namespace += 1
name = str(self._next_namespace)
return name
def add_namespace(self, namespace=None, name=None):
'''Метод для добавления пространств имен.'''
if name is None:
if namespace is None:
name = self._get_next_namespace_name()
else:
name = namespace._name
if namespace is None:
namespace = Namespace(name)
self.childs[name] = namespace
namespace.parent = self
namespace.root = self.root
return namespace
class HashVariable(Namespace):
'''Класс переменных, представляющих собой словарь.'''
BASE_CLASS = "HashVariable"
class HashValue(Variable):
BASE_CLASS = "HashValue"
def __init__(self, name, master_variable):
super().__init__(name)
self.parent = None
self.master_variable = master_variable
def get_value(self, subscription=None):
return self.master_variable.getHashValue(self.name, subscription)
def set_value(self, value, force=False):
return self.master_variable.setHashValue(self.name, value, force)
def invalidate(self):
self.master_variable.invalidate()
class Data(Variable):
BASE_CLASS = "Data"
def getHashValue(self, name, subscription=None):
return self.get_value(subscription)[name]
def setHashValue(self, name, value, force):
if name in self.readonly_vars and not force:
raise VariableError(
_("Attempting to rewrite readonly variable {}").
format(name))
data = self.get_value().copy()
data[name] = value
self.set_value(data, force)
def get_value(self, subscription=None):
return self.master_variable.get_value(subscription)
hash_vars = []
readonly_vars = []
def __init__(self, name, parent=None, hash_vars=None):
super().__init__(name)
if hash_vars is not None:
self.hash_vars = hash_vars
self.parent = parent
if not self.hash_vars:
raise VariableError(
_("Missed '{attrname}' attribute for hash variable {varname}").
format(attrname="hash_vars", varname=self.get_fullname()))
self.master_variable = self.Data(name)
self.master_variable.set_parent(parent)
self.master_variable.readonly_vars = self.readonly_vars
for varname in self.hash_vars:
var = self.HashValue(varname, self.master_variable)
self.add_variable(var)
def invalidate(self):
self.master_variable.invalidate()
class TableVariable(Namespace):
"""
Переменная представляет собой список словарей
"""
BASE_CLASS = "TableVariable"
class TableHashVariable(HashVariable):
BASE_CLASS = "TableHashVariable"
def __init__(self, name, parent=None, hash_vars=None,
master_variable=None, index=None):
super().__init__(name, parent, hash_vars)
self.master_variable._index = index
class Data(HashVariable.Data):
def getHashValue(self, name, subscription=None):
return self.vars.master_variable.getTableValue(name,
self._index,
subscription)
def setHashValue(self, name, value, force):
self.vars.master_variable.setTableValue(name, self._index,
value, force)
class Data(Variable):
BASE_CLASS = "Data"
def getTableValue(self, name, index, subscription=None):
data = self.get_value(subscription)
return data[index][name]
def setTableValue(self, name, index, value, force):
if name in self.readonly_vars and not force:
raise VariableError(
_("Attempting to rewrite readonly variable {}").format(
name))
data = [x.copy() for x in self.get_value()]
rowdata = data[index]
rowdata[name] = value
self.set_value(data, force)
@property
def childs(self):
if self._childs is None:
value = self.master_variable.get_value()
self._childs = {}
for i, row in enumerate(value):
hashvar = self.TableHashVariable(self.master_variable.name,
self, self.hash_vars,
self.master_variable, i)
self._childs[str(i)] = hashvar
return self._childs
@childs.setter
def childs(self, value):
self._childs = value
def get_value(self, subscription=None):
return self.master_variable.get_value(subscription)
def set_value(self, value, force=False):
self.master_variable.set_value(value, force)
def invalidate(self):
self.master_variable.invalidate()
hash_vars = []
readonly_vars = []
def _drop_childs(self):
self._childs = None
self.master_variable.update_subscribe(self._drop_childs)
def clear_childs(self):
super().clear_childs()
self._drop_childs()
def __init__(self, name, parent=None):
super().__init__(name)
self._childs = None
self.parent = parent
if not self.hash_vars:
raise VariableError(
_("Missed '{attrname}' attribute for table variable {varname}").
format(attrname="hash_vars",
varname=self.get_fullname()))
self.master_variable = self.Data(name)
self.master_variable.set_parent(parent)
self.master_variable.readonly_vars = self.readonly_vars
self.master_variable.update_subscribe(self._drop_childs)

@ -1,5 +0,0 @@
from calculate.vars.datavars import Variable, ReadonlyVariable
class Chroot(Variable):
properties = [ReadonlyVariable]
value = "/"

@ -1,88 +0,0 @@
from calculate.vars.datavars import Variable, ChoiceVariable, HashVariable, TableVariable, Namespace
from calculate.utils.files import stderr_devnull
import calculate.utils.fs as fs
import os
"""
gentoo
make_profile
profile.path
profile.name
repositories[*].name
repositories[*].path
config
"""
class MakeProfile(Variable):
"""
Путь до файла, указывающего на активный профиль
"""
value = '/etc/portage/make.profile'
class Profile(Namespace):
"""
Параметры текущего профиля
"""
class Path(Variable):
"""
Абсолютный путь до профиля
"""
def get(self):
make_profile = self.vars.parent.make_profile.getValue(self)
make_profile_dir = os.path.dirname(make_profile)
profileLink = fs.readlink(make_profile)
if profileLink:
profileLink = os.path.normpath(
os.path.join(make_profile_dir,profileLink))
return profileLink
else:
return ""
class Name(Variable):
"""
Название профиля
"""
def get(self):
profile_path = self.vars.path.getValue(self)
if not profile_path:
return ""
repositories = self.vars.parent.repositories
for rep in repositories:
reppath = rep.path.getValue(self)
repname = rep.name.getValue(self)
removepart = os.path.normpath(os.path.join(reppath,"profiles"))
if profile_path.startswith(removepart):
return "%s:%s"%(
repname,
profile_path[len(removepart)+1:])
return profile_path
class Repositories(TableVariable):
"""
Информация о репозиториях
name: имя репозитория
path: полный путь до репозитория
"""
class Data(TableVariable.Data):
def get(self):
config = self.vars.config.getValue(self)
return [
{
'name': name,
'path': path
}
for path, name in config.repositories.location_map.items()
]
hash_vars = ["name", "path"]
class Config(Variable):
"""
Объект текущей конфигурации Portage
"""
def get(self):
from portage.package.ebuild.config import config
chroot_path = self.vars.root.main.chroot.getValue(self)
if chroot_path == '/':
with stderr_devnull():
return config()

@ -1,335 +0,0 @@
import re
import sys
import os
import importlib
import importlib.util
import site
from calculate.variables.old_vars.datavars import Variable, Namespace,\
HashVariable, TableVariable,\
IniCreated, DefaultValue
from calculate.utils.gentoo import ProfileWalker
from calculate.utils.fs import readFile
from calculate.utils.files import list_directory
from pyparsing import Literal, Word, ZeroOrMore, Group, Dict, Optional,\
restOfLine, empty, printables, OneOrMore, oneOf, nums,\
lineno, line, col, Keyword, SkipTo, LineEnd, Combine
from enum import Enum
class Define(Enum):
assign = 0
append = 1
remove = 2
class CalculateIniParser:
'''Класс парсера calculate.ini файлов.'''
def __init__(self):
self._errors = []
self.operations = {"=": Define.assign,
"+=": Define.append,
"-=": Define.remove}
lbrack = Literal("[")
rbrack = Literal("]")
# comma = Literal(",").suppress()
comment_symbol = Literal(';') | Literal('#')
# Define = self.Define
value_operation = (Literal("=") | Combine(Literal("+") + Literal("="))
| Combine(Literal("-") + Literal("=")))
comment = comment_symbol + Optional(restOfLine)
section_name = Word(printables+'\t', excludeChars='[]')
value_name = Word(printables+'\t', excludeChars='=-+')
# non_comma = Word(printables+'\t', excludeChars=',')
clear_section = lbrack.suppress() + Group(empty) + rbrack.suppress()
section_start = Group(OneOrMore(lbrack.suppress() + section_name
+ rbrack.suppress())
+ (clear_section | ~lbrack())
+ LineEnd().suppress())
# Если содержимое ini-файла не предваряется заголовком секции,
# значит эта строка ошибочна.
unexpected = Group(~section_start + SkipTo(LineEnd(),
include=True))("error")
unexpected.setParseAction(self._unexpected_token)
key_value = (~lbrack + value_name
+ value_operation + empty
+ restOfLine + LineEnd().suppress())
def strip_key_value(tokens):
tokens[0] = tokens[0].strip()
tokens[1] = tokens[1].strip()
key_value.setParseAction(strip_key_value)
self.ini_section_parser = (section_start
+ Group(ZeroOrMore(
Group(key_value | unexpected)))
| unexpected)
self.ini_section_parser.ignore(comment)
def _unexpected_token(self, string, location, tokens):
'''Метод вызываемый парсером, если обнаружена некорректная строка,
предназначен для получения некорректной строки и ее дальнейшего
разбора.'''
print('UNEXPECTED TOKEN')
error_line = line(location, string).strip()
if error_line:
self._errors.append((error_line, lineno(location, string),
col(location, string)))
@property
def errors(self):
errors = self._errors
self._errors = []
return errors
def parse(self, data):
for tokens, start, end in self.ini_section_parser.scanString(data):
if tokens.getName() == "error":
continue
section, defkeys = tokens
section_list = section.asList()
if section_list[-1] == []:
yield {'clear_section': (section_list[:-1], )}
else:
yield {'start_section': (section.asList(), )}
for defkey in defkeys:
if defkey.getName() == "error":
continue
yield {'define_key': (section.asList(), defkey[0], defkey[2],
self.operations[defkey[1]])}
class NamespaceIniFiller:
'''Класс, предназначенный для наполнения Namespace объекта переменными
из calculate.ini файла.'''
def __init__(self):
self.ini_parser = CalculateIniParser()
def error(self, lineno, error_message):
self.errors.append(lineno, error_message)
def fill(self, namespace, ini_file_text):
self.namespace = namespace
self.current_namespace = self.namespace
self.errors = []
for parsed_line in self.ini_parser.parse(ini_file_text):
self._line_processor(**parsed_line)
def _line_processor(self, start_section=None,
clear_section=None,
define_key=None,
error=None, **kwargs):
if start_section is not None:
self.start_section(*start_section)
elif clear_section is not None:
self.clear_section(*clear_section)
elif define_key is not None:
self.define_key(*define_key)
for error in self.ini_parser.errors:
self.set_error(*error)
def start_section(self, sections):
self.current_namespace = self.namespace
for section in sections:
if section not in self.current_namespace.childs:
self.current_namespace.add_namespace(Namespace(section))
self.current_namespace = self.current_namespace[section]
def clear_section(self, sections):
current_namespace = self.namespace
for section in sections:
if section not in current_namespace.childs:
return
current_namespace = current_namespace[section]
current_namespace.clear_childs()
def change_value(self, key, value):
self.current_namespace[key].set_value(value)
def define_variable(self, key, value):
self.current_namespace.add_string_variable(key, value)
def append_value(self, key, value):
line = self.current_namespace[key].get_value().split(",")
vlist = value.split(",")
for v in vlist:
if v not in line:
line.append(v)
self.change_value(key, ",".join(line))
def remove_value(self, key, value):
line = self.current_namespace[key].get_value().split(",")
vlist = value.split(",")
for v in vlist:
if v in line:
line.remove(v)
self.change_value(key, ",".join(line))
def define_key(self, section, key, value, optype):
if optype == Define.assign:
if key not in self.current_namespace:
self.define_variable(key, value)
else:
self.change_value(key, value)
elif optype == Define.append:
if key not in self.current_namespace:
self.define_variable(key, value)
else:
self.append_value(key, value)
elif optype == Define.remove:
if key not in self.current_namespace:
self.define_variable(key, value)
else:
self.remove_value(key, value)
def set_error(self, line, lineno, col):
self.error(lineno, "Syntax error: %s".format(line))
class NamespaceIniFillerStrict(NamespaceIniFiller):
"""
Объект используемый для наполения Namespace объекта переменными
из calculate.ini файла, с возможность определять только
"""
availableSection = ["custom"]
def fill(self, namespace, data):
self.canCreate = False
for newns in self.availableSection:
if newns not in namespace:
namespace.add_namespace(Namespace(newns))
super().fill(namespace, data)
def start_section(self, sections):
self.current_namespace = self.namespace
self.canCreate = sections[0] in self.availableSection
for section in sections:
if section not in self.current_namespace.childs:
if isinstance(self.current_namespace,
TableVariable) or self.canCreate:
self.current_namespace.add_namespace(Namespace(section))
else:
self.current_namespace = None
self.current_namespace = self.current_namespace[section]
# def clear_section(self, sections):
# current_namespace = self.namespace
# self.canCreate = sections[0] in self.availableSection
# for section in sections:
# if section not in current_namespace.childs:
# return
# current_namespace = current_namespace[section]
# current_namespace.clear_childs()
def define_variable(self, key, value):
if not self.canCreate:
pass
var = Variable(key)
var.add_property(IniCreated(value))
self.current_namespace.add_variable(var)
def change_value(self, key, value):
var = self.current_namespace[key]
var.set_value(value)
if isinstance(var, HashVariable.HashValue):
var = var.master_variable
value = var.get_value()
prop = var.find_property(DefaultValue)
if prop:
prop.value = value
else:
var.add_property(DefaultValue(value))
var.invalidate()
class VariableLoader:
'''Класс, используемый для загрузки переменных из python модуля.'''
re_upper = re.compile("(.)([A-Z])")
def _get_varlike_attrs(self, obj):
'''Метод для получения списка аттрибутов похожих на переменные.'''
for attrname in (x for x in dir(obj) if x[:1].isupper()):
yield self.re_upper.sub(r"\1_\2", attrname).lower(), \
getattr(obj, attrname)
def fill(self, namespace, dirpath, package):
'''Загрузить в namespace переменные из указанных модулей.'''
for fullfn in list_directory(dirpath, fullpath=True):
dn, fn = os.path.split(fullfn)
if os.path.isdir(fullfn):
newns = namespace.add_namespace(Namespace(fn))
self.fill(newns, fullfn, "{}.{}".format(package, fn))
elif fn.endswith(".py"):
module = self._load_module_source(package, fn, fullfn)
for varname, cls in self._get_varlike_attrs(module):
if Variable.is_implementation(cls):
namespace.add_variable(cls(varname))
elif HashVariable.is_implementation(cls) or \
TableVariable.is_implementation(cls) or \
Namespace.is_implementation(cls):
_newns = namespace.add_namespace(cls(varname,
namespace))
for _varname, _cls in self._get_varlike_attrs(cls):
if Variable.is_implementation(_cls):
_newns.add_variable(_cls(_varname))
@classmethod
def default(cls):
site_dirs = [os.path.normpath(x) for x in site.getsitepackages()]
for site_dir in site_dirs:
calculate_dir = os.path.join(site_dir, "calculate/vars")
if os.path.exists(calculate_dir):
return (calculate_dir, "calculate.vars")
def _load_module_source(self, package, name, path):
if name.startswith('calculate.vars.'):
full_name = name
else:
full_name = '.'.join([package, name])
if full_name in sys.modules:
return sys.modules[full_name]
spec = importlib.util.spec_from_file_location(full_name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules[full_name] = module
return module
class ProfileFiller:
"""
Заполнитель значений переменных из файлов calculate.ini в профилях
"""
basename = "calculate.ini"
def get_repository_map(self, namespace):
return {
x.name.get_value(): x.path.get_value()
for x in namespace.os.gentoo.repositories
}
def fill(self, namespace, profile_path):
nif = NamespaceIniFillerStrict()
pw = ProfileWalker(self.basename, self.get_repository_map(namespace))
for fn in pw.find(profile_path):
nif.fill(namespace, readFile(fn))

@ -0,0 +1,727 @@
# vim: fileencoding=utf-8
#
from .datavars import DependenceAPI, VariableWrapper, NamespaceNode
from .loader import Datavars
from collections import OrderedDict
from contextlib import contextmanager
from typing import Tuple, Union
class ParameterError(Exception):
pass
class ValidationError(ParameterError):
pass
class CyclicValidationError(ValidationError):
def __init__(self, *queue):
self.queue = queue
def __str__(self):
return "Cyclic validation for parameters: {}".format(", ".join(
self.queue[:-1]))
class ParameterType:
'''Общий класс типов параметров.'''
descr = 'VALUE'
def process_value(self, value, parameter):
return value
class Integer(ParameterType):
'''Класс типа целочисленных параметров.'''
descr = 'INTEGER'
def process_value(self, value, parameter) -> int:
if isinstance(value, int):
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the int type")
def __repr__(self):
return '<Integer Parameter Type>'
class String(ParameterType):
'''Класс типа строковых параметров.'''
descr = 'STRING'
def process_value(self, value, parameter) -> str:
if isinstance(value, str):
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the string type")
def __repr__(self):
return '<String Parameter Type>'
class Bool(ParameterType):
'''Класс типа строковых параметров.'''
false_set = {'n', 'false', 'False', 'no'}
true_set = {'y', 'true', 'True', 'yes'}
descr = '[y/n]'
def process_value(self, value, parameter) -> bool:
if isinstance(value, bool):
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the bool type")
def __repr__(self):
return '<Bool Parameter Type>'
class Float(ParameterType):
'''Класс типа вещественночисловых параметров.'''
descr = 'FLOAT'
def process_value(self, value, parameter) -> float:
if isinstance(value, float):
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the float type")
def __repr__(self):
return '<Float Parameter Type>'
class Separator:
'''Класс объекта сепаратора, возможно, его не будет'''
def __repr__(self):
return "# # #"
class Choice(ParameterType):
'''Класс типа, представлющий собой любое значение из совокупности заданных.
'''
descr = 'CHOICES'
def __init__(self, choices=OrderedDict(), editable=False,
multichoice=False):
self.editable = editable
self.multichoice = multichoice
self.choices = choices
def add_choice(self, choice, comment):
self.choices.update({choice: comment})
def process_value(self, value, parameter):
if value in self.choices:
return value
else:
raise ValidationError(f"value '{value}' is not available in"
f" parameter '{parameter._name}' available"
f" values: '{', '.join(self.choices)}'")
def __repr__(self):
return (f'<Choice Parameter Type: choices={", ".join(self.choices)},'
f' editable={self.editable}, multichoice={self.multichoice}>')
class List(ParameterType):
descr = 'LIST'
def process_value(self, value, parameter):
if isinstance(value, list):
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the list type")
def __repr__(self):
return ('<List Parameter Type>')
class TableValue:
'''Класс значения таблицы.'''
def __init__(self, key, fields, fill_function=lambda x: x):
self._fields = fields
self._values = OrderedDict()
# Родительский параметр.
self._parent = None
# Ключ и его индекс в массиве.
self.primary_key = key
# Устанавливаем комментарии стобцам таблицы, по умолчанию то же, что и
# название столбца.
self._fields_comments = OrderedDict()
for field in self._fields:
self._fields_comments.update({field: field})
# Функция для заполнения пустых полей таблицы.
self.fill = fill_function
# Функция для кастомизации уведомления об ошибке.
def set_error(value: str, field: str, available: list) -> None:
raise ParameterError(f"{field} '{value}' is not found.")
self.set_error = set_error
def set_comments(self, *comments):
'''Метод для установки комментариев к полям таблицы.'''
if len(comments) < len(self._fields):
raise ParameterError("Not enough values to comment table fields.")
elif len(comments) > len(self._fields):
raise ParameterError("Too much values to comment table fields.")
self._fields_comments = OrderedDict()
for field, comment in zip(self._fields, comments):
if comment is not None:
self._fields_comments[field] = comment
def change(self, *values):
'''Метод для добавления новых или изменения существующих строк таблицы.
'''
if len(values) < len(self._fields):
raise ParameterError("Not enough values to fill table row.")
elif len(values) > len(self._fields):
raise ParameterError("Too much values to fill table row.")
# Проверяем типы устанавливаемых значений.
validated_values = OrderedDict()
for value, (field, field_type) in zip(values,
self._fields.items()):
if value is None or self._parent is None:
validated_values.update({field: value})
else:
validated_values.update({field: field_type.process_value(
value,
self._parent)})
current_key = validated_values[self.primary_key]
if current_key in self._values:
self._values[current_key] = validated_values
elif self._parent is None or self._parent._parameter_type.expandable:
self._values.update({current_key: validated_values})
else:
self.set_error(current_key,
self._fields_comments[self.primary_key],
list(self._values.keys()))
self._values = self.fill(self._values)
def check_values_types(self):
'''Метод для проверки по типам всех значений таблицы.'''
for row in self._values.values():
for field, field_type in self._fields.items():
if row[field] is not None:
row[field] = field_type.process_value(row[field],
self._parent)
def get_for_var(self):
'''Метод для преобразования данного описания таблицы в ту, которая
приемлема для инициализации переменных.'''
output = []
for value in self._values.values():
output.append(value)
return output
class Table(ParameterType):
'''Метод реализующий тип таблиц.'''
descr = 'LIST'
def __init__(self, expandable=False):
self.expandable = expandable
def process_value(self, value: TableValue, parameter):
if isinstance(value, TableValue):
# Если родитель не установлен -- значит параметр был только что
# инициализирован.его, и проводим полную проверку
# инициализированных значений таблицы.
if value._parent is None:
value._parent = parameter
value.check_values_types()
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the table type")
def __repr__(self):
return (f'<Table Parameter Type: expandable={self.expandable}>')
class File(ParameterType):
pass
class Password(ParameterType):
'''Класс типа строковых параметров.'''
descr = 'PASSWORD'
def __init__(self, param=False):
# TODO разобраться с параметрами, необходимыми для параметров типа
# Password.
self.param = param
def process_value(self, value, parameter) -> str:
if isinstance(value, str):
return value
raise ValidationError(f"Can not assign '{type(value)}' value to"
" parameter of the string type")
def __repr__(self):
return '<String Parameter Type>'
class Description:
'''Класс, содержащий описание параметра и способ его отображения.'''
# словарь со способами отображения параметра в графическом интерфейсе.
representations = {}
def __init__(self, short='', full='', usage=''):
self.short = short
self.full = full
self.usage = usage
self._gui_repr = None
def initialize(self, parameter_name, parameter_type, shortname=None):
'''Метод для инициализации представления по указанной пользователем
информации, типу параметра и его имени.'''
if not self.short:
self.short = parameter_name
if not self.full:
self.full = self.short
if not self.usage:
if shortname:
self.usage = f"-{shortname} {parameter_type.descr}, "
self.usage = self.usage +\
f"--{parameter_name} {parameter_type.descr}"
self._gui_repr = self._get_gui_repr(parameter_type)
def _get_gui_repr(self, parameter_type):
'''Метод для получения по типу параметра способа его представления.'''
# TODO реализовать его, когда удастся выделить совокупность доступных
# способов представления.
return None
@property
def gui_repr(self):
return self._gui_repr
class BaseParameter:
'''Класс базовый класс всех параметров.'''
default_type = ParameterType
def __init__(self, name, group, desctiption: Description,
shortname=None, argv=None):
self._group = group
self._name = name
self._shortname = shortname
self._argv = argv
# Ссылка на экземпляр контейнера параметров.
self.container = None
self._value = None
# Допустимые значения параметра, если таковые имеются.
# self.choices = OrderedDict()
# Комментарии, если таковой необходим.
self.comment = None
# Флаг, указывающий на то, что значение параметра было изменено
# установлено пользователем с помощью метода set.
self._set_by_user = False
self._validated = False
if hasattr(self, 'type'):
self._parameter_type = self.type
else:
self._parameter_type = self.default_type()
# Добавляем описание и инициализируем его.
self._description = desctiption
self._description.initialize(self._name, self._parameter_type,
shortname=self._shortname)
# Комментарий, наличие которого указывает на неактивность параметра.
self.disactivity_comment = None
# Коллекции текущих подписок, аргументов, из которых эти подписки
# формируются и подписчиков.
self._subscriptions = {}
self._args = []
self._subscribers = []
# Флаги для проверки того необходимо ли проводить поиск подписок или
# подписчиков.
self._args_is_found = False
self._sets_is_found = False
# Метод для указания того, что в данный момент осуществляется расчет
# значения параметра.
self._calculating = False
self._validation = False
@property
def choices(self):
if isinstance(self._parameter_type, Choice):
return self._parameter_type.choices
@property
def disactivity(self):
return bool(self.disactivity_comment)
def validate(self, container, datavars, value) -> None:
'''Метод для проверки корректности параметра. Переопределяется при
создании нового типа параметра.'''
pass
def validate_value(self, value):
'''Метод для запуска валидации параметра с использованием
переопределенного метода валидации.'''
if self._validation:
raise CyclicValidationError(self._name)
with self._start_validation():
try:
self.validate(self.container, self.container.datavars, value)
except CyclicValidationError as error:
raise CyclicValidationError(self._name, *error.queue)
@contextmanager
def _start_validation(self):
'''Контекстный менеджер предназначеный для переключения параметра в
режим валидации.'''
try:
self._validation = True
yield self
finally:
self._validation = False
def set(self, value) -> None:
'''Метод для установки значения параметра.'''
if isinstance(self._parameter_type, Table):
print('VALUE:', self.value)
self._value.change(*value)
else:
self._value = value
self._set_by_user = True
# Назначаем новые значения подписанным переменным.
self._set_variables()
@property
def value(self):
'''Метод для получения значения параметра. Выдается или дефолтное или
установленное пользователем.'''
if not self._set_by_user:
if self._value is None and not self._calculating:
self._value = self.update_value()
return self._value
def check_value_type(self, value):
'''Метод для запуска проверки значения по типу параметра.'''
return self._parameter_type.process_value(value, self)
def bind(self, *variables) -> None:
'''Метод для настройки взаимосвязи параметра с переменными.'''
self._args = variables
return self
def bind_method(self, *variables):
'''Метод переопределяемый для создания нового типа параметра. Должен
возвращать вычисленное значение и активность параметра None -- активен,
comment -- дизактивирован.'''
if variables:
return variables[0].value, None
else:
return None, None
def to_set(self, *variables):
'''Метод для добавления переменных, которым будет установлено значение
параметра.'''
self._subscribers = variables
return self
def _set_variables(self):
'''Метод для установки значения параметра переменным-подписчикам.'''
# Только если значение установлено пользователем.
if self._set_by_user:
if not self._sets_is_found:
self._subscribers = self.find_variables(self._subscribers)
self._sets_is_found = True
for subscriber in self._subscribers:
if isinstance(self._parameter_type, Table):
subscriber.set(self.value.get_for_var())
else:
subscriber.set(self.value)
def _invalidate(self):
'''Метод, через который переменные сообщают о необходимости пересчитать
значение параметра с дефолтным значением.'''
value = self.update_value()
if not self._set_by_user:
self._value = value
self._validated = False
def update_value(self):
'''Метод для получения дефолтного значения.'''
if not self._args_is_found:
self._args = self.find_variables(self._args)
self._args_is_found = True
with self._start_calculate():
value, subscriptions = self._calculate_binding()
# Обновляем подписки. Сначала убираем лишние.
for subscription in self._subscriptions:
if subscription not in subscriptions:
subscription.subscribers.remove(self)
# Теперь добавляем новые.
for subscription in subscriptions:
subscription.subscribers.add(self)
self._subscriptions = subscriptions
return self._parameter_type.process_value(value, self)
def _calculate_binding(self):
'''Метод для расчета значения параметра по умолчанию, используя
заданные переменные.'''
subscriptions = set()
args = tuple(VariableWrapper(arg, subscriptions) for arg in self._args)
try:
value, self.disactivity_comment = self.bind_method(*args)
return value, subscriptions
except Exception as error:
raise ParameterError('can not calculate using dependencies: {}'
' reason: {}'.format(', '.join(
[subscription.get_fullname()
for subscription
in self._args]),
str(error)))
@contextmanager
def _start_calculate(self):
'''Менеджер контекста устанавливающий флаг, указывающий, что данная
переменная в состоянии расчета.'''
try:
self._calculating = True
yield self
finally:
self._calculating = False
def find_variables(self, variables):
'''Метод для поиска переменных, по их названию. Обходит список
переменных и заменяет все строки с запросами в списке на результат
поиска по запросу.'''
output = []
for variable in variables:
if isinstance(variable, str):
variable = DependenceAPI.find_variable(variable,
self.container.datavars)
output.append(variable)
return output
def __repr__(self):
description = self._description.short or self._description.full
return (f"<Parameter: '{self._name}' = "
f"{self._value if self._value else 'NULL'}: "
f"{description}>")
class GroupWrapper:
'''Класс обертки параметров группы, который возможно будет использоваться
для организации работы графического интерфейса.'''
def __init__(self, group, container):
self.values = OrderedDict()
self.container = container
def set_group(self):
'''Метод для установки значений параметров, осносящихся к группе.'''
self.container.set_parameters(self.values)
class Parameters:
'''Класс контейнера параметров.'''
def __init__(self, datavars: Datavars = None, check_order=[]):
self._datavars = datavars
self._parameters = OrderedDict()
self._validation_dict = OrderedDict()
self._order = check_order
# Флаг указывающий на то, что в данный момент идет валидация параметров
self._validation = False
# Занятые имена параметров.
self._names = set()
# Список позиционных аргументов.
self._argvs = []
# Словарь функций для взаимодействия графического клиента с группами
# параметров.
self._gui_helpers = {}
self._initialized = False
def initialize(self, datavars: Union[Datavars, NamespaceNode]):
if not self._initialized:
self._datavars = datavars
if self._parameters:
parameters = self._parameters
self._parameters = OrderedDict()
self._add(parameters)
self._initialized = True
return self
def add(self, *parameters: Tuple[BaseParameter]):
'''Метод для добавления некоторой совокупности параметров.'''
if not self._initialized:
self._parameters = []
self._parameters.extend(parameters)
for parameter in parameters:
self.add_parameter(parameter)
def set_order(self, *parameters):
'''Метод для установки порядка проверки параметров.'''
self._order = []
for parameter in parameters:
if isinstance(parameter, str):
parameter = self[parameter]
self._order.append(parameter)
for parameter in self:
if parameter not in self._order:
self._order.append(parameter)
def add_parameter(self, parameter: BaseParameter) -> None:
'''Метод для добавления параметров в контейнер.'''
if parameter._name in self._names:
raise ParameterError(f"Can not add parameter '{parameter._name}'."
" Such name has already been added. ")
elif parameter._shortname and parameter._shortname in self._names:
raise ParameterError(f"Can not add parameter '{parameter._name}'."
" Such name has already been added. ")
elif (parameter._argv is not None and
len(self._argvs) > parameter._argv and
self._argvs[parameter._argv]):
raise ParameterError(f"Can not add positional parameter "
f"'{parameter._name}'. Position"
f"'{parameter._argv}' is already taken.")
parameter.container = self
if parameter._group in self._parameters:
self._parameters[parameter._group].append(parameter)
else:
self._parameters[parameter._group] = [parameter]
self._names.add(parameter._name)
if parameter._shortname:
self._names.add(parameter._shortname)
if parameter._argv is not None:
# Если нужно, расширяем список позиций.
if len(self._argvs) <= parameter._argv:
while(len(self._argvs) <= parameter._argv):
self._argvs.append(None)
self._argvs[parameter._argv] = parameter
self._order.append(parameter)
parameter.update_value()
def set_parameters(self, parameters: OrderedDict) -> None:
'''Метод для установки значений некоторого числа параметров их
проверки.'''
# Сначала проверяем все значения по типам и составляем словарь
# валидации.
for parameter_name, value in parameters.items():
parameter = self[parameter_name]
if not parameter.disactivity_comment:
self._validation_dict[parameter] =\
parameter._parameter_type.process_value(value,
parameter)
parameter._validated = False
# Теперь запускаем переопределенный метод для проверки параметров.
with self._run_validation():
self.validate_parameters(self._validation_dict)
def validate_parameters(self, parameters: OrderedDict):
'''Метод для запуска валидации параметров.'''
for parameter in self._order:
if parameter in self._validation_dict:
if (not parameter._validated
and not parameter.disactivity_comment):
parameter.validate_value(self._validation_dict[parameter])
parameter._validated = True
parameter.set(self._validation_dict[parameter])
def validate_all(self):
for parameter in self._order:
if (not parameter._validated and
not parameter.disactivity_comment):
parameter.validate_value(self._validation_dict[parameter])
parameter._validated = True
parameter.set(self._validation_dict[parameter])
@contextmanager
def _run_validation(self):
'''Контекстный менеджер предназначенный для перевода контейнера
параметров в режим валидации.'''
try:
self._validation = True
yield self
finally:
self._validation = False
self._validation_dict = OrderedDict()
def get_group_parameters(self, group_name):
'''Метод для получения списка параметров, относящихся к указанной
группе.'''
return self._parameters[group_name]
def get_descriptions(self):
'''Метод для получения словаря с описанием параметров.'''
output = OrderedDict()
for group, parameters in self._parameters.items():
for parameter in parameters:
usage = ' ' + parameter._description.usage
full = parameter._description.full
if len(usage) > 23:
usage = usage + '\n'
full = ' ' * 24 + full
elif len(usage) == 23:
usage = usage + ' '
else:
usage = usage.ljust(23) + ' '
parameter_description = (f"{usage}{full}")
if group in output:
output[group].append(parameter_description)
else:
output[group] = [parameter_description]
return output
@property
def datavars(self):
return self._datavars
def __getitem__(self, name):
for group, parameters in self._parameters.items():
for parameter in parameters:
if parameter._name == name or parameter._shortname == name:
if self._validation and not parameter._validated:
# В режиме валидации параметров, при попытке получить
# параметр сначала проверяем необходимость его проверки
# И если нужно проверяем его.
parameter_value =\
self._validation_dict.get(parameter, None) or\
parameter._value
parameter.validate_value(parameter_value)
parameter._validated = True
return parameter
else:
return parameter
raise ParameterError(f"No such parameter '{name}'.")
def __iter__(self):
for parameters in self._parameters.values():
for parameter in parameters:
yield parameter

@ -1,7 +1,22 @@
from calculate.variables.datavars import Variable, StringType
from calculate.variables.datavars import Variable, StringType, ListType
'''
main:
chroot -> string
cl_template_path -> string
'''
Variable('chroot', type=StringType.readonly, source='/')
Variable('cl_chroot_path', type=StringType.readonly, source='/')
Variable('cl_template_path', type=ListType.readonly,
source=['./tests/templates/testfiles/test_runner'])
Variable('cl_ignore_files', type=ListType.readonly, source=['*.swp'])
Variable('cl_config_path', type=StringType.readonly,
source='/var/lib/calculate/config')
Variable('cl_config_archive', type=StringType.readonly,
source='/var/lib/calculate/config-archive')
Variable('cl_exec_dir_path', type=StringType.readonly,
source='/var/lib/calculate/.execute')

@ -21,7 +21,7 @@ with Namespace('profile'):
def get_profile_link(make_profile):
make_profile_dir = os.path.dirname(make_profile.value)
try:
profile_link = read_link(make_profile)
profile_link = read_link(make_profile.value)
except FilesError:
return ""
@ -76,4 +76,5 @@ def get_config_object(chroot_path):
# Объект текущей конфигурации Portage
Variable('config', source=Dependence('main.chroot', depend=get_config_object))
Variable('config', source=Dependence('main.cl_chroot_path',
depend=get_config_object))

@ -7,7 +7,7 @@ system:
# Список мест, где есть calculate.ini файлы.
Variable('env_order', type=ListType.readonly,
source=['grp', 'system', 'etc', 'local', 'remote'])
source=[])
# Отображение множества мест, где есть calculate.ini файлы, на пути к ним.
Variable('env_path', type=HashType, source=dict())

@ -24,8 +24,6 @@ markers =
files_utils: marker for running tests for calculate.utils.files module.
package_utils: marker for running tests for calculate.utils.contents module.
old_vars: marker for running tests for datavars
vars: marker for testing alternative version of the datavars module.
gentoo: marker for running tests for utils.gentoo
calculateini: marker for running tests for utils.calculateini
template_engine: marker for running tests for TemplateEngine.
@ -33,3 +31,9 @@ markers =
template_executor: marker for running tests for TemplateAction.
template_wrapper: marker for running tests for TemplateWrapper.
parameters_processor: marker for running test for TemplateParameters.
vars: marker for testing of the datavars module.
parameters: marker for testing of the parameters module.
tasks: marker for testing of the tasks.
commands: marker for testing of the commands.

164
run.py

@ -1,57 +1,111 @@
from calculate.templates.template_engine import TemplateEngine, DIR, Variables
from calculate.templates.template_processor import TemplateAction
from calculate.utils.package import Version
from calculate.utils.files import read_file_lines
from pprint import pprint
import os
import shutil
from calculate.utils.files import join_paths
from calculate.templates.template_engine import Variables
from calculate.utils.package import PackageAtomName, Version, Package
from calculate.templates.template_processor import DirectoryProcessor,\
TemplateWrapper
CHROOT_PATH = os.path.join(os.getcwd(),
'tests/templates/testfiles/test_dir_processor_root')
CONFIG_PATH = os.path.join(CHROOT_PATH, 'var/lib/calculate/config')
CONFIG_ARCHIVE_PATH = os.path.join(CHROOT_PATH,
'var/lib/calculate/config-archive')
EXECUTE_ARCHIVE_PATH = os.path.join(CHROOT_PATH,
'var/lib/calculate/.execute')
test_package_name = PackageAtomName(
{'pkg_path': os.path.join(
CHROOT_PATH,
'var/db/pkg/test-category/test-package-1.0'),
'version': Version('1.0')})
other_package_name = PackageAtomName(
{'pkg_path': os.path.join(
CHROOT_PATH,
'var/db/pkg/test-category/other-package-1.1'),
'version': Version('1.1')})
new_package_name = PackageAtomName(
{'pkg_path': os.path.join(
CHROOT_PATH,
'var/db/pkg/test-category/new-package-0.1.1'),
'version': Version('1.0')})
# Вместо модуля переменных.
group = Variables({'bool': True,
'list': [1, 2, 3]})
variables = Variables({'variable_1': 'value_1',
'variable_2': 'value_2',
'group': group})
install = Variables({'os_disk_dev': 'os_disk_dev_value',
'version': 1.5,
'number': 128,
'boolean': False,
'type': 'static',
'path': '/usr/sbin'})
merge = Variables({'var_1': 674,
'var_2': 48,
'version': 1.0,
'calculate_domains': 'lists.calculate-linux.org',
'ip_value': '127.0.0.0/8'})
main = Variables({'cl_template_path':
('tests/templates/testfiles/template_dir_1,'
'tests/templates/testfiles/template_dir_2'),
'cl_chroot_path': '/'})
values = Variables({'val_1': 11, 'val_2': 13, 'val_3': 12})
datavars = Variables({'main': main, 'values': values})
print('\nREAD LINES EXPERIMENT\n')
config_dictionary = {}
generator = read_file_lines('/var/lib/portage/config')
for fileline in generator:
filename, md5_sum = fileline.strip().split(' ')
config_dictionary.update({filename: md5_sum})
pprint(config_dictionary)
print('\nFIRST TEMPLATE\n')
template_engine = TemplateEngine(
datavars_module=datavars,
appends_set=TemplateAction().available_appends)
template_engine.process_template_from_string(
"{% calculate env = 'values', name = 'dir_name', append = 'join' %}",
DIR)
template_engine.parameters.print_parameters_for_debug()
print('\nSECOND TEMPLATE\n')
template_engine.process_template_from_string(
'''{% calculate package = 'dev-lang/python-2.7', action = 'update' -%}
{% if pkg('category/name') < '3.6.9' -%}
The package exists.
{% else -%}
The package does not exist.
{% endif -%}
{% if Version('12.3.4-r1') < '12.2.5' %}
Version is correct.
{% endif -%}''',
DIR)
template_engine.parameters.print_parameters_for_debug()
print('\nTEMPLATE TEXT:\n{0}'.format(template_engine.template_text))
'{0},{1}'.format(os.path.join(CHROOT_PATH,
'templates'),
os.path.join(CHROOT_PATH,
'var/calculate/templates')),
'cl_chroot_path': CHROOT_PATH,
'cl_config_path': CONFIG_PATH,
'cl_config_archive': CONFIG_ARCHIVE_PATH,
'cl_exec_dir_path': EXECUTE_ARCHIVE_PATH,
'cl_ignore_files': '*.swp'})
test = ({'test_root': CHROOT_PATH})
datavars = Variables({'install': install,
'merge': merge,
'variables': variables,
'main': main,
'test': test,
'custom': Variables()})
TemplateWrapper._protected_is_set = False
shutil.copytree(os.path.join(CHROOT_PATH, 'etc.backup'),
os.path.join(CHROOT_PATH, 'etc'),
symlinks=True)
shutil.copytree(os.path.join(CHROOT_PATH, 'var.backup'),
os.path.join(CHROOT_PATH, 'var'),
symlinks=True)
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_20')
directory_processor = DirectoryProcessor('install',
datavars_module=datavars)
directory_processor.process_template_directories()
test_package = Package(test_package_name, chroot_path=CHROOT_PATH)
# Для разнообразия один из шаблонов удаляет файл, а не создает.
# Но в данном случае он ничего не сделает.
assert os.path.exists(join_paths(CHROOT_PATH,
'/etc/dir_20/file_0'))
assert '/etc/dir_20/file_0' not in test_package
assert not os.path.islink(join_paths(CHROOT_PATH, '/etc/dir_19'))
assert os.path.isdir(join_paths(CHROOT_PATH, '/etc/dir_19'))
assert os.path.exists(join_paths(CHROOT_PATH,
'/etc/dir_19/dir_0'))
assert os.path.exists(join_paths(CHROOT_PATH,
'/etc/dir_19/dir_0/file_0'))
assert '/etc/dir_19/dir_0/file_0' in test_package
shutil.rmtree(os.path.join(CHROOT_PATH, 'etc'))
shutil.rmtree(os.path.join(CHROOT_PATH, 'var'))

@ -0,0 +1,38 @@
#! /usr/bin/python3
import argparse
from calculate.templates.template_processor import DirectoryProcessor
from calculate.variables.loader import Datavars
from calculate.utils.io_module import IOModule
from calculate.utils.package import NonePackage
from calculate.utils.tools import flat_iterable
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,
help="atom name of a target package.")
parser.add_argument('--dbpkg', action='store_true',
help=("flag for switching template engine's mode from"
" standard mode to the mode allowing changing of"
"CONTENTS files."))
args = parser.parse_args()
datavars = Datavars()
io_module = IOModule()
if args.package == 'None':
package = NonePackage
else:
package = args.package
action = list(flat_iterable(args.action))
template_processor = DirectoryProcessor(action,
datavars_module=datavars,
package=package,
output_module=io_module,
dbpkg=args.dbpkg)
template_processor.process_template_directories()
if __name__ == "__main__":
main()

@ -0,0 +1,194 @@
import os
import shutil
import pytest
from calculate.commands.commands import Command
from calculate.scripts.scripts import Script, Task
from calculate.variables.loader import Datavars
from calculate.variables.parameters import Parameters, BaseParameter, Integer,\
String, ValidationError,\
Description
from calculate.utils.io_module import IOModule
TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/commands/testfiles')
@pytest.mark.commands
class TestCommands:
def test_to_make_testfiles(self):
shutil.copytree(os.path.join(TESTFILES_PATH, 'gentoo.backup'),
os.path.join(TESTFILES_PATH, 'gentoo'),
symlinks=True)
def test_commands_first(self):
datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables'))
# Скрипт выполняемый командой.
def action(output: IOModule, arg1: str) -> str:
return f'os.calculate = {arg1}'
# Для обеспечения многопоточности вероятно придется делать так.
def test_script():
return Script('test_script'
).tasks(Task(id='task',
name="Task",
action=action,
args=["os.calculate"])
)
# Параметры данной команды.
test_parameters = Parameters().initialize(datavars)
class MyShinyParameter(BaseParameter):
type = Integer()
def validate(self, var):
if var.value < 10:
raise ValidationError("The value must be greater than 10")
def bind_method(self, var):
return var.value, None
class AnotherParameter(BaseParameter):
type = String()
def bind_method(self):
return 'default string', None
def validate(self, value):
if not value.startswith('/var/lib'):
raise ValidationError("The value must starts with a"
" '/var/lib'")
class OneMoreParameter(BaseParameter):
type = String()
def bind_method(self):
return 'default string', None
def validate(self, value):
available_set = {'mystery', 'horror', 'weird'}
if value not in available_set:
raise ValidationError(f"The value '{value}' is not in"
f" available. Available values:"
f" {', '.join(available_set)}")
test_parameters.add(
MyShinyParameter('my-shiny', 'The Shiniest ones',
Description(
short='The shiny parameter',
full='The shiniest thing known to science.',
usage='-S COUNT, --my-shiny COUNT'),
shortname='S').bind('os.linux.test_5'),
AnotherParameter('my-other', 'The Others',
Description(short='Not from this world.'),
shortname='o'),
OneMoreParameter('one-more', 'The Others',
Description(full='Plan 9 from outer space.')))
command = Command(command_id='test',
category='Test Category',
title='Test',
script=test_script(),
parameters=test_parameters,
namespace='os',
rights=['group'])
command.initialize(datavars, IOModule())
command.script.run()
assert datavars.scripts.test_script.task.result ==\
'os.calculate = test1 test2'
def test_commands_with_setvar(self):
datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables'))
# Скрипт выполняемый командой.
def action(output: IOModule, arg1: str) -> str:
return f'os.calculate = {arg1}'
# Для обеспечения многопоточности вероятно придется делать так.
def test_script():
return Script('test_script'
).tasks(Task(id='task',
name="Task",
action=action,
args=["os.calculate"])
)
# Параметры данной команды.
test_parameters = Parameters().initialize(datavars)
class MyShinyParameter(BaseParameter):
type = Integer()
def validate(self, var):
if var.value < 10:
raise ValidationError("The value must be greater than 10")
def bind_method(self, var):
return var.value, None
class AnotherParameter(BaseParameter):
type = String()
def bind_method(self):
return 'default string', None
def validate(self, value):
if not value.startswith('/var/lib'):
raise ValidationError("The value must starts with a"
" '/var/lib'")
class OneMoreParameter(BaseParameter):
type = String()
def bind_method(self):
return 'default string', None
def validate(self, value):
available_set = {'mystery', 'horror', 'weird'}
if value not in available_set:
raise ValidationError(f"The value '{value}' is not in"
f" available. Available values:"
f" {', '.join(available_set)}")
test_parameters.add(
MyShinyParameter('my-shiny', 'The Shiniest ones',
Description(
short='The shiny parameter',
full='The shiniest thing known to science.',
usage='-S COUNT, --my-shiny COUNT'),
shortname='S').bind('os.linux.test_5'),
AnotherParameter('my-other', 'The Others',
Description(short='Not from this world.'),
shortname='o'),
OneMoreParameter('one-more', 'The Others',
Description(full='Plan 9 from outer space.')))
command = Command(command_id='test',
category='Test Category',
title='Test',
script=test_script(),
parameters=test_parameters,
namespace='os',
gui=True,
icon=['icon_1', 'icon_2'],
setvars={'os.hashvar_0': {'value1': 'new_1',
'value2': 'new_2'},
'.linux.test_5': 1349},
rights=['install'])
command.initialize(datavars, IOModule())
command.script.run()
assert datavars.os.hashvar_0.get_hash() == {'value1': 'new_1',
'value2': 'new_2'}
assert datavars.os.linux.test_5 == 1349
assert datavars.os.calculate == 'new_1 new_2'
assert datavars.scripts.test_script.task.result ==\
'os.calculate = new_1 new_2'
def test_for_removing_testfiles(self):
shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo'))

@ -0,0 +1,21 @@
[os][linux]
ver = 20
shortname = CLD
fullname = Calculate Linux Desktop
subname = KDE
[os][hashvar]
value1 = new1
value2 = new2
[os][tablevar][0]
dev = /dev/sda1
mount = swap
[os][tablevar][1]
dev = /dev/sda2
mount = /
[os][tablevar][2]
dev = /dev/sda5
mount = /var/calculate

@ -0,0 +1,4 @@
from calculate.variables.datavars import Variable, StringType
Variable('chroot', type=StringType.readonly, source='/')

@ -0,0 +1,66 @@
from calculate.variables.datavars import Namespace, Variable, Dependence,\
StringType, HashType, TableType,\
BooleanType, ListType, IntegerType
with Namespace('linux'):
Variable('shortname', source='', type=StringType)
Variable('ver', source='', type=StringType)
Variable('fullname', source='', type=StringType)
Variable('subname', source='', type=StringType)
Variable('arch', source='', type=StringType)
Variable('test_1', source=['choice_1',
'choice_2',
'choice_3',
'choice_4'], type=ListType)
Variable('test_2', source=False, type=BooleanType)
Variable('test_3', source='test string', type=StringType)
Variable('test_4', source='/dev/sda', type=StringType)
Variable('test_5', source=8, type=IntegerType)
Variable('test_6', source="Comment", type=StringType)
def get_title(subname, fullname, ver):
if subname.value:
return '{} {} {}'.format(fullname.value, subname.value, ver.value)
else:
return '{} {}'.format(fullname.value, ver.value)
Variable('title', type=StringType,
source=Dependence('.subname', '.fullname', '.ver',
depend=get_title))
Variable('hashvar', source={'value1': 'test1',
'value2': 'test2'}, type=HashType)
Variable('hashvar_0', source={'value1': 'test1',
'value2': 'test2'}, type=HashType)
Variable('hashvar_1', source={'key1': 'value1',
'key2': 'value2'}, type=HashType)
Variable('hashvar_2', source={'id_1': 1349,
'id_2': 1575}, type=HashType)
Variable('calculate', type=StringType,
source=Dependence('.hashvar_0',
depend=lambda hashvar: "{} {}".format(
hashvar.value['value1'],
hashvar.value['value2'])))
Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1",
"mount": "/"},
{"dev": "/dev/sdb2",
"mount": "/var/calculate"}])
Variable('dev_table', type=TableType, source=[{"dev": "/dev/sdb1",
"mount": "/"},
{"dev": "/dev/sdb2",
"mount": "/var/calculate"}])

@ -0,0 +1,57 @@
import os
from calculate.variables.datavars import Variable, Namespace, Dependence,\
StringType, TableType
'''
gentoo:
make_profile -> string
profile:
path -> string
name -> string
repositories[*]{name, path} -> table
config -> undefined
'''
TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/commands/testfiles')
# Путь до файла, указывающего на активный профиль
Variable('make_profile', type=StringType, source='/etc/portage/make.profile')
# Параметры текущего профиля.
with Namespace('profile'):
# Абсолютный путь до профиля
Variable('path', type=StringType,
source=os.path.join(TESTFILES_PATH,
"gentoo/repos/distros/profiles/CLD/amd64"))
def get_profile_name(path, repositories):
profile_path = path.value
if not profile_path:
return ""
for repository in repositories.value:
repository_path = repository['path']
repository_name = repository['name']
remove_part = os.path.normpath(os.path.join(repository_path,
"profiles"))
if profile_path.startswith(remove_part):
return "{}:{}".format(repository_name,
profile_path[len(remove_part) + 1:])
return profile_path
# Название профиля
Variable('name', type=StringType,
source=Dependence('.path', '..repositories',
depend=get_profile_name))
# Информация о репозиториях
# name: имя репозитория
# path: полный путь до репозитория
Variable('repositories', type=TableType,
source=[{'name': 'distros',
'path': os.path.join(TESTFILES_PATH,
"gentoo/repos/distros")},
{'name': 'calculate',
'path': os.path.join(TESTFILES_PATH,
"gentoo/repos/calculate")},
{'name': 'gentoo',
'path': os.path.join(TESTFILES_PATH,
"gentoo/portage")}])

@ -0,0 +1,18 @@
import os
from calculate.variables.datavars import Variable, ListType, HashType
'''
system:
env_order -> list
env_path -> hash
'''
TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/commands/testfiles')
# Список мест, где есть calculate.ini файлы.
Variable('env_order', type=ListType, source=['system'])
# Отображение множества мест, где есть calculate.ini файлы, на пути к ним.
Variable('env_path', type=HashType,
source={'system': os.path.join(TESTFILES_PATH, 'calculate.ini')})

File diff suppressed because it is too large Load Diff

@ -860,6 +860,54 @@ class TestDirectoryProcessor:
file_text = file_16.read()
assert file_text == 'There is a value = 128'
def test_multiple_actions(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_32')
directory_processor = DirectoryProcessor(['install', 'update'],
datavars_module=datavars)
directory_processor.process_template_directories()
test_package = Package(test_package_name, chroot_path=CHROOT_PATH)
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_32'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_32/file_0'))
assert '/etc/dir_32/file_0' in test_package
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_33'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_33/file_0'))
assert '/etc/dir_33/file_0' in test_package
def test_None_package(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_33')
directory_processor = DirectoryProcessor(['install', 'update'],
datavars_module=datavars)
directory_processor.process_template_directories()
test_package = Package(test_package_name, chroot_path=CHROOT_PATH)
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_35'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_35/file_0'))
assert '/etc/dir_35/file_0' in test_package
def test_multiple_package_value(self):
datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH,
'templates_34')
directory_processor = DirectoryProcessor(['install', 'update'],
datavars_module=datavars)
directory_processor.process_template_directories()
test_package = Package(test_package_name, chroot_path=CHROOT_PATH)
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_36'))
assert os.path.exists(join_paths(CHROOT_PATH,
'etc/dir_36/file_0'))
assert '/etc/dir_36/file_0' in test_package
def test_view_tree(self):
list_path = join_paths(CHROOT_PATH, '/etc')
show_tree(list_path)

@ -148,7 +148,7 @@ class TestTemplateEngine():
def test_if_an_input_template_contains_pkg_function_with_package_that_does_not_exist_in_its_argument__it_works_correctly_and_pkg_function_returns_empty_version_value(self):
input_template = '''{% calculate name = 'filename', force -%}
{% if pkg('category/name') == Version() -%}
{% if pkg() == Version() -%}
pkg() works correctly.
{%- else -%}
pkg() does not work correctly.
@ -202,3 +202,50 @@ class TestTemplateEngine():
template_engine.process_template_from_string(input_template, FILE)
except ConditionFailed:
pytest.fail('Unexpected ConditionFailed exception.')
def test_two_template_engines(self):
input_template_1 = '''{% calculate name = 'filename', force -%}
parameter_1 = {{ vars_1.var_1 }}'''
input_template_2 = '''{% calculate name = 'filename', force -%}
parameter_2 = {{ vars_1.var_1 }}'''
output_text_1 = '''parameter_1 = first'''
output_text_2 = '''parameter_2 = second'''
datavars_module_1 = Variables({'vars_1':
Variables({'var_1': 'first'})})
datavars_module_2 = Variables({'vars_1':
Variables({'var_1': 'second'})})
template_engine_1 = TemplateEngine(appends_set=APPENDS_SET,
datavars_module=datavars_module_1)
template_engine_1.process_template_from_string(input_template_1, DIR)
template_engine_2 = TemplateEngine(appends_set=APPENDS_SET,
datavars_module=datavars_module_2)
template_engine_2.process_template_from_string(input_template_2, DIR)
text_1 = template_engine_1.template_text
text_2 = template_engine_2.template_text
assert text_1 == output_text_1
assert text_2 == output_text_2
def test_pkg_with_vars(self):
datavars_module = Variables({'vars_1':
Variables({'var_1':
'test-category/test-package',
'var_2': 1.2})})
input_template = '''{% calculate name = 'filename', force -%}
{% if pkg(vars_1.var_1) < 2.7 -%}
pkg() works correctly.
{%- else -%}
pkg() does not work correctly.
{%- endif -%}'''
output_text = 'pkg() works correctly.'
template_engine = TemplateEngine(appends_set=APPENDS_SET,
datavars_module=datavars_module)
template_engine.process_template_from_string(input_template, FILE)
text = template_engine.template_text
assert text == output_text

@ -3,16 +3,12 @@ import grp
import stat
import shutil
import pytest
import getpass
import hashlib
from pprint import pprint
from collections import OrderedDict
from calculate.templates.template_processor import TemplateExecutor, DIR,\
FILE, CalculateConfigFile,\
TemplateExecutorError,\
TemplateWrapper,\
TemplateCollisionError,\
TemplateTypeConflict
TemplateWrapper
from calculate.templates.template_engine import ParametersContainer
from calculate.utils.package import PackageAtomName, Version, Package
from calculate.utils.files import join_paths
@ -2739,9 +2735,8 @@ print(os.getcwd())'''
shutil.rmtree(EXECUTE_ARCHIVE_PATH)
template_executor.execute_files = OrderedDict()
target_path = join_paths(
CHROOT_PATH,
'/etc/exec_parameter_testfiles/dir_1/file_0')
target_path = join_paths(CHROOT_PATH,
'/etc/exec_parameter_testfiles/dir_1/file_0')
parameters_object = ParametersContainer({'package': test_package_name,
'exec': '/usr/bin/python'})
with pytest.raises(TemplateExecutorError):

@ -0,0 +1,3 @@
{% calculate append = 'skip', action = 'install',
package = 'test-category/test-package' %}
{% calculate path = '/etc' %}

@ -0,0 +1,5 @@
{% calculate append = 'join', format = 'bind' -%}
options {
parameter-1 {{ variables.variable_1 }};
parameter-2 {{ variables.variable_2 }};
};

@ -0,0 +1,3 @@
{% calculate append = 'skip', action = 'update',
package = 'test-category/test-package' %}
{% calculate path = '/etc' %}

@ -0,0 +1,5 @@
{% calculate append = 'join', format = 'bind' -%}
options {
parameter-1 {{ variables.variable_1 }};
parameter-2 {{ variables.variable_2 }};
};

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

@ -0,0 +1 @@
{% calculate merge = 'test-category/test-package' -%}

@ -0,0 +1,3 @@
{% calculate append = 'skip', action = 'update',
package = 'test-category/test-package' %}
{% calculate path = '/etc' %}

@ -0,0 +1,5 @@
{% calculate append = 'join', format = 'bind' -%}
options {
parameter-1 {{ variables.variable_1 }};
parameter-2 {{ variables.variable_2 }};
};

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

@ -0,0 +1,2 @@
{% calculate append = 'skip',
package = ['test-category/test-package', 'test-category/other-package'] %}

@ -0,0 +1 @@
{% calculate merge = 'test-category/test-package' -%}

@ -0,0 +1,2 @@
{% calculate append = 'skip', action = 'install' %}
{% calculate path = '/home/divanov/Home/protected/test_dir' %}

@ -0,0 +1 @@
{% calculate append = 'join', package = 'app-editors/nano' %}

@ -0,0 +1,5 @@
{% calculate append = 'join', format = 'bind' -%}
options {
cl_chroot_path {{ main.cl_chroot_path }};
make_profiles {{ os.gentoo.make_profile }};
};

@ -0,0 +1 @@
{% calculate merge = 'app-editors/vim' -%}

@ -0,0 +1,2 @@
{% calculate append = 'skip', action = 'remove' %}
{% calculate path = '/home/divanov/Home/protected' %}

@ -0,0 +1 @@
{% calculate append = 'remove', package = 'app-editors/nano' %}

@ -0,0 +1,2 @@
{% calculate append = 'skip', action = 'update' %}
{% calculate path = '/home/divanov/Home/protected/test_dir' %}

@ -0,0 +1 @@
{% calculate append = 'join', package = 'app-editors/vim' %}

@ -0,0 +1,4 @@
{% calculate append = 'join', format = 'bind' -%}
options {
vim_version {{ pkg() }};
};

@ -0,0 +1 @@
{% calculate append = 'join', package = 'app-editors/nano' %}

@ -0,0 +1,4 @@
{% calculate append = 'join', format = 'bind' -%}
options {
cl_ignore_files {{ main.cl_ignore_files | join(', ') }};
};

@ -37,7 +37,7 @@ class TestUtils():
result = pipe_result.stdout.decode().strip()
assert output == result
def test_if_a_pipe_Process_object_uses_for_several_writes_without_readings__it_successfully_executes(self):
def test_if_a_pipe_Process_object_is_used_for_several_writes_without_readings__it_successfully_executes(self):
input_lines = ['KEY: important line.\n',
'NONE: not important line.\n',
'NONE: nothing interesting.\n',
@ -58,7 +58,7 @@ class TestUtils():
output = pipe.read()
assert output == output_text
def test_if_a_pipe_Process_object_uses_for_several_writes__it_successfully_executes_even_after_read(self):
def test_if_a_pipe_Process_object_is_used_for_several_writes__it_successfully_executes_even_after_read(self):
input_lines = ['KEY: important line.\n',
'NONE: not important line.\n',
'NONE: nothing interesting.\n',

@ -1,793 +0,0 @@
import pytest
import os
from calculate.variables.old_vars.datavars import Namespace, Variable,\
CyclicVariableError,\
VariableError,\
ReadonlyVariable,\
ChoiceVariable,\
IntegerVariable,\
StringVariable,\
DefaultValue,\
TableVariable, HashVariable,\
VariableNotFoundError
from calculate.variables.old_vars.vars_loader import NamespaceIniFiller,\
VariableLoader,\
ProfileFiller,\
NamespaceIniFillerStrict
@pytest.mark.old_vars
class TestNamespace:
def test_init_empty_namespace(self):
ns = Namespace()
assert ns.childs == {}
assert ns.variables == {}
def test_create_variable(self):
ns = Namespace()
ns.add_string_variable("test", "12345")
assert ns.test.get_value() == "12345"
ns.add_string_variable("zxcv", "23456")
assert ns.zxcv.get_value() == "23456"
def test_create_ns_with_vars(self):
ns = Namespace()
ns.add_namespace(Namespace("os"))
ns.os.add_string_variable("test", "123")
ns.os.add_namespace(Namespace("linux"))
ns.os.linux.add_string_variable("shortname", "CLD")
assert ns.os.test.get_value() == "123"
assert ns.os["test"].get_value() == "123"
assert ns.os.linux.shortname.get_value() == "CLD"
assert ns.os.root == ns
def test_fill_namespace_simple(self):
ns = Namespace()
nsif = NamespaceIniFiller()
nsif.fill(ns, """
[os]
test = 123
[os][linux]
shortname = CLD
fullname = Calculate Linux Desktop
""")
assert ns.os.test.get_value() == "123"
assert ns.os.linux.shortname.get_value() == "CLD"
assert ns.os.linux.fullname.get_value() == "Calculate Linux Desktop"
assert ns.os.linux.root == ns
nsif.fill(ns, """
[os][linux]
shortname = CLDX
""")
assert ns.os.linux.shortname.get_value() == "CLDX"
assert ns.os.linux.fullname.get_value() == "Calculate Linux Desktop"
def test_fill_namespace_append_and_remove(self):
ns = Namespace()
nsif = NamespaceIniFiller()
nsif.fill(ns, """
[os]
test = 123
[os]
test += 345
""")
assert ns.os.test.get_value() == "123,345"
nsif.fill(ns, """
[os]
test -= 123
""")
assert ns.os.test.get_value() == "345"
nsif.fill(ns, """
[os]
test += asdf,qwer,zxcv
""")
assert ns.os.test.get_value() == "345,asdf,qwer,zxcv"
nsif.fill(ns, """
[os]
test -= asdf,zxcv
""")
assert ns.os.test.get_value() == "345,qwer"
def test_fill_namespace_clear_namespaces(self):
ns = Namespace()
nsif = NamespaceIniFiller()
nsif.fill(ns, """
[test][0]
dev = /dev/sda1
mount = swap
[test][1]
dev = /dev/sda2
mount = /
[test][2]
dev = /dev/sda5
mount = /var/calculate
""")
assert [x.dev.get_value() for x in ns.test] == ['/dev/sda1',
'/dev/sda2',
'/dev/sda5']
nsif.fill(ns, """
[test][0]
dev = /dev/sdb1
mount = swap
[test][1]
dev = /dev/sdb2
mount = /
""")
assert [x.dev.get_value() for x in ns.test] == ['/dev/sdb1',
'/dev/sdb2',
'/dev/sda5']
nsif.fill(ns, """
[test][]
[test][0]
dev = /dev/sdb1
mount = swap
[test][1]
dev = /dev/sdb2
mount = /
""")
assert [x.dev.get_value() for x in ns.test] == ['/dev/sdb1',
'/dev/sdb2']
def test_fill_namespace_strict_clear(self):
ns = Namespace()
nsif = NamespaceIniFiller()
nsif.fill(ns, """
[os][test][0]
dev = /dev/sda1
mount = swap
[os][test][1]
dev = /dev/sda2
mount = /
[os][test][2]
dev = /dev/sda5
mount = /var/calculate
[custom][test]
zxcv = 1
[custom][test2]
zxcv = 2
""")
assert ns.custom.test.zxcv.get_value() == "1"
assert ns.custom.test2.zxcv.get_value() == "2"
nsifs = NamespaceIniFillerStrict()
nsifs.fill(ns, """
[os][test][]
[custom][]
""")
assert [x for x in ns.custom] == []
def test_get_namespace_attrs(self):
ns = Namespace()
os = Namespace("os")
os.add_string_variable("test", "zxcv")
ns.add_namespace(os)
assert ns.os.test.get_value() == "zxcv"
assert ns.os["test"].get_value() == "zxcv"
assert "test" in ns.os
ns.os.test.set_value("123")
assert ns.os.test.get_value() == "123"
ns.os["test"].set_value("234")
assert ns.os.test.get_value() == "234"
def test_variable_get_value(self):
class TestVar(Variable):
def get(self):
return "A"
var = TestVar("test")
assert var.get_value() == "A"
def test_namespace_lookup(self):
ns = Namespace()
os = Namespace("os")
device = Namespace("device")
linux = Namespace("linux")
ns.add_namespace(os)
os.add_namespace(linux)
os.add_namespace(device)
device1 = device.add_namespace()
device1.add_string_variable("dev", "/dev/sda")
device2 = device.add_namespace()
device2.add_string_variable("dev", "/dev/sdb")
device3 = device.add_namespace()
device3.add_string_variable("dev", "/dev/sdc")
ns.add_string_variable("first", "first")
os.add_string_variable("second", "second")
linux.add_string_variable("third", "third")
assert ns.first.get_value() == "first"
assert ns.root.os.second.get_value() == "second"
assert ns.os.second.get_value() == "second"
assert os.root.os.second.get_value() == "second"
assert os.second.get_value() == "second"
assert linux.third.get_value() == "third"
assert linux.root.os.second.get_value() == "second"
with pytest.raises(VariableNotFoundError):
os.third
assert ns.os.device[0].dev.get_value() == "/dev/sda"
assert ns.os.device["0"].dev.get_value() == "/dev/sda"
assert ns.os.device[1].dev.get_value() == "/dev/sdb"
assert ns.os.device[2].dev.get_value() == "/dev/sdc"
assert ns.os.device.parent.second.get_value() == "second"
assert ns.os.device.parent.parent.os.second.get_value() == "second"
assert ns.os.device.parent.parent.parent.os.second.\
get_value() == "second"
def test_variable_get_value_by_variable(self):
class TestVar1(Variable):
def get(self):
return "%s,B" % self.vars.test2.get_value(self)
class TestVar2(Variable):
def get(self):
return "A"
ns = Namespace()
ns.add_variable(TestVar1("test1"))
ns.add_variable(TestVar2("test2"))
assert ns.test1.get_value() == "A,B"
def test_variable_get_value_by_changed_variable(self):
class TestVar1(Variable):
counter = 0
def get(self):
self.counter += 1
return "%s,B" % self.vars.test2.get_value(self)
ns = Namespace()
test1 = TestVar1("test1")
ns.add_variable(test1)
ns.add_string_variable("test2", "A")
assert ns.test1.get_value() == "A,B"
assert test1.counter == 1
# test for get cached variable value
assert ns.test1.get_value() == "A,B"
assert test1.counter == 1
# change value of test2 for recalculate test1
ns.test2.set_value("C")
assert ns.test1.get_value() == "C,B"
assert test1.counter == 2
# change value of test2 for recalculate test1
ns.test2.set_value("D")
assert ns.test1.get_value() == "D,B"
assert test1.counter == 3
def test_cyclic_variable(self):
class TestVar1(Variable):
def get(self):
return "%s,test1" % self.vars.test2.get_value(self)
class TestVar2(Variable):
def get(self):
return "%s,test2" % self.vars.test3.get_value(self)
class TestVar3(Variable):
def get(self):
return "%s,test3" % self.vars.test1.get_value(self)
test1 = TestVar1("test1")
test2 = TestVar2("test2")
test3 = TestVar3("test3")
ns = Namespace()
ns.add_variable(test1)
ns.add_variable(test2)
ns.add_variable(test3)
with pytest.raises(CyclicVariableError) as e:
ns.test1.get_value()
assert e.value.queue[:-1] == ("test1", "test2", "test3")
with pytest.raises(VariableError) as e:
ns.test1.get_value()
def test_drop_invalidate_after_set_value(self):
class TestVar1(Variable):
counter = 0
def get(self):
self.counter += 1
return "%s,test1" % self.vars.test2.get_value(self)
class TestVar2(Variable):
def get(self):
return "ZZZZ"
test1 = TestVar1("test1")
test2 = TestVar2("test2")
ns = Namespace()
ns.add_variable(test1)
ns.add_variable(test2)
assert test1.get_value() == "ZZZZ,test1"
test1.set_value("VVVV")
assert test1.get_value() == "VVVV"
assert test1.counter == 1
test2.set_value("XXXX")
assert test1.get_value() == "VVVV"
assert test1.counter == 1
test1.invalidate()
assert test1.get_value() == "XXXX,test1"
assert test1.counter == 2
def test_change_invalidator_variable(self):
class VarTest(Variable):
counter = 0
def get(self):
self.counter += 1
if self.vars.ifvar.get_value(self):
return "%s,test1" % self.vars.vara.get_value(self)
else:
return "%s,test1" % self.vars.varb.get_value(self)
vartest = VarTest("vartest")
ns = Namespace()
ns.add_variable(vartest)
ns.add_string_variable("vara", "vara")
ns.add_string_variable("varb", "varb")
ns.add_string_variable("ifvar", "true")
assert vartest.get_value() == "vara,test1"
assert vartest.counter == 1
ns.vara.set_value("varc")
assert vartest.get_value() == "varc,test1"
assert vartest.counter == 2
ns.ifvar.set_value("")
assert vartest.get_value() == "varb,test1"
assert vartest.counter == 3
ns.vara.set_value("vard")
assert vartest.get_value() == "varb,test1"
assert vartest.counter == 3
def test_readonly_varaible(self):
class TestVar1(Variable):
properties = [ReadonlyVariable, StringVariable]
def get(self):
return "test1"
test1 = TestVar1("test1")
assert test1.get_value() == "test1"
with pytest.raises(VariableError):
test1.set_value("test2")
assert test1.get_value() == "test1"
test1.set_value("test2", force=True)
assert test1.get_value() == "test2"
def test_choice_variable(self):
class TestVar1(Variable):
properties = [ChoiceVariable]
def choice(self):
return ["test1", "test2"]
test1 = TestVar1("test1")
with pytest.raises(VariableError):
test1.set_value("test3")
test1.set_value("test2")
assert test1.get_value() == "test2"
def test_integer_variable(self):
class TestVar1(Variable):
properties = [IntegerVariable]
test1 = TestVar1("test1")
with pytest.raises(VariableError):
test1.set_value("test3")
test1.set_value("33")
assert test1.get_value() == 33
test1.set_value("-33")
assert test1.get_value() == -33
def test_default_value_property(self):
class TestVar1(Variable):
def get(self):
return "123"
test1 = TestVar1("test1")
assert test1.get_value() == "123"
test1.set_value("987")
test1.add_property(DefaultValue("567"))
assert test1.get_value() == "987"
test1.invalidate()
assert test1.get_value() == "567"
def test_get_comment(self):
class TestVar1(Variable):
def get(self):
return "123"
class TestVar2(Variable):
def get(self):
return "234"
def get_comment(self):
return "[%s]" % self.get_value()
class TestVar3(Variable):
def get(self):
return "ZXC %s" % self.vars.test2.get_comment_value(self)
ns = Namespace()
test1 = TestVar1("test1")
assert test1.get_value() == "123"
assert test1.get_comment() == "123"
test2 = TestVar2("test2")
assert test2.get_value() == "234"
assert test2.get_comment() == "[234]"
test3 = TestVar3("test3")
ns.add_variable(test1)
ns.add_variable(test2)
ns.add_variable(test3)
assert test3.get_value() == "ZXC [234]"
test2.set_value("567")
assert test3.get_value() == "ZXC [567]"
def test_wrong_choice_varaible(self):
class TestVar1(Variable):
properties = [ChoiceVariable]
class TestVar2(Variable):
properties = [ChoiceVariable]
def choice(self):
return ["test1", "test2"]
class TestVar3(Variable):
properties = [ChoiceVariable]
def choice_comment(self):
return [("test1", "Test1"),
("test2", "Test2")]
test1 = TestVar1("test1")
test2 = TestVar2("test2")
test3 = TestVar3("test3")
with pytest.raises(VariableError):
test1.choice()
with pytest.raises(VariableError):
test1.choice_comment()
assert test2.choice() == ["test1", "test2"]
assert test2.choice_comment() == [("test1", "test1"),
("test2", "test2")]
assert test3.choice() == ["test1", "test2"]
assert test3.choice_comment() == [("test1", "Test1"),
("test2", "Test2")]
def test_loading_test_variable_module(self):
ns = Namespace()
vl = VariableLoader()
vl.fill(ns, "tests/vars/variables", "testvars")
assert ns.level.simple.get_value() == "simple value"
assert ns.level.uselocalsimple.get_value() == "Using simple value"
assert ns.level.usefullsimple.get_value() == "Using simple value"
with pytest.raises(VariableError):
ns.level.badchoice.choice()
with pytest.raises(VariableError):
ns.level.badchoice.choice_comment()
assert ns.level.simple_choice.choice() == ["/dev/sda1",
"/dev/sda2",
"/dev/sda3"]
assert ns.level.comment_choice.choice() == ["/dev/sda1",
"/dev/sda2",
"/dev/sda3"]
assert ns.level.comment_choice.choice_comment() == [
("/dev/sda1", "SWAP"),
("/dev/sda2", "ROOT"),
("/dev/sda3", "DATA")]
ns.level.disks.set_value(["/dev/sda2", "/dev/sda1"])
assert ns.level.disks.get_value() == ["/dev/sda2", "/dev/sda1"]
assert ns.level.comment_choice.choice() == ["/dev/sda2", "/dev/sda1"]
assert ns.level.comment_choice.choice_comment() == [
("/dev/sda2", "ROOT"),
("/dev/sda1", "SWAP")]
assert ns.level is not ns.level.level2.root
assert ns is ns.level.level2.root
assert ns.level.level2.vargetter.get_value() == "/ test"
def test_hash_variable(self):
# hash variable
ns = Namespace()
vl = VariableLoader()
vl.fill(ns, "tests/vars/variables", "testvars")
assert ns.level.linux.get_fullname() == "level.linux"
assert ns.level.linux.ver.fullname == "level.linux.ver"
assert ns.level.linux.ver.get_value() == "1.0"
assert ns.level.linux.shortname.get_value() == "CLD"
# проверка обновления значения hash переменной при обновлении
# значения у зависимой перемнной
ns.level.version.set_value("2.0")
assert ns.level.linux.ver.get_value() == "2.0"
# проверка установки значения hash переменной
ns.level.linux.ver.set_value("3.0")
assert ns.level.linux.ver.get_value() == "3.0"
# после установки хотя бы одного значения в hash переменной
# обновление остальных прекращаются до инвалидации (так как
# значения рассматриваются комплексно)
ns.level.myshortname.set_value("CLDG")
assert ns.level.linux.shortname.get_value() == "CLD"
# проверка попытки изменить readonly переменную
with pytest.raises(VariableError):
ns.level.linux.shortname.set_value("CLDX")
# проверка сбора значения hash перемнной
ns.level.linux.invalidate()
assert ns.level.linux.ver.get_value() == "2.0"
assert ns.level.linux.shortname.get_value() == "CLDG"
assert ns.level.linux.test.get_value() == "my test - 2.0"
# проверка обновления значения переменной, используеющей одно
# из значений hash переменной
assert ns.level.shortname_test.get_value() == "CLDG test"
ns.level.linux.shortname.set_value("CLDX", force=True)
assert ns.level.shortname_test.get_value() == "CLDX test"
def test_table_variable(self):
# table variable
ns = Namespace()
vl = VariableLoader()
vl.fill(ns, "tests/vars/variables", "testvars")
assert ns.level.device[0].dev.get_value() == "/dev/sda"
assert ns.level.device[1].dev.get_value() == "/dev/sdb"
assert ns.level.device.get_value() == [{"dev": "/dev/sda",
"type": "hdd",
"name": "Samsung SSD"},
{"dev": "/dev/sdb",
"type": "flash",
"name": "Transcend 64GB"}]
assert ns.level.device[1].type.get_value() == "flash"
# проверка обновления списка пространства имён у табличной переменной
ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb", "/dev/sdc"])
assert ns.level.device[2].type.get_value() == "usbhdd"
assert [x.type.get_value() for x in ns.level.device] == ["hdd",
"flash",
"usbhdd"]
assert ns.level.device_child.get_value() == "hdd"
ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb"])
ns.level.device[0].type.set_value("flash")
assert ns.level.device.get_value() == [{"dev": "/dev/sda",
"type": "flash",
"name": "Samsung SSD"},
{"dev": "/dev/sdb",
"type": "flash",
"name": "Transcend 64GB"}]
assert ns.level.device_child.get_value() == "flash"
# после установки хотя бы одного значения в table переменной
# обновление остальных прекращаются до инвалидации (так как
# значения рассматриваются комплексно)
ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb", "/dev/sdc"])
assert [x.dev.get_value() for x in ns.level.device] == ["/dev/sda",
"/dev/sdb"]
ns.level.device.invalidate()
assert [x.dev.get_value() for x in ns.level.device] == ["/dev/sda",
"/dev/sdb",
"/dev/sdc"]
# проверить на повторное изменение, убедится, что _drop_child
# отрабатывает
ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb",
"/dev/sdc", "/dev/sdd"])
ns.level.device.invalidate()
assert [x.dev.get_value() for x in ns.level.device] == ["/dev/sda",
"/dev/sdb",
"/dev/sdc",
"/dev/sdd"]
def test_wrong_table_variable(self):
class Testtable(TableVariable):
class Data(TableVariable.Data):
def get(self):
return [{'dev': '123', 'name': '098'}]
ns = Namespace()
ns.add_namespace(Namespace("test"))
ns.test.add_namespace(Namespace("test2"))
error_message = ("Missed 'hash_vars' attribute for table "
"variable test.test2.testtable")
with pytest.raises(VariableError) as e:
ns.test.test2.add_namespace(Testtable("testtable", ns.test.test2))
assert str(e.value) == error_message
def test_wrong_hash_variable(self):
class Testhash(HashVariable):
class Data(HashVariable.Data):
def get(self):
return {'dev': '123', 'name': '098'}
ns = Namespace()
ns.add_namespace(Namespace("test"))
ns.test.add_namespace(Namespace("test2"))
error_message = ("Missed 'hash_vars' attribute for hash "
"variable test.test2.testhash")
with pytest.raises(VariableError) as e:
ns.test.test2.add_namespace(Testhash("testhash", ns.test.test2))
assert str(e.value) == error_message
def test_namespace_iteration(self):
ns = Namespace()
ns0 = ns.add_namespace()
ns0.add_string_variable("test", "123")
ns1 = ns.add_namespace()
ns1.add_string_variable("test", "234")
ns2 = ns.add_namespace()
ns2.add_string_variable("test", "456")
assert [x.test.get_value() for x in ns] == ["123", "234", "456"]
def test_subnamespace(self):
ns = Namespace()
vl = VariableLoader()
vl.fill(ns, "tests/vars/variables", "testvars")
assert ns.level.level3.my_var1.get_value() == "testing"
assert ns.level.level3.myvar2.get_value() == "testing2"
def test_variable_not_found(self):
ns = Namespace()
vl = VariableLoader()
vl.fill(ns, "tests/vars/variables", "testvars")
error_message = "Variable or namespace level.level3.myvar3 not found"
with pytest.raises(VariableError) as e:
ns.level.level3.myvar3.get_value()
assert str(e.value) == error_message
# TODO: тест использует значения на конкретной машине
# def test_simple(self):
# ns = Namespace()
# vl = VariableLoader()
# vl.fill(ns, *VariableLoader.default())
# # нужно исправить тест, так
# # assert [x.name.get_value() for x in ns.os.gentoo.repositories] ==\
# ["gentoo","distros","calculate","custom"]
# assert ns.os.gentoo.make_profile.get_value() ==\
# "/etc/portage/make.profile"
# assert ns.os.gentoo.profile.path.get_value() ==\
# "/var/db/repos/distros/profiles/CLD/amd64/20"
# assert ns.os.gentoo.profile.name.get_value() ==\
# "distros:CLD/amd64/20"
def test_profile_filler(self):
ns = Namespace()
vl = VariableLoader()
vl.fill(ns, "tests/vars/variables", "testvars")
class ProfileFillerTest(ProfileFiller):
def get_repository_map(self, ns):
curdir = os.getcwd()
return {'distros':
os.path.join(curdir,
"tests/utils/gentoo/repos/distros"),
'calculate':
os.path.join(curdir,
"tests/utils/gentoo/repos/calculate"),
'gentoo':
os.path.join(curdir,
"tests/utils/gentoo/portage")}
assert ns.os.hashvar.value1.get_value() == "test1"
assert ns.os.hashvar.value2.get_value() == "test2"
assert ns.os.tablevar[0].dev.get_value() == "/dev/sdb1"
assert ns.os.tablevar[1].dev.get_value() == "/dev/sdb2"
pf = ProfileFillerTest()
pf.fill(ns, "tests/utils/gentoo/repos/distros/profiles/CLD/amd64")
assert ns.os.linux.test.get_value() == "test"
assert ns.os.linux.arch.get_value() == "amd64"
assert ns.os.linux.title.get_value() ==\
"Calculate Linux Desktop KDE 20"
# Hash
assert ns.os.hashvar.value1.get_value() == "20"
assert ns.os.hashvar.value2.get_value() == "30"
ns.os.hashvar.value1.set_value("40")
assert ns.os.hashvar.value1.get_value() == "40"
ns.os.hashvar.value1.invalidate()
assert ns.os.hashvar.value1.get_value() == "20"
# Table
assert ns.os.tablevar[0].dev.get_value() == "/dev/sda1"
assert ns.os.tablevar[1].dev.get_value() == "/dev/sda2"
assert ns.os.tablevar[2].dev.get_value() == "/dev/sda5"
ns.os.tablevar[0].dev.invalidate()
assert ns.os.tablevar[0].dev.get_value() == "/dev/sda1"
assert ns.os.tablevar[1].dev.get_value() == "/dev/sda2"
assert ns.os.tablevar[2].dev.get_value() == "/dev/sda5"
def test_fill_namespace_by_module(self):
pass
# ns = Namespace()
# vl = VariableLoader()
# vl.fill(ns, *VariableLoader.default())
# assert "os" in ns
# assert "config" in ns.os.gentoo
# assert "main" in ns
# assert ns.os.gentoo.config.get_value() is not None

@ -1,119 +0,0 @@
from calculate.vars.datavars import Namespace, Variable, ChoiceVariable,\
ReadonlyVariable, ListVariable,\
HashVariable, TableVariable, ListVariable
class Simple(Variable):
def get(self):
return "simple value"
class Uselocalsimple(Variable):
def get(self):
return "Using %s" % self.vars.simple.get_value(self)
class Usefullsimple(Variable):
def get(self):
return "Using %s" % self.vars.root.level.simple.get_value(self)
class Badchoice(Variable):
properties = [ChoiceVariable]
class Disks(Variable):
properties = [ListVariable]
def get(self):
return ["/dev/sda1", "/dev/sda2", "/dev/sda3"]
def get_comment(self):
mymap = {'/dev/sda1': 'SWAP',
'/dev/sda2': 'ROOT',
'/dev/sda3': 'DATA'}
return [mymap.get(x) for x in self.get_value()]
class SimpleChoice(Variable):
properties = [ChoiceVariable]
def choice(self):
return self.vars.disks.get_value(self)
class CommentChoice(Variable):
properties = [ChoiceVariable]
def choice_comment(self):
return list(zip(self.vars.disks.get_value(),
self.vars.disks.get_comment()))
class Version(Variable):
value = "1.0"
class Myshortname(Variable):
value = "CLD"
class Linux(HashVariable):
class Data(HashVariable.Data):
def get(self):
return {
'ver': self.vars.version.get_value(self),
'shortname': self.vars.myshortname.get_value(self)
}
class Test(Variable):
def get(self):
return "my test - %s" % self.vars.ver.get_value(self)
readonly_vars = ['shortname']
hash_vars = ["ver", "shortname"]
class ShortnameTest(Variable):
def get(self):
return "{} test".format(self.vars.linux.shortname.get_value(self))
class Devicelist(Variable):
properties = [ListVariable]
def get(self):
return ["/dev/sda", "/dev/sdb"]
class Device(TableVariable):
class Data(TableVariable.Data):
def get(self):
mapData = {'/dev/sda': ["hdd", "Samsung SSD"],
'/dev/sdb': ["flash", "Transcend 64GB"],
'/dev/sdc': ["usbhdd", "WD 1TB"]}
defaultValue = ["hdd", "Unknown"]
return [
{"dev": x,
"type": mapData.get(x, defaultValue)[0],
"name": mapData.get(x, defaultValue)[1]}
for x in self.vars.devicelist.get_value(self)
]
hash_vars = ["dev", "type", "name"]
readonly_vars = ["name"]
class DeviceChild(Variable):
def get(self):
return self.vars.device[0].type.get_value(self)
class Level3(Namespace):
class MyVar1(Variable):
def get(self):
return "testing"
class Myvar2(Variable):
def get(self):
return "testing2"

@ -1,6 +0,0 @@
from calculate.vars.datavars import Variable
class Vargetter(Variable):
def get(self):
return "{} test".format(self.vars.root.main.chroot.get_value(self))

@ -1,6 +0,0 @@
from calculate.vars.datavars import Variable, ReadonlyVariable
class Chroot(Variable):
properties = [ReadonlyVariable]
value = "/"

@ -1,65 +0,0 @@
from calculate.vars.datavars import Variable, Namespace, HashVariable, TableVariable
class Linux(Namespace):
class Shortname(Variable):
value = ""
class Ver(Variable):
value = ""
class Fullname(Variable):
value = ""
class Subname(Variable):
value = ""
class Arch(Variable):
value = ""
class Test(Variable):
value = ""
class Title(Variable):
def get(self):
subname = self.vars.subname.get_value(self)
fullname = self.vars.fullname.get_value(self)
ver = self.vars.ver.get_value(self)
if subname:
return "%s %s %s" % (fullname, subname, ver)
else:
return "%s %s" % (fullname, ver)
class Hashvar(HashVariable):
class Data(HashVariable.Data):
def get(self):
return {
'value1': 'test1',
'value2': 'test2',
}
class Calculate(Variable):
def get(self):
return "%s %s" % (
self.vars.value1.get_value(self),
self.vars.value2.get_value(self)
)
hash_vars = ["value1", "value2"]
class Tablevar(TableVariable):
class Data(TableVariable.Data):
def get(self):
return [
{
"dev": "/dev/sdb1",
"mount": "/",
},
{
"dev": "/dev/sdb2",
"mount": "/var/calculate",
}
]
hash_vars = ["dev", "mount"]

@ -4,11 +4,6 @@ from calculate.variables.loader import CalculateIniParser, Define
@pytest.mark.vars
class TestCalculateIni:
def test_empty_calculate_ini(self):
pass
# ns = Namespace(varPath=None)
# assert ns.varPath is None
def test_section_values(self):
ini_parser = CalculateIniParser()

@ -8,8 +8,10 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\
VariableType, VariableError,\
TableType, BooleanType,\
VariableTypeError, FloatType,\
ListType
from calculate.templates.template_engine import TemplateEngine, FILE
ListType, VariableNotFoundError,\
Copy, Format, Calculate, Static
from calculate.templates.template_engine import TemplateEngine, FILE,\
Variables, DIR
from calculate.templates.template_processor import TemplateExecutor
from calculate.variables.loader import NamespaceIniFiller, Datavars
from calculate.utils.files import stderr_devnull, read_file
@ -199,7 +201,7 @@ class TestDatavars:
Variable('var_1', source='val_1', type=StringType)
with Namespace('namespace_2'):
assert Dependence._find_variable('namespace_1.var_1') ==\
assert Dependence._get_variable('namespace_1.var_1') ==\
datavars.namespace_1['var_1']
def test_if_dependence_interface_object_is_used_for_finding_an_unexisting_variable_from_an_other_namespace_using_absolute_variable_name__the_find_variable_method_raises_the_VariableError_exception(self):
@ -210,7 +212,7 @@ class TestDatavars:
with Namespace('namespace_2'):
with pytest.raises(VariableError):
Dependence._find_variable('namespace_1.var_2')
Dependence._get_variable('namespace_1.var_2')
def test_if_dependence_interface_object_is_used_for_finding_an_existing_variable_from_a_same_namespace_using_relative_variable_name__the_find_variable_method_returns_the_desired_variable(self):
Namespace.reset()
@ -224,9 +226,9 @@ class TestDatavars:
with Namespace('namespace_1_1'):
Variable('var_1', source='val_1', type=StringType)
assert Dependence._find_variable('.var_1') ==\
assert Dependence._get_variable('.var_1') ==\
datavars.namespace_1.namespace_1_1['var_1']
assert Dependence._find_variable('..var_3') ==\
assert Dependence._get_variable('..var_3') ==\
datavars.namespace_1['var_3']
def test_if_dependence_interface_object_is_used_for_finding_an_unexisting_variable_from_a_same_namespace_using_relative_variable_name__the_find_variable_method_raises_the_VariableError_exception(self):
@ -238,7 +240,7 @@ class TestDatavars:
with Namespace('namespace_1_1'):
with pytest.raises(VariableError):
Dependence._find_variable('..var_3')
Dependence._get_variable('..var_3')
def test_if_variable_is_created_with_dependence_in_its_source_and_the_dependence_has_only_set_function_without_any_arguments__the_variable_updates_its_value_using_only_set_function(self):
Namespace.reset()
@ -252,6 +254,84 @@ class TestDatavars:
assert datavars.namespace_1.var == 'value_from_function'
def test_if_a_variable_is_created_using_a_Copy_of_a_variable_in_the_source__the_new_variable_uses_this_variable_s_value_to_create_its_own_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
Variable('var_1', source='value_1', type=StringType)
Variable('var_2', type=StringType, source=Copy('.var_1'))
assert datavars.namespace_1.var_2 == 'value_1'
def test_if_a_variable_is_created_using_a_Format_with_a_format_string_and_a_variable_in_the_source__the_new_variable_uses_the_variable_and_format_string_to_produce_a_new_string_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
Variable('var_1', source='value_1', type=StringType)
Variable('var_2', type=StringType,
source=Format('var_1 = { .var_1 }'))
assert datavars.namespace_1.var_2 == 'var_1 = value_1'
def test_if_a_variable_is_created_using_a_Calculate_with_a_variable_and_some_function_in_the_source__the_new_variable_uses_the_variable_and_the_function_for_calculating_of_its_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
Variable('var_1', source='value_1', type=StringType)
def formatter(var_1):
return 'var_1 = {}'.format(var_1.value)
Variable('var_2', type=StringType,
source=Calculate(formatter, '.var_1'))
assert datavars.namespace_1.var_2 == 'var_1 = value_1'
def test_if_a_variable_is_created_using_a_Dependence_with_a_static_value_a_variable_and_depend_function_in_the_source__the_new_variable_uses_the_variable_the_function_and_the_static_value_for_calculating_of_its_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
def source_function(static_value):
return 'static value = {}'.format(static_value.value)
Variable('var', type=StringType,
source=Dependence(Static('value'),
depend=source_function))
assert datavars.namespace_1.var == 'static value = value'
def test_if_a_variable_is_created_using_a_Calculate_with_a_static_value_a_variable_and_some_function_in_the_source__the_new_variable_uses_the_variable_the_function_and_the_static_value_for_calculating_of_its_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
Variable('var_1', source='var value', type=StringType)
def suspicious_function(static_value, var_1):
return '{} and {}'.format(static_value.value, var_1.value)
Variable('var_2', type=StringType,
source=Calculate(suspicious_function,
Static('static value'), '.var_1'))
assert datavars.namespace_1.var_2 == 'static value and var value'
def test_if_a_variable_is_created_using_a_Calculate_with_a_non_string_value_a_variable_and_some_function_in_the_source__the_new_variable_uses_the_variable_the_function_and_the_static_value_for_calculating_of_its_value(self):
Namespace.reset()
datavars = Namespace.datavars
with Namespace('namespace_1'):
Variable('var_1', source=10, type=IntegerType)
def suspicious_function(static_value, var_1):
return static_value.value + var_1.value
Variable('var_2', type=IntegerType,
source=Calculate(suspicious_function,
12, '.var_1'))
assert datavars.namespace_1.var_2 == 22
def test_if_a_variable_is_created_using_dependence_with_three_variables_and_a_set_function_but_during_the_calculation_only_two_variables_was_used__the_unused_variable_is_not_in_variable_subscriptions_and_is_invalidated_before_first_usage(self):
Namespace.reset()
datavars = Namespace.datavars
@ -313,7 +393,7 @@ class TestDatavars:
assert datavars.namespace_2.var_1 == 'less'
datavars.namespace_1['var_1'].source = 5
assert datavars.namespace_2['var_1'].value is None
assert datavars.namespace_2['var_1']._invalidated
assert datavars.namespace_2.var_1 == 'greater'
def test_if_variable_was_created_using_variables_api_and_then_was_created_again_with_simple_source_value__the_variable_just_changes_its_source(self):
@ -375,16 +455,6 @@ class TestDatavars:
with pytest.raises(CyclicVariableError):
datavars.namespace_1.var_3
def test_if_a_variable_is_created_as_readonly_variable_and_there_is_a_try_to_change_its_source__the_variable_raises_the_VariableError_exception(self):
Namespace.reset()
with Namespace('namespace_1'):
Variable('var_1', source='value_1', type=StringType.readonly)
with Namespace('namespace_1'):
with pytest.raises(VariableError):
Variable('var_1', source='value_2', type=StringType)
def test_if_variables_with_string_type_is_created_with_correct_source__the_variable_api_creates_this_variables_and_the_variables_contains_strings(self):
Namespace.reset()
datavars = Namespace.datavars
@ -817,22 +887,62 @@ varval4 = value4
[section][][sub]
[section][][sub][]'''
errors = ['SyntaxError:1: [section',
'SyntaxError:2: varval1 = value1',
'SyntaxError:3: varval2 = value2',
("VariableError:4: can not create namespace '<root>.section'"
" in not 'custom' namespace."),
'SyntaxError:6: eee',
'SyntaxError:8: [section2',
("VariableError:10: can not clear namespace 'section'."
" Namespace is not found."),
'SyntaxError:11: [section][][sub]',
'SyntaxError:12: [section][][sub][]']
if isinstance(datavars, Datavars):
errors = ['SyntaxError:1: [section',
'SyntaxError:2: varval1 = value1',
'SyntaxError:3: varval2 = value2',
("VariableError:4: variables package 'section' is not"
" found."),
'SyntaxError:6: eee',
'SyntaxError:8: [section2',
("VariableError:10: can not clear namespace 'section'."
" Variables package 'section' is not found."),
'SyntaxError:11: [section][][sub]',
'SyntaxError:12: [section][][sub][]']
else:
errors = ['SyntaxError:1: [section',
'SyntaxError:2: varval1 = value1',
'SyntaxError:3: varval2 = value2',
("VariableError:4: can not create namespace"
" '<root>.section' in not 'custom' namespace."),
'SyntaxError:6: eee',
'SyntaxError:8: [section2',
("VariableError:10: can not clear namespace 'section'."
" Namespace is not found."),
'SyntaxError:11: [section][][sub]',
'SyntaxError:12: [section][][sub][]']
namespace_filler.fill(datavars, input_text)
for parsed_error, error in zip(namespace_filler.errors, errors):
assert parsed_error == error
def test_if_a_variable_is_created_as_readonly_variable_and_there_is_a_try_to_new_value_using_ini_file__the_ini_parser_does_not_change_the_variable_and_sets_variable_error(self):
Namespace.reset()
datavars = Namespace.datavars
namespace_filler = NamespaceIniFiller()
with Namespace('os'):
Variable('var_1', source='value_1', type=StringType.readonly)
Variable('var_2', type=HashType.readonly,
source={'key_1': 'value_1', 'key_2': 'value_2'})
first_ini_text = """[os]
var_1 = new_value
[os][var_2]
key_1 = new_value
"""
namespace_filler.fill(datavars, first_ini_text)
assert datavars.os.var_1 == 'value_1'
assert datavars.os.var_2.get_hash() == {'key_1': 'value_1',
'key_2': 'value_2'}
assert namespace_filler.errors == ["VariableError:2: can not change"
" readonly variable 'os.var_1'",
"VariableError:5: can not change"
" readonly hash variable 'os.var_2'"
]
def test_if_calculate_ini_file_is_used_for_changing_of_an_unfixed_hash_variable_created_using_the_variables_api_and_for_adding_of_the_new_key_in_it__the_NamespaceIniFiller_object_changes_hash_value_and_adds_a_new_key(self):
Namespace.reset()
datavars = Namespace.datavars
@ -848,7 +958,6 @@ key_3 = value_3
"""
namespace_filler.fill(datavars, first_ini_text)
print('NEW HASH VALUE: {}'.format(datavars.os.var_1.get_hash()))
assert datavars.os.var_1.get_hash() == {'key_1': 'new_value',
'key_2': 'value_2',
'key_3': 'value_3'}
@ -984,6 +1093,7 @@ value = weird_value
"""
namespace_filler.fill(datavars, first_ini_text)
assert datavars.custom.test ==\
[{'name': 'name_0', 'value': 'value_0'}]
assert datavars.os.test ==\
@ -1136,8 +1246,8 @@ value = another_value
assert datavars.os.linux.subname == 'KDE'
assert datavars.os.linux.arch == 'amd64'
assert datavars.os.hashvar.value1 == '20'
assert datavars.os.hashvar.value2 == '30'
assert datavars.os.hashvar.value1 == 'new1'
assert datavars.os.hashvar.value2 == 'new2'
assert datavars.os.tablevar == [
{'dev': '/dev/sda1', 'mount': 'swap'},
@ -1154,8 +1264,8 @@ value = another_value
assert datavars.os.linux.subname == 'KDE'
assert datavars.os.linux.arch == 'amd64'
assert datavars.os.hashvar.value1 == '20'
assert datavars.os.hashvar.value2 == '30'
assert datavars.os.hashvar.value1 == 'new1'
assert datavars.os.hashvar.value2 == 'new2'
assert datavars.os.tablevar == [
{'dev': '/dev/sda1', 'mount': 'swap'},
@ -1233,6 +1343,25 @@ value = another_value
assert datavars.os.calculate == 'new1 new2'
def test_if_Datavars_object_is_created_and_all_variables_are_loaded__the_set_method_can_be_used_for_changing_hash_values_with_saving_of_their_sources(self):
datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables_6'))
datavars.os.hashvar['value1'].set('old but gold')
assert datavars.os.hashvar.get_hash() == {'value1': 'old but gold',
'value2': 'new2'}
datavars.os['hashvar'].reset()
assert datavars.os.hashvar.get_hash() == {'value1': 'new1',
'value2': 'new2'}
datavars.os.hashvar['value2'].set('almost blue')
assert datavars.os.hashvar.get_hash() == {'value1': 'new1',
'value2': 'almost blue'}
datavars.os.hashvar['value2'].reset()
assert datavars.os.hashvar.get_hash() == {'value1': 'new1',
'value2': 'new2'}
# Теперь тестируем применение объекта Datavars в шаблонах.
def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object__variables_from_the_Datavars_object_can_be_inserted_in_the_processed_template(self):
datavars = Datavars(
@ -1394,16 +1523,12 @@ id_2 = 1349'''
read_file(datavars.system.env_path['system'].value)
# Теперь тестируем обработку ошибок.
def test_process_ini_errors(self):
def test_if_calculate_ini_file_has_some_syntax_errors_in_its_text__loader_skips_incorrect_lines_and_puts_some_error_messages_in_the_output(self):
io_module = IOModule(save_messages=True)
datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables_12'),
io_module=io_module)
Datavars(variables_path=os.path.join(TESTFILES_PATH, 'variables_12'),
io_module=io_module)
messages = [('info', ("Loading variables from file: /home/divanov/Home"
"/development/calculate-lib/tests/variables/testfiles/"
"ini_vars/calculate_8.ini")),
('error', 'SyntaxError:1: [section'),
messages = [('error', 'SyntaxError:1: [section'),
('error', 'SyntaxError:2: varval1 = value1'),
('error', 'SyntaxError:3: varval2 = value2'),
('error', ("VariableError:4: variables package 'section'"
@ -1415,13 +1540,70 @@ id_2 = 1349'''
" found.")),
('error', 'SyntaxError:11: [section][][sub]'),
('error', 'SyntaxError:12: [section][][sub][]'),
('warning', 'Some variables was not loaded.')]
('warning', 'Some variables was not loaded.'),
('success', "Variables from 'system' are loaded"),
('info', "Loading variables from file: 'local'"),
('warning', ("File 'local' is not found. Variables"
" are not loaded"))]
for message, datavars_message in zip(
messages,
io_module.messages[-len(messages):]):
assert message == datavars_message
assert False
def test_if_any_variables_module_has_syntax_error_in_its_code__the_variables_loader_raises_the_VariableError_exception(self):
with pytest.raises(VariableError):
io_module = IOModule(save_messages=True)
Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables_13'),
io_module=io_module)
def test_if_datavars_object_is_used_to_get_access_to_an_unexisting_variable__the_datavars_module_raises_the_VariableNot_Found_exception(self):
io_module = IOModule(save_messages=True)
datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables_14'),
io_module=io_module)
with pytest.raises(VariableNotFoundError):
datavars.level.variable
def test_if_some_template_processed_by_the_template_engine_tries_to_get_value_of_a_variable_that_does_not_exist__the_template_engine_raises_the_VariableNotFound_error(self):
datavars = Datavars(
variables_path=os.path.join(TESTFILES_PATH,
'variables_15'))
template_engine = TemplateEngine(appends_set=APPENDS_SET,
chroot_path=TESTFILES_PATH,
datavars_module=datavars)
input_template = '''{% calculate name = 'filename', force -%}
os.linux.test_3 = {{ os.linux.test_3 }}
'''
with pytest.raises(VariableNotFoundError):
template_engine.process_template_from_string(input_template, FILE)
def test_two_datavars_objects(self):
datavars_1 = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables_7'))
datavars_2 = Datavars(variables_path=os.path.join(TESTFILES_PATH,
'variables_7'))
input_template_1 = '''{% calculate name = 'filename', force -%}
{%- save os.linux.test = 'new_first' -%}'''
input_template_2 = '''{% calculate name = 'filename', force -%}
{%- save os.linux.test = 'new_second' -%}'''
template_engine_1 = TemplateEngine(appends_set=APPENDS_SET,
chroot_path=TESTFILES_PATH,
datavars_module=datavars_1)
template_engine_2 = TemplateEngine(appends_set=APPENDS_SET,
chroot_path=TESTFILES_PATH,
datavars_module=datavars_2)
template_engine_1.process_template_from_string(input_template_1, FILE)
template_engine_2.process_template_from_string(input_template_2, FILE)
assert datavars_1.os.linux.test == 'new_first'
assert datavars_2.os.linux.test == 'new_second'
def test_for_removing_testfiles(self):
shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo'))

File diff suppressed because it is too large Load Diff

@ -5,8 +5,8 @@ fullname = Calculate Linux Desktop
subname = KDE
[os][hashvar]
value1 = 20
value2 = 30
value1 = new1
value2 = new2
[os][tablevar][0]
dev = /dev/sda1

@ -1,7 +1,6 @@
from calculate.variables.datavars import Variable, StringType, Dependence
from calculate.variables.datavars import Variable, StringType, Calculate
Variable('vargetter', type=StringType,
source=Dependence('main.chroot',
depend=lambda chroot:
'{} test'.format(chroot.value)))
source=Calculate(lambda chroot: '{} test'.format(chroot.value),
'main.chroot'))

@ -0,0 +1,4 @@
from calculate.variables.datavars import Variable, StringType
Variable('chroot', type=StringType.readonly, source='/')

@ -0,0 +1,50 @@
from calculate.variables.datavars import Namespace, Variable, Dependence,\
StringType, HashType, TableType,\
ListType, IntegerType, FloatType
with Namespace('linux'):
Variable('shortname', source='', type=StringType)
Variable('ver', source='', type=StringType)
Variable('fullname', source='', type=StringType)
Variable('subname', source='', type=StringType)
Variable('arch', source='', type=StringType)
Variable('test_1', source=12, type=IntegerType)
Variable('test_2', source=1.2, type=FloatType)
def get_title(subname, fullname, ver):
if subname.value:
return '{} {} {}'.format(fullname.value, subname.value, ver.value)
else:
return '{} {}'.format(fullname.value, ver.value)
Variable('title', type=StringType,
source=Dependence('.subname', '.fullname', '.ver',
depend=get_title))
Variable('hashvar', source={'value1': 'test1',
'value2': 'test2'}, type=HashType)
Variable('hashvar_0', source={'value1': 'test1',
'value2': 'test2'}, type=HashType)
Variable('hashvar_1', source={'key1': 'value1',
'key2': 'value2'}, type=HashType)
Variable('hashvar_2', source={'id_1': 1349,
'id_2': 1575}, type=HashType)
Variable('calculate', type=StringType,
source=Dependence('.hashvar_0',
depend=lambda hashvar: "{} {}".format(
hashvar.value['value1'],
hashvar.value['value2'])))
Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1",
"mount": "/"},
{"dev": "/dev/sdb2",
"mount": "/var/calculate"}])

@ -0,0 +1,57 @@
import os
from calculate.variables.datavars import Variable, Namespace, Dependence,\
StringType, TableType
'''
gentoo:
make_profile -> string
profile:
path -> string
name -> string
repositories[*]{name, path} -> table
config -> undefined
'''
TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles')
# Путь до файла, указывающего на активный профиль
Variable('make_profile', type=StringType, source='/etc/portage/make.profile')
# Параметры текущего профиля.
with Namespace('profile')
# Абсолютный путь до профиля
Variable('path', type=StringType,
source=os.path.join(TESTFILES_PATH,
"gentoo/repos/distros/profiles/CLD/amd64"))
def get_profile_name(path, repositories):
profile_path = path.value
if not profile_path:
return ""
for repository in repositories.value:
repository_path = repository['path']
repository_name = repository['name']
remove_part = os.path.normpath(os.path.join(repository_path,
"profiles"))
if profile_path.startswith(remove_part):
return "{}:{}".format(repository_name,
profile_path[len(remove_part) + 1:])
return profile_path
# Название профиля
Variable('name', type=StringType,
source=Dependence('.path', '..repositories',
depend=get_profile_name))
# Информация о репозиториях
# name: имя репозитория
# path: полный путь до репозитория
Variable('repositories', type=TableType,
source=[{'name': 'distros',
'path': os.path.join(TESTFILES_PATH,
"gentoo/repos/distros")},
{'name': 'calculate',
'path': os.path.join(TESTFILES_PATH,
"gentoo/repos/calculate")},
{'name': 'gentoo',
'path': os.path.join(TESTFILES_PATH,
"gentoo/portage")}])

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save