Compare commits
6 Commits
712175d56b
...
529bb1ae7d
Author | SHA1 | Date |
---|---|---|
Emmanuel Garette | 529bb1ae7d | |
Emmanuel Garette | 06e7196280 | |
Emmanuel Garette | 3e2d221cc4 | |
Emmanuel Garette | 82e8057e9d | |
Emmanuel Garette | e34d285eb3 | |
Emmanuel Garette | 1d0a5fae18 |
|
@ -3,7 +3,9 @@
|
||||||
from .group import GroupAnnotator
|
from .group import GroupAnnotator
|
||||||
from .service import ServiceAnnotator, ERASED_ATTRIBUTES
|
from .service import ServiceAnnotator, ERASED_ATTRIBUTES
|
||||||
from .variable import VariableAnnotator, CONVERT_OPTION
|
from .variable import VariableAnnotator, CONVERT_OPTION
|
||||||
from .constrainte import ConstrainteAnnotator
|
from .check import CheckAnnotator
|
||||||
|
from .condition import Conditionnnotator
|
||||||
|
from .fill import FillAnnotator
|
||||||
from .family import FamilyAnnotator, modes
|
from .family import FamilyAnnotator, modes
|
||||||
from .property import PropertyAnnotator
|
from .property import PropertyAnnotator
|
||||||
|
|
||||||
|
@ -15,7 +17,11 @@ class SpaceAnnotator:
|
||||||
GroupAnnotator(objectspace)
|
GroupAnnotator(objectspace)
|
||||||
ServiceAnnotator(objectspace)
|
ServiceAnnotator(objectspace)
|
||||||
VariableAnnotator(objectspace)
|
VariableAnnotator(objectspace)
|
||||||
ConstrainteAnnotator(objectspace,
|
CheckAnnotator(objectspace,
|
||||||
|
eosfunc_file,
|
||||||
|
)
|
||||||
|
Conditionnnotator(objectspace)
|
||||||
|
FillAnnotator(objectspace,
|
||||||
eosfunc_file,
|
eosfunc_file,
|
||||||
)
|
)
|
||||||
FamilyAnnotator(objectspace)
|
FamilyAnnotator(objectspace)
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
"""Annotate check
|
||||||
|
"""
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
|
from typing import List, Any
|
||||||
|
|
||||||
|
from .variable import CONVERT_OPTION
|
||||||
|
|
||||||
|
from ..i18n import _
|
||||||
|
from ..error import DictConsistencyError
|
||||||
|
|
||||||
|
INTERNAL_FUNCTIONS = ['valid_enum', 'valid_in_network', 'valid_differ', 'valid_entier']
|
||||||
|
|
||||||
|
class CheckAnnotator:
|
||||||
|
"""Annotate check
|
||||||
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
objectspace,
|
||||||
|
eosfunc_file,
|
||||||
|
):
|
||||||
|
if not hasattr(objectspace.space, 'constraints') or \
|
||||||
|
not hasattr(objectspace.space.constraints, 'check'):
|
||||||
|
return
|
||||||
|
self.objectspace = objectspace
|
||||||
|
eosfunc = SourceFileLoader('eosfunc', eosfunc_file).load_module()
|
||||||
|
self.functions = dir(eosfunc)
|
||||||
|
self.functions.extend(INTERNAL_FUNCTIONS)
|
||||||
|
self.check_check()
|
||||||
|
self.check_valid_enum()
|
||||||
|
self.check_change_warning()
|
||||||
|
self.convert_check()
|
||||||
|
|
||||||
|
def check_check(self):
|
||||||
|
"""valid and manage <check>
|
||||||
|
"""
|
||||||
|
remove_indexes = []
|
||||||
|
for check_idx, check in enumerate(self.objectspace.space.constraints.check):
|
||||||
|
if not check.name in self.functions:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'cannot find check function "{check.name}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 1)
|
||||||
|
check_name = check.target
|
||||||
|
# let's replace the target by the an object
|
||||||
|
try:
|
||||||
|
check.target = self.objectspace.paths.get_variable_obj(check.target)
|
||||||
|
except DictConsistencyError as err:
|
||||||
|
if err.errno == 36:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'the target "{check.target}" in check cannot be a dynamic '
|
||||||
|
f'variable in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 22)
|
||||||
|
raise err
|
||||||
|
check.is_in_leadership = self.objectspace.paths.is_in_leadership(check_name)
|
||||||
|
if not hasattr(check, 'param'):
|
||||||
|
continue
|
||||||
|
param_option_indexes = []
|
||||||
|
for idx, param in enumerate(check.param):
|
||||||
|
if param.type == 'variable':
|
||||||
|
if not self.objectspace.paths.path_is_defined(param.text):
|
||||||
|
if not param.optional:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'cannot find check param "{param.text}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 2)
|
||||||
|
param_option_indexes.append(idx)
|
||||||
|
else:
|
||||||
|
# let's replace params by the path
|
||||||
|
param.text = self.objectspace.paths.get_variable_obj(param.text)
|
||||||
|
param_option_indexes.sort(reverse=True)
|
||||||
|
for idx in param_option_indexes:
|
||||||
|
check.param.pop(idx)
|
||||||
|
if check.param == []:
|
||||||
|
remove_indexes.append(check_idx)
|
||||||
|
remove_indexes.sort(reverse=True)
|
||||||
|
for idx in remove_indexes:
|
||||||
|
del self.objectspace.space.constraints.check[idx]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_valid_enum_value(variable,
|
||||||
|
values,
|
||||||
|
) -> None:
|
||||||
|
"""check that values in valid_enum are valid
|
||||||
|
"""
|
||||||
|
for value in variable.value:
|
||||||
|
if value.name not in values:
|
||||||
|
msg = _(f'value "{value.name}" of variable "{variable.name}" is not in list '
|
||||||
|
f'of all expected values ({values})')
|
||||||
|
raise DictConsistencyError(msg, 15)
|
||||||
|
|
||||||
|
def check_valid_enum(self):
|
||||||
|
"""verify valid_enum
|
||||||
|
"""
|
||||||
|
remove_indexes = []
|
||||||
|
for idx, check in enumerate(self.objectspace.space.constraints.check):
|
||||||
|
if check.name != 'valid_enum':
|
||||||
|
continue
|
||||||
|
if check.target.path in self.objectspace.valid_enums:
|
||||||
|
check_xmlfiles = self.objectspace.valid_enums[check.target.path]['xmlfiles']
|
||||||
|
old_xmlfiles = self.objectspace.display_xmlfiles(check_xmlfiles)
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'valid_enum define in {xmlfiles} but already set in {old_xmlfiles} '
|
||||||
|
f'for "{check.target.name}", did you forget remove_check?')
|
||||||
|
raise DictConsistencyError(msg, 3)
|
||||||
|
if not hasattr(check, 'param'):
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'param is mandatory for a valid_enum of variable "{check.target.name}" '
|
||||||
|
f'in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 4)
|
||||||
|
variable_type = check.target.type
|
||||||
|
values = self._set_valid_enum(check.target,
|
||||||
|
check,
|
||||||
|
)
|
||||||
|
if values:
|
||||||
|
if hasattr(check.target, 'value'):
|
||||||
|
# check value
|
||||||
|
self.check_valid_enum_value(check.target, values)
|
||||||
|
else:
|
||||||
|
# no value, set the first choice has default value
|
||||||
|
new_value = self.objectspace.value(check.xmlfiles)
|
||||||
|
new_value.name = values[0]
|
||||||
|
new_value.type = variable_type
|
||||||
|
check.target.value = [new_value]
|
||||||
|
remove_indexes.append(idx)
|
||||||
|
remove_indexes.sort(reverse=True)
|
||||||
|
for idx in remove_indexes:
|
||||||
|
del self.objectspace.space.constraints.check[idx]
|
||||||
|
|
||||||
|
def _set_valid_enum(self,
|
||||||
|
variable,
|
||||||
|
check,
|
||||||
|
) -> List[Any]:
|
||||||
|
# value for choice's variable is mandatory
|
||||||
|
variable.mandatory = True
|
||||||
|
# build choice
|
||||||
|
variable.choice = []
|
||||||
|
variable_type = variable.type
|
||||||
|
variable.type = 'choice'
|
||||||
|
|
||||||
|
has_variable = False
|
||||||
|
values = []
|
||||||
|
for param in check.param:
|
||||||
|
if has_variable:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
||||||
|
msg = _(f'only one "variable" parameter is allowed for valid_enum '
|
||||||
|
f'of variable "{variable.name}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 5)
|
||||||
|
param_type = variable_type
|
||||||
|
if param.type == 'variable':
|
||||||
|
has_variable = True
|
||||||
|
if param.optional is True:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
||||||
|
msg = _(f'optional parameter in valid_enum for variable "{variable.name}" '
|
||||||
|
f'is not allowed in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 14)
|
||||||
|
param_variable = param.text
|
||||||
|
if not param_variable.multi:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
||||||
|
msg = _(f'only multi "variable" parameter is allowed for valid_enum '
|
||||||
|
f'of variable "{variable.name}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 6)
|
||||||
|
param_type = 'calculation'
|
||||||
|
value = param.text
|
||||||
|
else:
|
||||||
|
if 'type' in vars(param) and variable_type != param.type:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
||||||
|
msg = _(f'parameter in valid_enum has incompatible type "{param.type}" '
|
||||||
|
f'with type of the variable "{variable.name}" ("{variable_type}") '
|
||||||
|
f'in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 7)
|
||||||
|
if hasattr(param, 'text'):
|
||||||
|
try:
|
||||||
|
value = CONVERT_OPTION[variable_type].get('func', str)(param.text)
|
||||||
|
except ValueError as err:
|
||||||
|
msg = _(f'unable to change type of a valid_enum entry "{param.text}" '
|
||||||
|
f'is not a valid "{variable_type}" for "{variable.name}"')
|
||||||
|
raise DictConsistencyError(msg, 13) from err
|
||||||
|
else:
|
||||||
|
if param.type == 'number':
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
||||||
|
msg = _('param type is number, so value is mandatory for valid_enum '
|
||||||
|
f'of variable "{variable.name}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 8)
|
||||||
|
value = None
|
||||||
|
values.append(value)
|
||||||
|
choice = self.objectspace.choice(variable.xmlfiles)
|
||||||
|
choice.name = value
|
||||||
|
choice.type = param_type
|
||||||
|
variable.choice.append(choice)
|
||||||
|
|
||||||
|
if has_variable:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.objectspace.valid_enums[check.target.path] = {'type': variable_type,
|
||||||
|
'values': values,
|
||||||
|
'xmlfiles': check.xmlfiles,
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
|
||||||
|
def check_change_warning(self):
|
||||||
|
"""convert level to "warnings_only"
|
||||||
|
"""
|
||||||
|
for check in self.objectspace.space.constraints.check:
|
||||||
|
check.warnings_only = check.level == 'warning'
|
||||||
|
check.level = None
|
||||||
|
|
||||||
|
def convert_check(self) -> None:
|
||||||
|
"""valid and manage <check>
|
||||||
|
"""
|
||||||
|
for check in self.objectspace.space.constraints.check:
|
||||||
|
if check.name == 'valid_entier':
|
||||||
|
if not hasattr(check, 'param'):
|
||||||
|
msg = _(f'{check.name} must have, at least, 1 param')
|
||||||
|
raise DictConsistencyError(msg, 17)
|
||||||
|
for param in check.param:
|
||||||
|
if param.type != 'number':
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'param in "valid_entier" must be an "integer", not "{param.type}"'
|
||||||
|
f' in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 18)
|
||||||
|
if param.name == 'mini':
|
||||||
|
check.target.min_number = int(param.text)
|
||||||
|
elif param.name == 'maxi':
|
||||||
|
check.target.max_number = int(param.text)
|
||||||
|
else:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
||||||
|
msg = _(f'unknown parameter "{param.name}" in check "valid_entier" '
|
||||||
|
f'for variable "{check.target.name}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 19)
|
||||||
|
else:
|
||||||
|
if not hasattr(check.target, 'check'):
|
||||||
|
check.target.check = []
|
||||||
|
check.target.check.append(check)
|
|
@ -0,0 +1,295 @@
|
||||||
|
"""Annotate condition
|
||||||
|
"""
|
||||||
|
from typing import List, Any
|
||||||
|
|
||||||
|
|
||||||
|
from ..i18n import _
|
||||||
|
from ..error import DictConsistencyError
|
||||||
|
from ..config import Config
|
||||||
|
|
||||||
|
FREEZE_AUTOFREEZE_VARIABLE = 'module_instancie'
|
||||||
|
|
||||||
|
|
||||||
|
class Conditionnnotator:
|
||||||
|
"""Annotate condition
|
||||||
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
objectspace,
|
||||||
|
):
|
||||||
|
self.objectspace = objectspace
|
||||||
|
if hasattr(objectspace.space, 'variables'):
|
||||||
|
self.convert_auto_freeze()
|
||||||
|
if not hasattr(objectspace.space, 'constraints') or \
|
||||||
|
not hasattr(self.objectspace.space.constraints, 'condition'):
|
||||||
|
return
|
||||||
|
self.convert_condition_target()
|
||||||
|
self.convert_xxxlist_to_variable()
|
||||||
|
self.check_condition_fallback()
|
||||||
|
self.convert_condition_source()
|
||||||
|
self.check_choice_option_condition()
|
||||||
|
self.remove_condition_with_empty_target()
|
||||||
|
self.convert_condition()
|
||||||
|
|
||||||
|
def convert_auto_freeze(self):
|
||||||
|
"""convert auto_freeze
|
||||||
|
only if FREEZE_AUTOFREEZE_VARIABLE == 'oui' this variable is frozen
|
||||||
|
"""
|
||||||
|
def _convert_auto_freeze(variable):
|
||||||
|
if not variable.auto_freeze:
|
||||||
|
return
|
||||||
|
if variable.namespace != Config['variable_namespace']:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles)
|
||||||
|
msg = _(f'auto_freeze is not allowed in extra "{variable.namespace}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 49)
|
||||||
|
new_condition = self.objectspace.condition(variable.xmlfiles)
|
||||||
|
new_condition.name = 'auto_frozen_if_not_in'
|
||||||
|
new_condition.namespace = variable.namespace
|
||||||
|
new_condition.source = FREEZE_AUTOFREEZE_VARIABLE
|
||||||
|
new_param = self.objectspace.param(variable.xmlfiles)
|
||||||
|
new_param.text = 'oui'
|
||||||
|
new_condition.param = [new_param]
|
||||||
|
new_target = self.objectspace.target(variable.xmlfiles)
|
||||||
|
new_target.type = 'variable'
|
||||||
|
new_target.name = variable.name
|
||||||
|
new_condition.target = [new_target]
|
||||||
|
if not hasattr(self.objectspace.space, 'constraints'):
|
||||||
|
self.objectspace.space.constraints = self.objectspace.constraints(variable.xmlfiles)
|
||||||
|
if not hasattr(self.objectspace.space.constraints, 'condition'):
|
||||||
|
self.objectspace.space.constraints.condition = []
|
||||||
|
self.objectspace.space.constraints.condition.append(new_condition)
|
||||||
|
for variables in self.objectspace.space.variables.values():
|
||||||
|
for family in variables.family.values():
|
||||||
|
if not hasattr(family, 'variable'):
|
||||||
|
continue
|
||||||
|
for variable in family.variable.values():
|
||||||
|
if isinstance(variable, self.objectspace.leadership):
|
||||||
|
for follower in variable.variable:
|
||||||
|
_convert_auto_freeze(follower)
|
||||||
|
else:
|
||||||
|
_convert_auto_freeze(variable)
|
||||||
|
|
||||||
|
def convert_condition_target(self):
|
||||||
|
"""verify and manage target in condition
|
||||||
|
"""
|
||||||
|
for condition in self.objectspace.space.constraints.condition:
|
||||||
|
if not hasattr(condition, 'target'):
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(condition.xmlfiles)
|
||||||
|
msg = _(f'target is mandatory in a condition for source "{condition.source}" '
|
||||||
|
f'in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 9)
|
||||||
|
remove_targets = []
|
||||||
|
for index, target in enumerate(condition.target):
|
||||||
|
try:
|
||||||
|
if target.type == 'variable':
|
||||||
|
if condition.source == target.name:
|
||||||
|
msg = _('target name and source name must be different: '
|
||||||
|
f'{condition.source}')
|
||||||
|
raise DictConsistencyError(msg, 11)
|
||||||
|
target.name = self.objectspace.paths.get_variable_obj(target.name)
|
||||||
|
elif target.type == 'family':
|
||||||
|
target.name = self.objectspace.paths.get_family(target.name,
|
||||||
|
condition.namespace,
|
||||||
|
)
|
||||||
|
elif target.type.endswith('list') and \
|
||||||
|
condition.name not in ['disabled_if_in', 'disabled_if_not_in']:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(target.xmlfiles)
|
||||||
|
msg = _(f'target "{target.type}" not allow in condition "{condition.name}" '
|
||||||
|
f'in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 10)
|
||||||
|
except DictConsistencyError as err:
|
||||||
|
if err.errno != 42:
|
||||||
|
raise err
|
||||||
|
# for optional variable
|
||||||
|
if not target.optional:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(condition.xmlfiles)
|
||||||
|
msg = f'cannot found target "{target.name}" in the condition in {xmlfiles}'
|
||||||
|
raise DictConsistencyError(_(msg), 12)
|
||||||
|
remove_targets.append(index)
|
||||||
|
remove_targets.sort(reverse=True)
|
||||||
|
for index in remove_targets:
|
||||||
|
condition.target.pop(index)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_xxxlist_to_variable(self):
|
||||||
|
"""transform *list to variable or family
|
||||||
|
"""
|
||||||
|
for condition in self.objectspace.space.constraints.condition:
|
||||||
|
new_targets = []
|
||||||
|
remove_targets = []
|
||||||
|
for target_idx, target in enumerate(condition.target):
|
||||||
|
if target.type.endswith('list'):
|
||||||
|
listname = target.type
|
||||||
|
listvars = self.objectspace.list_conditions.get(listname,
|
||||||
|
{}).get(target.name)
|
||||||
|
if listvars:
|
||||||
|
for listvar in listvars:
|
||||||
|
type_ = 'variable'
|
||||||
|
new_target = self.objectspace.target(listvar.xmlfiles)
|
||||||
|
new_target.type = type_
|
||||||
|
new_target.name = listvar
|
||||||
|
new_targets.append(new_target)
|
||||||
|
remove_targets.append(target_idx)
|
||||||
|
remove_targets.sort(reverse=True)
|
||||||
|
for target_idx in remove_targets:
|
||||||
|
condition.target.pop(target_idx)
|
||||||
|
condition.target.extend(new_targets)
|
||||||
|
|
||||||
|
def check_condition_fallback(self):
|
||||||
|
"""a condition with a fallback **and** the source variable doesn't exist
|
||||||
|
"""
|
||||||
|
remove_conditions = []
|
||||||
|
for idx, condition in enumerate(self.objectspace.space.constraints.condition):
|
||||||
|
# fallback
|
||||||
|
if condition.fallback is True and \
|
||||||
|
not self.objectspace.paths.path_is_defined(condition.source):
|
||||||
|
apply_action = False
|
||||||
|
if condition.name in ['disabled_if_in', 'mandatory_if_in', 'hidden_if_in']:
|
||||||
|
apply_action = not condition.force_condition_on_fallback
|
||||||
|
else:
|
||||||
|
apply_action = condition.force_inverse_condition_on_fallback
|
||||||
|
remove_conditions.append(idx)
|
||||||
|
if apply_action:
|
||||||
|
self.force_actions_to_variable(condition)
|
||||||
|
remove_conditions = list(set(remove_conditions))
|
||||||
|
remove_conditions.sort(reverse=True)
|
||||||
|
for idx in remove_conditions:
|
||||||
|
self.objectspace.space.constraints.condition.pop(idx)
|
||||||
|
|
||||||
|
def force_actions_to_variable(self,
|
||||||
|
condition: 'self.objectspace.condition',
|
||||||
|
) -> None:
|
||||||
|
"""force property to a variable
|
||||||
|
for example disabled_if_not_in => variable.disabled = True
|
||||||
|
"""
|
||||||
|
actions = self.get_actions_from_condition(condition.name)
|
||||||
|
for target in condition.target:
|
||||||
|
leader_or_var, variables = self._get_family_variables_from_target(target)
|
||||||
|
main_action = actions[0]
|
||||||
|
setattr(leader_or_var, main_action, True)
|
||||||
|
for action in actions[1:]:
|
||||||
|
for variable in variables:
|
||||||
|
setattr(variable, action, True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_actions_from_condition(condition_name: str) -> List[str]:
|
||||||
|
"""get action's name from a condition
|
||||||
|
"""
|
||||||
|
if condition_name.startswith('hidden_if_'):
|
||||||
|
return ['hidden', 'frozen', 'force_default_on_freeze']
|
||||||
|
if condition_name == 'auto_frozen_if_not_in':
|
||||||
|
return ['auto_frozen']
|
||||||
|
return [condition_name.split('_', 1)[0]]
|
||||||
|
|
||||||
|
def _get_family_variables_from_target(self,
|
||||||
|
target,
|
||||||
|
):
|
||||||
|
if target.type == 'variable':
|
||||||
|
if not self.objectspace.paths.is_leader(target.name.path):
|
||||||
|
return target.name, [target.name]
|
||||||
|
# it's a leader, so apply property to leadership
|
||||||
|
family_name = self.objectspace.paths.get_variable_family_path(target.name.path)
|
||||||
|
family = self.objectspace.paths.get_family(family_name,
|
||||||
|
target.name.namespace,
|
||||||
|
)
|
||||||
|
return family, family.variable
|
||||||
|
# it's a family
|
||||||
|
variable = self.objectspace.paths.get_family(target.name.path,
|
||||||
|
target.namespace,
|
||||||
|
)
|
||||||
|
return variable, list(variable.variable.values())
|
||||||
|
|
||||||
|
def convert_condition_source(self):
|
||||||
|
"""remove condition for ChoiceOption that don't have param
|
||||||
|
"""
|
||||||
|
for condition in self.objectspace.space.constraints.condition:
|
||||||
|
try:
|
||||||
|
condition.source = self.objectspace.paths.get_variable_obj(condition.source)
|
||||||
|
except DictConsistencyError as err:
|
||||||
|
if err.errno == 36:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(condition.xmlfiles)
|
||||||
|
msg = _(f'the source "{condition.source}" in condition cannot be a dynamic '
|
||||||
|
f'variable in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 20)
|
||||||
|
|
||||||
|
def check_choice_option_condition(self):
|
||||||
|
"""remove condition for ChoiceOption that don't have param
|
||||||
|
"""
|
||||||
|
remove_conditions = []
|
||||||
|
for condition_idx, condition in enumerate(self.objectspace.space.constraints.condition):
|
||||||
|
# FIXME only string?
|
||||||
|
if condition.source.path in self.objectspace.valid_enums and \
|
||||||
|
self.objectspace.valid_enums[condition.source.path]['type'] == 'string':
|
||||||
|
valid_enum = self.objectspace.valid_enums[condition.source.path]['values']
|
||||||
|
remove_param = [param_idx for param_idx, param in enumerate(condition.param) \
|
||||||
|
if param.text not in valid_enum]
|
||||||
|
remove_param.sort(reverse=True)
|
||||||
|
for idx in remove_param:
|
||||||
|
del condition.param[idx]
|
||||||
|
if not condition.param and condition.name.endswith('_if_not_in'):
|
||||||
|
self.force_actions_to_variable(condition)
|
||||||
|
remove_conditions.append(condition_idx)
|
||||||
|
remove_conditions.sort(reverse=True)
|
||||||
|
for idx in remove_conditions:
|
||||||
|
self.objectspace.space.constraints.condition.pop(idx)
|
||||||
|
|
||||||
|
def remove_condition_with_empty_target(self):
|
||||||
|
"""remove condition with empty target
|
||||||
|
"""
|
||||||
|
# optional target are remove, condition could be empty
|
||||||
|
remove_conditions = [condition_idx for condition_idx, condition in \
|
||||||
|
enumerate(self.objectspace.space.constraints.condition) \
|
||||||
|
if not condition.target]
|
||||||
|
remove_conditions.sort(reverse=True)
|
||||||
|
for idx in remove_conditions:
|
||||||
|
self.objectspace.space.constraints.condition.pop(idx)
|
||||||
|
|
||||||
|
def convert_condition(self):
|
||||||
|
"""valid and manage <condition>
|
||||||
|
"""
|
||||||
|
for condition in self.objectspace.space.constraints.condition:
|
||||||
|
actions = self.get_actions_from_condition(condition.name)
|
||||||
|
for param in condition.param:
|
||||||
|
text = getattr(param, 'text', None)
|
||||||
|
for target in condition.target:
|
||||||
|
leader_or_variable, variables = self._get_family_variables_from_target(target)
|
||||||
|
# if option is already disable, do not apply disable_if_in
|
||||||
|
# check only the first action (example of multiple actions:
|
||||||
|
# 'hidden', 'frozen', 'force_default_on_freeze')
|
||||||
|
main_action = actions[0]
|
||||||
|
if getattr(leader_or_variable, main_action, False) is True:
|
||||||
|
continue
|
||||||
|
self.build_property(leader_or_variable,
|
||||||
|
text,
|
||||||
|
condition,
|
||||||
|
main_action,
|
||||||
|
)
|
||||||
|
if isinstance(leader_or_variable, self.objectspace.variable) and \
|
||||||
|
(leader_or_variable.auto_save or leader_or_variable.auto_freeze) and \
|
||||||
|
'force_default_on_freeze' in actions:
|
||||||
|
continue
|
||||||
|
for action in actions[1:]:
|
||||||
|
# other actions are set to the variable or children of family
|
||||||
|
for variable in variables:
|
||||||
|
self.build_property(variable,
|
||||||
|
text,
|
||||||
|
condition,
|
||||||
|
action,
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_property(self,
|
||||||
|
obj,
|
||||||
|
text: Any,
|
||||||
|
condition: 'self.objectspace.condition',
|
||||||
|
action: str,
|
||||||
|
) -> 'self.objectspace.property_':
|
||||||
|
"""build property_ for a condition
|
||||||
|
"""
|
||||||
|
prop = self.objectspace.property_(obj.xmlfiles)
|
||||||
|
prop.type = 'calculation'
|
||||||
|
prop.inverse = condition.name.endswith('_if_not_in')
|
||||||
|
prop.source = condition.source
|
||||||
|
prop.expected = text
|
||||||
|
prop.name = action
|
||||||
|
if not hasattr(obj, 'property'):
|
||||||
|
obj.property = []
|
||||||
|
obj.property.append(prop)
|
|
@ -1,605 +0,0 @@
|
||||||
"""Annotate constraints
|
|
||||||
"""
|
|
||||||
from importlib.machinery import SourceFileLoader
|
|
||||||
from typing import List, Any
|
|
||||||
|
|
||||||
from .variable import CONVERT_OPTION
|
|
||||||
|
|
||||||
from ..i18n import _
|
|
||||||
from ..utils import normalize_family
|
|
||||||
from ..error import DictConsistencyError
|
|
||||||
from ..config import Config
|
|
||||||
|
|
||||||
FREEZE_AUTOFREEZE_VARIABLE = 'module_instancie'
|
|
||||||
|
|
||||||
INTERNAL_FUNCTIONS = ['valid_enum', 'valid_in_network', 'valid_differ', 'valid_entier']
|
|
||||||
|
|
||||||
|
|
||||||
def get_actions_from_condition(condition_name: str) -> List[str]:
|
|
||||||
"""get action's name from a condition
|
|
||||||
"""
|
|
||||||
if condition_name.startswith('hidden_if_'):
|
|
||||||
return ['hidden', 'frozen', 'force_default_on_freeze']
|
|
||||||
if condition_name == 'auto_frozen_if_not_in':
|
|
||||||
return ['auto_frozen']
|
|
||||||
return [condition_name.split('_', 1)[0]]
|
|
||||||
|
|
||||||
|
|
||||||
def check_valid_enum_value(variable,
|
|
||||||
values,
|
|
||||||
) -> None:
|
|
||||||
"""check that values in valid_enum are valid
|
|
||||||
"""
|
|
||||||
for value in variable.value:
|
|
||||||
if value.name not in values:
|
|
||||||
msg = _(f'value "{value.name}" of variable "{variable.name}" is not in list '
|
|
||||||
f'of all expected values ({values})')
|
|
||||||
raise DictConsistencyError(msg, 15)
|
|
||||||
|
|
||||||
|
|
||||||
class ConstrainteAnnotator:
|
|
||||||
"""Annotate constrainte
|
|
||||||
"""
|
|
||||||
def __init__(self,
|
|
||||||
objectspace,
|
|
||||||
eosfunc_file,
|
|
||||||
):
|
|
||||||
self.objectspace = objectspace
|
|
||||||
eosfunc = SourceFileLoader('eosfunc', eosfunc_file).load_module()
|
|
||||||
self.functions = dir(eosfunc)
|
|
||||||
self.functions.extend(INTERNAL_FUNCTIONS)
|
|
||||||
self.valid_enums = {}
|
|
||||||
if hasattr(objectspace.space, 'variables'):
|
|
||||||
self.convert_auto_freeze()
|
|
||||||
if not hasattr(objectspace.space, 'constraints'):
|
|
||||||
return
|
|
||||||
if hasattr(self.objectspace.space.constraints, 'check'):
|
|
||||||
self.check_check()
|
|
||||||
self.check_valid_enum()
|
|
||||||
self.check_change_warning()
|
|
||||||
self.convert_check()
|
|
||||||
if hasattr(self.objectspace.space.constraints, 'condition'):
|
|
||||||
self.convert_condition_target()
|
|
||||||
self.convert_xxxlist_to_variable()
|
|
||||||
self.check_condition_fallback_optional()
|
|
||||||
self.check_choice_option_condition()
|
|
||||||
self.remove_condition_with_empty_target()
|
|
||||||
self.convert_condition()
|
|
||||||
if hasattr(self.objectspace.space.constraints, 'fill'):
|
|
||||||
self.convert_fill()
|
|
||||||
del self.objectspace.space.constraints
|
|
||||||
|
|
||||||
def convert_auto_freeze(self):
|
|
||||||
"""convert auto_freeze
|
|
||||||
only if FREEZE_AUTOFREEZE_VARIABLE == 'oui' this variable is frozen
|
|
||||||
"""
|
|
||||||
def _convert_auto_freeze(variable):
|
|
||||||
if not variable.auto_freeze:
|
|
||||||
return
|
|
||||||
if variable.namespace != Config['variable_namespace']:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles)
|
|
||||||
msg = _(f'auto_freeze is not allowed in extra "{variable.namespace}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 49)
|
|
||||||
new_condition = self.objectspace.condition(variable.xmlfiles)
|
|
||||||
new_condition.name = 'auto_frozen_if_not_in'
|
|
||||||
new_condition.namespace = variable.namespace
|
|
||||||
new_condition.source = FREEZE_AUTOFREEZE_VARIABLE
|
|
||||||
new_param = self.objectspace.param(variable.xmlfiles)
|
|
||||||
new_param.text = 'oui'
|
|
||||||
new_condition.param = [new_param]
|
|
||||||
new_target = self.objectspace.target(variable.xmlfiles)
|
|
||||||
new_target.type = 'variable'
|
|
||||||
new_target.name = variable.name
|
|
||||||
new_condition.target = [new_target]
|
|
||||||
if not hasattr(self.objectspace.space, 'constraints'):
|
|
||||||
self.objectspace.space.constraints = self.objectspace.constraints(variable.xmlfiles)
|
|
||||||
if not hasattr(self.objectspace.space.constraints, 'condition'):
|
|
||||||
self.objectspace.space.constraints.condition = []
|
|
||||||
self.objectspace.space.constraints.condition.append(new_condition)
|
|
||||||
for variables in self.objectspace.space.variables.values():
|
|
||||||
for family in variables.family.values():
|
|
||||||
if not hasattr(family, 'variable'):
|
|
||||||
continue
|
|
||||||
for variable in family.variable.values():
|
|
||||||
if isinstance(variable, self.objectspace.leadership):
|
|
||||||
for follower in variable.variable:
|
|
||||||
_convert_auto_freeze(follower)
|
|
||||||
else:
|
|
||||||
_convert_auto_freeze(variable)
|
|
||||||
|
|
||||||
def check_check(self):
|
|
||||||
"""valid and manage <check>
|
|
||||||
"""
|
|
||||||
remove_indexes = []
|
|
||||||
for check_idx, check in enumerate(self.objectspace.space.constraints.check):
|
|
||||||
if not check.name in self.functions:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
|
||||||
msg = _(f'cannot find check function "{check.name}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 1)
|
|
||||||
if not hasattr(check, 'param'):
|
|
||||||
continue
|
|
||||||
param_option_indexes = []
|
|
||||||
for idx, param in enumerate(check.param):
|
|
||||||
if param.type == 'variable':
|
|
||||||
if not self.objectspace.paths.path_is_defined(param.text):
|
|
||||||
if not param.optional:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
|
||||||
msg = _(f'cannot find check param "{param.text}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 2)
|
|
||||||
param_option_indexes.append(idx)
|
|
||||||
else:
|
|
||||||
# let's replace params by the path
|
|
||||||
param.text = self.objectspace.paths.get_variable_path(param.text,
|
|
||||||
check.namespace,
|
|
||||||
)
|
|
||||||
param_option_indexes.sort(reverse=True)
|
|
||||||
for idx in param_option_indexes:
|
|
||||||
check.param.pop(idx)
|
|
||||||
if check.param == []:
|
|
||||||
remove_indexes.append(check_idx)
|
|
||||||
continue
|
|
||||||
# let's replace the target by the path
|
|
||||||
check.target = self.objectspace.paths.get_variable_path(check.target,
|
|
||||||
check.namespace,
|
|
||||||
)
|
|
||||||
check.is_in_leadership = self.objectspace.paths.get_leader(check.target) is not None
|
|
||||||
remove_indexes.sort(reverse=True)
|
|
||||||
for idx in remove_indexes:
|
|
||||||
del self.objectspace.space.constraints.check[idx]
|
|
||||||
|
|
||||||
def check_valid_enum(self):
|
|
||||||
"""verify valid_enum
|
|
||||||
"""
|
|
||||||
remove_indexes = []
|
|
||||||
for idx, check in enumerate(self.objectspace.space.constraints.check):
|
|
||||||
if check.name == 'valid_enum':
|
|
||||||
if check.target in self.valid_enums:
|
|
||||||
check_xmlfiles = self.valid_enums[check.target]['xmlfiles']
|
|
||||||
old_xmlfiles = self.objectspace.display_xmlfiles(check_xmlfiles)
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
|
||||||
msg = _(f'valid_enum define in {xmlfiles} but already set in {old_xmlfiles} '
|
|
||||||
f'for "{check.target}", did you forget remove_check?')
|
|
||||||
raise DictConsistencyError(msg, 3)
|
|
||||||
if not hasattr(check, 'param'):
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
|
||||||
msg = _(f'param is mandatory for a valid_enum of variable "{check.target}" '
|
|
||||||
f'in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 4)
|
|
||||||
variable = self.objectspace.paths.get_variable_obj(check.target)
|
|
||||||
variable_type = variable.type
|
|
||||||
values = self._set_valid_enum(variable,
|
|
||||||
check,
|
|
||||||
)
|
|
||||||
if values:
|
|
||||||
if hasattr(variable, 'value'):
|
|
||||||
# check value
|
|
||||||
check_valid_enum_value(variable, values)
|
|
||||||
else:
|
|
||||||
# no value, set the first choice has default value
|
|
||||||
new_value = self.objectspace.value(check.xmlfiles)
|
|
||||||
new_value.name = values[0]
|
|
||||||
new_value.type = variable_type
|
|
||||||
variable.value = [new_value]
|
|
||||||
remove_indexes.append(idx)
|
|
||||||
remove_indexes.sort(reverse=True)
|
|
||||||
for idx in remove_indexes:
|
|
||||||
del self.objectspace.space.constraints.check[idx]
|
|
||||||
|
|
||||||
def _set_valid_enum(self,
|
|
||||||
variable,
|
|
||||||
check,
|
|
||||||
) -> List[Any]:
|
|
||||||
# value for choice's variable is mandatory
|
|
||||||
variable.mandatory = True
|
|
||||||
# build choice
|
|
||||||
variable.choice = []
|
|
||||||
variable_type = variable.type
|
|
||||||
variable.type = 'choice'
|
|
||||||
|
|
||||||
has_variable = False
|
|
||||||
values = []
|
|
||||||
for param in check.param:
|
|
||||||
if has_variable:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
|
||||||
msg = _(f'only one "variable" parameter is allowed for valid_enum '
|
|
||||||
f'of variable "{variable.name}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 5)
|
|
||||||
param_type = variable_type
|
|
||||||
if param.type == 'variable':
|
|
||||||
has_variable = True
|
|
||||||
if param.optional is True:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
|
||||||
msg = _(f'optional parameter in valid_enum for variable "{variable.name}" '
|
|
||||||
f'is not allowed in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 14)
|
|
||||||
param_variable = self.objectspace.paths.get_variable_obj(param.text)
|
|
||||||
if not param_variable.multi:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
|
||||||
msg = _(f'only multi "variable" parameter is allowed for valid_enum '
|
|
||||||
f'of variable "{variable.name}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 6)
|
|
||||||
param_type = 'calculation'
|
|
||||||
value = param.text
|
|
||||||
else:
|
|
||||||
if 'type' in vars(param) and variable_type != param.type:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
|
||||||
msg = _(f'parameter in valid_enum has incompatible type "{param.type}" '
|
|
||||||
f'with type of the variable "{variable.name}" ("{variable_type}") '
|
|
||||||
f'in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 7)
|
|
||||||
if hasattr(param, 'text'):
|
|
||||||
try:
|
|
||||||
value = CONVERT_OPTION[variable_type].get('func', str)(param.text)
|
|
||||||
except ValueError as err:
|
|
||||||
msg = _(f'unable to change type of a valid_enum entry "{param.text}" '
|
|
||||||
f'is not a valid "{variable_type}" for "{variable.name}"')
|
|
||||||
raise DictConsistencyError(msg, 13) from err
|
|
||||||
else:
|
|
||||||
if param.type == 'number':
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles)
|
|
||||||
msg = _('param type is number, so value is mandatory for valid_enum '
|
|
||||||
f'of variable "{variable.name}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 8)
|
|
||||||
value = None
|
|
||||||
values.append(value)
|
|
||||||
choice = self.objectspace.choice(variable.xmlfiles)
|
|
||||||
choice.name = value
|
|
||||||
choice.type = param_type
|
|
||||||
variable.choice.append(choice)
|
|
||||||
|
|
||||||
if has_variable:
|
|
||||||
return None
|
|
||||||
|
|
||||||
self.valid_enums[check.target] = {'type': variable_type,
|
|
||||||
'values': values,
|
|
||||||
'xmlfiles': check.xmlfiles,
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
|
|
||||||
def check_change_warning(self):
|
|
||||||
"""convert level to "warnings_only"
|
|
||||||
"""
|
|
||||||
for check in self.objectspace.space.constraints.check:
|
|
||||||
check.warnings_only = check.level == 'warning'
|
|
||||||
check.level = None
|
|
||||||
|
|
||||||
def _get_family_variables_from_target(self,
|
|
||||||
target,
|
|
||||||
):
|
|
||||||
if target.type == 'variable':
|
|
||||||
variable = self.objectspace.paths.get_variable_obj(target.name)
|
|
||||||
family = self.objectspace.paths.get_family(target.name.rsplit('.', 1)[0],
|
|
||||||
variable.namespace,
|
|
||||||
)
|
|
||||||
# it's a leader, so apply property to leadership
|
|
||||||
if isinstance(family, self.objectspace.leadership) and family.variable[0].name == variable.name:
|
|
||||||
return family, family.variable
|
|
||||||
return variable, [variable]
|
|
||||||
# it's a family
|
|
||||||
variable = self.objectspace.paths.get_family(target.name,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
return variable, list(variable.variable.values())
|
|
||||||
|
|
||||||
def convert_condition_target(self):
|
|
||||||
"""verify and manage target in condition
|
|
||||||
"""
|
|
||||||
for condition in self.objectspace.space.constraints.condition:
|
|
||||||
if not hasattr(condition, 'target'):
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(condition.xmlfiles)
|
|
||||||
msg = _(f'target is mandatory in a condition for source "{condition.source}" '
|
|
||||||
f'in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 9)
|
|
||||||
for target in condition.target:
|
|
||||||
if target.type == 'variable':
|
|
||||||
if condition.source == target.name:
|
|
||||||
msg = f'target name and source name must be different: {condition.source}'
|
|
||||||
raise DictConsistencyError(_(msg), 11)
|
|
||||||
target_names = '.'.join([normalize_family(name) \
|
|
||||||
for name in target.name.split('.')])
|
|
||||||
try:
|
|
||||||
target.name = self.objectspace.paths.get_variable_path(target_names,
|
|
||||||
condition.namespace,
|
|
||||||
)
|
|
||||||
except DictConsistencyError as err:
|
|
||||||
# for optional variable
|
|
||||||
if not target.optional or err.errno != 42:
|
|
||||||
raise err
|
|
||||||
elif target.type == 'family':
|
|
||||||
target_path = '.'.join([normalize_family(name) \
|
|
||||||
for name in target.name.split('.')])
|
|
||||||
if not self.objectspace.paths.family_is_defined(target_path,
|
|
||||||
condition.namespace,
|
|
||||||
):
|
|
||||||
raise DictConsistencyError(_(f'cannot found family {target.name}'), 12)
|
|
||||||
target.name = target_path
|
|
||||||
elif target.type.endswith('list') and \
|
|
||||||
condition.name not in ['disabled_if_in', 'disabled_if_not_in']:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(target.xmlfiles)
|
|
||||||
msg = _(f'target "{target.type}" not allow in condition "{condition.name}" '
|
|
||||||
f'in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 10)
|
|
||||||
|
|
||||||
def convert_xxxlist_to_variable(self):
|
|
||||||
"""transform *list to variable or family
|
|
||||||
"""
|
|
||||||
for condition in self.objectspace.space.constraints.condition:
|
|
||||||
new_targets = []
|
|
||||||
remove_targets = []
|
|
||||||
for target_idx, target in enumerate(condition.target):
|
|
||||||
if target.type.endswith('list'):
|
|
||||||
listname = target.type
|
|
||||||
listvars = self.objectspace.list_conditions.get(listname,
|
|
||||||
{}).get(target.name)
|
|
||||||
if listvars:
|
|
||||||
for listvar in listvars:
|
|
||||||
variable = self.objectspace.paths.get_variable_obj(listvar)
|
|
||||||
type_ = 'variable'
|
|
||||||
new_target = self.objectspace.target(variable.xmlfiles)
|
|
||||||
new_target.type = type_
|
|
||||||
new_target.name = listvar
|
|
||||||
new_targets.append(new_target)
|
|
||||||
remove_targets.append(target_idx)
|
|
||||||
remove_targets.sort(reverse=True)
|
|
||||||
for target_idx in remove_targets:
|
|
||||||
condition.target.pop(target_idx)
|
|
||||||
condition.target.extend(new_targets)
|
|
||||||
|
|
||||||
def check_condition_fallback_optional(self):
|
|
||||||
"""a condition with a fallback **and** the source variable doesn't exist
|
|
||||||
"""
|
|
||||||
remove_conditions = []
|
|
||||||
for idx, condition in enumerate(self.objectspace.space.constraints.condition):
|
|
||||||
# fallback
|
|
||||||
if condition.fallback is True and \
|
|
||||||
not self.objectspace.paths.path_is_defined(condition.source):
|
|
||||||
apply_action = False
|
|
||||||
if condition.name in ['disabled_if_in', 'mandatory_if_in', 'hidden_if_in']:
|
|
||||||
apply_action = not condition.force_condition_on_fallback
|
|
||||||
else:
|
|
||||||
apply_action = condition.force_inverse_condition_on_fallback
|
|
||||||
remove_conditions.append(idx)
|
|
||||||
if apply_action:
|
|
||||||
self.force_actions_to_variable(condition)
|
|
||||||
continue
|
|
||||||
|
|
||||||
remove_targets = []
|
|
||||||
# optional
|
|
||||||
for index, target in enumerate(condition.target):
|
|
||||||
if target.optional is True and \
|
|
||||||
not self.objectspace.paths.path_is_defined(target.name):
|
|
||||||
remove_targets.append(index)
|
|
||||||
remove_targets = list(set(remove_targets))
|
|
||||||
remove_targets.sort(reverse=True)
|
|
||||||
for index in remove_targets:
|
|
||||||
condition.target.pop(index)
|
|
||||||
remove_conditions = list(set(remove_conditions))
|
|
||||||
remove_conditions.sort(reverse=True)
|
|
||||||
for idx in remove_conditions:
|
|
||||||
self.objectspace.space.constraints.condition.pop(idx)
|
|
||||||
|
|
||||||
def force_actions_to_variable(self,
|
|
||||||
condition: 'self.objectspace.condition',
|
|
||||||
) -> None:
|
|
||||||
"""force property to a variable
|
|
||||||
for example disabled_if_not_in => variable.disabled = True
|
|
||||||
"""
|
|
||||||
actions = get_actions_from_condition(condition.name)
|
|
||||||
for target in condition.target:
|
|
||||||
leader_or_var, variables = self._get_family_variables_from_target(target)
|
|
||||||
main_action = actions[0]
|
|
||||||
setattr(leader_or_var, main_action, True)
|
|
||||||
for action in actions[1:]:
|
|
||||||
for variable in variables:
|
|
||||||
setattr(variable, action, True)
|
|
||||||
|
|
||||||
def check_choice_option_condition(self):
|
|
||||||
"""remove condition for ChoiceOption that don't have param
|
|
||||||
"""
|
|
||||||
remove_conditions = []
|
|
||||||
for condition_idx, condition in enumerate(self.objectspace.space.constraints.condition):
|
|
||||||
namespace = condition.namespace
|
|
||||||
condition.source, suffix = self.objectspace.paths.get_variable_path(condition.source,
|
|
||||||
namespace,
|
|
||||||
allow_source=True,
|
|
||||||
with_suffix=True,
|
|
||||||
)
|
|
||||||
if suffix:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(condition.xmlfiles)
|
|
||||||
msg = _(f'the source "{condition.source}" in condition cannot be a dynamic '
|
|
||||||
f'variable in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 20)
|
|
||||||
# FIXME only string?
|
|
||||||
if condition.source in self.valid_enums and \
|
|
||||||
self.valid_enums[condition.source]['type'] == 'string':
|
|
||||||
valid_enum = self.valid_enums[condition.source]['values']
|
|
||||||
remove_param = [param_idx for param_idx, param in enumerate(condition.param) \
|
|
||||||
if param.text not in valid_enum]
|
|
||||||
remove_param.sort(reverse=True)
|
|
||||||
for idx in remove_param:
|
|
||||||
del condition.param[idx]
|
|
||||||
if not condition.param and condition.name.endswith('_if_not_in'):
|
|
||||||
self.force_actions_to_variable(condition)
|
|
||||||
remove_conditions.append(condition_idx)
|
|
||||||
remove_conditions.sort(reverse=True)
|
|
||||||
for idx in remove_conditions:
|
|
||||||
self.objectspace.space.constraints.condition.pop(idx)
|
|
||||||
|
|
||||||
def remove_condition_with_empty_target(self):
|
|
||||||
"""remove condition with empty target
|
|
||||||
"""
|
|
||||||
# optional target are remove, condition could be empty
|
|
||||||
remove_conditions = [condition_idx for condition_idx, condition in \
|
|
||||||
enumerate(self.objectspace.space.constraints.condition) \
|
|
||||||
if not condition.target]
|
|
||||||
remove_conditions.sort(reverse=True)
|
|
||||||
for idx in remove_conditions:
|
|
||||||
self.objectspace.space.constraints.condition.pop(idx)
|
|
||||||
|
|
||||||
def convert_condition(self):
|
|
||||||
"""valid and manage <condition>
|
|
||||||
"""
|
|
||||||
for condition in self.objectspace.space.constraints.condition:
|
|
||||||
actions = get_actions_from_condition(condition.name)
|
|
||||||
for param in condition.param:
|
|
||||||
text = getattr(param, 'text', None)
|
|
||||||
for target in condition.target:
|
|
||||||
leader_or_variable, variables = self._get_family_variables_from_target(target)
|
|
||||||
# if option is already disable, do not apply disable_if_in
|
|
||||||
# check only the first action (example of multiple actions:
|
|
||||||
# 'hidden', 'frozen', 'force_default_on_freeze')
|
|
||||||
main_action = actions[0]
|
|
||||||
if getattr(leader_or_variable, main_action, False) is True:
|
|
||||||
continue
|
|
||||||
self.build_property(leader_or_variable,
|
|
||||||
text,
|
|
||||||
condition,
|
|
||||||
main_action,
|
|
||||||
)
|
|
||||||
if isinstance(leader_or_variable, self.objectspace.variable) and \
|
|
||||||
(leader_or_variable.auto_save or leader_or_variable.auto_freeze) and \
|
|
||||||
'force_default_on_freeze' in actions:
|
|
||||||
continue
|
|
||||||
for action in actions[1:]:
|
|
||||||
# other actions are set to the variable or children of family
|
|
||||||
for variable in variables:
|
|
||||||
self.build_property(variable,
|
|
||||||
text,
|
|
||||||
condition,
|
|
||||||
action,
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_property(self,
|
|
||||||
obj,
|
|
||||||
text: Any,
|
|
||||||
condition: 'self.objectspace.condition',
|
|
||||||
action: str,
|
|
||||||
) -> 'self.objectspace.property_':
|
|
||||||
"""build property_ for a condition
|
|
||||||
"""
|
|
||||||
prop = self.objectspace.property_(obj.xmlfiles)
|
|
||||||
prop.type = 'calculation'
|
|
||||||
prop.inverse = condition.name.endswith('_if_not_in')
|
|
||||||
prop.source = condition.source
|
|
||||||
prop.expected = text
|
|
||||||
prop.name = action
|
|
||||||
if not hasattr(obj, 'property'):
|
|
||||||
obj.property = []
|
|
||||||
obj.property.append(prop)
|
|
||||||
|
|
||||||
def convert_check(self) -> None:
|
|
||||||
"""valid and manage <check>
|
|
||||||
"""
|
|
||||||
for check in self.objectspace.space.constraints.check:
|
|
||||||
variable = self.objectspace.paths.get_variable_obj(check.target)
|
|
||||||
if check.name == 'valid_entier':
|
|
||||||
if not hasattr(check, 'param'):
|
|
||||||
msg = _(f'{check.name} must have, at least, 1 param')
|
|
||||||
raise DictConsistencyError(msg, 17)
|
|
||||||
for param in check.param:
|
|
||||||
if param.type != 'number':
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
|
||||||
msg = _(f'param in "valid_entier" must be an "integer", not "{param.type}"'
|
|
||||||
f' in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 18)
|
|
||||||
if param.name == 'mini':
|
|
||||||
variable.min_number = int(param.text)
|
|
||||||
elif param.name == 'maxi':
|
|
||||||
variable.max_number = int(param.text)
|
|
||||||
else:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles)
|
|
||||||
msg = _(f'unknown parameter "{param.name}" in check "valid_entier" '
|
|
||||||
f'for variable "{check.target}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 19)
|
|
||||||
else:
|
|
||||||
if not hasattr(variable, 'check'):
|
|
||||||
variable.check = []
|
|
||||||
variable.check.append(check)
|
|
||||||
|
|
||||||
def convert_fill(self) -> None:
|
|
||||||
"""valid and manage <fill>
|
|
||||||
"""
|
|
||||||
targets = []
|
|
||||||
for fill in self.objectspace.space.constraints.fill:
|
|
||||||
# test if it's redefined calculation
|
|
||||||
if fill.target in targets:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
|
||||||
msg = _(f'A fill already exists for the target of "{fill.target}" created '
|
|
||||||
f'in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 24)
|
|
||||||
targets.append(fill.target)
|
|
||||||
|
|
||||||
# test if the function exists
|
|
||||||
if fill.name not in self.functions:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
|
||||||
msg = _(f'cannot find fill function "{fill.name}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 25)
|
|
||||||
|
|
||||||
# let's replace the target by the path
|
|
||||||
fill.target, suffix = self.objectspace.paths.get_variable_path(fill.target,
|
|
||||||
fill.namespace,
|
|
||||||
with_suffix=True,
|
|
||||||
)
|
|
||||||
if suffix is not None:
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
|
||||||
msg = _(f'Cannot add fill function to "{fill.target}" only '
|
|
||||||
f'for the suffix "{suffix}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 26)
|
|
||||||
|
|
||||||
# get the target variable
|
|
||||||
variable = self.objectspace.paths.get_variable_obj(fill.target)
|
|
||||||
|
|
||||||
# create an object value
|
|
||||||
value = self.objectspace.value(fill.xmlfiles)
|
|
||||||
value.type = 'calculation'
|
|
||||||
value.name = fill.name
|
|
||||||
variable.value = [value]
|
|
||||||
|
|
||||||
# manage params
|
|
||||||
if not hasattr(fill, 'param'):
|
|
||||||
continue
|
|
||||||
self.convert_fill_param(fill)
|
|
||||||
if fill.param:
|
|
||||||
value.param = fill.param
|
|
||||||
|
|
||||||
def convert_fill_param(self,
|
|
||||||
fill: "self.objectspace.fill",
|
|
||||||
) -> None:
|
|
||||||
""" valid and convert fill's param
|
|
||||||
"""
|
|
||||||
param_to_delete = []
|
|
||||||
for param_idx, param in enumerate(fill.param):
|
|
||||||
if param.type == 'string' and not hasattr(param, 'text'):
|
|
||||||
param.text = None
|
|
||||||
if param.type == 'suffix':
|
|
||||||
if hasattr(param, 'text'):
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
|
||||||
msg = _(f'"{param.type}" variables must not have a value in order '
|
|
||||||
f'to calculate "{fill.target}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 28)
|
|
||||||
if not self.objectspace.paths.variable_is_dynamic(fill.target):
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
|
||||||
msg = _('Cannot set suffix target to the none dynamic variable '
|
|
||||||
f'"{fill.target}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 53)
|
|
||||||
elif not hasattr(param, 'text'):
|
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
|
||||||
msg = _(f'All "{param.type}" variables must have a value in order '
|
|
||||||
f'to calculate "{fill.target}" in {xmlfiles}')
|
|
||||||
raise DictConsistencyError(msg, 27)
|
|
||||||
if param.type == 'variable':
|
|
||||||
try:
|
|
||||||
text, suffix = self.objectspace.paths.get_variable_path(param.text,
|
|
||||||
fill.namespace,
|
|
||||||
with_suffix=True,
|
|
||||||
)
|
|
||||||
param.text = text
|
|
||||||
if suffix:
|
|
||||||
param.suffix = suffix
|
|
||||||
except DictConsistencyError as err:
|
|
||||||
if err.errno != 42 or not param.optional:
|
|
||||||
raise err
|
|
||||||
param_to_delete.append(param_idx)
|
|
||||||
param_to_delete.sort(reverse=True)
|
|
||||||
for param_idx in param_to_delete:
|
|
||||||
fill.param.pop(param_idx)
|
|
|
@ -169,10 +169,9 @@ class FamilyAnnotator:
|
||||||
for family in families.family.values():
|
for family in families.family.values():
|
||||||
if 'dynamic' not in vars(family):
|
if 'dynamic' not in vars(family):
|
||||||
continue
|
continue
|
||||||
obj = self.objectspace.paths.get_variable_obj(family.dynamic)
|
family.dynamic = self.objectspace.paths.get_variable_obj(family.dynamic)
|
||||||
if not obj.multi:
|
if not family.dynamic.multi:
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(family.xmlfiles)
|
xmlfiles = self.objectspace.display_xmlfiles(family.xmlfiles)
|
||||||
msg = _(f'dynamic family "{family.name}" must be linked '
|
msg = _(f'dynamic family "{family.name}" must be linked '
|
||||||
f'to multi variable in {xmlfiles}')
|
f'to multi variable in {xmlfiles}')
|
||||||
raise DictConsistencyError(msg, 16)
|
raise DictConsistencyError(msg, 16)
|
||||||
family.dynamic = obj.path
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
"""Fill annotator
|
||||||
|
"""
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
|
|
||||||
|
from ..i18n import _
|
||||||
|
|
||||||
|
from ..error import DictConsistencyError
|
||||||
|
|
||||||
|
|
||||||
|
class FillAnnotator:
|
||||||
|
"""Fill annotator
|
||||||
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
objectspace,
|
||||||
|
eosfunc_file,
|
||||||
|
):
|
||||||
|
self.objectspace = objectspace
|
||||||
|
if not hasattr(objectspace.space, 'constraints') or \
|
||||||
|
not hasattr(self.objectspace.space.constraints, 'fill'):
|
||||||
|
return
|
||||||
|
eosfunc = SourceFileLoader('eosfunc', eosfunc_file).load_module()
|
||||||
|
self.functions = dir(eosfunc)
|
||||||
|
self.convert_fill()
|
||||||
|
|
||||||
|
def convert_fill(self) -> None:
|
||||||
|
"""valid and manage <fill>
|
||||||
|
"""
|
||||||
|
targets = []
|
||||||
|
for fill in self.objectspace.space.constraints.fill:
|
||||||
|
# test if it's redefined calculation
|
||||||
|
if fill.target in targets:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
||||||
|
msg = _(f'A fill already exists for the target of "{fill.target}" created '
|
||||||
|
f'in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 24)
|
||||||
|
targets.append(fill.target)
|
||||||
|
|
||||||
|
# test if the function exists
|
||||||
|
if fill.name not in self.functions:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
||||||
|
msg = _(f'cannot find fill function "{fill.name}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 25)
|
||||||
|
|
||||||
|
# let's replace the target by the path
|
||||||
|
fill.target, suffix = self.objectspace.paths.get_variable_path(fill.target,
|
||||||
|
fill.namespace,
|
||||||
|
)
|
||||||
|
if suffix is not None:
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
||||||
|
msg = _(f'Cannot add fill function to "{fill.target}" only '
|
||||||
|
f'for the suffix "{suffix}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 26)
|
||||||
|
|
||||||
|
# get the target variable
|
||||||
|
variable = self.objectspace.paths.get_variable_obj(fill.target)
|
||||||
|
|
||||||
|
# create an object value
|
||||||
|
value = self.objectspace.value(fill.xmlfiles)
|
||||||
|
value.type = 'calculation'
|
||||||
|
value.name = fill.name
|
||||||
|
variable.value = [value]
|
||||||
|
|
||||||
|
# manage params
|
||||||
|
if not hasattr(fill, 'param'):
|
||||||
|
continue
|
||||||
|
self.convert_fill_param(fill)
|
||||||
|
if fill.param:
|
||||||
|
value.param = fill.param
|
||||||
|
|
||||||
|
def convert_fill_param(self,
|
||||||
|
fill: "self.objectspace.fill",
|
||||||
|
) -> None:
|
||||||
|
""" valid and convert fill's param
|
||||||
|
"""
|
||||||
|
param_to_delete = []
|
||||||
|
for param_idx, param in enumerate(fill.param):
|
||||||
|
if param.type == 'string' and not hasattr(param, 'text'):
|
||||||
|
param.text = None
|
||||||
|
if param.type == 'suffix':
|
||||||
|
if hasattr(param, 'text'):
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
||||||
|
msg = _(f'"{param.type}" variables must not have a value in order '
|
||||||
|
f'to calculate "{fill.target}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 28)
|
||||||
|
if not self.objectspace.paths.variable_is_dynamic(fill.target):
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
||||||
|
msg = _('Cannot set suffix target to the none dynamic variable '
|
||||||
|
f'"{fill.target}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 53)
|
||||||
|
elif not hasattr(param, 'text'):
|
||||||
|
xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles)
|
||||||
|
msg = _(f'All "{param.type}" variables must have a value in order '
|
||||||
|
f'to calculate "{fill.target}" in {xmlfiles}')
|
||||||
|
raise DictConsistencyError(msg, 27)
|
||||||
|
if param.type == 'variable':
|
||||||
|
try:
|
||||||
|
path, suffix = self.objectspace.paths.get_variable_path(param.text,
|
||||||
|
fill.namespace,
|
||||||
|
)
|
||||||
|
param.text = self.objectspace.paths.get_variable_obj(path)
|
||||||
|
if suffix:
|
||||||
|
param.suffix = suffix
|
||||||
|
except DictConsistencyError as err:
|
||||||
|
if err.errno != 42 or not param.optional:
|
||||||
|
raise err
|
||||||
|
param_to_delete.append(param_idx)
|
||||||
|
param_to_delete.sort(reverse=True)
|
||||||
|
for param_idx in param_to_delete:
|
||||||
|
fill.param.pop(param_idx)
|
|
@ -22,22 +22,26 @@ class GroupAnnotator:
|
||||||
def convert_groups(self): # pylint: disable=C0111
|
def convert_groups(self): # pylint: disable=C0111
|
||||||
"""convert groups
|
"""convert groups
|
||||||
"""
|
"""
|
||||||
|
cache_paths = {}
|
||||||
for group in self.objectspace.space.constraints.group:
|
for group in self.objectspace.space.constraints.group:
|
||||||
leader_fullname = group.leader
|
leader_fullname = group.leader
|
||||||
leader = self.objectspace.paths.get_variable_obj(leader_fullname)
|
leader = self.objectspace.paths.get_variable_obj(leader_fullname)
|
||||||
leader_family_name = self.objectspace.paths.get_variable_family_name(leader_fullname)
|
if leader_fullname in cache_paths:
|
||||||
|
leader_family_path = cache_paths[leader_fullname]
|
||||||
|
else:
|
||||||
|
leader_family_path = self.objectspace.paths.get_variable_family_path(leader_fullname)
|
||||||
|
cache_paths[leader_fullname] = leader_family_path
|
||||||
if '.' not in leader_fullname:
|
if '.' not in leader_fullname:
|
||||||
leader_fullname = '.'.join([leader.namespace, leader_family_name, leader_fullname])
|
leader_fullname = '.'.join([leader_family_path, leader_fullname])
|
||||||
follower_names = list(group.follower.keys())
|
follower_names = list(group.follower.keys())
|
||||||
leader_family = leader_fullname.rsplit('.', 1)[0]
|
ori_leader_family = self.objectspace.paths.get_family(leader_family_path,
|
||||||
ori_leader_family = self.objectspace.paths.get_family(leader_family,
|
|
||||||
leader.namespace,
|
leader.namespace,
|
||||||
)
|
)
|
||||||
has_a_leader = False
|
has_a_leader = False
|
||||||
for variable in list(ori_leader_family.variable.values()):
|
for variable in list(ori_leader_family.variable.values()):
|
||||||
if has_a_leader:
|
if has_a_leader:
|
||||||
# it's a follower
|
# it's a follower
|
||||||
self.manage_follower(leader_family_name,
|
self.manage_follower(leader_family_path,
|
||||||
variable,
|
variable,
|
||||||
leadership_name,
|
leadership_name,
|
||||||
follower_names,
|
follower_names,
|
||||||
|
@ -64,13 +68,12 @@ class GroupAnnotator:
|
||||||
else:
|
else:
|
||||||
leadership_name = leader.name
|
leadership_name = leader.name
|
||||||
leader_is_hidden = self.manage_leader(leader_space,
|
leader_is_hidden = self.manage_leader(leader_space,
|
||||||
leader_family_name,
|
leader_family_path,
|
||||||
leadership_name,
|
leadership_name,
|
||||||
leader.name,
|
leader.name,
|
||||||
variable,
|
variable,
|
||||||
group,
|
group,
|
||||||
)
|
)
|
||||||
leader_space.path = leader_fullname
|
|
||||||
has_a_leader = True
|
has_a_leader = True
|
||||||
else:
|
else:
|
||||||
xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles)
|
xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles)
|
||||||
|
@ -111,25 +114,25 @@ class GroupAnnotator:
|
||||||
else:
|
else:
|
||||||
leader_space.doc = leadership_name
|
leader_space.doc = leadership_name
|
||||||
namespace = variable.namespace
|
namespace = variable.namespace
|
||||||
leadership_path = namespace + '.' + leader_family_name + '.' + leadership_name
|
leadership_path = leader_family_name + '.' + leadership_name
|
||||||
self.objectspace.paths.add_leadership(namespace,
|
self.objectspace.paths.add_leadership(namespace,
|
||||||
leadership_path,
|
leadership_path,
|
||||||
leader_space,
|
leader_space,
|
||||||
)
|
)
|
||||||
leader_family = self.objectspace.space.variables[namespace].family[leader_family_name]
|
leader_family = self.objectspace.space.variables[namespace].family[leader_family_name.rsplit('.', 1)[-1]]
|
||||||
leader_family.variable[leader_name] = leader_space
|
leader_family.variable[leader_name] = leader_space
|
||||||
leader_space.variable.append(variable)
|
leader_space.variable.append(variable)
|
||||||
self.objectspace.paths.set_leader(namespace,
|
self.objectspace.paths.set_leader(namespace,
|
||||||
leader_family_name,
|
leader_family_name,
|
||||||
leader_name,
|
|
||||||
leadership_name,
|
leadership_name,
|
||||||
|
leader_name,
|
||||||
)
|
)
|
||||||
return leader_is_hidden
|
return leader_is_hidden
|
||||||
|
|
||||||
def manage_follower(self,
|
def manage_follower(self,
|
||||||
leader_family_name: str,
|
leader_family_name: str,
|
||||||
variable: 'Variable',
|
variable: 'Variable',
|
||||||
leader_name: str,
|
leadership_name: str,
|
||||||
follower_names: List[str],
|
follower_names: List[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""manage follower
|
"""manage follower
|
||||||
|
@ -142,6 +145,6 @@ class GroupAnnotator:
|
||||||
raise DictConsistencyError(msg, 33)
|
raise DictConsistencyError(msg, 33)
|
||||||
self.objectspace.paths.set_leader(variable.namespace,
|
self.objectspace.paths.set_leader(variable.namespace,
|
||||||
leader_family_name,
|
leader_family_name,
|
||||||
|
leadership_name,
|
||||||
variable.name,
|
variable.name,
|
||||||
leader_name,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -84,6 +84,7 @@ class ServiceAnnotator:
|
||||||
and build elements and its attributes (the `Options` in tiramisu terms)
|
and build elements and its attributes (the `Options` in tiramisu terms)
|
||||||
"""
|
"""
|
||||||
families = []
|
families = []
|
||||||
|
listname = '{}list'.format(elttype)
|
||||||
for elt in elts:
|
for elt in elts:
|
||||||
# try to launch _update_xxxx() function
|
# try to launch _update_xxxx() function
|
||||||
update_elt = '_update_' + elttype
|
update_elt = '_update_' + elttype
|
||||||
|
@ -99,17 +100,21 @@ class ServiceAnnotator:
|
||||||
elt.xmlfiles,
|
elt.xmlfiles,
|
||||||
)
|
)
|
||||||
family.variable = []
|
family.variable = []
|
||||||
activate_path = '.'.join([subpath, 'activate'])
|
activate_obj = self._generate_element('boolean',
|
||||||
|
'activate',
|
||||||
|
True,
|
||||||
|
elt.xmlfiles,
|
||||||
|
'.'.join([subpath, 'activate']),
|
||||||
|
)
|
||||||
for key in dir(elt):
|
for key in dir(elt):
|
||||||
if key.startswith('_') or key.endswith('_type') or key in ERASED_ATTRIBUTES:
|
if key.startswith('_') or key.endswith('_type') or key in ERASED_ATTRIBUTES:
|
||||||
continue
|
continue
|
||||||
value = getattr(elt, key)
|
value = getattr(elt, key)
|
||||||
listname = '{}list'.format(elttype)
|
|
||||||
if key == listname:
|
if key == listname:
|
||||||
self.objectspace.list_conditions.setdefault(listname,
|
self.objectspace.list_conditions.setdefault(listname,
|
||||||
{}).setdefault(
|
{}).setdefault(
|
||||||
value,
|
value,
|
||||||
[]).append(activate_path)
|
[]).append(activate_obj)
|
||||||
continue
|
continue
|
||||||
family.variable.append(self._generate_element(self._get_type(key,
|
family.variable.append(self._generate_element(self._get_type(key,
|
||||||
elttype,
|
elttype,
|
||||||
|
@ -122,12 +127,7 @@ class ServiceAnnotator:
|
||||||
))
|
))
|
||||||
# FIXME ne devrait pas etre True par défaut
|
# FIXME ne devrait pas etre True par défaut
|
||||||
# devrait etre un calcule
|
# devrait etre un calcule
|
||||||
family.variable.append(self._generate_element('boolean',
|
family.variable.append(activate_obj)
|
||||||
'activate',
|
|
||||||
True,
|
|
||||||
elt.xmlfiles,
|
|
||||||
activate_path,
|
|
||||||
))
|
|
||||||
families.append(family)
|
families.append(family)
|
||||||
return families
|
return families
|
||||||
|
|
||||||
|
@ -146,7 +146,10 @@ class ServiceAnnotator:
|
||||||
if idx:
|
if idx:
|
||||||
c_name += f'_{idx}'
|
c_name += f'_{idx}'
|
||||||
subpath = '{}.{}'.format(path, c_name)
|
subpath = '{}.{}'.format(path, c_name)
|
||||||
if not self.objectspace.paths.family_is_defined(subpath, 'services'):
|
try:
|
||||||
|
self.objectspace.paths.get_family(subpath, 'services')
|
||||||
|
except DictConsistencyError as err:
|
||||||
|
if err.errno == 42:
|
||||||
return c_name, subpath
|
return c_name, subpath
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
|
@ -177,9 +180,7 @@ class ServiceAnnotator:
|
||||||
variable.mode = None
|
variable.mode = None
|
||||||
variable.type = type_
|
variable.type = type_
|
||||||
if type_ == 'symlink':
|
if type_ == 'symlink':
|
||||||
variable.opt = self.objectspace.paths.get_variable_path(value,
|
variable.opt = self.objectspace.paths.get_variable_obj(value)
|
||||||
'services',
|
|
||||||
)
|
|
||||||
variable.multi = None
|
variable.multi = None
|
||||||
else:
|
else:
|
||||||
variable.doc = key
|
variable.doc = key
|
||||||
|
|
|
@ -16,11 +16,6 @@ class TemplateDisabled(TemplateError):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class OperationError(Exception):
|
|
||||||
"""Type error or value Error for Creole variable's type or values
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SpaceObjShallNotBeUpdated(Exception):
|
class SpaceObjShallNotBeUpdated(Exception):
|
||||||
"""Specific behavior in case of the presence or not
|
"""Specific behavior in case of the presence or not
|
||||||
of an object in the space object
|
of an object in the space object
|
||||||
|
|
|
@ -71,6 +71,7 @@ class RougailObjSpace:
|
||||||
|
|
||||||
self.forced_text_elts_as_name = set(FORCED_TEXT_ELTS_AS_NAME)
|
self.forced_text_elts_as_name = set(FORCED_TEXT_ELTS_AS_NAME)
|
||||||
self.list_conditions = {}
|
self.list_conditions = {}
|
||||||
|
self.valid_enums = {}
|
||||||
self.booleans_attributs = []
|
self.booleans_attributs = []
|
||||||
|
|
||||||
self.make_object_space_classes(xmlreflector)
|
self.make_object_space_classes(xmlreflector)
|
||||||
|
@ -312,7 +313,7 @@ class RougailObjSpace:
|
||||||
name = space.path + '.' + name
|
name = space.path + '.' + name
|
||||||
if not self.paths.path_is_defined(name):
|
if not self.paths.path_is_defined(name):
|
||||||
return None
|
return None
|
||||||
old_family_name = namespace + '.' + self.paths.get_variable_family_name(name)
|
old_family_name = self.paths.get_variable_family_path(name)
|
||||||
if space.path != old_family_name:
|
if space.path != old_family_name:
|
||||||
xmlfiles = self.display_xmlfiles(space.xmlfiles)
|
xmlfiles = self.display_xmlfiles(space.xmlfiles)
|
||||||
msg = _(f'Variable was previously create in family "{old_family_name}", '
|
msg = _(f'Variable was previously create in family "{old_family_name}", '
|
||||||
|
@ -437,7 +438,7 @@ class RougailObjSpace:
|
||||||
family_name = normalize_family(document.attrib['name'])
|
family_name = normalize_family(document.attrib['name'])
|
||||||
self.paths.add_variable(namespace,
|
self.paths.add_variable(namespace,
|
||||||
variableobj.name,
|
variableobj.name,
|
||||||
family_name,
|
namespace + '.' + family_name,
|
||||||
document.attrib.get('dynamic') is not None,
|
document.attrib.get('dynamic') is not None,
|
||||||
variableobj,
|
variableobj,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .error import OperationError, DictConsistencyError
|
from .error import DictConsistencyError
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
from .utils import normalize_family
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
class Path:
|
||||||
|
@ -25,6 +26,8 @@ class Path:
|
||||||
full_name = '.'.join([namespace, name])
|
full_name = '.'.join([namespace, name])
|
||||||
self.full_paths_families[name] = full_name
|
self.full_paths_families[name] = full_name
|
||||||
else:
|
else:
|
||||||
|
if '.' not in name:
|
||||||
|
raise DictConsistencyError(_(f'Variable "{name}" in namespace "{namespace}" must have dot'), 39)
|
||||||
full_name = name
|
full_name = name
|
||||||
if full_name in self.families and \
|
if full_name in self.families and \
|
||||||
self.families[full_name]['variableobj'] != variableobj: # pragma: no cover
|
self.families[full_name]['variableobj'] != variableobj: # pragma: no cover
|
||||||
|
@ -46,52 +49,47 @@ class Path:
|
||||||
)
|
)
|
||||||
variableobj.path = path
|
variableobj.path = path
|
||||||
|
|
||||||
def _get_family(self,
|
|
||||||
name: str,
|
|
||||||
namespace: str=None,
|
|
||||||
):
|
|
||||||
# if main namespace, get full_path
|
|
||||||
if '.' not in name and namespace in [None, Config['variable_namespace']] and name in self.full_paths_families:
|
|
||||||
name = self.full_paths_families[name]
|
|
||||||
dico = self.families[name]
|
|
||||||
if namespace and dico['namespace'] != Config['variable_namespace'] and namespace != dico['namespace']:
|
|
||||||
raise DictConsistencyError(_(f'A family located in the "{dico["namespace"]}" namespace shall not be used in the "{namespace}" namespace'), 38)
|
|
||||||
return dico
|
|
||||||
|
|
||||||
def get_family(self,
|
def get_family(self,
|
||||||
name: str,
|
name: str,
|
||||||
namespace: str,
|
current_namespace: str,
|
||||||
) -> 'Family': # pylint: disable=C0111
|
) -> 'Family': # pylint: disable=C0111
|
||||||
return self._get_family(name, namespace)['variableobj']
|
name = '.'.join([normalize_family(subname) for subname in name.split('.')])
|
||||||
|
if name not in self.families and name in self.full_paths_families:
|
||||||
def family_is_defined(self,
|
name = self.full_paths_families[name]
|
||||||
name: str,
|
if name not in self.families:
|
||||||
namespace: str,
|
raise DictConsistencyError(_('unknown option {}').format(name), 42)
|
||||||
) -> str: # pylint: disable=C0111
|
dico = self.families[name]
|
||||||
try:
|
if current_namespace not in [Config['variable_namespace'], 'services'] and current_namespace != dico['namespace']:
|
||||||
self._get_family(name, namespace)
|
raise DictConsistencyError(_(f'A family located in the "{dico["namespace"]}" namespace shall not be used in the "{current_namespace}" namespace'), 38)
|
||||||
return True
|
return dico['variableobj']
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Leadership
|
# Leadership
|
||||||
def set_leader(self,
|
def set_leader(self,
|
||||||
namespace: str,
|
namespace: str,
|
||||||
leader_family_name: str,
|
leader_family_name: str,
|
||||||
|
leadership_name: str,
|
||||||
name: str,
|
name: str,
|
||||||
leader_name: str,
|
|
||||||
) -> None: # pylint: disable=C0111
|
) -> None: # pylint: disable=C0111
|
||||||
# need rebuild path and move object in new path
|
# need rebuild path and move object in new path
|
||||||
old_path = namespace + '.' + leader_family_name + '.' + name
|
old_path = leader_family_name + '.' + name
|
||||||
new_path = namespace + '.' + leader_family_name + '.' + leader_name + '.' + name
|
leadership_path = leader_family_name + '.' + leadership_name
|
||||||
|
new_path = leadership_path + '.' + name
|
||||||
self.variables[new_path] = self.variables.pop(old_path)
|
self.variables[new_path] = self.variables.pop(old_path)
|
||||||
self.variables[new_path]['leader'] = leader_name
|
self.variables[new_path]['leader'] = leadership_path
|
||||||
self.variables[new_path]['variableobj'].path = new_path
|
self.variables[new_path]['variableobj'].path = new_path
|
||||||
|
self.variables[new_path]['family'] = leadership_path
|
||||||
if namespace == Config['variable_namespace']:
|
if namespace == Config['variable_namespace']:
|
||||||
self.full_paths_variables[name] = new_path
|
self.full_paths_variables[name] = new_path
|
||||||
|
|
||||||
def get_leader(self, name): # pylint: disable=C0111
|
def is_in_leadership(self, name):
|
||||||
return self._get_variable(name)['leader']
|
return self._get_variable(name)['leader'] is not None
|
||||||
|
|
||||||
|
def is_leader(self, path): # pylint: disable=C0111
|
||||||
|
variable = self._get_variable(path)
|
||||||
|
if not variable['leader']:
|
||||||
|
return False
|
||||||
|
leadership = self.get_family(variable['leader'], variable['variableobj'].namespace)
|
||||||
|
return leadership.variable[0].path == path
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
def add_variable(self,
|
def add_variable(self,
|
||||||
|
@ -102,7 +100,7 @@ class Path:
|
||||||
variableobj,
|
variableobj,
|
||||||
) -> str: # pylint: disable=C0111
|
) -> str: # pylint: disable=C0111
|
||||||
if '.' not in name:
|
if '.' not in name:
|
||||||
full_path = '.'.join([namespace, family, name])
|
full_path = '.'.join([family, name])
|
||||||
if namespace == Config['variable_namespace']:
|
if namespace == Config['variable_namespace']:
|
||||||
self.full_paths_variables[name] = full_path
|
self.full_paths_variables[name] = full_path
|
||||||
else:
|
else:
|
||||||
|
@ -110,7 +108,6 @@ class Path:
|
||||||
variableobj.path = full_path
|
variableobj.path = full_path
|
||||||
self.variables[full_path] = dict(name=name,
|
self.variables[full_path] = dict(name=name,
|
||||||
family=family,
|
family=family,
|
||||||
namespace=namespace,
|
|
||||||
leader=None,
|
leader=None,
|
||||||
is_dynamic=is_dynamic,
|
is_dynamic=is_dynamic,
|
||||||
variableobj=variableobj,
|
variableobj=variableobj,
|
||||||
|
@ -119,9 +116,12 @@ class Path:
|
||||||
def get_variable_obj(self,
|
def get_variable_obj(self,
|
||||||
name: str,
|
name: str,
|
||||||
) -> 'Variable': # pylint: disable=C0111
|
) -> 'Variable': # pylint: disable=C0111
|
||||||
return self._get_variable(name)['variableobj']
|
variable, suffix = self._get_variable(name, with_suffix=True)
|
||||||
|
if suffix:
|
||||||
|
raise DictConsistencyError(_(f"{name} is a dynamic variable"), 36)
|
||||||
|
return variable['variableobj']
|
||||||
|
|
||||||
def get_variable_family_name(self,
|
def get_variable_family_path(self,
|
||||||
name: str,
|
name: str,
|
||||||
) -> str: # pylint: disable=C0111
|
) -> str: # pylint: disable=C0111
|
||||||
return self._get_variable(name)['family']
|
return self._get_variable(name)['family']
|
||||||
|
@ -129,27 +129,14 @@ class Path:
|
||||||
def get_variable_path(self,
|
def get_variable_path(self,
|
||||||
name: str,
|
name: str,
|
||||||
current_namespace: str,
|
current_namespace: str,
|
||||||
allow_source: str=False,
|
|
||||||
with_suffix: bool=False,
|
|
||||||
) -> str: # pylint: disable=C0111
|
) -> str: # pylint: disable=C0111
|
||||||
if current_namespace is None: # pragma: no cover
|
|
||||||
raise OperationError('current_namespace must not be None')
|
|
||||||
if with_suffix:
|
|
||||||
dico, suffix = self._get_variable(name,
|
dico, suffix = self._get_variable(name,
|
||||||
with_suffix=True,
|
with_suffix=True,
|
||||||
)
|
)
|
||||||
else:
|
namespace = dico['variableobj'].namespace
|
||||||
dico = self._get_variable(name)
|
if namespace not in [Config['variable_namespace'], 'services'] and current_namespace != namespace:
|
||||||
if not allow_source and dico['namespace'] not in [Config['variable_namespace'], 'services'] and current_namespace != dico['namespace']:
|
raise DictConsistencyError(_(f'A variable located in the "{namespace}" namespace shall not be used in the "{current_namespace}" namespace'), 41)
|
||||||
raise DictConsistencyError(_(f'A variable located in the "{dico["namespace"]}" namespace shall not be used in the "{current_namespace}" namespace'), 41)
|
return dico['variableobj'].path, suffix
|
||||||
list_path = [dico['namespace'], dico['family']]
|
|
||||||
if dico['leader'] is not None:
|
|
||||||
list_path.append(dico['leader'])
|
|
||||||
list_path.append(dico['name'])
|
|
||||||
value = '.'.join(list_path)
|
|
||||||
if with_suffix:
|
|
||||||
return value, suffix
|
|
||||||
return value
|
|
||||||
|
|
||||||
def path_is_defined(self,
|
def path_is_defined(self,
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -167,6 +154,7 @@ class Path:
|
||||||
name: str,
|
name: str,
|
||||||
with_suffix: bool=False,
|
with_suffix: bool=False,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
name = '.'.join([normalize_family(subname) for subname in name.split('.')])
|
||||||
if name not in self.variables:
|
if name not in self.variables:
|
||||||
if '.' not in name and name in self.full_paths_variables:
|
if '.' not in name and name in self.full_paths_variables:
|
||||||
name = self.full_paths_variables[name]
|
name = self.full_paths_variables[name]
|
||||||
|
|
|
@ -12,6 +12,10 @@ FORCE_INFORMATIONS = ['help', 'test', 'separator', 'manage']
|
||||||
ATTRIBUTES_ORDER = ('name', 'doc', 'default', 'multi')
|
ATTRIBUTES_ORDER = ('name', 'doc', 'default', 'multi')
|
||||||
|
|
||||||
|
|
||||||
|
class Root():
|
||||||
|
path = '.'
|
||||||
|
|
||||||
|
|
||||||
class TiramisuReflector:
|
class TiramisuReflector:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
xmlroot,
|
xmlroot,
|
||||||
|
@ -31,7 +35,7 @@ class TiramisuReflector:
|
||||||
]
|
]
|
||||||
self.make_tiramisu_objects(xmlroot)
|
self.make_tiramisu_objects(xmlroot)
|
||||||
# parse object
|
# parse object
|
||||||
self.storage.get('.').get()
|
self.storage.get(Root()).get()
|
||||||
|
|
||||||
def make_tiramisu_objects(self,
|
def make_tiramisu_objects(self,
|
||||||
xmlroot,
|
xmlroot,
|
||||||
|
@ -152,7 +156,7 @@ class TiramisuReflector:
|
||||||
return subpath + '.' + elt.name
|
return subpath + '.' + elt.name
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
return '\n'.join(self.storage.get('.').get_text())
|
return '\n'.join(self.storage.get(Root()).get_text())
|
||||||
|
|
||||||
|
|
||||||
class BaseElt:
|
class BaseElt:
|
||||||
|
@ -172,7 +176,8 @@ class ElementStorage:
|
||||||
self.paths[path] = (elt, self.index)
|
self.paths[path] = (elt, self.index)
|
||||||
self.index += 1
|
self.index += 1
|
||||||
|
|
||||||
def get(self, path):
|
def get(self, obj):
|
||||||
|
path = obj.path
|
||||||
return self.paths[path][0]
|
return self.paths[path][0]
|
||||||
|
|
||||||
def get_name(self, path):
|
def get_name(self, path):
|
||||||
|
@ -402,7 +407,7 @@ class Variable(Common):
|
||||||
):
|
):
|
||||||
option_name = self.storage.get(param['option']).get()
|
option_name = self.storage.get(param['option']).get()
|
||||||
if 'suffix' in param:
|
if 'suffix' in param:
|
||||||
family = '.'.join(param['option'].split('.')[:-1])
|
family = '.'.join(param['option'].path.split('.')[:-1])
|
||||||
family_option = self.storage.get_name(family)
|
family_option = self.storage.get_name(family)
|
||||||
return f"ParamDynOption({option_name}, '{param['suffix']}', {family_option}, notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})"
|
return f"ParamDynOption({option_name}, '{param['suffix']}', {family_option}, notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})"
|
||||||
return f"ParamOption({option_name}, notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})"
|
return f"ParamOption({option_name}, notraisepropertyerror={param['notraisepropertyerror']}, todict={param['todict']})"
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
<variable name="int" type="number" description="No change"/>
|
<variable name="int" type="number" description="No change"/>
|
||||||
</family>
|
</family>
|
||||||
</variables>
|
</variables>
|
||||||
|
|
||||||
<constraints>
|
<constraints>
|
||||||
<check name="valid_entier" target="int">
|
<check name="valid_entier" target="int">
|
||||||
<param name="mini" type="number">0</param>
|
<param name="mini" type="number">0</param>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<rougail>
|
||||||
|
<variables>
|
||||||
|
<family name='general'>
|
||||||
|
<variable name='varname' type='string' description="No change" multi="True">
|
||||||
|
<value>val1</value>
|
||||||
|
<value>val2</value>
|
||||||
|
</variable>
|
||||||
|
</family>
|
||||||
|
<family name='dyn' dynamic="varname">
|
||||||
|
<variable name='vardyn' type='number' description="No change"/>
|
||||||
|
</family>
|
||||||
|
</variables>
|
||||||
|
<constraints>
|
||||||
|
<check name="valid_entier" target="vardynval1">
|
||||||
|
<param name="mini" type="number">0</param>
|
||||||
|
<param name="maxi" type="number">100</param>
|
||||||
|
</check>
|
||||||
|
</constraints>
|
||||||
|
</rougail>
|
|
@ -102,7 +102,9 @@ def test_error_dictionary(test_dir_error):
|
||||||
errno = int(i.split('_')[1])
|
errno = int(i.split('_')[1])
|
||||||
with raises(DictConsistencyError) as err:
|
with raises(DictConsistencyError) as err:
|
||||||
launch_flattener(test_dir)
|
launch_flattener(test_dir)
|
||||||
assert err.value.errno == errno, f'expected errno: {errno}, errno: {err.value.errno}, value: {err.value}'
|
if err.value.errno != errno:
|
||||||
|
print(f'expected errno: {errno}, errno: {err.value.errno}')
|
||||||
|
launch_flattener(test_dir)
|
||||||
assert getcwd() == ORI_DIR
|
assert getcwd() == ORI_DIR
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue