Compare commits

..

6 Commits

19 changed files with 755 additions and 706 deletions

View File

@ -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,9 +17,13 @@ class SpaceAnnotator:
GroupAnnotator(objectspace) GroupAnnotator(objectspace)
ServiceAnnotator(objectspace) ServiceAnnotator(objectspace)
VariableAnnotator(objectspace) VariableAnnotator(objectspace)
ConstrainteAnnotator(objectspace, CheckAnnotator(objectspace,
eosfunc_file, eosfunc_file,
) )
Conditionnnotator(objectspace)
FillAnnotator(objectspace,
eosfunc_file,
)
FamilyAnnotator(objectspace) FamilyAnnotator(objectspace)
PropertyAnnotator(objectspace) PropertyAnnotator(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,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)

View File

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

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

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

View File

@ -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,8 +146,11 @@ 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:
return c_name, subpath self.objectspace.paths.get_family(subpath, 'services')
except DictConsistencyError as err:
if err.errno == 42:
return c_name, subpath
idx += 1 idx += 1
def _gen_family(self, def _gen_family(self,
@ -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

View File

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

View File

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

View File

@ -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 dico, suffix = self._get_variable(name,
raise OperationError('current_namespace must not be None') with_suffix=True,
if with_suffix: )
dico, suffix = self._get_variable(name, namespace = dico['variableobj'].namespace
with_suffix=True, if namespace not in [Config['variable_namespace'], 'services'] and current_namespace != namespace:
) raise DictConsistencyError(_(f'A variable located in the "{namespace}" namespace shall not be used in the "{current_namespace}" namespace'), 41)
else: return dico['variableobj'].path, suffix
dico = self._get_variable(name)
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 "{dico["namespace"]}" namespace shall not be used in the "{current_namespace}" namespace'), 41)
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]

View File

@ -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']})"

View File

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

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>

View File

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