rougail/src/rougail/tiramisureflector.py

453 lines
17 KiB
Python

"""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']:
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]) + ']'