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.

888 lines
39 KiB

  1. # vim: fileencoding=utf-8
  2. #
  3. import os
  4. import logging
  5. import importlib
  6. import importlib.util
  7. from jinja2 import Environment, PackageLoader
  8. from calculate.variables.datavars import (
  9. NamespaceNode,
  10. VariableNode,
  11. ListType,
  12. IntegerType,
  13. FloatType,
  14. IniType,
  15. TableType,
  16. Namespace,
  17. HashType,
  18. VariableNotFoundError,
  19. VariableError,
  20. )
  21. from calculate.utils.gentoo import ProfileWalker
  22. from calculate.utils.files import read_file, FilesError
  23. from calculate.utils.tools import Singleton
  24. from pyparsing import (
  25. Literal,
  26. Word,
  27. ZeroOrMore,
  28. Group,
  29. Optional,
  30. restOfLine,
  31. empty,
  32. printables,
  33. OneOrMore,
  34. lineno,
  35. line,
  36. SkipTo,
  37. LineEnd,
  38. Combine,
  39. nums,
  40. )
  41. from enum import Enum
  42. from contextlib import contextmanager
  43. class LoaderError(Exception):
  44. '''Исключение выбрасываемое загрузчиком, если тот в принципе не может
  45. загрузить переменные.'''
  46. pass
  47. class Define(Enum):
  48. assign = 0
  49. append = 1
  50. remove = 2
  51. class CalculateIniParser(metaclass=Singleton):
  52. '''Класс парсера calculate.ini файлов.'''
  53. def __init__(self):
  54. self.operations = {"=": Define.assign,
  55. "+=": Define.append,
  56. "-=": Define.remove}
  57. lbrack = Literal("[")
  58. rbrack = Literal("]")
  59. # comma = Literal(",").suppress()
  60. comment_symbol = Literal(';') | Literal('#')
  61. # Define = self.Define
  62. value_operation = (Literal("=") | Combine(Literal("+") + Literal("="))
  63. | Combine(Literal("-") + Literal("=")))
  64. comment = comment_symbol + Optional(restOfLine)
  65. section_name = (lbrack.suppress() + (~Word(nums)
  66. + Word(printables+'\t',
  67. excludeChars='[]'))
  68. + rbrack.suppress())
  69. value_name = Word(printables+'\t', excludeChars='=-+')
  70. # non_comma = Word(printables+'\t', excludeChars=',')
  71. clear_section = lbrack.suppress() + Group(empty) + rbrack.suppress()
  72. row_index = lbrack.suppress() + Word(nums) + rbrack.suppress()
  73. namespace_start = Group(OneOrMore(section_name)
  74. + (clear_section | ~lbrack)
  75. + LineEnd().suppress())
  76. table_start = Group(OneOrMore(section_name)
  77. + (row_index | clear_section | ~lbrack)
  78. + LineEnd().suppress())
  79. def add_lineno(string, location, tokens):
  80. tokens.append(lineno(location, string))
  81. section_start = (namespace_start('namespace') |
  82. table_start('table'))
  83. section_start.setParseAction(add_lineno)
  84. # Если содержимое ini-файла не предваряется заголовком секции,
  85. # значит эта строка ошибочна.
  86. unexpected = Group(~section_start + SkipTo(LineEnd(),
  87. include=True))("error")
  88. unexpected.setParseAction(self._unexpected_token)
  89. key_value = (~lbrack + value_name
  90. + value_operation + empty
  91. + restOfLine + LineEnd().suppress())
  92. def process_key_value(string, location, tokens):
  93. tokens[0] = tokens[0].strip()
  94. tokens[1] = tokens[1].strip()
  95. tokens.append(lineno(location, string))
  96. key_value.setParseAction(process_key_value)
  97. self.ini_section_parser = (section_start
  98. + Group(ZeroOrMore(
  99. Group(key_value
  100. | unexpected)))
  101. | unexpected)
  102. self.ini_section_parser.ignore(comment)
  103. def parse(self, data: str):
  104. for tokens, start, end in self.ini_section_parser.scanString(data):
  105. if tokens.getName() == "error":
  106. if tokens[1].strip():
  107. yield({'error': (tokens[0], tokens[1])})
  108. continue
  109. section, section_lineno, defkeys = tokens
  110. if section.getName() == 'namespace':
  111. section_list = section.asList()
  112. if section_list[-1] == []:
  113. yield {'clear_section': (section_list[:-1],
  114. section_lineno)}
  115. else:
  116. yield {'start_section': (section_list, section_lineno)}
  117. for defkey in defkeys:
  118. if defkey.getName() == "error":
  119. if defkey[1].strip():
  120. yield({'error': (defkey[0], defkey[1])})
  121. continue
  122. yield {'define_key': (defkey[0], defkey[2],
  123. self.operations[defkey[1]],
  124. defkey[3])}
  125. else:
  126. table_list = section.asList()
  127. table_values = {}
  128. for defkey in defkeys:
  129. if defkey.getName() == "error":
  130. if defkey[1].strip():
  131. yield({'error': (defkey[0], defkey[1])})
  132. continue
  133. table_values.update({defkey[0]: defkey[2]})
  134. yield {'start_table': (table_list, table_values,
  135. section_lineno)}
  136. def _unexpected_token(self, string, location, tokens):
  137. '''Метод вызываемый парсером, если обнаружена некорректная строка,
  138. предназначен для получения некорректной строки и ее дальнейшего
  139. разбора.'''
  140. return [lineno(location, string), line(location, string)]
  141. class NamespaceIniFiller:
  142. '''Класс, предназначенный для наполнения Namespace объекта переменными
  143. из calculate.ini файла.'''
  144. available_sections = {'custom'}
  145. def __init__(self, restrict_creation=True):
  146. self.ini_parser = CalculateIniParser()
  147. self._errors = []
  148. # Флаги, определяющие возможность создания новых переменных и новых
  149. # пространств имен в данном пространстве имен.
  150. self.restricted = restrict_creation
  151. self.modify_only = False
  152. def fill(self, namespace: NamespaceNode, ini_file_text: str) -> None:
  153. '''Метод для разбора calculate.ini файла и добавления всех его
  154. переменных в указанное пространство имен.'''
  155. self.namespace = namespace
  156. self.current_namespace = self.namespace
  157. self._errors = []
  158. for parsed_line in self.ini_parser.parse(ini_file_text):
  159. self._line_processor(**parsed_line)
  160. def _line_processor(self, start_section=None,
  161. clear_section=None,
  162. start_table=None,
  163. define_key=None,
  164. error=None, **kwargs):
  165. '''Метод вызывающий обработку токенов, выдаваемых парсером в
  166. зависимости от их типа.'''
  167. if start_section is not None:
  168. self.start_section(*start_section)
  169. elif clear_section is not None:
  170. self.clear_section(*clear_section)
  171. elif start_table is not None:
  172. self.start_table(*start_table)
  173. elif define_key is not None:
  174. self.define_key(*define_key)
  175. elif error is not None:
  176. self._set_error(error[0], 'SyntaxError', error[1])
  177. def start_section(self, sections: str, lineno) -> None:
  178. '''Метод для получения доступа и создания пространств имен.'''
  179. if self.restricted:
  180. self.modify_only = sections[0] not in self.available_sections
  181. self.current_namespace = self.namespace
  182. for section in sections:
  183. if isinstance(self.current_namespace, Datavars):
  184. if section not in self.current_namespace:
  185. self._set_error(lineno, 'VariableError',
  186. "variables package '{}' is not found.".
  187. format(section))
  188. self.current_namespace = None
  189. return
  190. elif isinstance(self.current_namespace, NamespaceNode):
  191. if section not in self.current_namespace._namespaces:
  192. if (section in self.current_namespace._variables and
  193. self.current_namespace[section].variable_type
  194. is HashType):
  195. # Если секция является хэшем, используем ее.
  196. self.current_namespace = self.current_namespace.\
  197. _variables[section]
  198. return
  199. elif not self.modify_only:
  200. self.current_namespace.add_namespace(
  201. NamespaceNode(section))
  202. else:
  203. self._set_error(lineno, 'VariableError',
  204. "can not create namespace '{}.{}' in"
  205. " not 'custom' namespace.".format(
  206. self.current_namespace.get_fullname(),
  207. section))
  208. self.current_namespace = None
  209. return
  210. self.current_namespace = self.current_namespace.\
  211. _namespaces[section]
  212. def clear_section(self, sections: list, lineno) -> None:
  213. '''Метод для очистки пространства имен.'''
  214. if self.restricted:
  215. self.modify_only = sections[0] not in self.available_sections
  216. current_namespace = self.namespace
  217. for section in sections:
  218. if isinstance(current_namespace, Datavars):
  219. if section in current_namespace:
  220. current_namespace = current_namespace[section]
  221. else:
  222. self._set_error(lineno, 'VariableError',
  223. "can not clear"" namespace '{}'. Variables"
  224. " package '{}' is not found.".format(
  225. ".".join(sections),
  226. section))
  227. return
  228. elif isinstance(current_namespace, NamespaceNode):
  229. if section in current_namespace._namespaces:
  230. current_namespace = current_namespace[section]
  231. elif (section in current_namespace._variables and
  232. current_namespace._variables[section].variable_type
  233. is TableType):
  234. table_variable = current_namespace._variables[section]
  235. table_to_clear = table_variable.get_value()
  236. table_to_clear.clear()
  237. table_variable.source = table_to_clear
  238. return
  239. else:
  240. self._set_error(lineno, 'VariableError',
  241. "can not clear namespace '{}'. Namespace"
  242. " is not found.".format(
  243. ".".join(sections)))
  244. return
  245. if not self.modify_only:
  246. current_namespace.clear()
  247. else:
  248. self._set_error(lineno, 'VariableError',
  249. "can not clear namespace '{}' from not 'custom'"
  250. " namespace.".format(current_namespace.
  251. get_fullname()))
  252. def start_table(self, sections: str, row, lineno) -> None:
  253. '''Метод для создания и модификации таблиц.'''
  254. if self.restricted:
  255. self.modify_only = sections[0] not in self.available_sections
  256. self.current_namespace = self.namespace
  257. row_index = int(sections.pop())
  258. table_name = sections.pop()
  259. for section in sections:
  260. if section not in self.current_namespace._namespaces:
  261. if not self.modify_only:
  262. self.current_namespace.add_namespace(
  263. NamespaceNode(section))
  264. else:
  265. self._set_error(lineno, 'VariableError',
  266. "can not create table '{}.{}', namespace"
  267. " '{}' is not found.".format(
  268. ".".join(sections),
  269. table_name, section))
  270. self.current_namespace = None
  271. return
  272. self.current_namespace = self.current_namespace.\
  273. _namespaces[section]
  274. if table_name not in self.current_namespace._variables:
  275. if not self.modify_only:
  276. table_variable = VariableNode(table_name,
  277. self.current_namespace,
  278. variable_type=TableType,
  279. source=[row])
  280. else:
  281. self._set_error(lineno, 'VariableError',
  282. "can not create table '{}.{}' in not 'custom'"
  283. " namespace.".format(self.current_namespace.
  284. get_fullname(),
  285. table_name))
  286. else:
  287. table_variable = self.current_namespace._variables[table_name]
  288. table = table_variable.get_value()
  289. if row_index < len(table):
  290. table.change_row(row, row_index)
  291. else:
  292. table.add_row(row)
  293. table_variable.source = table
  294. def define_key(self, key: str, value: str, optype, lineno) -> None:
  295. '''Метод для создания и модификации переменных.'''
  296. if self.current_namespace is None:
  297. return
  298. if (isinstance(self.current_namespace, VariableNode) and
  299. self.current_namespace.variable_type is HashType):
  300. self.update_hash(key, value, optype, lineno)
  301. else:
  302. if optype == Define.assign:
  303. if key not in self.current_namespace:
  304. self.define_variable(key, value, lineno)
  305. else:
  306. self.change_value(key, value, lineno)
  307. elif optype == Define.append:
  308. if key not in self.current_namespace:
  309. self.define_variable(key, value, lineno)
  310. else:
  311. self.append_value(key, value, lineno)
  312. elif optype == Define.remove:
  313. if key not in self.current_namespace:
  314. self.define_variable(key, value, lineno)
  315. else:
  316. self.remove_value(key, value, lineno)
  317. def change_value(self, key: str, value: str, lineno) -> None:
  318. '''Метод для изменения значения переменной.'''
  319. variable = self.current_namespace[key]
  320. if variable.readonly:
  321. self._set_error(lineno, 'VariableError',
  322. "can not change readonly variable "
  323. f"'{self.current_namespace.get_fullname()}.{key}'")
  324. return
  325. variable.source = value
  326. def define_variable(self, key: str, value: str, lineno) -> None:
  327. '''Метод для создания переменных в calculate.ini файле.'''
  328. if not self.modify_only:
  329. VariableNode(key, self.current_namespace, variable_type=IniType,
  330. source=value)
  331. else:
  332. self._set_error(lineno, 'VariableError',
  333. "can not create variable '{}.{}' in not 'custom'"
  334. " namespace.".format(
  335. self.current_namespace.get_fullname(),
  336. key))
  337. def append_value(self, key: str, value: str, lineno) -> None:
  338. '''Метод выполняющий действия возложенные на оператор +=.'''
  339. variable = self.current_namespace[key]
  340. if variable.readonly:
  341. self._set_error(lineno, 'VariableError',
  342. "can not change readonly variable "
  343. f"'{self.current_namespace.get_fullname()}.{key}'")
  344. return
  345. variable_value = variable.get_value()
  346. if variable.variable_type is IniType:
  347. value_list = value.split(',')
  348. variable_list = variable_value.split(',')
  349. for item in value_list:
  350. if item not in variable_list:
  351. variable_list.append(item.strip())
  352. variable_value = ','.join(variable_list)
  353. elif variable.variable_type is ListType:
  354. value_list = value.split(',')
  355. for item in value_list:
  356. if item not in variable_value:
  357. variable_value.append(item.strip())
  358. elif variable.variable_type is IntegerType:
  359. variable_value += int(value)
  360. elif variable.variable_type is FloatType:
  361. variable_value += float(value)
  362. variable.source = variable_value
  363. def remove_value(self, key: str, value: str, lineno) -> None:
  364. '''Метод выполняющий действия возложенные на оператор -=.'''
  365. variable = self.current_namespace[key]
  366. if variable.readonly:
  367. self._set_error(lineno, 'VariableError',
  368. "can not change readonly variable "
  369. f"'{self.current_namespace.get_fullname()}.{key}'")
  370. return
  371. variable_value = variable.get_value()
  372. if variable.variable_type is IniType:
  373. value_list = value.split(',')
  374. variable_list = [item.strip() for item in
  375. variable_value.split(',')]
  376. for item in value_list:
  377. if item in variable_list:
  378. variable_list.remove(item.strip())
  379. variable_value = ','.join(variable_list)
  380. elif variable.variable_type is ListType:
  381. value_list = value.split(',')
  382. for item in value_list:
  383. if item in variable_value:
  384. variable_value.remove(item.strip())
  385. elif variable.variable_type is IntegerType:
  386. variable_value -= int(value)
  387. elif variable.variable_type is FloatType:
  388. variable_value -= float(value)
  389. variable.source = variable_value
  390. def update_hash(self, key: str, value: str, optype, lineno):
  391. '''Метод для изменения переменных хэшей через calculate.ini.'''
  392. if self.current_namespace.readonly:
  393. self._set_error(lineno, 'VariableError',
  394. "can not change readonly hash variable "
  395. f"'{self.current_namespace.get_fullname()}'")
  396. return
  397. hash_to_update = self.current_namespace.get_value().get_hash()
  398. if key not in hash_to_update:
  399. # Если ключ отсутствует в хэше, то проверяем, является ли он
  400. # фиксированным.
  401. if self.current_namespace.fixed:
  402. self._set_error(lineno, 'VariableError',
  403. "key '{}' is unavailable for fixed hash '{}',"
  404. " available keys: '{}'.".format(
  405. key,
  406. self.current_namespace.get_fullname(),
  407. ', '.join(self.current_namespace.
  408. get_value()._fields)))
  409. return
  410. else:
  411. hash_to_update.update({key: value})
  412. elif optype == Define.assign:
  413. hash_to_update.update({key: value})
  414. elif optype == Define.append:
  415. current_value = hash_to_update[key]
  416. hash_to_update[key] = current_value + value
  417. elif optype == Define.remove:
  418. current_value = hash_to_update[key]
  419. if (isinstance(current_value, int) or
  420. isinstance(current_value, float)):
  421. hash_to_update[key] = current_value - value
  422. elif isinstance(current_value, str):
  423. value_list = [item.strip() for item in value.split(',')]
  424. current_value = [item.strip() for item in
  425. current_value.split(',')]
  426. for value_to_remove in value_list:
  427. if value_to_remove in current_value:
  428. current_value.remove(value_to_remove)
  429. hash_to_update[key] = ','.join(current_value)
  430. self.current_namespace.source = hash_to_update
  431. def _set_error(self, lineno, error_type, line):
  432. '''Метод для добавления ошибки в лог.'''
  433. self._errors.append("{}:{}: {}".format(error_type, lineno, line))
  434. @property
  435. def errors(self):
  436. errors = self._errors
  437. self._errors = []
  438. return errors
  439. class VariableLoader:
  440. '''Класс загрузчика переменных из python-файлов и из ini-файлов.'''
  441. ini_basename = "calculate.ini"
  442. def __init__(self, datavars, variables_path, repository_map=None):
  443. self.datavars = datavars
  444. self.logger = datavars.logger
  445. self.ini_filler = NamespaceIniFiller()
  446. self.variables_path = os.path.join(
  447. __file__[:-len("calculate/variables/loader.py")],
  448. variables_path)
  449. self.variables_package = '.'.join(os.path.normpath(
  450. variables_path).split("/"))
  451. self.repository_map = repository_map
  452. def load_variables_package(self, package_name: str) -> None:
  453. '''Метод для загрузки пакетов с переменными.'''
  454. directory_path = os.path.join(self.variables_path, package_name)
  455. package = '{}.{}'.format(self.variables_package, package_name)
  456. package_namespace = NamespaceNode(package_name)
  457. self.datavars.root.add_namespace(package_namespace)
  458. self._fill_from_package(package_namespace, directory_path, package)
  459. def load_from_profile(self):
  460. '''Метод для загрузки переменных из calculate.ini профиля.'''
  461. # Проверяем наличие таблицы репозиториев в переменных.
  462. if self.repository_map == {}:
  463. return
  464. if self.repository_map is None:
  465. repositories_variable_path = ['os', 'gentoo', 'repositories']
  466. current_namespace = self.datavars
  467. for section in repositories_variable_path:
  468. if section in current_namespace:
  469. current_namespace = current_namespace[section]
  470. else:
  471. self.logger.error("Variable 'os.gentoo.repositories'"
  472. " is not found. Can not load profile"
  473. " variables.")
  474. return
  475. self.repository_map = self._get_repository_map(self.datavars)
  476. # Проверяем наличие пути к профилю в переменных.
  477. if ('profile' in self.datavars.os.gentoo and
  478. 'path' in self.datavars.os.gentoo.profile):
  479. profile_path = self.datavars.os.gentoo.profile.path
  480. else:
  481. self.logger.error("Variable 'os.gentoo.profile.path'"
  482. " is not found. Can not load profile"
  483. " variables.")
  484. return
  485. self.logger.info("Load variables from profile: '{}'.".format(
  486. profile_path))
  487. self._fill_from_profile_ini(profile_path)
  488. def load_user_variables(self):
  489. '''Метод для загрузки переменных из calculate.ini указанных в
  490. переменных env_order и env_path.'''
  491. try:
  492. env_order = self.datavars.main.cl.system.env_order
  493. env_path = self.datavars.main.cl.system.env_path
  494. except VariableNotFoundError as error:
  495. self.logger.warning("Can not load additional variables: {}".
  496. format(str(error)))
  497. return
  498. for ini_file in env_order:
  499. self.logger.info("Loading variables from file: '{}'".format(
  500. ini_file))
  501. if ini_file in env_path:
  502. self.fill_from_custom_ini(env_path[ini_file].value)
  503. self.logger.info("Variables from '{}' are loaded".format(
  504. ini_file))
  505. else:
  506. self.logger.warning("File '{}' is not found. Variables are"
  507. " not loaded".format(ini_file))
  508. def _fill_from_package(self, current_namespace: NamespaceNode,
  509. directory_path: str, package: str) -> None:
  510. '''Метод для зaполнения переменных из python-файла.'''
  511. file_nodes = []
  512. directory_nodes = []
  513. # Просматриваем директорию
  514. for node in os.scandir(directory_path):
  515. if node.is_dir():
  516. directory_nodes.append(node)
  517. elif node.is_file() and node.name.endswith('.py'):
  518. file_nodes.append(node)
  519. # Сначала загружаем переменные из файлов.
  520. for file_node in file_nodes:
  521. if not file_node.name.endswith('.py'):
  522. continue
  523. file_name = file_node.name[:-3]
  524. Namespace.set_current_namespace(current_namespace)
  525. # with self.test(file_name, current_namespace):
  526. # importlib.import_module('{}.{}'.format(package, file_name))
  527. spec = importlib.util.spec_from_file_location(
  528. '{}.{}'.format(package, file_name),
  529. file_node.path)
  530. module = importlib.util.module_from_spec(spec)
  531. spec.loader.exec_module(module)
  532. if hasattr(module, 'import_variables'):
  533. module.import_variables()
  534. # Обходим остальные директории.
  535. for directory_node in directory_nodes:
  536. namespace = NamespaceNode(directory_node.name)
  537. current_namespace.add_namespace(namespace)
  538. self._fill_from_package(namespace, directory_node.path,
  539. '{}.{}'.format(package,
  540. directory_node.name))
  541. def _fill_from_profile_ini(self, profile_path):
  542. '''Метод для зaполнения переменных из ini-файла.'''
  543. profile_walker = ProfileWalker(self.ini_basename,
  544. self.repository_map)
  545. for file_path in profile_walker.find(profile_path):
  546. try:
  547. ini_file_text = read_file(file_path)
  548. self.ini_filler.fill(self.datavars, ini_file_text)
  549. except FilesError:
  550. self.logger.error("Can not load profile variables from"
  551. " unexisting file: {}".format(file_path))
  552. def _get_repository_map(self, datavars):
  553. '''Метод для получения из переменной словаря с репозиториями и путями
  554. к ним.'''
  555. return {repo['name']: repo['path']
  556. for repo in datavars.os.gentoo.repositories}
  557. def fill_from_custom_ini(self, file_path: str):
  558. '''Метод для заполнения переменных из конкретного указанного файла.'''
  559. if os.path.exists(file_path):
  560. ini_file_text = read_file(file_path)
  561. self.ini_filler.fill(self.datavars, ini_file_text)
  562. parsing_errors = self.ini_filler.errors
  563. if parsing_errors:
  564. for error in parsing_errors:
  565. self.logger.error(error)
  566. self.logger.warning('Some variables was not loaded.')
  567. else:
  568. self.logger.info('All variables are loaded.')
  569. else:
  570. self.logger.error("Variables are not loaded. File '{}' does"
  571. " not exist.".format(file_path))
  572. @contextmanager
  573. def test(self, file_name, namespace):
  574. '''Контекстный менеджер для тестирования.'''
  575. print('IMPORT: {}.{}'.format(namespace.get_fullname(), file_name))
  576. try:
  577. yield self
  578. finally:
  579. print('IMPORTED FROM: {}.{}'.format(namespace.get_fullname(),
  580. file_name))
  581. class CalculateIniSaver:
  582. '''Класс для сохранения значений переменных в указанные ini-файлы.'''
  583. def __init__(self, ini_parser=None):
  584. self.ini_parser = CalculateIniParser()
  585. self.operations = {Define.assign: '=',
  586. Define.append: '+=',
  587. Define.remove: '-='}
  588. file_loader = PackageLoader('calculate', 'variables')
  589. environment = Environment(loader=file_loader)
  590. self.ini_template = environment.get_template('ini_template')
  591. def save_to_ini(self, target_path, variables_to_save):
  592. '''Метод для сохранения переменных в указанный ini-файл.'''
  593. ini_file_text = read_file(target_path)
  594. ini_dictionary = self._parse_ini(ini_file_text)
  595. for namespace in variables_to_save:
  596. if namespace in ini_dictionary:
  597. ini_dictionary[namespace].update(variables_to_save[namespace])
  598. else:
  599. ini_dictionary[namespace] = variables_to_save[namespace]
  600. ini_file_text = self._get_ini_text(ini_dictionary)
  601. with open(target_path, 'w') as ini_file:
  602. ini_file.write(ini_file_text)
  603. def _parse_ini(self, ini_file_text):
  604. '''Метод для разбора текста ini-файла в словарь, в который далее будут
  605. добавляться измененные переменные.'''
  606. current_namespace = None
  607. ini_dictionary = dict()
  608. for parsed_line in self.ini_parser.parse(ini_file_text):
  609. line_type = next(iter(parsed_line))
  610. line_content = parsed_line[line_type]
  611. if (line_type == 'start_section' or
  612. line_type == 'start_table'):
  613. current_namespace = tuple(line_content[0])
  614. if current_namespace not in ini_dictionary:
  615. ini_dictionary[current_namespace] = dict()
  616. elif (line_type == 'clear_section' or
  617. line_type == 'clear_table'):
  618. current_namespace = (*line_content[0], '')
  619. ini_dictionary[current_namespace] = dict()
  620. elif line_type == 'define_key':
  621. namespace = ini_dictionary[current_namespace]
  622. namespace.update({line_content[0]:
  623. (self.operations[line_content[2]],
  624. line_content[1])})
  625. return ini_dictionary
  626. def _get_ini_text(self, ini_dictionary):
  627. '''Метод для получения текста ini файла, полученного в результате
  628. наложения изменений из тегов save в шаблонах.'''
  629. ini_text = self.ini_template.render(ini_dictionary=ini_dictionary)
  630. return ini_text
  631. class Datavars:
  632. '''Класс для хранения переменных и управления ими.'''
  633. def __init__(self, variables_path='calculate/vars', repository_map=None,
  634. logger=None):
  635. self._variables_path = variables_path
  636. self._available_packages = self._get_available_packages()
  637. if logger is not None:
  638. self.logger = logger
  639. else:
  640. logger = logging.getLogger("main")
  641. # stream_handler = logging.StreamHandler()
  642. # logger.addHandler(stream_handler)
  643. self.logger = logger
  644. self.root = NamespaceNode('<root>')
  645. self._loader = VariableLoader(self, self._variables_path,
  646. repository_map=repository_map)
  647. Namespace.reset()
  648. Namespace.set_datavars(self)
  649. self._loader.load_from_profile()
  650. self._loader.load_user_variables()
  651. # Создаем словарь переменных, которые нужно сохранить потом в
  652. # ini-файлах.
  653. try:
  654. self.variables_to_save = {target: dict() for target in
  655. self.main.cl.system.env_order
  656. if target in
  657. self.main.cl.system.env_path}
  658. except VariableNotFoundError:
  659. self.variables_to_save = dict()
  660. def reset(self):
  661. '''Метод для сброса модуля переменных.'''
  662. self.root.clear()
  663. self.root = NamespaceNode('<root>')
  664. self._available_packages.clear()
  665. self._available_packages = self._get_available_packages()
  666. Namespace.set_datavars(self)
  667. def _get_available_packages(self) -> dict:
  668. '''Метод для получения словаря с имеющимися пакетами переменных
  669. и путями к ним.'''
  670. variables_path = os.path.join(
  671. __file__[:-len("calculate/variables/loader.py")],
  672. self._variables_path)
  673. available_packages = dict()
  674. for file_name in os.listdir(variables_path):
  675. if file_name.startswith('__'):
  676. continue
  677. file_path = os.path.join(variables_path, file_name)
  678. if os.path.isdir(file_path):
  679. available_packages.update({file_name: file_path})
  680. return available_packages
  681. def _load_package(self, package_name):
  682. '''Метод для загрузки переменных содержащихся в указанном пакете.'''
  683. self.logger.info("Loading datavars package '{}'".format(package_name))
  684. try:
  685. self._loader.load_variables_package(package_name)
  686. except Exception as error:
  687. raise VariableError("Can not load datavars package: {}".
  688. format(error))
  689. @property
  690. def available_packages(self):
  691. packages = set(self._available_packages)
  692. packages.update({'custom'})
  693. return packages
  694. def __getattr__(self, package_name: str):
  695. '''Метод возвращает ноду пространства имен, соответствующего искомому
  696. пакету.'''
  697. if package_name in self.root._namespaces:
  698. return self.root[package_name]
  699. elif package_name == 'custom':
  700. custom_namespace = NamespaceNode('custom')
  701. self.root.add_namespace(custom_namespace)
  702. return self.root[package_name]
  703. elif package_name not in self._available_packages:
  704. raise VariableNotFoundError("datavars package '{}' is not found".
  705. format(package_name))
  706. else:
  707. self._load_package(package_name)
  708. return self.root[package_name]
  709. def __getitem__(self, package_name: str) -> None:
  710. '''Метод возвращает ноду пространства имен, соответствующего искомому
  711. пакету.'''
  712. if package_name in self.root:
  713. return self.root[package_name]
  714. elif package_name == 'custom':
  715. custom_namespace = NamespaceNode('custom')
  716. self.root.add_namespace(custom_namespace)
  717. return self.root[package_name]
  718. elif package_name == 'tasks':
  719. self.create_tasks()
  720. return self.root[package_name]
  721. elif package_name not in self._available_packages:
  722. raise VariableNotFoundError("variables package '{}' is not found".
  723. format(package_name))
  724. else:
  725. self._load_package(package_name)
  726. return self.root[package_name]
  727. def __contains__(self, package_name):
  728. if package_name in self.root._namespaces:
  729. return True
  730. elif package_name == 'custom':
  731. custom_namespace = NamespaceNode('custom')
  732. self.root.add_namespace(custom_namespace)
  733. return True
  734. elif package_name == 'tasks':
  735. self.create_tasks()
  736. return True
  737. elif (package_name not in self._available_packages
  738. and package_name != 'custom'):
  739. return False
  740. else:
  741. self._load_package(package_name)
  742. return True
  743. def add_namespace(self, namespace_node):
  744. self.root.add_namespace(namespace_node)
  745. def create_tasks(self):
  746. '''Метод для создания всех необходимых пространств имен для работы
  747. задач.'''
  748. tasks = NamespaceNode('tasks')
  749. self.add_namespace(tasks)
  750. env = NamespaceNode('env')
  751. tasks.add_namespace(env)
  752. env.add_namespace('loop')
  753. @property
  754. def _namespaces(self):
  755. return self.root._namespaces
  756. def save_variables(self):
  757. '''Метод для сохранения значений переменных в calculate.ini файлах.'''
  758. target_paths = self.main.cl.system.env_path
  759. saver = CalculateIniSaver()
  760. for target in self.variables_to_save:
  761. if self.variables_to_save[target]:
  762. dict_to_save = self.variables_to_save[target]
  763. target_path = target_paths[target].value
  764. saver.save_to_ini(target_path, dict_to_save)
  765. self.logger.info("Variables for '{}' is saved in the"
  766. " file: {}".format(target, target_path))