選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

254 行
10 KiB

このファイルには曖昧(ambiguous)なUnicode文字が含まれています!

このファイルには曖昧(ambiguous)なUnicode文字が含まれており、あなたが使用しているロケールにおいて他の文字と混同する可能性があります。 あなたのユースケースが意図的かつ正当な場合はこの警告を無視して構いません。 それらの文字をハイライトするにはエスケープボタンを使用します。

from typing import Union, Callable, List
from ..scripts.scripts import Script
from ..parameters.parameters import BaseParameter, Parameters
from ..variables.datavars import NamespaceNode, DependenceAPI,\
VariableNotFoundError
from ..variables.loader import Datavars
from ..utils.io_module import IOModule
class CommandDescriptionError(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 = '',
command: str = '',
script: Union[Callable, Script, None] = None,
args: Union[tuple, list] = tuple(),
parameters: Union[List[BaseParameter], None] = None,
namespace: Union[str, NamespaceNode, None] = None,
gui: bool = False,
icon: Union[str, List[str]] = '',
setvars: dict = {},
rights: List[str] = []):
# Идентификатор команды обязателен.
if not command_id:
raise CommandDescriptionError('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 CommandDescriptionError("title is not set",
command_id=command_id)
self.__title: str = title
# Категория, к которой относится команда.
if not category:
raise CommandDescriptionError("category is not set",
command_title=title)
self.__category: str = category
# Пространство имен относительно которого, будет выполняться скрипт.
self.__namespace: Union[str, NamespaceNode, None] = namespace
# Параметры, указываемые при вызове этой команды.
if parameters is None:
raise CommandDescriptionError("parameters is not set",
command_title=title)
self.__parameters: List[BaseParameter] = parameters
# Скрипт выполняемый при вызове этой команды.
if not script:
raise CommandDescriptionError("script is not set",
command_title=title)
self.__script: Script = script
# Аргументы, с которыми будет запущен скрипт. Пока только статичные
# значения.
self.__args = args
# Параметр указывающий, должена ли данная команда отображаться в
# графическом интерфейсе.
self.__gui: bool = gui
# Устанавливаем название иконки.
if not icon and self.__gui:
raise CommandDescriptionError("icon is not set",
command_title=title)
self.__icon: Union[str, List[str]] = icon
# Словарь с переменными и значениями, которые нужно им присвоить.
self.__setvars: dict = setvars
# Права, необходимые данной команде.
# TODO разобраться с тем, как использовать.
if not rights:
raise CommandDescriptionError("rights is not set",
title=title)
self.__rights = rights
def make_runner(self, datavars: Union[Datavars, NamespaceNode],
output: IOModule):
'''Метод создающий на основе данного описания команды экземпляр раннера.
'''
return CommandRunner(self, datavars, output)
# Интерфейс иммутабельного объекта описания команды.
@property
def id(self) -> str:
return self.__id
@property
def command(self) -> str:
return self.__command
@property
def title(self) -> str:
return self.__title
@property
def category(self) -> str:
return self.__category
@property
def namespace(self) -> str:
return self.__namespace
@property
def parameters(self) -> str:
return self.__parameters
@property
def script(self) -> Script:
return self.__script
@property
def args(self) -> tuple:
return self.__args
@property
def gui(self) -> bool:
return self.__gui
@property
def icon(self) -> Union[str, List[str]]:
return self.__icon
@property
def setvars(self) -> dict:
return self.__setvars
@property
def rights(self) -> List[str]:
return self.__rights
def __repr__(self):
return f"<Command: {self.__title}>"
class CommandRunner:
def __init__(self, command: Command,
datavars: Union[Datavars, NamespaceNode],
output: IOModule):
'''Класс инкапcулирующий все данные о команде, а также необходимые для
нее параметры, модуль ввода-вывода, переменные и прочее. Предназначен
для конфигурации команды и запуска ее в воркере.'''
self._command = command
self._datavars = datavars
self._output = output
# Ищем пространство имен.
if self._command.namespace is not None:
if isinstance(self._command.namespace, str):
self._namespace = self._find_namespace(
self._command.namespace,
datavars)
# Инициализируем параметры.
self._parameters = Parameters(self._datavars)
self._parameters.add(*self._command.parameters)
# Получаем запускатель скрипта.
self._script_launcher = self._command.script.make_launcher(
output,
datavars,
self._namespace)
# Устанавливаем переменные, если нужно.
if self._command.setvars:
self._set_variables(self._command.setvars,
datavars, self._namespace)
def run_command(self):
args = self._command.args
self._script_launcher(*args)
def set_parameters(self, values: dict):
pass
def get_parameters(self, group: Union[str, None] = None):
'''Метод для установки параметров.'''
pass
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}'.")