diff --git a/calculate/templates/template_engine.py b/calculate/templates/template_engine.py index eaecb37..307990e 100644 --- a/calculate/templates/template_engine.py +++ b/calculate/templates/template_engine.py @@ -19,8 +19,7 @@ from ..utils.files import join_paths, check_directory_link, check_command,\ FilesError from calculate.variables.datavars import HashType, NamespaceNode,\ VariableNode, IniType, IntegerType,\ - FloatType, ListType,\ - VariableNotFoundError + FloatType, ListType # Типы шаблона: директория или файл. @@ -852,7 +851,7 @@ class CalculateExtension(Extension): self.CONDITION_TOKENS_TYPES): # разбираем параметр. # 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', [name_node, @@ -977,11 +976,6 @@ class CalculateExtension(Extension): optype, context): '''Метод для сохранения значений переменных указанных в теге save.''' 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: raise SaveError("can not save variable '{}'. The variable's" " package '{}' is not found".format( @@ -989,12 +983,42 @@ class CalculateExtension(Extension): variable[0])) modify_only = (variable[0] != 'custom') - current_container = datavars[variable[0]] - variable_path = variable[1:] - variable_name = variable_path.pop() + package = datavars[variable[0]] + variable_name = variable[-1] + + 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: if section in current_container.namespaces: current_container = current_container.namespaces[section] @@ -1003,7 +1027,7 @@ class CalculateExtension(Extension): current_container = current_container.variables[section] if section != variable_path[-1]: # Если обнаружен хэш, но в пути к переменной кроме ключа - # хэша есть что-то еще -- значит путь к переменной + # хэша есть еще что-то далее -- значит путь к переменной # ошибочен. raise SaveError("can not save variable '{}'. Other" " variable '{}' on the path".format( @@ -1018,57 +1042,64 @@ class CalculateExtension(Extension): '.'.join(variable), section, current_container.get_fullname())) - - # Теперь меняем знaчение переменной. - if isinstance(current_container, NamespaceNode): - if variable_name in current_container.variables: - variable_node = current_container[variable_name] - if optype == self.ASSIGN: - variable_node.set(right_value) - elif optype == self.APPEND: - new_value = self._append_variable_value(variable_node, - right_value) - variable_node.set(new_value) - elif optype == self.REMOVE: - new_value = self._remove_variable_value(variable_node, - right_value) - variable_node.set(new_value) - - if target_file: - self.save_to_target(variable[:-1], variable_name, - new_value, target_file) - elif not modify_only: - VariableNode(variable_name, current_container, - variable_type=IniType, source=str(right_value)) - - if target_file: - self.save_to_target(variable[:-1], variable_name, - right_value, target_file) - else: - raise SaveError("can not create variable '{}' in the not" - " 'custom' namespace". - format('.'.join(variable))) - elif isinstance(current_container, VariableNode): - 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 + return current_container + + def _modify_variables(self, variable, namespace, new_value, optype, + target='', modify_only=True): + '''Метод для модификации значения переменной.''' + variable_name = variable[-1] + + if variable_name in namespace.variables: + variable_node = namespace[variable_name] + if optype == self.ASSIGN: + variable_node.set(new_value) + elif optype == self.APPEND: + new_value = self._append_variable_value(variable_node, + new_value) + variable_node.set(new_value) + elif optype == self.REMOVE: + new_value = self._remove_variable_value(variable_node, + new_value) + variable_node.set(new_value) + elif not modify_only: + VariableNode(variable_name, namespace, + variable_type=IniType, source=str(new_value)) + else: + raise SaveError("can not create variable '{}' in the not" + " 'custom' namespace". + format('.'.join(variable))) + + if target: + if namespace.variables[variable_name].variable_type is HashType: + for key, value in new_value.items(): + self._save_to_target(variable, key, value, target) else: - new_value = right_value - - hash_value.update({variable_name: new_value}) - current_container.set(hash_value) - if target_file: - self.save_to_target(variable[:-1], variable_name, - new_value, target_file) - print('VARIABLE IS SAVED') - return '' - - def save_to_target(self, namespace_name, variable_name, value, target): + self._save_to_target(variable[:-1], variable_name, + new_value, target) + + def _modify_hash(self, variable, hash_variable, new_value, optype, + target=''): + '''Метод для модификации значения в переменной-хэше.''' + value_name = variable[-1] + hash_value = hash_variable.get_value().get_hash() + + if value_name in hash_value: + 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) target_file_dict = self._datavars.variables_to_save[target] if namespace_name not in target_file_dict: @@ -1083,6 +1114,7 @@ class CalculateExtension(Extension): variable.variable_type is FloatType): variable_value += value return variable_value + elif variable.variable_type is ListType: if isinstance(value, str): value = value.split(',') @@ -1093,6 +1125,7 @@ class CalculateExtension(Extension): else: variable_value.append(value) return variable_value + elif variable.variable_type is IniType: if not isinstance(variable_value, str): variable_value = str(variable_value) @@ -1115,10 +1148,12 @@ class CalculateExtension(Extension): def _remove_variable_value(self, variable, value): '''Метод описывающий операцию -= в теге save.''' variable_value = variable.get_value() + if (variable.variable_type is IntegerType or variable.variable_type is FloatType): variable_value -= value return variable_value + elif variable.variable_type is ListType: if isinstance(value, str): value = value.split(',') @@ -1129,6 +1164,7 @@ class CalculateExtension(Extension): elif value in variable_value: variable_value.remove(value) return variable_value + elif variable.variable_type is IniType: if not isinstance(variable_value, list): if not isinstance(variable_value, str): @@ -1149,8 +1185,8 @@ class CalculateExtension(Extension): # значение. return variable_value - def get_parameter(self): - '''Метод для разбра параметров, содержащихся в теге calculate.''' + def _get_parameter(self): + '''Метод для разбора параметров, содержащихся в теге calculate.''' lineno = self.stream.current.lineno parameter_name = self.stream.expect('name').value @@ -1177,7 +1213,12 @@ class CalculateExtension(Extension): return '' @contextfunction - def pkg(self, context, *args): + def pkg(self, context, *args) -> Version: + '''Метод, реализующий функцию pkg() шаблонов. Функция предназначена для + получения версии пакета, к которому уже привязан шаблон, если + аргументов нет, или версию пакета в аргументе функции. Если аргументов + нет, а шаблон не привязан к какому-либо пакету, или если указанного в + аргументе пакета нет -- функция возвращает пустой объект Version().''' package_atom_parser = PackageAtomParser() if args: diff --git a/calculate/templates/template_processor.py b/calculate/templates/template_processor.py index 7002a9d..04ba596 100644 --- a/calculate/templates/template_processor.py +++ b/calculate/templates/template_processor.py @@ -1,7 +1,6 @@ # vim: fileencoding=utf-8 # from pprint import pprint -from typing import Tuple from ..utils.package import PackageAtomParser, Package, PackageNotFound,\ PackageAtomName, Version from ..utils.files import join_paths, write_file, read_file_lines, FilesError,\ @@ -21,7 +20,6 @@ import errno import stat import glob import copy -import re import os @@ -723,30 +721,6 @@ class TemplateExecutor: # Пока сохраняем только получившееся содержимое config-файла. 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({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: '''Метод описывающий действия для append = "join", если шаблон -- директория. Создает директорию, если ее нет.''' @@ -1401,7 +1375,7 @@ class TemplateExecutor: chown_value['gid']), str(error))) - def _chmod_file(self, target_path: str, chmod_value: int): + def _chmod_file(self, target_path: str, chmod_value: int) -> None: '''Метод для смены прав доступа к директории.''' try: if not os.path.exists(target_path): @@ -1415,7 +1389,7 @@ class TemplateExecutor: 'Can not chmod file: {0}, reason: {1}'. 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): raise TemplateExecutorError( @@ -1424,7 +1398,7 @@ class TemplateExecutor: file_stat = os.stat(file_path) 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 значений для владельца указанного файла.''' if not os.path.exists(file_path): diff --git a/calculate/variables/datavars.py b/calculate/variables/datavars.py index 552f0ff..fe1dc11 100644 --- a/calculate/variables/datavars.py +++ b/calculate/variables/datavars.py @@ -1,9 +1,12 @@ +# vim: fileencoding=utf-8 +# import ast import dis from typing import List, Any from contextlib import contextmanager from inspect import signature, getsource from types import FunctionType, LambdaType +from calculate.utils.tools import Singleton class DependenceError(Exception): @@ -418,7 +421,10 @@ class DependenceSource: in self._args]), str(error))) - def get_args(self, namespace): + def _get_args(self, namespace): + '''Метод для преобразования списка аргументов функции зависимости, + содержащего переменные и строки, в список аргументов состоящий только + из нод переменных и значений хэшей.''' if not self._args_founded: for index in range(0, len(self._args)): if isinstance(self._args[index], str): @@ -546,7 +552,7 @@ class VariableNode: format(self.get_fullname())) if isinstance(self._source, DependenceSource): - self._source.get_args(self.namespace) + self._source._get_args(self.namespace) with self._start_calculate(): try: value = self._source.calculate_value() @@ -705,11 +711,21 @@ class NamespaceNode: 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}) variable.namespace = self 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}) namespace.parent = self @@ -762,7 +778,7 @@ class NamespaceNode: return ''.format(self.get_fullname()) -class DependenceAPI: +class DependenceAPI(metaclass=Singleton): '''Класс образующий интерфейс для создания зависимостей.''' def __init__(self): self.current_namespace = None @@ -813,7 +829,7 @@ class DependenceAPI: return search_result -class VariableAPI: +class VariableAPI(metaclass=Singleton): '''Класс для создания переменных при задании их через python-скрипты.''' def __init__(self): @@ -849,7 +865,7 @@ class VariableAPI: return variable -class NamespaceAPI: +class NamespaceAPI(metaclass=Singleton): '''Класс для создания пространств имен при задании переменных через python-скрипты.''' def __init__(self, var_fabric: VariableAPI, @@ -891,6 +907,8 @@ class NamespaceAPI: self._dependence_fabric.current_namespace = self._datavars def set_current_namespace(self, namespace: NamespaceNode): + '''Метод для установки текущего пространства имен, в которое будут + добавляться далее переменные и пространства имен.''' self.current_namespace = namespace self._variables_fabric.current_namespace = namespace self._dependence_fabric.current_namespace = namespace diff --git a/calculate/variables/ini_template b/calculate/variables/ini_template index e69de29..bbcc948 100644 --- a/calculate/variables/ini_template +++ b/calculate/variables/ini_template @@ -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 -%} diff --git a/calculate/variables/loader.py b/calculate/variables/loader.py index 2adf818..a909430 100644 --- a/calculate/variables/loader.py +++ b/calculate/variables/loader.py @@ -1,7 +1,9 @@ +# vim: fileencoding=utf-8 +# import os import importlib import importlib.util -from pprint import pprint +from jinja2 import Environment, FileSystemLoader from calculate.variables.datavars import NamespaceNode, VariableNode,\ ListType, IntegerType,\ FloatType, IniType, TableType,\ @@ -9,6 +11,8 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\ VariableNotFoundError from calculate.utils.gentoo import ProfileWalker 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,\ empty, printables, OneOrMore, lineno, line, col, SkipTo,\ LineEnd, Combine, nums @@ -16,13 +20,19 @@ from enum import Enum from contextlib import contextmanager +class LoaderError(Exception): + '''Исключение выбрасываемое загрузчиком, если тот в принципе не может + загрузить переменные.''' + pass + + class Define(Enum): assign = 0 append = 1 remove = 2 -class CalculateIniParser: +class CalculateIniParser(metaclass=Singleton): '''Класс парсера calculate.ini файлов.''' def __init__(self): @@ -140,11 +150,16 @@ class NamespaceIniFiller: из calculate.ini файла.''' available_sections = {'custom'} - def __init__(self, restrict_creation=True): + def __init__(self, restrict_creation=True, ouput=None): self.ini_parser = CalculateIniParser() + + # Флаги, определяющие возможность создания новых переменных и новых + # пространств имен в данном пространстве имен. self.restricted = restrict_creation self.modify_only = False + self.output = ouput + def error(self, lineno, error_message): self.errors.append(lineno, error_message) @@ -184,12 +199,14 @@ class NamespaceIniFiller: '''Метод для получения доступа и создания пространств имен.''' if self.restricted: self.modify_only = sections[0] not in self.available_sections + self.current_namespace = self.namespace for section in sections: if isinstance(self.current_namespace, Datavars): if section not in self.current_namespace: - raise VariableError("variables package '{}' is not found". - format(section)) + # TODO Поменять на простой вывод. + raise VariableNotFoundError("variables package '{}' is not" + " found".format(section)) elif isinstance(self.current_namespace, NamespaceNode): if section not in self.current_namespace.namespaces: if (section in self.current_namespace.variables and @@ -206,6 +223,7 @@ class NamespaceIniFiller: else: self.current_namespace = None return + self.current_namespace = self.current_namespace.namespaces[section] def clear_section(self, sections: list) -> None: @@ -266,6 +284,7 @@ class NamespaceIniFiller: table_variable.source = table def define_key(self, key: str, value: str, optype) -> None: + '''Метод для создания и модификации переменных.''' if self.current_namespace is None: return @@ -301,7 +320,10 @@ class NamespaceIniFiller: source=value) else: # 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: '''Метод выполняющий действия возложенные на оператор +=.''' @@ -405,6 +427,7 @@ class VariableLoader: def __init__(self, datavars, variables_path, repository_map=None): self.datavars = datavars + self.output = datavars.output self.ini_filler = NamespaceIniFiller() self.variables_path = variables_path @@ -432,8 +455,9 @@ class VariableLoader: if section in current_namespace: current_namespace = current_namespace[section] else: - # TODO детальнее продумать действия при отсутствии нужной - # переменной. + self.output.set_error("Variable 'os.gentoo.repositories'" + " is not found. Can not load profile" + " variables.") return self.repository_map = self._get_repository_map(self.datavars) @@ -442,8 +466,9 @@ class VariableLoader: 'path' in self.datavars.os.gentoo.profile): profile_path = self.datavars.os.gentoo.profile.path else: - # TODO детальнее продумать действия при отсутствии нужной - # переменной. + self.output.set_error("Variable 'os.gentoo.profile.path'" + " is not found. Can not load profile" + " variables.") return self._fill_from_ini(profile_path) @@ -451,13 +476,17 @@ class VariableLoader: def load_user_variables(self): '''Метод для загрузки переменных из calculate.ini указанных в переменных env_order и env_path.''' - if ('system' in self.datavars and 'env_order' in self.datavars.system - and 'env_path' in self.datavars.system): + try: env_order = self.datavars.system.env_order env_path = self.datavars.system.env_path - for ini_file in env_order: - if ini_file in env_path: - self.fill_from_custom_ini(env_path[ini_file].value) + except VariableNotFoundError as error: + self.output.set_warning("Can not load additional variables: {}". + 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, directory_path: str, package: str) -> None: @@ -500,8 +529,8 @@ class VariableLoader: ini_file_text = read_file(file_path) self.ini_filler.fill(self.datavars, ini_file_text) except FilesError: - # TODO продумать обработку ошибок. - pass + self.output.set_error("Can not load profile variables from" + " unexisting file: {}".format(file_path)) def _get_repository_map(self, datavars): '''Метод для получения из переменной словаря с репозиториями и путями @@ -517,6 +546,7 @@ class VariableLoader: @contextmanager def test(self, file_name, namespace): + '''Контекстный менеджер для тестирования.''' print('IMPORT: {}.{}'.format(namespace.get_fullname(), file_name)) try: yield self @@ -526,16 +556,73 @@ class VariableLoader: class CalculateIniSaver: - '''Класс для сохранения значений пользовательских переменных.''' - def __init__(self): + '''Класс для сохранения значений переменных в указанные ini-файлы.''' + def __init__(self, ini_parser=None): 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: '''Класс для хранения переменных и управления ими.''' - 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._available_packages = self._get_available_packages() + self.output = io_module self.root = NamespaceNode('') self._loader = VariableLoader(self, self._variables_path, @@ -619,59 +706,20 @@ class Datavars: self._loader.load_variables_package(package_name) return True + def add_namespace(self, namespace_node): + self.root.add_namespace(namespace_node) + @property def namespaces(self): return self.root.namespaces def save_variables(self): - '''Метод для сохранения в calculate.ini файлах ''' - ini_parser = self._loader.ini_filler.ini_parser + '''Метод для сохранения значений переменных в calculate.ini файлах.''' target_paths = self.system.env_path - - operations = {Define.assign: '=', - Define.append: '+=', - Define.remove: '-='} + saver = CalculateIniSaver() for target in self.variables_to_save: if self.variables_to_save[target]: dict_to_save = self.variables_to_save[target] - - # Распарсим файл в словарь. - 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 + target_path = target_paths[target].value + saver.save_to_ini(target_path, dict_to_save) diff --git a/calculate/vars/os/gentoo/__init__.py b/calculate/vars/os/gentoo/__init__.py index 235f09d..c0c0027 100644 --- a/calculate/vars/os/gentoo/__init__.py +++ b/calculate/vars/os/gentoo/__init__.py @@ -52,7 +52,7 @@ with Namespace('profile'): # Название профиля Variable('name', type=StringType, source=Dependence('.path', '..repositories', - depend=get_profile_link)) + depend=get_profile_name)) def get_repository_table(config): diff --git a/tests/templates/test_template_wrapper.py b/tests/templates/test_template_wrapper.py index ffabae1..852cab8 100644 --- a/tests/templates/test_template_wrapper.py +++ b/tests/templates/test_template_wrapper.py @@ -1157,7 +1157,7 @@ class TestTemplateWrapper: join_paths(CHROOT_PATH, '/etc/dir/file.conf'), parameters_object, FILE, - '/path/to/template', + '/path/to/template', chroot_path=CHROOT_PATH, config_archive_path=CONFIG_ARCHIVE_PATH) except Exception as error: @@ -1210,13 +1210,11 @@ class TestTemplateWrapper: 'mirror': True}) with pytest.raises(TemplateExecutorError): - template_wrapper = TemplateWrapper( - join_paths(CHROOT_PATH, - '/etc/dir/none'), - parameters_object, FILE, - '/path/to/template', - chroot_path=CHROOT_PATH, - config_archive_path=CONFIG_ARCHIVE_PATH) + TemplateWrapper(join_paths(CHROOT_PATH, '/etc/dir/none'), + parameters_object, FILE, + '/path/to/template', + 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): parameters_object = ParametersContainer({'package': test_package_name, diff --git a/tests/variables/test_variables.py b/tests/variables/test_variables.py index d016a28..1aadcf0 100644 --- a/tests/variables/test_variables.py +++ b/tests/variables/test_variables.py @@ -1,7 +1,6 @@ import os import shutil import pytest -from pprint import pprint from calculate.variables.datavars import NamespaceNode, VariableNode,\ Namespace, Variable, Dependence,\ 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_processor import TemplateExecutor 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') @@ -25,7 +24,7 @@ APPENDS_SET = TemplateExecutor(cl_config_path=os.path.join( @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): @@ -1016,6 +1015,8 @@ value = another_value {'name': 'common_name', 'value': 'another_value'}, {'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): datavars = Datavars( variables_path='tests/variables/testfiles/variables_0', @@ -1197,6 +1198,7 @@ value = another_value 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): datavars = Datavars( variables_path=os.path.join(TESTFILES_PATH, 'variables_7')) @@ -1217,7 +1219,7 @@ os.calculate = new1 new2''' text = template_engine.template_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( variables_path=os.path.join(TESTFILES_PATH, 'variables_7')) template_engine = TemplateEngine(appends_set=APPENDS_SET, @@ -1246,7 +1248,7 @@ os.calculate = new1 new2''' assert datavars.os.hashvar.value2 == '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( variables_path=os.path.join(TESTFILES_PATH, 'variables_8')) 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.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( variables_path=os.path.join(TESTFILES_PATH, 'variables_9')) template_engine = TemplateEngine(appends_set=APPENDS_SET, @@ -1299,13 +1301,64 @@ os.calculate = new1 new2''' input_template_1 = '''{% calculate name = 'filename', force -%} {% save.local custom.ns.var_3 = 'value' -%} {% 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) - print('VARIABLES TO SAVE:') - pprint(datavars.variables_to_save) 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): shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo')) diff --git a/tests/variables/testfiles/ini_vars.backup/calculate_4.ini b/tests/variables/testfiles/ini_vars.backup/calculate_4.ini index 8ce36e8..681bce1 100644 --- a/tests/variables/testfiles/ini_vars.backup/calculate_4.ini +++ b/tests/variables/testfiles/ini_vars.backup/calculate_4.ini @@ -1,3 +1,6 @@ [custom][ns] var_1 = 1 var_2 = val1,val2,val3 + +[os][linux] +test_1 += 12 diff --git a/tests/variables/testfiles/ini_vars.backup/calculate_5.ini b/tests/variables/testfiles/ini_vars.backup/calculate_5.ini index 8ce36e8..5c1d61d 100644 --- a/tests/variables/testfiles/ini_vars.backup/calculate_5.ini +++ b/tests/variables/testfiles/ini_vars.backup/calculate_5.ini @@ -1,3 +1,6 @@ [custom][ns] -var_1 = 1 -var_2 = val1,val2,val3 +var_5 = /the/true/path/to/eternity/in/the/abyss +var_6 = 12 + +[os][linux] +test = new value diff --git a/tests/variables/testfiles/ini_vars.backup/calculate_6.ini b/tests/variables/testfiles/ini_vars.backup/calculate_6.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/variables/testfiles/variables_10/main/__init__.py b/tests/variables/testfiles/variables_10/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/variables/testfiles/variables_10/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/variables/testfiles/variables_10/os/__init__.py b/tests/variables/testfiles/variables_10/os/__init__.py new file mode 100644 index 0000000..5538dda --- /dev/null +++ b/tests/variables/testfiles/variables_10/os/__init__.py @@ -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"}]) diff --git a/tests/variables/testfiles/variables_10/os/gentoo/__init__.py b/tests/variables/testfiles/variables_10/os/gentoo/__init__.py new file mode 100644 index 0000000..d704c66 --- /dev/null +++ b/tests/variables/testfiles/variables_10/os/gentoo/__init__.py @@ -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")}]) diff --git a/tests/variables/testfiles/variables_10/system/__init__.py b/tests/variables/testfiles/variables_10/system/__init__.py new file mode 100644 index 0000000..4b3f3cb --- /dev/null +++ b/tests/variables/testfiles/variables_10/system/__init__.py @@ -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')}) diff --git a/tests/variables/testfiles/variables_9/system/__init__.py b/tests/variables/testfiles/variables_9/system/__init__.py index d18e36b..ea76ed2 100644 --- a/tests/variables/testfiles/variables_9/system/__init__.py +++ b/tests/variables/testfiles/variables_9/system/__init__.py @@ -15,7 +15,7 @@ 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_4.ini'), - 'local': os.path.join(TESTFILES_PATH, - 'ini_vars/calculate_5.ini')}) + source={'local': os.path.join(TESTFILES_PATH, + 'ini_vars/calculate_4.ini'), + 'system': os.path.join(TESTFILES_PATH, + 'ini_vars/calculate_5.ini')})