453 lines
17 KiB
Python
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]) + ']'
|