split check, condition and fill

This commit is contained in:
Emmanuel Garette 2021-01-17 18:00:29 +01:00
parent 06e7196280
commit 529bb1ae7d
10 changed files with 666 additions and 602 deletions

View File

@ -3,7 +3,9 @@
from .group import GroupAnnotator
from .service import ServiceAnnotator, ERASED_ATTRIBUTES
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 .property import PropertyAnnotator
@ -15,7 +17,11 @@ class SpaceAnnotator:
GroupAnnotator(objectspace)
ServiceAnnotator(objectspace)
VariableAnnotator(objectspace)
ConstrainteAnnotator(objectspace,
CheckAnnotator(objectspace,
eosfunc_file,
)
Conditionnnotator(objectspace)
FillAnnotator(objectspace,
eosfunc_file,
)
FamilyAnnotator(objectspace)

View File

@ -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)

View File

@ -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)

View File

@ -1,597 +0,0 @@
"""Annotate constraints
"""
from importlib.machinery import SourceFileLoader
from typing import List, Any
from .variable import CONVERT_OPTION
from ..i18n import _
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()
self.convert_condition_source()
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)
check.is_in_leadership = self.objectspace.paths.is_in_leadership(check.target)
# let's replace the target by the path
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
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]
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.valid_enums:
check_xmlfiles = self.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
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.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 _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_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 = f'target name and source name must be different: {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)
raise DictConsistencyError(_(f'cannot found target "{target.name}" in the condition in {xmlfiles}'), 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 = 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 convert_condition_source(self):
"""remove condition for ChoiceOption that don't have param
"""
remove_conditions = []
for condition_idx, condition in enumerate(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):
namespace = condition.namespace
# FIXME only string?
if condition.source.path in self.valid_enums and \
self.valid_enums[condition.source.path]['type'] == 'string':
valid_enum = self.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 = 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:
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)
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)

View File

@ -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)

View File

@ -71,6 +71,7 @@ class RougailObjSpace:
self.forced_text_elts_as_name = set(FORCED_TEXT_ELTS_AS_NAME)
self.list_conditions = {}
self.valid_enums = {}
self.booleans_attributs = []
self.make_object_space_classes(xmlreflector)

View File

@ -8,7 +8,6 @@
<variable name="int" type="number" description="No change"/>
</family>
</variables>
<constraints>
<check name="valid_entier" target="int">
<param name="mini" type="number">0</param>

View File

@ -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>