Now save tag can save variables to the calculate.ini files. Tested adding to calculate ini file of hash and simple variables. Developing of the errors processing is started.

packages
Иванов Денис 4 years ago
parent 970f19cd3b
commit a2ce6f87f7

@ -19,8 +19,7 @@ from ..utils.files import join_paths, check_directory_link, check_command,\
FilesError FilesError
from calculate.variables.datavars import HashType, NamespaceNode,\ from calculate.variables.datavars import HashType, NamespaceNode,\
VariableNode, IniType, IntegerType,\ VariableNode, IniType, IntegerType,\
FloatType, ListType,\ FloatType, ListType
VariableNotFoundError
# Типы шаблона: директория или файл. # Типы шаблона: директория или файл.
@ -852,7 +851,7 @@ class CalculateExtension(Extension):
self.CONDITION_TOKENS_TYPES): self.CONDITION_TOKENS_TYPES):
# разбираем параметр. # разбираем параметр.
# pairs_list.append(self.get_parameter_node()) # pairs_list.append(self.get_parameter_node())
name_node, value_node = self.get_parameter() name_node, value_node = self._get_parameter()
check_node = self.call_method('check_parameter', check_node = self.call_method('check_parameter',
[name_node, [name_node,
@ -977,11 +976,6 @@ class CalculateExtension(Extension):
optype, context): optype, context):
'''Метод для сохранения значений переменных указанных в теге save.''' '''Метод для сохранения значений переменных указанных в теге save.'''
datavars = context.parent['__datavars__'] datavars = context.parent['__datavars__']
optypes = {self.ASSIGN: '=', self.APPEND: '+=', self.REMOVE: '-='}
print('SAVE VARIABLE: {} {} {}'.format('.'.join(variable),
optypes[optype],
right_value))
print('TARGET FILE: {}'.format(target_file))
if variable[0] not in datavars: if variable[0] not in datavars:
raise SaveError("can not save variable '{}'. The variable's" raise SaveError("can not save variable '{}'. The variable's"
" package '{}' is not found".format( " package '{}' is not found".format(
@ -989,12 +983,42 @@ class CalculateExtension(Extension):
variable[0])) variable[0]))
modify_only = (variable[0] != 'custom') modify_only = (variable[0] != 'custom')
current_container = datavars[variable[0]] package = datavars[variable[0]]
variable_path = variable[1:] variable_name = variable[-1]
variable_name = variable_path.pop()
value_container = self._find_value_container(variable, package,
modify_only=modify_only)
# Теперь меняем знaчение переменной.
if isinstance(value_container, NamespaceNode):
self._modify_variables(variable, value_container, right_value,
optype, target=target_file,
modify_only=modify_only)
elif isinstance(value_container, VariableNode):
hash_value = value_container.get_value().get_hash()
if variable_name in hash_value:
if optype == self.ASSIGN:
new_value = right_value
elif optype == self.APPEND:
new_value = new_value + right_value
elif optype == self.REMOVE:
new_value = new_value - right_value
else:
new_value = right_value
hash_value.update({variable_name: new_value})
value_container.set(hash_value)
if target_file:
self._save_to_target(variable[:-1], variable_name,
new_value, target_file)
return ''
def _find_value_container(self, variable, vars_package, modify_only=True):
'''Метод для поиска контейнера, путь к которому указан в аргументе.
Этим контейнером может быть пространство имен или хэш.'''
current_container = vars_package
variable_path = variable[1:-1]
# Ищем пространство имен или в хэш, в котором должна быть переменная
# или значение.
for section in variable_path: for section in variable_path:
if section in current_container.namespaces: if section in current_container.namespaces:
current_container = current_container.namespaces[section] current_container = current_container.namespaces[section]
@ -1003,7 +1027,7 @@ class CalculateExtension(Extension):
current_container = current_container.variables[section] current_container = current_container.variables[section]
if section != variable_path[-1]: if section != variable_path[-1]:
# Если обнаружен хэш, но в пути к переменной кроме ключа # Если обнаружен хэш, но в пути к переменной кроме ключа
# хэша есть что-то еще -- значит путь к переменной # хэша есть еще что-то далее -- значит путь к переменной
# ошибочен. # ошибочен.
raise SaveError("can not save variable '{}'. Other" raise SaveError("can not save variable '{}'. Other"
" variable '{}' on the path".format( " variable '{}' on the path".format(
@ -1018,57 +1042,64 @@ class CalculateExtension(Extension):
'.'.join(variable), '.'.join(variable),
section, section,
current_container.get_fullname())) current_container.get_fullname()))
return current_container
# Теперь меняем знaчение переменной.
if isinstance(current_container, NamespaceNode): def _modify_variables(self, variable, namespace, new_value, optype,
if variable_name in current_container.variables: target='', modify_only=True):
variable_node = current_container[variable_name] '''Метод для модификации значения переменной.'''
if optype == self.ASSIGN: variable_name = variable[-1]
variable_node.set(right_value)
elif optype == self.APPEND: if variable_name in namespace.variables:
new_value = self._append_variable_value(variable_node, variable_node = namespace[variable_name]
right_value) if optype == self.ASSIGN:
variable_node.set(new_value) variable_node.set(new_value)
elif optype == self.REMOVE: elif optype == self.APPEND:
new_value = self._remove_variable_value(variable_node, new_value = self._append_variable_value(variable_node,
right_value) new_value)
variable_node.set(new_value) variable_node.set(new_value)
elif optype == self.REMOVE:
if target_file: new_value = self._remove_variable_value(variable_node,
self.save_to_target(variable[:-1], variable_name, new_value)
new_value, target_file) variable_node.set(new_value)
elif not modify_only: elif not modify_only:
VariableNode(variable_name, current_container, VariableNode(variable_name, namespace,
variable_type=IniType, source=str(right_value)) variable_type=IniType, source=str(new_value))
else:
if target_file: raise SaveError("can not create variable '{}' in the not"
self.save_to_target(variable[:-1], variable_name, " 'custom' namespace".
right_value, target_file) format('.'.join(variable)))
else:
raise SaveError("can not create variable '{}' in the not" if target:
" 'custom' namespace". if namespace.variables[variable_name].variable_type is HashType:
format('.'.join(variable))) for key, value in new_value.items():
elif isinstance(current_container, VariableNode): self._save_to_target(variable, key, value, target)
hash_value = current_container.get_value().get_hash()
if variable_name in hash_value:
if optype == self.ASSIGN:
new_value = right_value
elif optype == self.APPEND:
new_value = new_value + right_value
elif optype == self.REMOVE:
new_value = new_value - right_value
else: else:
new_value = right_value self._save_to_target(variable[:-1], variable_name,
new_value, target)
hash_value.update({variable_name: new_value})
current_container.set(hash_value) def _modify_hash(self, variable, hash_variable, new_value, optype,
if target_file: target=''):
self.save_to_target(variable[:-1], variable_name, '''Метод для модификации значения в переменной-хэше.'''
new_value, target_file) value_name = variable[-1]
print('VARIABLE IS SAVED') hash_value = hash_variable.get_value().get_hash()
return ''
if value_name in hash_value:
def save_to_target(self, namespace_name, variable_name, value, target): if optype == self.APPEND:
new_value = hash_value[value_name] + new_value
elif optype == self.REMOVE:
new_value = hash_value[value_name] - new_value
hash_value.update({value_name: new_value})
hash_variable.set(hash_value)
if target:
self._save_to_target(variable[:-1], value_name,
new_value, target)
def _save_to_target(self, namespace_name, variable_name, value, target):
'''Метод для добавления переменной в список переменных, значение
которых было установлено через тег save и при этом должно быть
сохранено в указанном файле: save.target_file.'''
namespace_name = tuple(namespace_name) namespace_name = tuple(namespace_name)
target_file_dict = self._datavars.variables_to_save[target] target_file_dict = self._datavars.variables_to_save[target]
if namespace_name not in target_file_dict: if namespace_name not in target_file_dict:
@ -1083,6 +1114,7 @@ class CalculateExtension(Extension):
variable.variable_type is FloatType): variable.variable_type is FloatType):
variable_value += value variable_value += value
return variable_value return variable_value
elif variable.variable_type is ListType: elif variable.variable_type is ListType:
if isinstance(value, str): if isinstance(value, str):
value = value.split(',') value = value.split(',')
@ -1093,6 +1125,7 @@ class CalculateExtension(Extension):
else: else:
variable_value.append(value) variable_value.append(value)
return variable_value return variable_value
elif variable.variable_type is IniType: elif variable.variable_type is IniType:
if not isinstance(variable_value, str): if not isinstance(variable_value, str):
variable_value = str(variable_value) variable_value = str(variable_value)
@ -1115,10 +1148,12 @@ class CalculateExtension(Extension):
def _remove_variable_value(self, variable, value): def _remove_variable_value(self, variable, value):
'''Метод описывающий операцию -= в теге save.''' '''Метод описывающий операцию -= в теге save.'''
variable_value = variable.get_value() variable_value = variable.get_value()
if (variable.variable_type is IntegerType or if (variable.variable_type is IntegerType or
variable.variable_type is FloatType): variable.variable_type is FloatType):
variable_value -= value variable_value -= value
return variable_value return variable_value
elif variable.variable_type is ListType: elif variable.variable_type is ListType:
if isinstance(value, str): if isinstance(value, str):
value = value.split(',') value = value.split(',')
@ -1129,6 +1164,7 @@ class CalculateExtension(Extension):
elif value in variable_value: elif value in variable_value:
variable_value.remove(value) variable_value.remove(value)
return variable_value return variable_value
elif variable.variable_type is IniType: elif variable.variable_type is IniType:
if not isinstance(variable_value, list): if not isinstance(variable_value, list):
if not isinstance(variable_value, str): if not isinstance(variable_value, str):
@ -1149,8 +1185,8 @@ class CalculateExtension(Extension):
# значение. # значение.
return variable_value return variable_value
def get_parameter(self): def _get_parameter(self):
'''Метод для разбра параметров, содержащихся в теге calculate.''' '''Метод для разбора параметров, содержащихся в теге calculate.'''
lineno = self.stream.current.lineno lineno = self.stream.current.lineno
parameter_name = self.stream.expect('name').value parameter_name = self.stream.expect('name').value
@ -1177,7 +1213,12 @@ class CalculateExtension(Extension):
return '' return ''
@contextfunction @contextfunction
def pkg(self, context, *args): def pkg(self, context, *args) -> Version:
'''Метод, реализующий функцию pkg() шаблонов. Функция предназначена для
получения версии пакета, к которому уже привязан шаблон, если
аргументов нет, или версию пакета в аргументе функции. Если аргументов
нет, а шаблон не привязан к какому-либо пакету, или если указанного в
аргументе пакета нет -- функция возвращает пустой объект Version().'''
package_atom_parser = PackageAtomParser() package_atom_parser = PackageAtomParser()
if args: if args:

