Files
rougail/src/rougail/tiramisureflector.py

453 lines
17 KiB
Python
Raw Normal View History

2020-07-06 20:58:11 +02:00
"""loader
2019-11-23 08:17:35 +01:00
flattened XML specific
2021-01-30 08:15:26 +01:00
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
2019-11-23 08:17:35 +01:00
"""
2021-01-25 17:30:03 +01:00
from json import dumps
2021-02-14 10:10:48 +01:00
from os.path import isfile
2021-01-25 17:30:03 +01:00
2022-01-09 13:07:54 +01:00
from .i18n import _
2021-01-23 21:21:45 +01:00
from .annotator import CONVERT_OPTION
2021-01-23 13:44:21 +01:00
from .objspace import RootRougailObject
2022-01-09 13:07:54 +01:00
from .error import DictConsistencyError
2019-11-23 08:17:35 +01:00
2021-01-23 13:44:21 +01:00
class BaseElt: # pylint: disable=R0903
"""Base element
"""
name = 'baseoption'
doc = 'baseoption'
path = '.'
class TiramisuReflector:
2021-01-18 17:46:21 +01:00
"""Convert object to tiramisu representation
"""
2020-07-20 18:13:53 +02:00
def __init__(self,
2021-01-23 13:44:21 +01:00
objectspace,
funcs_paths,
2021-12-10 23:35:44 +01:00
internal_functions,
2020-07-20 18:13:53 +02:00
):
2021-01-18 17:46:21 +01:00
self.index = 0
2021-02-14 10:10:48 +01:00
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))",
])
2021-12-10 23:35:44 +01:00
if internal_functions:
for func in internal_functions:
self.text.append(f"setattr(func, '{func}', {func})")
2021-02-14 10:10:48 +01:00
self.text.extend(["try:",
" from tiramisu3 import *",
"except:",
" from tiramisu import *",
])
2021-01-23 13:44:21 +01:00
self.objectspace = objectspace
self.make_tiramisu_objects()
2021-02-18 17:00:12 +01:00
if self.objectspace.has_dyn_option is True:
2021-02-14 10:10:48 +01:00
self.text.append("from rougail.tiramisu import ConvertDynOptionDescription")
2020-07-20 18:13:53 +02:00
2021-01-23 13:44:21 +01:00
def make_tiramisu_objects(self) -> None:
2021-01-18 17:46:21 +01:00
"""make tiramisu objects
"""
2021-12-10 23:35:44 +01:00
providers = {}
2021-01-18 17:46:21 +01:00
baseelt = BaseElt()
self.set_name(baseelt)
2021-12-10 23:35:44 +01:00
dynamic_path = ''
dynamic = False
2021-01-18 17:46:21 +01:00
basefamily = Family(baseelt,
2021-01-19 18:04:13 +01:00
self.text,
2021-01-24 18:50:24 +01:00
self.objectspace,
2021-01-18 17:46:21 +01:00
)
2021-01-23 13:44:21 +01:00
for elt in self.reorder_family():
self.populate_family(basefamily,
elt,
2021-12-10 23:35:44 +01:00
providers,
dynamic,
dynamic_path,
2021-01-23 13:44:21 +01:00
)
2021-12-10 23:35:44 +01:00
basefamily.elt.information = providers
basefamily.populate_informations()
2021-02-14 10:10:48 +01:00
self.baseelt = baseelt
2021-01-18 17:46:21 +01:00
2021-01-23 13:44:21 +01:00
def reorder_family(self):
2021-01-18 17:46:21 +01:00
"""variable_namespace family has to be loaded before any other family
because `extra` family could use `variable_namespace` variables.
"""
2021-01-23 13:44:21 +01:00
if hasattr(self.objectspace.space, 'variables'):
2021-02-18 17:00:12 +01:00
variable_namespace = self.objectspace.rougailconfig['variable_namespace']
if variable_namespace in self.objectspace.space.variables:
yield self.objectspace.space.variables[variable_namespace]
2021-01-23 13:44:21 +01:00
for elt, value in self.objectspace.space.variables.items():
2021-02-16 12:08:45 +01:00
if elt != self.objectspace.rougailconfig['variable_namespace']:
2020-07-29 08:59:40 +02:00
yield value
2021-01-23 13:44:21 +01:00
if hasattr(self.objectspace.space, 'services'):
yield self.objectspace.space.services
2019-11-26 20:33:24 +01:00
2020-07-07 18:12:16 +02:00
def populate_family(self,
parent_family,
elt,
2021-12-10 23:35:44 +01:00
providers,
dynamic,
dynamic_path,
2020-07-07 18:12:16 +02:00
):
2021-01-18 17:46:21 +01:00
"""Populate family
"""
self.set_name(elt)
2020-07-07 18:12:16 +02:00
family = Family(elt,
2021-01-19 18:04:13 +01:00
self.text,
2021-01-24 18:50:24 +01:00
self.objectspace,
2020-07-07 18:12:16 +02:00
)
2021-12-10 23:35:44 +01:00
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
2020-07-07 18:12:16 +02:00
parent_family.add(family)
2021-01-23 13:44:21 +01:00
for children in vars(elt).values():
if isinstance(children, self.objectspace.family):
self.populate_family(family,
children,
2021-12-10 23:35:44 +01:00
providers,
dynamic,
dynamic_path,
2021-01-23 13:44:21 +01:00
)
continue
2021-01-18 17:46:21 +01:00
if isinstance(children, dict):
children = list(children.values())
if isinstance(children, list):
2021-01-23 13:44:21 +01:00
for child in children:
if isinstance(child, self.objectspace.property_) or \
not isinstance(child, RootRougailObject):
continue
if isinstance(child, self.objectspace.variable):
2021-01-24 18:11:43 +01:00
self.set_name(child)
2021-12-10 23:35:44 +01:00
sub_dynamic_path = dynamic_path + '.' + child.name
if dynamic:
sub_dynamic_path += '{suffix}'
2021-01-24 18:11:43 +01:00
family.add(Variable(child,
self.text,
self.objectspace,
2021-12-10 23:35:44 +01:00
providers,
sub_dynamic_path,
2021-01-24 18:11:43 +01:00
))
2021-01-23 13:44:21 +01:00
else:
2021-01-24 18:11:43 +01:00
self.populate_family(family,
child,
2021-12-10 23:35:44 +01:00
providers,
dynamic,
dynamic_path,
2021-01-24 18:11:43 +01:00
)
2020-07-07 18:12:16 +02:00
2021-01-18 17:46:21 +01:00
def set_name(self,
elt,
):
2021-01-19 18:04:13 +01:00
"""Set name
"""
2021-01-18 17:46:21 +01:00
elt.reflector_name = f'option_{self.index}'
self.index += 1
2019-11-23 08:17:35 +01:00
2020-07-20 18:13:53 +02:00
def get_text(self):
2021-01-18 17:46:21 +01:00
"""Get text
"""
2022-01-19 18:24:00 +01:00
self.baseelt.reflector_object.get([]) # pylint: disable=E1101
2021-01-18 17:46:21 +01:00
return '\n'.join(self.text)
2020-07-20 18:13:53 +02:00
class Common:
2021-01-18 17:46:21 +01:00
"""Common function for variable and family
"""
2020-07-20 18:13:53 +02:00
def __init__(self,
2021-01-19 18:04:13 +01:00
elt,
text,
2021-01-24 18:50:24 +01:00
objectspace,
2020-07-20 18:13:53 +02:00
):
2021-01-19 18:04:13 +01:00
self.elt = elt
2020-07-20 18:13:53 +02:00
self.option_name = None
2021-01-19 18:04:13 +01:00
self.text = text
2021-01-24 18:50:24 +01:00
self.objectspace = objectspace
2021-01-18 17:46:21 +01:00
self.elt.reflector_object = self
2021-01-24 18:11:43 +01:00
self.object_type = None
2022-01-19 18:24:00 +01:00
def get(self, calls):
2021-01-24 18:11:43 +01:00
"""Get tiramisu's object
"""
2022-01-19 18:24:00 +01:00
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
2021-01-24 18:11:43 +01:00
if self.option_name is None:
self.option_name = self.elt.reflector_name
self.populate_attrib()
self.populate_informations()
2021-12-10 23:35:44 +01:00
if hasattr(self.elt, 'provider'):
2022-01-09 13:07:54 +01:00
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
2021-01-24 18:11:43 +01:00
return self.option_name
def populate_attrib(self):
"""Populate attributes
"""
2021-01-24 18:50:24 +01:00
keys = {'name': self.convert_str(self.elt.name)}
2021-01-24 18:11:43 +01:00
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)
2021-01-24 18:50:24 +01:00
attrib = ', '.join([f'{key}={value}' for key, value in keys.items()])
2021-01-24 18:11:43 +01:00
self.text.append(f'{self.option_name} = {self.object_type}({attrib})')
def _populate_attrib(self,
keys: dict,
2021-01-24 18:18:40 +01:00
) -> None: # pragma: no cover
2021-01-24 18:11:43 +01:00
raise NotImplementedError()
2020-07-20 18:13:53 +02:00
2021-01-23 21:21:45 +01:00
@staticmethod
2021-01-24 18:11:43 +01:00
def convert_str(value):
"""convert string
2021-01-18 17:46:21 +01:00
"""
2021-01-25 17:30:03 +01:00
return dumps(value, ensure_ascii=False)
2020-07-20 18:13:53 +02:00
2021-01-24 18:11:43 +01:00
def properties_to_string(self,
2021-01-24 18:50:24 +01:00
values: list,
2021-01-24 18:11:43 +01:00
) -> None:
2021-01-19 18:04:13 +01:00
"""Change properties to string
"""
properties = [self.convert_str(property_) for property_ in values
if isinstance(property_, str)]
2021-01-24 18:50:24 +01:00
calc_properties = [self.calc_properties(property_) for property_ in values \
if isinstance(property_, self.objectspace.property_)]
2021-01-23 21:15:26 +01:00
return 'frozenset({' + ', '.join(sorted(properties) + calc_properties) + '})'
2021-01-19 18:04:13 +01:00
def calc_properties(self,
child,
) -> str:
2021-01-24 18:11:43 +01:00
"""Populate properties
2021-01-18 17:46:21 +01:00
"""
2022-01-19 18:24:00 +01:00
option_name = child.source.reflector_object.get(self.calls)
2021-02-27 17:24:18 +01:00
kwargs = (f"'condition': ParamOption({option_name}, todict=True, notraisepropertyerror=True), "
2021-02-12 18:08:28 +01:00
f"'expected': {self.populate_param(child.expected)}")
2021-01-24 18:11:43 +01:00
if child.inverse:
kwargs += ", 'reverse_condition': ParamValue(True)"
return (f"Calculation(func.calc_value, Params(ParamValue('{child.name}'), "
f"kwargs={{{kwargs}}}))")
2020-07-20 18:13:53 +02:00
def populate_informations(self):
2021-01-18 17:46:21 +01:00
"""Populate Tiramisu's informations
"""
2021-01-23 11:57:46 +01:00
if not hasattr(self.elt, 'information'):
return
2021-12-10 23:35:44 +01:00
if isinstance(self.elt.information, dict):
informations = self.elt.information
else:
informations = vars(self.elt.information)
for key, value in informations.items():
2021-01-23 11:57:46 +01:00
if key == 'xmlfiles':
continue
if isinstance(value, str):
2021-01-24 18:50:24 +01:00
value = self.convert_str(value)
self.text.append(f"{self.option_name}.impl_set_information('{key}', {value})")
2020-07-20 18:13:53 +02:00
2021-02-12 18:08:28 +01:00
def populate_param(self,
param,
):
"""Populate variable parameters
"""
2021-05-01 18:32:45 +02:00
if param.type in ['number', 'boolean', 'nil', 'string', 'port', 'choice']:
2021-02-12 18:08:28 +01:00
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)'
2021-02-12 18:08:28 +01:00
if param.type == 'suffix':
return 'ParamSuffix()'
2021-02-17 22:32:28 +01:00
if param.type == 'index':
return 'ParamIndex()'
2022-01-09 13:07:54 +01:00
raise Exception(_(f'unknown type {param.type}')) # pragma: no cover
2021-02-12 18:08:28 +01:00
2022-01-19 18:24:00 +01:00
def build_option_param(self,
param,
2021-02-12 18:08:28 +01:00
) -> str:
"""build variable parameters
"""
2022-01-19 18:24:00 +01:00
option_name = param.text.reflector_object.get(self.calls)
2021-02-12 18:08:28 +01:00
params = [f'{option_name}']
if hasattr(param, 'suffix'):
param_type = 'ParamDynOption'
2022-01-19 18:24:00 +01:00
family = param.family.reflector_object.get(self.calls)
2021-02-28 21:07:36 +01:00
params.extend([f"'{param.suffix}'", f'{family}'])
2021-02-12 18:08:28 +01:00
else:
param_type = 'ParamOption'
if not param.propertyerror:
params.append('notraisepropertyerror=True')
return "{}({})".format(param_type, ', '.join(params))
2020-07-20 18:13:53 +02:00
class Variable(Common):
2021-01-18 17:46:21 +01:00
"""Manage variable
"""
2020-07-20 18:13:53 +02:00
def __init__(self,
elt,
2021-01-19 18:04:13 +01:00
text,
2021-01-23 21:15:26 +01:00
objectspace,
2021-12-10 23:35:44 +01:00
providers,
dynamic_path,
2020-07-20 18:13:53 +02:00
):
2021-12-10 23:35:44 +01:00
self.providers = providers
self.dynamic_path = dynamic_path
2021-01-24 18:50:24 +01:00
super().__init__(elt, text, objectspace)
self.object_type = CONVERT_OPTION[elt.type]['opttype']
2020-07-20 18:13:53 +02:00
2021-01-24 18:11:43 +01:00
def _populate_attrib(self,
keys: dict,
):
if hasattr(self.elt, 'opt'):
2022-01-19 18:24:00 +01:00
keys['opt'] = self.elt.opt.reflector_object.get(self.calls)
2021-05-01 18:32:45 +02:00
if hasattr(self.elt, 'choice'):
values = self.elt.choice
if values[0].type == 'variable':
2022-01-19 18:24:00 +01:00
value = values[0].name.reflector_object.get(self.calls)
2021-01-24 18:50:24 +01:00
keys['values'] = f"Calculation(func.calc_value, Params((ParamOption({value}))))"
elif values[0].type == 'function':
2021-05-01 18:32:45 +02:00
keys['values'] = self.calculation_value(values[0], [])
2021-01-24 18:11:43 +01:00
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)
2021-01-24 18:11:43 +01:00
keys[key] = value
if hasattr(self.elt, 'validators'):
keys['validators'] = '[' + ', '.join([self.calculation_value(val,
['ParamSelfOption(whole=False)']) for val in self.elt.validators]) + ']'
2021-01-24 18:11:43 +01:00
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():
2021-01-30 17:32:21 +01:00
if isinstance(value, str):
value = f"'{value}'"
2021-01-24 18:11:43 +01:00
keys[key] = value
2020-07-20 18:13:53 +02:00
2021-01-18 17:46:21 +01:00
def calculation_value(self,
child,
args,
calc_multi=False,
2021-01-18 17:46:21 +01:00
) -> str:
"""Generate calculated value
"""
2020-07-20 18:13:53 +02:00
kwargs = []
2020-12-24 18:31:13 +01:00
# has parameters
function = child.name
2021-02-28 21:07:36 +01:00
new_args = []
2020-12-24 18:31:13 +01:00
if hasattr(child, 'param'):
for param in child.param:
2021-02-12 18:08:28 +01:00
value = self.populate_param(param)
2020-12-24 18:31:13 +01:00
if not hasattr(param, 'name'):
2021-02-28 21:07:36 +01:00
new_args.append(str(value))
2020-12-24 18:31:13 +01:00
else:
kwargs.append(f"'{param.name}': " + value)
2021-02-28 21:07:36 +01:00
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) + ')'
2021-01-24 18:18:40 +01:00
if kwargs:
ret += ', kwargs={' + ', '.join(kwargs) + '}'
ret += ')'
2020-07-29 08:59:40 +02:00
if hasattr(child, 'warnings_only'):
ret += f', warnings_only={child.warnings_only}'
ret = ret + ')'
if calc_multi:
ret = '[' + ret + ']'
return ret
2020-07-20 18:13:53 +02:00
2019-11-23 08:17:35 +01:00
2019-11-26 20:33:24 +01:00
class Family(Common):
2021-01-18 17:46:21 +01:00
"""Manage family
"""
2020-07-07 18:12:16 +02:00
def __init__(self,
elt,
2021-01-19 18:04:13 +01:00
text,
2021-01-24 18:50:24 +01:00
objectspace,
2020-07-07 18:12:16 +02:00
):
2021-01-24 18:50:24 +01:00
super().__init__(elt, text, objectspace)
2021-01-24 18:11:43 +01:00
if hasattr(self.elt, 'suffixes'):
2021-02-14 10:10:48 +01:00
self.objectspace.has_dyn_option = True
2021-01-24 18:11:43 +01:00
self.object_type = 'ConvertDynOptionDescription'
elif hasattr(self.elt, 'leadership') and self.elt.leadership:
2021-01-24 18:11:43 +01:00
self.object_type = 'Leadership'
else:
self.object_type = 'OptionDescription'
2019-11-23 08:17:35 +01:00
self.children = []
def add(self, child):
2021-01-18 17:46:21 +01:00
"""Add a child
"""
2019-11-23 08:17:35 +01:00
self.children.append(child)
2021-01-24 18:11:43 +01:00
def _populate_attrib(self,
keys: list,
) -> None:
if hasattr(self.elt, 'suffixes'):
2022-01-19 18:24:00 +01:00
dyn = self.elt.suffixes.reflector_object.get(self.calls)
keys['suffixes'] = f"Calculation(func.calc_value, Params((ParamOption({dyn}, notraisepropertyerror=True))))"
2022-01-19 18:24:00 +01:00
keys['children'] = '[' + ', '.join([child.get(self.calls) for child in self.children]) + ']'