"""loader flattened XML specific """ from .config import Config from .annotator import ERASED_ATTRIBUTES, CONVERT_OPTION FUNC_TO_DICT = [] ATTRIBUTES_ORDER = ('name', 'doc', 'default', 'multi', 'properties', 'min_number', 'max_number', 'dynamic') class Root(): # pylint: disable=R0903 """Root classes """ path = '.' class TiramisuReflector: """Convert object to tiramisu representation """ def __init__(self, space, funcs_path, ): self.index = 0 self.text = ["from importlib.machinery import SourceFileLoader", "from importlib.util import spec_from_loader, module_from_spec", f"loader = SourceFileLoader('func', '{funcs_path}')", "spec = spec_from_loader(loader.name, loader)", "func = module_from_spec(spec)", "loader.exec_module(func)", "for key, value in dict(locals()).items():", " if key != ['SourceFileLoader', 'func']:", " setattr(func, key, value)", "try:", " from tiramisu3 import *", "except:", " from tiramisu import *", "from rougail.tiramisu import ConvertDynOptionDescription", ] self.make_tiramisu_objects(space) def make_tiramisu_objects(self, space, ): """make tiramisu objects """ baseelt = BaseElt() self.set_name(baseelt) basefamily = Family(baseelt, self.text, ) for elt in self.reorder_family(space): self.iter_family(basefamily, elt, ) # parse object baseelt.reflector_object.get() # pylint: disable=E1101 @staticmethod def reorder_family(space): """variable_namespace family has to be loaded before any other family because `extra` family could use `variable_namespace` variables. """ if hasattr(space, 'variables'): if Config['variable_namespace'] in space.variables: yield space.variables[Config['variable_namespace']] for elt, value in space.variables.items(): if elt != Config['variable_namespace']: yield value if hasattr(space, 'services'): yield space.services def get_attributes(self, space): # pylint: disable=R0201 """Get attributes """ for attr in dir(space): if not attr.startswith('_') and attr not in ERASED_ATTRIBUTES: yield attr def iter_family(self, family, child, ): """Iter each family """ tag = child.__class__.__name__ if tag == 'Variable': function = self.populate_variable elif tag == 'Property': # property already imported with family return else: function = self.populate_family function(family, child, ) def populate_family(self, parent_family, elt, ): """Populate family """ self.set_name(elt) family = Family(elt, self.text, ) parent_family.add(family) for children in self.get_children(elt): for child in children: self.iter_family(family, child, ) def get_children(self, space, ): """Get children """ for tag in self.get_attributes(space): children = getattr(space, tag) if children.__class__.__name__ == 'Family': children = [children] if isinstance(children, dict): children = list(children.values()) if isinstance(children, list): yield children def populate_variable(self, family, elt, ): """Populate variable """ if family.is_leadership: is_leader = elt.name == family.elt.variable[0].name is_follower = not is_leader else: is_leader = False is_follower = False self.set_name(elt) family.add(Variable(elt, self.text, is_follower, is_leader, )) def set_name(self, elt, ): """Set name """ elt.reflector_name = f'option_{self.index}' self.index += 1 def get_text(self): """Get text """ return '\n'.join(self.text) class BaseElt: # pylint: disable=R0903 """Base element """ name = 'baseoption' doc = 'baseoption' path = '.' class Common: """Common function for variable and family """ def __init__(self, elt, text, ): self.elt = elt self.option_name = None self.attrib = {} self.text = text self.elt.reflector_object = self def populate_properties(self, child): """Populate properties """ assert child.type == 'calculation' action = f"ParamValue('{child.name}')" option_name = child.source.reflector_object.get() kwargs = (f"'condition': ParamOption({option_name}, todict=True), " f"'expected': ParamValue('{child.expected}')") if child.inverse: kwargs += ", 'reverse_condition': ParamValue(True)" prop = 'Calculation(calc_value, Params(' + action + ', kwargs={' + kwargs + '}))' if self.attrib['properties']: self.attrib['properties'] += ', ' self.attrib['properties'] += prop def properties_to_string(self): """Change properties to string """ if not self.attrib['properties']: self.attrib['properties'] = '' else: self.attrib['properties'] = "'" + "', '".join(sorted(self.attrib['properties'])) + "'" def get_attrib(self): """Get attributes """ ret_list = [] for key, value in self.attrib.items(): if value is None: continue if key == 'properties': if not self.attrib[key]: continue value = "frozenset({" + self.attrib[key] + "})" elif key in ['default', 'multi', 'suffixes', 'validators', 'values']: value = self.attrib[key] elif isinstance(value, str) and key != 'opt' and not value.startswith('['): value = "'" + value.replace("'", "\\\'") + "'" ret_list.append(f'{key}={value}') return ', '.join(ret_list) def populate_informations(self): """Populate Tiramisu's informations """ if not hasattr(self.elt, 'information'): return for key, value in vars(self.elt.information).items(): if key == 'xmlfiles': continue if isinstance(value, str): value = '"' + value.replace('"', '\"') + '"' self.text.append(f'{self.option_name}.impl_set_information("{key}", {value})') def get_attributes(self, space): # pylint: disable=R0201 """Get attributes """ for attr in ATTRIBUTES_ORDER: if hasattr(space, attr): yield attr @staticmethod def get_children(space): """Get children """ for attr in dir(space): if not attr.startswith('_') and attr not in ERASED_ATTRIBUTES: if isinstance(getattr(space, attr), list): yield attr, getattr(space, attr) class Variable(Common): """Manage variable """ def __init__(self, elt, text, is_follower, is_leader, ): super().__init__(elt, text) self.is_leader = is_leader self.is_follower = is_follower convert_option = CONVERT_OPTION[elt.type] self.object_type = convert_option['opttype'] self.attrib.update(convert_option.get('initkwargs', {})) if self.object_type != 'SymLinkOption': self.attrib['properties'] = [] self.attrib['validators'] = [] def get(self): """Get tiramisu's object """ if self.option_name is None: self.populate_attrib() if self.object_type == 'SymLinkOption': self.attrib['opt'] = self.elt.opt.reflector_object.get() else: self.parse_children() attrib = self.get_attrib() self.option_name = self.elt.reflector_name self.text.append(f'{self.option_name} = {self.object_type}({attrib})') self.populate_informations() return self.option_name def populate_attrib(self): """Populate attributes """ for key in self.get_attributes(self.elt): self.attrib[key] = getattr(self.elt, key) def parse_children(self): """Parse children """ if 'default' not in self.attrib or self.attrib['multi']: self.attrib['default'] = [] if self.attrib['multi'] == 'submulti' and self.is_follower: self.attrib['default_multi'] = [] choices = [] self.properties_to_string() for tag, children in self.get_children(self.elt): for child in children: self.parse_child(tag, child, choices, ) if choices: self.attrib['values'] = str(tuple(choices)) if not self.attrib['default']: del self.attrib['default'] elif not self.attrib['multi'] and isinstance(self.attrib['default'], list): self.attrib['default'] = self.attrib['default'][-1] if not self.attrib['validators']: del self.attrib['validators'] else: self.attrib['validators'] = '[' + ', '.join(self.attrib['validators']) + ']' def parse_child(self, tag, child, choices, ) -> None: """Parse child """ if tag == 'property': self.populate_properties(child) elif tag == 'value': self.populate_value(child) elif tag == 'check': validator = self.calculation_value(child, ['ParamSelfOption()']) self.attrib['validators'].append(validator) elif tag == 'choice': self.calculate_choice(child, choices, ) else: # pragma: no cover raise Exception(f'unknown tag {tag}') def calculate_choice(self, child, choices: list, ) -> None: """Calculating choice """ if child.type == 'calculation': value = child.name.reflector_object.get() self.attrib['values'] = f"Calculation(func.calc_value, Params((ParamOption({value}))))" else: choices.append(child.name) def calculation_value(self, child, args, ) -> str: """Generate calculated value """ kwargs = [] # has parameters function = child.name if hasattr(child, 'param'): for param in child.param: value = self.populate_param(function, param) if not hasattr(param, 'name'): args.append(str(value)) else: kwargs.append(f"'{param.name}': " + value) ret = f"Calculation(func.{function}, Params((" + ', '.join(args) + \ "), kwargs=" + "{" + ', '.join(kwargs) + "})" if hasattr(child, 'warnings_only'): ret += f', warnings_only={child.warnings_only}' return ret + ')' def populate_param(self, function: str, param, ): """Populate variable parameters """ if param.type == 'string': return f'ParamValue("{param.text}")' if param.type == 'number': return f'ParamValue({param.text})' if param.type == 'variable': value = {'option': param.text, 'notraisepropertyerror': param.notraisepropertyerror, 'todict': function in FUNC_TO_DICT, } if hasattr(param, 'suffix'): value['suffix'] = param.suffix value['family'] = param.family return self.build_param(value) if param.type == 'information': return f'ParamInformation("{param.text}", None)' if param.type == 'suffix': return 'ParamSuffix()' return '' # pragma: no cover def populate_value(self, child, ): """Populate variable's values """ if child.type == 'calculation': self.attrib['default'] = self.calculation_value(child, []) else: value = child.name if self.attrib['multi'] == 'submulti': self.attrib['default_multi'].append(value) elif self.is_follower: self.attrib['default_multi'] = value elif self.attrib['multi']: self.attrib['default'].append(value) if not self.is_leader: self.attrib['default_multi'] = value elif isinstance(value, (int, float)) or value is None: self.attrib['default'].append(value) else: self.attrib['default'].append("'" + value + "'") @staticmethod def build_param(param) -> str: """build variable parameters """ option_name = param['option'].reflector_object.get() ends = f"notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})" if 'suffix' in param: family_name = param['family'].reflector_name return f"ParamDynOption({option_name}, '{param['suffix']}', {family_name}, {ends}" return f"ParamOption({option_name}, {ends}" class Family(Common): """Manage family """ def __init__(self, elt, text, ): super().__init__(elt, text) self.is_leadership = self.elt.__class__.__name__ == 'Leadership' self.children = [] def add(self, child): """Add a child """ self.children.append(child) def get(self): """Get tiramisu's object """ if not self.option_name: self.populate_attrib() self.parse_properties() self.option_name = self.elt.reflector_name object_name = self.get_object_name() attrib = self.get_attrib() + \ ', children=[' + ', '.join([child.get() for child in self.children]) + ']' self.text.append(f'{self.option_name} = {object_name}({attrib})') self.populate_informations() return self.option_name def populate_attrib(self): """parse a populate attributes """ for key in self.get_attributes(self.elt): value = getattr(self.elt, key) if key == 'dynamic': dynamic = value.reflector_object.get() self.attrib['suffixes'] = \ f"Calculation(func.calc_value, Params((ParamOption({dynamic}))))" else: self.attrib[key] = value def parse_properties(self): """parse current children """ if 'properties' in self.attrib: self.properties_to_string() if hasattr(self.elt, 'property'): for child in self.elt.property: self.populate_properties(child) def get_object_name(self): """Get family object's name """ if 'suffixes' in self.attrib: return 'ConvertDynOptionDescription' if self.is_leadership: return 'Leadership' return 'OptionDescription'