You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
10 KiB

  1. from typing import Union, Callable, List
  2. from ..scripts.scripts import Script
  3. from ..parameters.parameters import BaseParameter, Parameters
  4. from ..variables.datavars import NamespaceNode, DependenceAPI,\
  5. VariableNotFoundError
  6. from ..variables.loader import Datavars
  7. from ..utils.io_module import IOModule
  8. class CommandDescriptionError(Exception):
  9. '''Исключение кидаемое при наличии ошибок во время создания объекта
  10. команды.'''
  11. def __init__(self, message: str, command_id: str = '', title: str = ''):
  12. self.message: str = message
  13. self.command_id: str = command_id
  14. self.command_title: str = title
  15. def __str__(self) -> str:
  16. if self.command_title:
  17. return (f'can not create command {self.command_title}:'
  18. f' {self.message}')
  19. if self.command_id:
  20. return f'can not create command {self.command_id}: {self.message}'
  21. return f'can not create command: {self.message}'
  22. class CommandInitializationError(Exception):
  23. '''Исключение кидаемое при наличии ошибок во время инициализации.'''
  24. def __init__(self, message: str, title: str):
  25. self.message: str = message
  26. self.command_title: str = title
  27. def __str__(self) -> str:
  28. if self.command_title:
  29. return (f'can not initialize command {self.command_title}:'
  30. f' {self.message}')
  31. class Command:
  32. '''Класс описания команды, предназначен для хранения информации о команде
  33. и создания по данному описанию объекта лаунчера команды.'''
  34. def __init__(self, command_id: str = '',
  35. category: str = '',
  36. title: str = '',
  37. command: str = '',
  38. script: Union[Callable, Script, None] = None,
  39. args: Union[tuple, list] = tuple(),
  40. parameters: Union[List[BaseParameter], None] = None,
  41. namespace: Union[str, NamespaceNode, None] = None,
  42. gui: bool = False,
  43. icon: Union[str, List[str]] = '',
  44. setvars: dict = {},
  45. rights: List[str] = []):
  46. # Идентификатор команды обязателен.
  47. if not command_id:
  48. raise CommandDescriptionError('command ID is not set')
  49. self.__id: str = command_id
  50. # Если собственно команда не была указана, получаем ее из ID.
  51. if command:
  52. self.__command: str = command
  53. else:
  54. self.__command: str = f'cl_{self.__id}'
  55. # Название команды.
  56. if not title:
  57. raise CommandDescriptionError("title is not set",
  58. command_id=command_id)
  59. self.__title: str = title
  60. # Категория, к которой относится команда.
  61. if not category:
  62. raise CommandDescriptionError("category is not set",
  63. command_title=title)
  64. self.__category: str = category
  65. # Пространство имен относительно которого, будет выполняться скрипт.
  66. self.__namespace: Union[str, NamespaceNode, None] = namespace
  67. # Параметры, указываемые при вызове этой команды.
  68. if parameters is None:
  69. raise CommandDescriptionError("parameters is not set",
  70. command_title=title)
  71. self.__parameters: List[BaseParameter] = parameters
  72. # Скрипт выполняемый при вызове этой команды.
  73. if not script:
  74. raise CommandDescriptionError("script is not set",
  75. command_title=title)
  76. self.__script: Script = script
  77. # Аргументы, с которыми будет запущен скрипт. Пока только статичные
  78. # значения.
  79. self.__args = args
  80. # Параметр указывающий, должена ли данная команда отображаться в
  81. # графическом интерфейсе.
  82. self.__gui: bool = gui
  83. # Устанавливаем название иконки.
  84. if not icon and self.__gui:
  85. raise CommandDescriptionError("icon is not set",
  86. command_title=title)
  87. self.__icon: Union[str, List[str]] = icon
  88. # Словарь с переменными и значениями, которые нужно им присвоить.
  89. self.__setvars: dict = setvars
  90. # Права, необходимые данной команде.
  91. # TODO разобраться с тем, как использовать.
  92. if not rights:
  93. raise CommandDescriptionError("rights is not set",
  94. title=title)
  95. self.__rights = rights
  96. def make_runner(self, datavars: Union[Datavars, NamespaceNode],
  97. output: IOModule):
  98. '''Метод создающий на основе данного описания команды экземпляр раннера.
  99. '''
  100. return CommandRunner(self, datavars, output)
  101. # Интерфейс иммутабельного объекта описания команды.
  102. @property
  103. def id(self) -> str:
  104. return self.__id
  105. @property
  106. def command(self) -> str:
  107. return self.__command
  108. @property
  109. def title(self) -> str:
  110. return self.__title
  111. @property
  112. def category(self) -> str:
  113. return self.__category
  114. @property
  115. def namespace(self) -> str:
  116. return self.__namespace
  117. @property
  118. def parameters(self) -> str:
  119. return self.__parameters
  120. @property
  121. def script(self) -> Script:
  122. return self.__script
  123. @property
  124. def args(self) -> tuple:
  125. return self.__args
  126. @property
  127. def gui(self) -> bool:
  128. return self.__gui
  129. @property
  130. def icon(self) -> Union[str, List[str]]:
  131. return self.__icon
  132. @property
  133. def setvars(self) -> dict:
  134. return self.__setvars
  135. @property
  136. def rights(self) -> List[str]:
  137. return self.__rights
  138. def __repr__(self):
  139. return f"<Command: {self.__title}>"
  140. class CommandRunner:
  141. def __init__(self, command: Command,
  142. datavars: Union[Datavars, NamespaceNode],
  143. output: IOModule):
  144. '''Класс инкапcулирующий все данные о команде, а также необходимые для
  145. нее параметры, модуль ввода-вывода, переменные и прочее. Предназначен
  146. для конфигурации команды и запуска ее в воркере.'''
  147. self._command = command
  148. self._datavars = datavars
  149. self._output = output
  150. # Ищем пространство имен.
  151. if self._command.namespace is not None:
  152. if isinstance(self._command.namespace, str):
  153. self._namespace = self._find_namespace(
  154. self._command.namespace,
  155. datavars)
  156. # Инициализируем параметры.
  157. self._parameters = Parameters(self._datavars)
  158. self._parameters.add(*self._command.parameters)
  159. # Получаем запускатель скрипта.
  160. self._script_launcher = self._command.script.make_launcher(
  161. output,
  162. datavars,
  163. self._namespace)
  164. # Устанавливаем переменные, если нужно.
  165. if self._command.setvars:
  166. self._set_variables(self._command.setvars,
  167. datavars, self._namespace)
  168. def run_command(self):
  169. args = self._command.args
  170. self._script_launcher(*args)
  171. def set_parameters(self, values: dict):
  172. pass
  173. def get_parameters(self, group: Union[str, None] = None):
  174. '''Метод для установки параметров.'''
  175. pass
  176. def _find_namespace(self, namespace_path: str,
  177. datavars: Union[Datavars, NamespaceNode]
  178. ) -> NamespaceNode:
  179. '''Метод для поиска пространств имен.'''
  180. # TODO сделать где-нибудь единственный статический вариант.
  181. parts = namespace_path.split('.')
  182. namespace = datavars
  183. for part in parts:
  184. namespace = namespace[part]
  185. return namespace
  186. def _set_variables(self, setvars: dict,
  187. datavars: Union[Datavars, NamespaceNode],
  188. namespace: Union[NamespaceNode, None]) -> None:
  189. '''Метод для установки указанных значений, указанным переменным.'''
  190. force = False
  191. for varname, value in setvars.items():
  192. if varname.endswith('!'):
  193. varname = varname[:-1]
  194. force = True
  195. else:
  196. force = False
  197. try:
  198. variable = DependenceAPI.find_variable(
  199. varname, datavars,
  200. current_namespace=namespace)
  201. except VariableNotFoundError:
  202. self._output.set_error(f"Can not set variable '{varname}':"
  203. " variable does not exist.")
  204. if not variable.readonly or force:
  205. variable.set(value)
  206. else:
  207. self._output.set_error("Can not set readonly variable "
  208. f"'{variable.get_fullname}'.")