"""loader flattened XML specific Created by: EOLE (http://eole.orion.education.fr) Copyright (C) 2005-2018 Forked by: Cadoles (http://www.cadoles.com) Copyright (C) 2019-2021 distribued with GPL-2 or later license This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from json import dumps from os.path import isfile from .i18n import _ from .annotator import CONVERT_OPTION from .objspace import RootRougailObject from .error import DictConsistencyError class BaseElt: # pylint: disable=R0903 """Base element """ name = 'baseoption' doc = 'baseoption' path = '.' class TiramisuReflector: """Convert object to tiramisu representation """ def __init__(self, objectspace, funcs_paths, internal_functions, ): self.index = 0 self.text = [] if funcs_paths: self.text.extend(["from importlib.machinery import SourceFileLoader as _SourceFileLoader", "from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec", "class func:", " pass", ]) for funcs_path in funcs_paths: if not isfile(funcs_path): continue self.text.extend([f"_loader = _SourceFileLoader('func', '{funcs_path}')", "_spec = _spec_from_loader(_loader.name, _loader)", "_func = _module_from_spec(_spec)", "_loader.exec_module(_func)", "for function in dir(_func):", " if function.startswith('_'):", " continue", " setattr(func, function, getattr(_func, function))", ]) if internal_functions: for func in internal_functions: self.text.append(f"setattr(func, '{func}', {func})") self.text.extend(["try:", " from tiramisu3 import *", "except:", " from tiramisu import *", ]) self.objectspace = objectspace self.make_tiramisu_objects() if self.objectspace.has_dyn_option is True: self.text.append("from rougail.tiramisu import ConvertDynOptionDescription") def make_tiramisu_objects(self) -> None: """make tiramisu objects """ providers = {} baseelt = BaseElt() self.set_name(baseelt) dynamic_path = '' dynamic = False basefamily = Family(baseelt, self.text, self.objectspace, ) for elt in self.reorder_family(): self.populate_family(basefamily, elt, providers, dynamic, dynamic_path, ) basefamily.elt.information = providers basefamily.populate_informations() self.baseelt = baseelt def reorder_family(self): """variable_namespace family has to be loaded before any other family because `extra` family could use `variable_namespace` variables. """ if hasattr(self.objectspace.space, 'variables'): variable_namespace = self.objectspace.rougailconfig['variable_namespace'] if variable_namespace in self.objectspace.space.variables: yield self.objectspace.space.variables[variable_namespace] for elt, value in self.objectspace.space.variables.items(): if elt != self.objectspace.rougailconfig['variable_namespace']: yield value if hasattr(self.objectspace.space, 'services'): yield self.objectspace.space.services def populate_family(self, parent_family, elt, providers, dynamic, dynamic_path, ): """Populate family """ self.set_name(elt) family = Family(elt, self.text, self.objectspace, ) if not dynamic_path: dynamic_path = elt.name else: dynamic_path = dynamic_path + '.' + elt.name if dynamic or hasattr(elt, 'suffixes'): dynamic_path += '{suffix}' dynamic = True parent_family.add(family) for children in vars(elt).values(): if isinstance(children, self.objectspace.family): self.populate_family(family, children, providers, dynamic, dynamic_path, ) continue if isinstance(children, dict): children = list(children.values()) if isinstance(children, list): for child in children: if isinstance(child, self.objectspace.property_) or \ not isinstance(child, RootRougailObject): continue if isinstance(child, self.objectspace.variable): self.set_name(child) sub_dynamic_path = dynamic_path + '.' + child.name if dynamic: sub_dynamic_path += '{suffix}' family.add(Variable(child, self.text, self.objectspace, providers, sub_dynamic_path, )) else: self.populate_family(family, child, providers, dynamic, dynamic_path, ) def set_name(self, elt, ): """Set name """ elt.reflector_name = f'option_{self.index}' self.index += 1 def get_text(self): """Get text """ self.baseelt.reflector_object.get([]) # pylint: disable=E1101 return '\n'.join(self.text) class Common: """Common function for variable and family """ def __init__(self, elt, text, objectspace, ): self.elt = elt self.option_name = None self.text = text self.objectspace = objectspace self.elt.reflector_object = self self.object_type = None def get(self, calls): """Get tiramisu's object """ self_calls = calls.copy() if self.elt.path in self_calls: msg = f'"{self.elt.path}" will make an infinite loop' raise DictConsistencyError(msg, 80, self.elt.xmlfiles) self_calls.append(self.elt.path) self.calls = self_calls if self.option_name is None: self.option_name = self.elt.reflector_name self.populate_attrib() self.populate_informations() if hasattr(self.elt, 'provider'): name = 'provider:' + self.elt.provider if name in self.providers: msg = f'provider {name} declare multiple time' raise DictConsistencyError(msg, 79, self.elt.xmlfiles) self.providers[name] = self.dynamic_path return self.option_name def populate_attrib(self): """Populate attributes """ keys = {'name': self.convert_str(self.elt.name)} if hasattr(self.elt, 'doc'): keys['doc'] = self.convert_str(self.elt.doc) self._populate_attrib(keys) if hasattr(self.elt, 'properties'): keys['properties'] = self.properties_to_string(self.elt.properties) attrib = ', '.join([f'{key}={value}' for key, value in keys.items()]) self.text.append(f'{self.option_name} = {self.object_type}({attrib})') def _populate_attrib(self, keys: dict, ) -> None: # pragma: no cover raise NotImplementedError() @staticmethod def convert_str(value): """convert string """ return dumps(value, ensure_ascii=False) def properties_to_string(self, values: list, ) -> None: """Change properties to string """ properties = [self.convert_str(property_) for property_ in values if isinstance(property_, str)] calc_properties = [self.calc_properties(property_) for property_ in values \ if isinstance(property_, self.objectspace.property_)] return 'frozenset({' + ', '.join(sorted(properties) + calc_properties) + '})' def calc_properties(self, child, ) -> str: """Populate properties """ option_name = child.source.reflector_object.get(self.calls) kwargs = (f"'condition': ParamOption({option_name}, todict=True, notraisepropertyerror=True), " f"'expected': {self.populate_param(child.expected)}") if child.inverse: kwargs += ", 'reverse_condition': ParamValue(True)" return (f"Calculation(func.calc_value, Params(ParamValue('{child.name}'), " f"kwargs={{{kwargs}}}))") def populate_informations(self): """Populate Tiramisu's informations """ if not hasattr(self.elt, 'information'): return if isinstance(self.elt.information, dict): informations = self.elt.information else: informations = vars(self.elt.information) for key, value in informations.items(): if key == 'xmlfiles': continue if isinstance(value, str): value = self.convert_str(value) self.text.append(f"{self.option_name}.impl_set_information('{key}', {value})") def populate_param(self, param, ): """Populate variable parameters """ if param.type in ['number', 'boolean', 'nil', 'string', 'port', 'choice', 'space']: value = param.text if param.type == 'string' and value is not None: value = self.convert_str(value) return f'ParamValue({value})' if param.type == 'variable': return self.build_option_param(param) if param.type == 'information': if hasattr(self.elt, 'multi') and self.elt.multi: default = [] else: default = None return f'ParamInformation("{param.text}", {default})' if param.type == 'target_information': return f'ParamSelfInformation("{param.text}", None)' if param.type == 'suffix': return 'ParamSuffix()' if param.type == 'index': return 'ParamIndex()' raise Exception(_(f'unknown type {param.type}')) # pragma: no cover def build_option_param(self, param, ) -> str: """build variable parameters """ option_name = param.text.reflector_object.get(self.calls) params = [f'{option_name}'] if hasattr(param, 'suffix'): param_type = 'ParamDynOption' family = param.family.reflector_object.get(self.calls) params.extend([f"'{param.suffix}'", f'{family}']) else: param_type = 'ParamOption' if not param.propertyerror: params.append('notraisepropertyerror=True') return "{}({})".format(param_type, ', '.join(params)) class Variable(Common): """Manage variable """ def __init__(self, elt, text, objectspace, providers, dynamic_path, ): self.providers = providers self.dynamic_path = dynamic_path super().__init__(elt, text, objectspace) self.object_type = CONVERT_OPTION[elt.type]['opttype'] def _populate_attrib(self, keys: dict, ): if hasattr(self.elt, 'opt'): keys['opt'] = self.elt.opt.reflector_object.get(self.calls) if hasattr(self.elt, 'choice'): values = self.elt.choice if values[0].type == 'variable': value = values[0].name.reflector_object.get(self.calls) keys['values'] = f"Calculation(func.calc_value, Params((ParamOption({value}))))" elif values[0].type == 'function': keys['values'] = self.calculation_value(values[0], []) else: keys['values'] = str(tuple([val.name for val in values])) if hasattr(self.elt, 'multi') and self.elt.multi: keys['multi'] = self.elt.multi for key in ['default', 'default_multi']: if hasattr(self.elt, key) and getattr(self.elt, key) is not None: value = getattr(self.elt, key) if isinstance(value, str): value = self.convert_str(value) elif isinstance(value, self.objectspace.value): value = self.calculation_value(value, [], calc_multi=value.calc_multi) keys[key] = value if hasattr(self.elt, 'validators'): keys['validators'] = '[' + ', '.join([self.calculation_value(val, ['ParamSelfOption(whole=False)']) for val in self.elt.validators]) + ']' for key in ['min_number', 'max_number']: if hasattr(self.elt, key): keys[key] = getattr(self.elt, key) for key, value in CONVERT_OPTION[self.elt.type].get('initkwargs', {}).items(): if isinstance(value, str): value = f"'{value}'" keys[key] = value def calculation_value(self, child, args, calc_multi=False, ) -> str: """Generate calculated value """ kwargs = [] # has parameters function = child.name new_args = [] if hasattr(child, 'param'): for param in child.param: value = self.populate_param(param) if not hasattr(param, 'name'): new_args.append(str(value)) else: kwargs.append(f"'{param.name}': " + value) if function == 'valid_network_netmask': new_args.extend(args) else: args.extend(new_args) new_args = args ret = f'Calculation(func.{function}, Params((' + ', '.join(new_args) + ')' if kwargs: ret += ', kwargs={' + ', '.join(kwargs) + '}' ret += ')' if hasattr(child, 'warnings_only'): ret += f', warnings_only={child.warnings_only}' ret = ret + ')' if calc_multi: ret = '[' + ret + ']' return ret class Family(Common): """Manage family """ def __init__(self, elt, text, objectspace, ): super().__init__(elt, text, objectspace) if hasattr(self.elt, 'suffixes'): self.objectspace.has_dyn_option = True self.object_type = 'ConvertDynOptionDescription' elif hasattr(self.elt, 'leadership') and self.elt.leadership: self.object_type = 'Leadership' else: self.object_type = 'OptionDescription' self.children = [] def add(self, child): """Add a child """ self.children.append(child) def _populate_attrib(self, keys: list, ) -> None: if hasattr(self.elt, 'suffixes'): dyn = self.elt.suffixes.reflector_object.get(self.calls) keys['suffixes'] = f"Calculation(func.calc_value, Params((ParamOption({dyn}, notraisepropertyerror=True))))" keys['children'] = '[' + ', '.join([child.get(self.calls) for child in self.children]) + ']'