@ -1,7 +1,6 @@
# vim: fileencoding=utf-8 # vim: fileencoding=utf-8
# #
from pprint import pprint from pprint import pprint
from typing import Tuple
from ..utils.package import PackageAtomParser, Package, PackageNotFound,\ from ..utils.package import PackageAtomParser, Package, PackageNotFound,\
PackageAtomName, Version PackageAtomName, Version
from ..utils.files import join_paths, write_file, read_file_lines, FilesError,\ from ..utils.files import join_paths, write_file, read_file_lines, FilesError,\
@ -21,7 +20,6 @@ import errno
import stat import stat
import glob import glob
import copy import copy
import re
import os import os
@ -723,30 +721,6 @@ class TemplateExecutor:
# Пока сохраняем только получившееся содержимое config-файла. # Пока сохраняем только получившееся содержимое config-файла.
self.calculate_config_file.save_changes() self.calculate_config_file.save_changes()
def _get_header_and_file_text(self, input_text: str, target_path: str,
target_format: Format, template_path: str):
header_pattern = (r'^{0}' + r'-' * 79 + r'\n' +
r'{0} Modified by Calculate Utilities [\d\w\.]*\n' +
r'{0} Processed template files:\n' +
r'(?P<template_paths>({0}\s*[/\w\d\-_\.]*\n)+)' +
r'{0}' + r'-' * 79 + r'\n?').format(
target_format.comment_symbol)
template_paths = [template_path]
if target_path in self.processed_targets:
header_regex = re.compile(header_pattern)
parsing_result = header_regex.search(input_text)
template_paths.extend(parsing_result.groupdict()[
'template_paths'].strip().split('\n'))
else:
self.processed_targets.append(target_path)
header = ('{0}' + '-' * 79 +
'{0} Modified by Calculate Utilities {1}\n' +
'{0} Processsed temlate files:\n' +
'{0}' + '\n{0} '.join + '\n' +
'{0}' + '-' * 79 + '\n').format(target_format.comment_symbol,
CALCULATE_VERSION)
return header, re.sub(header_pattern, '', input_text)
def _append_join_directory(self, template_object: TemplateWrapper) -> None: def _append_join_directory(self, template_object: TemplateWrapper) -> None:
'''Метод описывающий действия для append = "join", если шаблон -- '''Метод описывающий действия для append = "join", если шаблон --
директория. Создает директорию, если ее нет.''' директория. Создает директорию, если ее нет.'''
@ -1401,7 +1375,7 @@ class TemplateExecutor:
chown_value['gid']), chown_value['gid']),
str(error))) str(error)))
def _chmod_file(self, target_path: str, chmod_value: int): def _chmod_file(self, target_path: str, chmod_value: int) -> None:
'''Метод для смены прав доступа к директории.''' '''Метод для смены прав доступа к директории.'''
try: try:
if not os.path.exists(target_path): if not os.path.exists(target_path):
@ -1415,7 +1389,7 @@ class TemplateExecutor:
'Can not chmod file: {0}, reason: {1}'. 'Can not chmod file: {0}, reason: {1}'.
format(target_path, str(error))) format(target_path, str(error)))
def _get_file_mode(self, file_path: str): def _get_file_mode(self, file_path: str) -> int:
'''Метод для получения прав доступа для указанного файла.''' '''Метод для получения прав доступа для указанного файла.'''
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise TemplateExecutorError( raise TemplateExecutorError(
@ -1424,7 +1398,7 @@ class TemplateExecutor:
file_stat = os.stat(file_path) file_stat = os.stat(file_path)
return stat.S_IMODE(file_stat.st_mode) return stat.S_IMODE(file_stat.st_mode)
def _get_file_owner(self, file_path: str): def _get_file_owner(self, file_path: str) -> dict:
'''Метод для получения uid и gid значений для владельца указанного '''Метод для получения uid и gid значений для владельца указанного
файла.''' файла.'''
if not os.path.exists(file_path): if not os.path.exists(file_path):

@ -1,9 +1,12 @@
# vim: fileencoding=utf-8
#
import ast import ast
import dis import dis
from typing import List, Any from typing import List, Any
from contextlib import contextmanager from contextlib import contextmanager
from inspect import signature, getsource from inspect import signature, getsource
from types import FunctionType, LambdaType from types import FunctionType, LambdaType
from calculate.utils.tools import Singleton
class DependenceError(Exception): class DependenceError(Exception):
@ -418,7 +421,10 @@ class DependenceSource:
in self._args]), in self._args]),
str(error))) str(error)))
def get_args(self, namespace): def _get_args(self, namespace):
'''Метод для преобразования списка аргументов функции зависимости,
содержащего переменные и строки, в список аргументов состоящий только
из нод переменных и значений хэшей.'''
if not self._args_founded: if not self._args_founded:
for index in range(0, len(self._args)): for index in range(0, len(self._args)):
if isinstance(self._args[index], str): if isinstance(self._args[index], str):
@ -546,7 +552,7 @@ class VariableNode:
format(self.get_fullname())) format(self.get_fullname()))
if isinstance(self._source, DependenceSource): if isinstance(self._source, DependenceSource):
self._source.get_args(self.namespace) self._source._get_args(self.namespace)
with self._start_calculate(): with self._start_calculate():
try: try:
value = self._source.calculate_value() value = self._source.calculate_value()
@ -705,11 +711,21 @@ class NamespaceNode:
def add_variable(self, variable: VariableNode) -> None: def add_variable(self, variable: VariableNode) -> None:
'''Метод для добавления переменной в пространство имен.''' '''Метод для добавления переменной в пространство имен.'''
if variable.name in self.namespaces:
raise VariableError("namespace with the name '{}' is already in"
" the namespace '{}'".format(
variable.name,
self.get_fullname()))
self.variables.update({variable.name: variable}) self.variables.update({variable.name: variable})
variable.namespace = self variable.namespace = self
def add_namespace(self, namespace) -> None: def add_namespace(self, namespace) -> None:
'''Метод для добавления пространства имен в пространство имен.''' '''Метод для добавления пространства имен в пространство имен.'''
if namespace.name in self.variables:
raise VariableError("variable with the name '{}' is already in"
" the namespace '{}'".format(
namespace.name,
self.get_fullname()))
self.namespaces.update({namespace.name: namespace}) self.namespaces.update({namespace.name: namespace})
namespace.parent = self namespace.parent = self
@ -762,7 +778,7 @@ class NamespaceNode:
return '<Namespace: {}>'.format(self.get_fullname()) return '<Namespace: {}>'.format(self.get_fullname())
class DependenceAPI: class DependenceAPI(metaclass=Singleton):
'''Класс образующий интерфейс для создания зависимостей.''' '''Класс образующий интерфейс для создания зависимостей.'''
def __init__(self): def __init__(self):
self.current_namespace = None self.current_namespace = None
@ -813,7 +829,7 @@ class DependenceAPI:
return search_result return search_result
class VariableAPI: class VariableAPI(metaclass=Singleton):
'''Класс для создания переменных при задании их через '''Класс для создания переменных при задании их через
python-скрипты.''' python-скрипты.'''
def __init__(self): def __init__(self):
@ -849,7 +865,7 @@ class VariableAPI:
return variable return variable
class NamespaceAPI: class NamespaceAPI(metaclass=Singleton):
'''Класс для создания пространств имен при задании переменных через '''Класс для создания пространств имен при задании переменных через
python-скрипты.''' python-скрипты.'''
def __init__(self, var_fabric: VariableAPI, def __init__(self, var_fabric: VariableAPI,
@ -891,6 +907,8 @@ class NamespaceAPI:
self._dependence_fabric.current_namespace = self._datavars self._dependence_fabric.current_namespace = self._datavars
def set_current_namespace(self, namespace: NamespaceNode): def set_current_namespace(self, namespace: NamespaceNode):
'''Метод для установки текущего пространства имен, в которое будут
добавляться далее переменные и пространства имен.'''
self.current_namespace = namespace self.current_namespace = namespace
self._variables_fabric.current_namespace = namespace self._variables_fabric.current_namespace = namespace
self._dependence_fabric.current_namespace = namespace self._dependence_fabric.current_namespace = namespace

@ -0,0 +1,6 @@
{% for namespace_name, namespace in ini_dictionary.items() -%}
[{{ namespace_name | join('][') }}]
{% for variable, value in namespace.items() -%}
{{ variable }} {{ value | join(' ') }}
{% endfor %}
{% endfor -%}

@ -1,7 +1,9 @@
# vim: fileencoding=utf-8
#
import os import os
import importlib import importlib
import importlib.util import importlib.util
from pprint import pprint from jinja2 import Environment, FileSystemLoader
from calculate.variables.datavars import NamespaceNode, VariableNode,\ from calculate.variables.datavars import NamespaceNode, VariableNode,\
ListType, IntegerType,\ ListType, IntegerType,\
FloatType, IniType, TableType,\ FloatType, IniType, TableType,\
@ -9,6 +11,8 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\
VariableNotFoundError VariableNotFoundError
from calculate.utils.gentoo import ProfileWalker from calculate.utils.gentoo import ProfileWalker
from calculate.utils.files import read_file, FilesError from calculate.utils.files import read_file, FilesError
from calculate.utils.tools import Singleton
from calculate.utils.io_module import IOModule
from pyparsing import Literal, Word, ZeroOrMore, Group, Optional, restOfLine,\ from pyparsing import Literal, Word, ZeroOrMore, Group, Optional, restOfLine,\
empty, printables, OneOrMore, lineno, line, col, SkipTo,\ empty, printables, OneOrMore, lineno, line, col, SkipTo,\
LineEnd, Combine, nums LineEnd, Combine, nums
@ -16,13 +20,19 @@ from enum import Enum
from contextlib import contextmanager from contextlib import contextmanager
class LoaderError(Exception):
'''Исключение выбрасываемое загрузчиком, если тот в принципе не может
загрузить переменные.'''
pass
class Define(Enum): class Define(Enum):
assign = 0 assign = 0
append = 1 append = 1
remove = 2 remove = 2
class CalculateIniParser: class CalculateIniParser(metaclass=Singleton):
'''Класс парсера calculate.ini файлов.''' '''Класс парсера calculate.ini файлов.'''
def __init__(self): def __init__(self):
@ -140,11 +150,16 @@ class NamespaceIniFiller:
из calculate.ini файла.''' из calculate.ini файла.'''
available_sections = {'custom'} available_sections = {'custom'}
def __init__(self, restrict_creation=True): def __init__(self, restrict_creation=True, ouput=None):
self.ini_parser = CalculateIniParser() self.ini_parser = CalculateIniParser()
# Флаги, определяющие возможность создания новых переменных и новых
# пространств имен в данном пространстве имен.
self.restricted = restrict_creation self.restricted = restrict_creation
self.modify_only = False self.modify_only = False
self.output = ouput
def error(self, lineno, error_message): def error(self, lineno, error_message):
self.errors.append(lineno, error_message) self.errors.append(lineno, error_message)
@ -184,12 +199,14 @@ class NamespaceIniFiller:
'''Метод для получения доступа и создания пространств имен.''' '''Метод для получения доступа и создания пространств имен.'''
if self.restricted: if self.restricted:
self.modify_only = sections[0] not in self.available_sections self.modify_only = sections[0] not in self.available_sections
self.current_namespace = self.namespace self.current_namespace = self.namespace
for section in sections: for section in sections:
if isinstance(self.current_namespace, Datavars): if isinstance(self.current_namespace, Datavars):
if section not in self.current_namespace: if section not in self.current_namespace:
raise VariableError("variables package '{}' is not found". # TODO Поменять на простой вывод.
format(section)) raise VariableNotFoundError("variables package '{}' is not"
" found".format(section))
elif isinstance(self.current_namespace, NamespaceNode): elif isinstance(self.current_namespace, NamespaceNode):
if section not in self.current_namespace.namespaces: if section not in self.current_namespace.namespaces:
if (section in self.current_namespace.variables and if (section in self.current_namespace.variables and
@ -206,6 +223,7 @@ class NamespaceIniFiller:
else: else:
self.current_namespace = None self.current_namespace = None
return return
self.current_namespace = self.current_namespace.namespaces[section] self.current_namespace = self.current_namespace.namespaces[section]
def clear_section(self, sections: list) -> None: def clear_section(self, sections: list) -> None:
@ -266,6 +284,7 @@ class NamespaceIniFiller:
table_variable.source = table table_variable.source = table
def define_key(self, key: str, value: str, optype) -> None: def define_key(self, key: str, value: str, optype) -> None:
'''Метод для создания и модификации переменных.'''
if self.current_namespace is None: if self.current_namespace is None:
return return
@ -301,7 +320,10 @@ class NamespaceIniFiller:
source=value) source=value)
else: else:
# TODO Какая-то обработка ошибки. # TODO Какая-то обработка ошибки.
pass self.output.set_error("Can not create variable '{}.{}' in not"
" 'custom' namespace".format(
self.current_namespace.get_fullname(),
key))
def append_value(self, key: str, value: str) -> None: def append_value(self, key: str, value: str) -> None:
'''Метод выполняющий действия возложенные на оператор +=.''' '''Метод выполняющий действия возложенные на оператор +=.'''
@ -405,6 +427,7 @@ class VariableLoader:
def __init__(self, datavars, variables_path, repository_map=None): def __init__(self, datavars, variables_path, repository_map=None):
self.datavars = datavars self.datavars = datavars
self.output = datavars.output
self.ini_filler = NamespaceIniFiller() self.ini_filler = NamespaceIniFiller()
self.variables_path = variables_path self.variables_path = variables_path
@ -432,8 +455,9 @@ class VariableLoader:
if section in current_namespace: if section in current_namespace:
current_namespace = current_namespace[section] current_namespace = current_namespace[section]
else: else:
# TODO детальнее продумать действия при отсутствии нужной self.output.set_error("Variable 'os.gentoo.repositories'"
# переменной. " is not found. Can not load profile"
" variables.")
return return
self.repository_map = self._get_repository_map(self.datavars) self.repository_map = self._get_repository_map(self.datavars)
@ -442,8 +466,9 @@ class VariableLoader:
'path' in self.datavars.os.gentoo.profile): 'path' in self.datavars.os.gentoo.profile):
profile_path = self.datavars.os.gentoo.profile.path profile_path = self.datavars.os.gentoo.profile.path
else: else:
# TODO детальнее продумать действия при отсутствии нужной self.output.set_error("Variable 'os.gentoo.profile.path'"
# переменной. " is not found. Can not load profile"
" variables.")
return return
self._fill_from_ini(profile_path) self._fill_from_ini(profile_path)
@ -451,13 +476,17 @@ class VariableLoader:
def load_user_variables(self): def load_user_variables(self):
'''Метод для загрузки переменных из calculate.ini указанных в '''Метод для загрузки переменных из calculate.ini указанных в
переменных env_order и env_path.''' переменных env_order и env_path.'''
if ('system' in self.datavars and 'env_order' in self.datavars.system try:
and 'env_path' in self.datavars.system):
env_order = self.datavars.system.env_order env_order = self.datavars.system.env_order
env_path = self.datavars.system.env_path env_path = self.datavars.system.env_path
for ini_file in env_order: except VariableNotFoundError as error:
if ini_file in env_path: self.output.set_warning("Can not load additional variables: {}".
self.fill_from_custom_ini(env_path[ini_file].value) format(str(error)))
return
for ini_file in env_order:
if ini_file in env_path:
self.fill_from_custom_ini(env_path[ini_file].value)
def _fill_from_package(self, current_namespace: NamespaceNode, def _fill_from_package(self, current_namespace: NamespaceNode,
directory_path: str, package: str) -> None: directory_path: str, package: str) -> None:
@ -500,8 +529,8 @@ class VariableLoader:
ini_file_text = read_file(file_path) ini_file_text = read_file(file_path)
self.ini_filler.fill(self.datavars, ini_file_text) self.ini_filler.fill(self.datavars, ini_file_text)
except FilesError: except FilesError:
# TODO продумать обработку ошибок. self.output.set_error("Can not load profile variables from"
pass " unexisting file: {}".format(file_path))
def _get_repository_map(self, datavars): def _get_repository_map(self, datavars):
'''Метод для получения из переменной словаря с репозиториями и путями '''Метод для получения из переменной словаря с репозиториями и путями
@ -517,6 +546,7 @@ class VariableLoader:
@contextmanager @contextmanager
def test(self, file_name, namespace): def test(self, file_name, namespace):
'''Контекстный менеджер для тестирования.'''
print('IMPORT: {}.{}'.format(namespace.get_fullname(), file_name)) print('IMPORT: {}.{}'.format(namespace.get_fullname(), file_name))
try: try:
yield self yield self
@ -526,16 +556,73 @@ class VariableLoader:
class CalculateIniSaver: class CalculateIniSaver:
'''Класс для сохранения значений пользовательских переменных.''' '''Класс для сохранения значений переменных в указанные ini-файлы.'''
def __init__(self): def __init__(self, ini_parser=None):
self.ini_parser = CalculateIniParser() self.ini_parser = CalculateIniParser()
self.operations = {Define.assign: '=',
Define.append: '+=',
Define.remove: '-='}
file_loader = FileSystemLoader('calculate/variables')
environment = Environment(loader=file_loader)
self.ini_template = environment.get_template('ini_template')
def save_to_ini(self, target_path, variables_to_save):
'''Метод для сохранения переменных в указанный ini-файл.'''
ini_file_text = read_file(target_path)
ini_dictionary = self._parse_ini(ini_file_text)
for namespace in variables_to_save:
if namespace in ini_dictionary:
ini_dictionary[namespace].update(variables_to_save[namespace])
else:
ini_dictionary[namespace] = variables_to_save[namespace]
ini_file_text = self._get_ini_text(ini_dictionary)
with open(target_path, 'w') as ini_file:
ini_file.write(ini_file_text)
def _parse_ini(self, ini_file_text):
'''Метод для разбора текста ini-файла в словарь, в который далее будут
добавляться измененные переменные.'''
current_namespace = None
ini_dictionary = dict()
for parsed_line in self.ini_parser.parse(ini_file_text):
line_type = next(iter(parsed_line))
line_content = parsed_line[line_type]
if (line_type == 'start_section' or
line_type == 'start_table'):
current_namespace = tuple(line_content[0])
if current_namespace not in ini_dictionary:
ini_dictionary[current_namespace] = dict()
elif (line_type == 'clear_section' or
line_type == 'clear_table'):
current_namespace = (*line_content[0], '')
ini_dictionary[current_namespace] = dict()
elif line_type == 'define_key':
namespace = ini_dictionary[current_namespace]
namespace.update({line_content[0]:
(self.operations[line_content[2]],
line_content[1])})
return ini_dictionary
def _get_ini_text(self, ini_dictionary):
'''Метод для получения текста ini файла, полученного в результате
наложения изменений из тегов save в шаблонах.'''
ini_text = self.ini_template.render(ini_dictionary=ini_dictionary)
return ini_text.strip()
class Datavars: class Datavars:
'''Класс для хранения переменных и управления ими.''' '''Класс для хранения переменных и управления ими.'''
def __init__(self, variables_path='calculate/vars', repository_map=None): def __init__(self, variables_path='calculate/vars', repository_map=None,
io_module=IOModule()):
self._variables_path = variables_path self._variables_path = variables_path
self._available_packages = self._get_available_packages() self._available_packages = self._get_available_packages()
self.output = io_module
self.root = NamespaceNode('<root>') self.root = NamespaceNode('<root>')
self._loader = VariableLoader(self, self._variables_path, self._loader = VariableLoader(self, self._variables_path,
@ -619,59 +706,20 @@ class Datavars:
self._loader.load_variables_package(package_name) self._loader.load_variables_package(package_name)
return True return True
def add_namespace(self, namespace_node):
self.root.add_namespace(namespace_node)
@property @property
def namespaces(self): def namespaces(self):
return self.root.namespaces return self.root.namespaces
def save_variables(self): def save_variables(self):
'''Метод для сохранения в calculate.ini файлах ''' '''Метод для сохранения значений переменных в calculate.ini файлах.'''
ini_parser = self._loader.ini_filler.ini_parser
target_paths = self.system.env_path target_paths = self.system.env_path
saver = CalculateIniSaver()
operations = {Define.assign: '=',
Define.append: '+=',
Define.remove: '-='}
for target in self.variables_to_save: for target in self.variables_to_save:
if self.variables_to_save[target]: if self.variables_to_save[target]:
dict_to_save = self.variables_to_save[target] dict_to_save = self.variables_to_save[target]
target_path = target_paths[target].value
# Распарсим файл в словарь. saver.save_to_ini(target_path, dict_to_save)
current_namespace = None
ini_dictionary = dict()
ini_file_text = read_file(target_paths[target].value)
for parsed_line in ini_parser.parse(ini_file_text):
line_type = next(iter(parsed_line))
print('line_type: {}'.format(line_type))
line_content = parsed_line[line_type]
print('line_content: {}'.format(line_content))
if (line_type == 'start_section' or
line_type == 'start_table'):
current_namespace = tuple(line_content[0])
if current_namespace not in ini_dictionary:
ini_dictionary[current_namespace] = dict()
elif (line_type == 'clear_section' or
line_type == 'clear_table'):
current_namespace = (*line_content[0], '')
ini_dictionary[current_namespace] = dict()
elif line_type == 'define_key':
namespace = ini_dictionary[current_namespace]
namespace.update({line_content[0]:
(operations[line_content[2]],
line_content[1])})
for namespace in dict_to_save:
if namespace in ini_dictionary:
ini_dictionary[namespace].update(
dict_to_save[namespace])
else:
ini_dictionary[namespace] = dict_to_save[namespace]
print('INI DICTIONARY FOR {}:'.format(target))
pprint(ini_dictionary)
def _get_text(self, ini_dictionary):
pass

@ -52,7 +52,7 @@ with Namespace('profile'):
# Название профиля # Название профиля
Variable('name', type=StringType, Variable('name', type=StringType,
source=Dependence('.path', '..repositories', source=Dependence('.path', '..repositories',
depend=get_profile_link)) depend=get_profile_name))
def get_repository_table(config): def get_repository_table(config):

@ -1157,7 +1157,7 @@ class TestTemplateWrapper:
join_paths(CHROOT_PATH, join_paths(CHROOT_PATH,
'/etc/dir/file.conf'), '/etc/dir/file.conf'),
parameters_object, FILE, parameters_object, FILE,
'/path/to/template', '/path/to/template',
chroot_path=CHROOT_PATH, chroot_path=CHROOT_PATH,
config_archive_path=CONFIG_ARCHIVE_PATH) config_archive_path=CONFIG_ARCHIVE_PATH)
except Exception as error: except Exception as error:
@ -1210,13 +1210,11 @@ class TestTemplateWrapper:
'mirror': True}) 'mirror': True})
with pytest.raises(TemplateExecutorError): with pytest.raises(TemplateExecutorError):
template_wrapper = TemplateWrapper( TemplateWrapper(join_paths(CHROOT_PATH, '/etc/dir/none'),
join_paths(CHROOT_PATH, parameters_object, FILE,
'/etc/dir/none'), '/path/to/template',
parameters_object, FILE, chroot_path=CHROOT_PATH,
'/path/to/template', config_archive_path=CONFIG_ARCHIVE_PATH)
chroot_path=CHROOT_PATH,
config_archive_path=CONFIG_ARCHIVE_PATH)
def test_if_mirror_parameter_is_set_and_file_from_the_source_parameter_does_not_exist__a_TemplateWrapper_object_sets_remove_original_flag_as_True(self): def test_if_mirror_parameter_is_set_and_file_from_the_source_parameter_does_not_exist__a_TemplateWrapper_object_sets_remove_original_flag_as_True(self):
parameters_object = ParametersContainer({'package': test_package_name, parameters_object = ParametersContainer({'package': test_package_name,

@ -1,7 +1,6 @@
import os import os
import shutil import shutil
import pytest import pytest
from pprint import pprint
from calculate.variables.datavars import NamespaceNode, VariableNode,\ from calculate.variables.datavars import NamespaceNode, VariableNode,\
Namespace, Variable, Dependence,\ Namespace, Variable, Dependence,\
CyclicVariableError, HashType,\ CyclicVariableError, HashType,\
@ -13,7 +12,7 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\
from calculate.templates.template_engine import TemplateEngine, FILE from calculate.templates.template_engine import TemplateEngine, FILE
from calculate.templates.template_processor import TemplateExecutor from calculate.templates.template_processor import TemplateExecutor
from calculate.variables.loader import NamespaceIniFiller, Datavars from calculate.variables.loader import NamespaceIniFiller, Datavars
from calculate.utils.files import stderr_devnull from calculate.utils.files import stderr_devnull, read_file
TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles')
@ -25,7 +24,7 @@ APPENDS_SET = TemplateExecutor(cl_config_path=os.path.join(
@pytest.mark.vars @pytest.mark.vars
class TestNamespace: class TestDatavars:
# Сначала тестируем классы и методы необходимые для построения дерева # Сначала тестируем классы и методы необходимые для построения дерева
# переменных и пространств имен. # переменных и пространств имен.
def test_if_NamespaceNode_just_initialized_with_its_name__the_NamespaceNode_object_contains_empty_namespaces_and_variables_dictionaries_and_fullname_method_returns_only_the_namespace_s_name(self): def test_if_NamespaceNode_just_initialized_with_its_name__the_NamespaceNode_object_contains_empty_namespaces_and_variables_dictionaries_and_fullname_method_returns_only_the_namespace_s_name(self):
@ -1016,6 +1015,8 @@ value = another_value
{'name': 'common_name', 'value': 'another_value'}, {'name': 'common_name', 'value': 'another_value'},
{'name': 'name_3', 'value': 'value_3'}] {'name': 'name_3', 'value': 'value_3'}]
# Теперь тестируем применение объекта Datavars, через который
# осуществляется доступ к переменным и их загрузка.
def test_if_a_Datavars_object_is_created_with_path_to_the_variables_without_any_Dependencies_and_then_used_to_get_access_to_the_some_variables_from__the_datavars_object_dynamically_loads_variables_and_retruns_necessary_variables(self): def test_if_a_Datavars_object_is_created_with_path_to_the_variables_without_any_Dependencies_and_then_used_to_get_access_to_the_some_variables_from__the_datavars_object_dynamically_loads_variables_and_retruns_necessary_variables(self):
datavars = Datavars( datavars = Datavars(
variables_path='tests/variables/testfiles/variables_0', variables_path='tests/variables/testfiles/variables_0',
@ -1197,6 +1198,7 @@ value = another_value
assert datavars.os.calculate == 'new1 new2' assert datavars.os.calculate == 'new1 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): 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( datavars = Datavars(
variables_path=os.path.join(TESTFILES_PATH, 'variables_7')) variables_path=os.path.join(TESTFILES_PATH, 'variables_7'))
@ -1217,7 +1219,7 @@ os.calculate = new1 new2'''
text = template_engine.template_text text = template_engine.template_text
assert text == output_text assert text == output_text
def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object_and_save_tag_is_used_for_saving_some_variables_and_target_file_is_not_set_for_the_save_tag__variables_from_the_Datavars_object_can_be_inserted_in_the_processed_template(self): def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object_and_save_tag_is_used_for_saving_some_variables_and_target_file_is_not_set_for_the_save_tag__the_Datavars_object_saves_and_modifies_variables_from_the_save_tag(self):
datavars = Datavars( datavars = Datavars(
variables_path=os.path.join(TESTFILES_PATH, 'variables_7')) variables_path=os.path.join(TESTFILES_PATH, 'variables_7'))
template_engine = TemplateEngine(appends_set=APPENDS_SET, template_engine = TemplateEngine(appends_set=APPENDS_SET,
@ -1246,7 +1248,7 @@ os.calculate = new1 new2'''
assert datavars.os.hashvar.value2 == 'weird2' assert datavars.os.hashvar.value2 == 'weird2'
assert datavars.os.calculate == 'new1 weird2' assert datavars.os.calculate == 'new1 weird2'
def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object_and_save_tag_is_used_for_appending_and_removing_some_variable_s_values_and_target_file_is_not_set_for_the_save_tag__variables_from_the_Datavars_object_can_be_inserted_in_the_processed_template(self): def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object_and_save_tag_is_used_for_appending_and_removing_some_variable_s_values_and_target_file_is_not_set_for_the_save_tag__the_Datavars_object_changes_variables_from_the_save_tag(self):
datavars = Datavars( datavars = Datavars(
variables_path=os.path.join(TESTFILES_PATH, 'variables_8')) variables_path=os.path.join(TESTFILES_PATH, 'variables_8'))
template_engine = TemplateEngine(appends_set=APPENDS_SET, template_engine = TemplateEngine(appends_set=APPENDS_SET,
@ -1289,7 +1291,7 @@ os.calculate = new1 new2'''
assert datavars.os.linux.test_4 == [2, 4, 5] assert datavars.os.linux.test_4 == [2, 4, 5]
assert datavars.custom.ns.var_2 == 'val2,val3,val5' assert datavars.custom.ns.var_2 == 'val2,val3,val5'
def test_save_variables_to_target_files(self): def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object_and_save_tag_is_used_for_changing_some_variable_s_values_and_target_file_is_set_for_the_save_tag__the_Datavars_object_creates_new_and_modifies_existing_variables_and_saves_them_to_the_target_files(self):
datavars = Datavars( datavars = Datavars(
variables_path=os.path.join(TESTFILES_PATH, 'variables_9')) variables_path=os.path.join(TESTFILES_PATH, 'variables_9'))
template_engine = TemplateEngine(appends_set=APPENDS_SET, template_engine = TemplateEngine(appends_set=APPENDS_SET,
@ -1299,13 +1301,64 @@ os.calculate = new1 new2'''
input_template_1 = '''{% calculate name = 'filename', force -%} input_template_1 = '''{% calculate name = 'filename', force -%}
{% save.local custom.ns.var_3 = 'value' -%} {% save.local custom.ns.var_3 = 'value' -%}
{% save.local custom.ns.var_2 += 'val4' -%} {% save.local custom.ns.var_2 += 'val4' -%}
{% save.system custom.ns.var_5 = '/highway/to/hell' -%}
''' '''
local_ini_result = '''[custom][ns]
var_1 = 1
var_2 = val1,val2,val3,val4
var_3 = value
[os][linux]
test_1 += 12'''
system_ini_result = '''[custom][ns]
var_5 = /highway/to/hell
var_6 = 12
[os][linux]
test = new value'''
template_engine.process_template_from_string(input_template_1, FILE) template_engine.process_template_from_string(input_template_1, FILE)
print('VARIABLES TO SAVE:')
pprint(datavars.variables_to_save)
datavars.save_variables() datavars.save_variables()
assert False
assert local_ini_result == read_file(
datavars.system.env_path['local'].value)
assert system_ini_result == read_file(
datavars.system.env_path['system'].value)
def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object_and_save_tag_is_used_for_changing_some_hash_variable_s_values_and_target_file_is_set_for_the_save_tag__the_Datavars_object_modifies_hash_variables_and_saves_them_to_the_target_files_as_namespaces(self):
datavars = Datavars(
variables_path=os.path.join(TESTFILES_PATH, 'variables_10'))
template_engine = TemplateEngine(appends_set=APPENDS_SET,
chroot_path=TESTFILES_PATH,
datavars_module=datavars)
input_template_1 = '''{% calculate name = 'filename', force -%}
{% save.system os.hashvar_0 = {'value1': 'new1', 'value2': 'new2'} -%}
{% save.system os.hashvar_1.key1 = 'weird1' -%}
{% save.system os.hashvar_2.id_1 = 1575 -%}
{% save.system os.hashvar_2.id_2 = 1349 -%}
'''
system_ini_result = '''[os][hashvar_0]
value1 = new1
value2 = new2
[os][hashvar_1]
key1 = weird1
[os][hashvar_2]
id_1 = 1575
id_2 = 1349'''
template_engine.process_template_from_string(input_template_1, FILE)
datavars.save_variables()
assert system_ini_result ==\
read_file(datavars.system.env_path['system'].value)
# Теперь тестируем обработку ошибок.
def test_for_removing_testfiles(self): def test_for_removing_testfiles(self):
shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo')) shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo'))

@ -1,3 +1,6 @@
[custom][ns] [custom][ns]
var_1 = 1 var_1 = 1
var_2 = val1,val2,val3 var_2 = val1,val2,val3
[os][linux]
test_1 += 12

@ -1,3 +1,6 @@
[custom][ns] [custom][ns]
var_1 = 1 var_5 = /the/true/path/to/eternity/in/the/abyss
var_2 = val1,val2,val3 var_6 = 12
[os][linux]
test = new value

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

@ -0,0 +1,47 @@
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_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")}])

@ -0,0 +1,19 @@
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/variables/testfiles')
# Список мест, где есть calculate.ini файлы.
Variable('env_order', type=ListType, source=['system', 'local'])
# Отображение множества мест, где есть calculate.ini файлы, на пути к ним.
Variable('env_path', type=HashType,
source={'system': os.path.join(TESTFILES_PATH,
'ini_vars/calculate_6.ini')})

@ -15,7 +15,7 @@ Variable('env_order', type=ListType, source=['system', 'local'])
# Отображение множества мест, где есть calculate.ini файлы, на пути к ним. # Отображение множества мест, где есть calculate.ini файлы, на пути к ним.
Variable('env_path', type=HashType, Variable('env_path', type=HashType,
source={'system': os.path.join(TESTFILES_PATH, source={'local': os.path.join(TESTFILES_PATH,
'ini_vars/calculate_4.ini'), 'ini_vars/calculate_4.ini'),
'local': os.path.join(TESTFILES_PATH, 'system': os.path.join(TESTFILES_PATH,
'ini_vars/calculate_5.ini')}) 'ini_vars/calculate_5.ini')})

Loading…
Cancel
Save