diff --git a/src/rougail/annotator/constrainte.py b/src/rougail/annotator/constrainte.py index 17feee3c..4f246888 100644 --- a/src/rougail/annotator/constrainte.py +++ b/src/rougail/annotator/constrainte.py @@ -1,4 +1,7 @@ +"""Annotate constraints +""" from importlib.machinery import SourceFileLoader +from typing import List, Any from .variable import CONVERT_OPTION @@ -12,7 +15,32 @@ 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, @@ -23,18 +51,16 @@ class ConstrainteAnnotator: eosfunc = SourceFileLoader('eosfunc', eosfunc_file).load_module() self.functions = dir(eosfunc) self.functions.extend(INTERNAL_FUNCTIONS) + self.valid_enums = {} self.convert_auto_freeze() - self.valid_enums = {} if hasattr(self.objectspace.space.constraints, 'check'): self.check_check() - self.check_replace_text() self.check_valid_enum() self.check_change_warning() self.convert_check() if hasattr(self.objectspace.space.constraints, 'condition'): - self.check_params_target() - self.filter_targets() + self.convert_condition_target() self.convert_xxxlist_to_variable() self.check_condition_fallback_optional() self.check_choice_option_condition() @@ -42,16 +68,19 @@ class ConstrainteAnnotator: self.convert_condition() if hasattr(self.objectspace.space.constraints, 'fill'): self.convert_fill() - self.remove_constraints() + del self.objectspace.space.constraints - def convert_auto_freeze(self): # pylint: disable=C0111 + def convert_auto_freeze(self): + """convert auto_freeze to continue + """ def _convert_auto_freeze(variable, namespace): if variable.auto_freeze: if namespace != Config['variable_namespace']: xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) - raise DictConsistencyError(_(f'auto_freeze is not allowed in extra "{namespace}" in {xmlfiles}'), 49) + msg = _(f'auto_freeze is not allowed in extra "{namespace}" in {xmlfiles}') + raise DictConsistencyError(msg, 49) new_condition = self.objectspace.condition(variable.xmlfiles) - new_condition.name = 'auto_hidden_if_not_in' + new_condition.name = 'auto_frozen_if_not_in' new_condition.namespace = namespace new_condition.source = FREEZE_AUTOFREEZE_VARIABLE new_param = self.objectspace.param(variable.xmlfiles) @@ -65,82 +94,176 @@ class ConstrainteAnnotator: self.objectspace.space.constraints.condition = [] self.objectspace.space.constraints.condition.append(new_condition) for variables in self.objectspace.space.variables.values(): - if hasattr(variables, 'family'): - namespace = variables.name - for family in variables.family.values(): - if hasattr(family, 'variable'): - for variable in family.variable.values(): - if isinstance(variable, self.objectspace.leadership): - for follower in variable.variable: - _convert_auto_freeze(follower, namespace) - else: - _convert_auto_freeze(variable, namespace) + if not hasattr(variables, 'family'): + continue + 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, + variables.namespace, + ) + else: + _convert_auto_freeze(variable, + variables.namespace, + ) def check_check(self): + """valid and manage + """ 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) - raise DictConsistencyError(_(f'cannot find check function "{check.name}" in {xmlfiles}'), 1) - if hasattr(check, 'param'): - param_option_indexes = [] - for idx, param in enumerate(check.param): - if param.type == 'variable' and not self.objectspace.paths.path_is_defined(param.text): - if param.optional is True: - param_option_indexes.append(idx) - else: + 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) - raise DictConsistencyError(_(f'cannot find check param "{param.text}" in {xmlfiles}'), 2) - if param.type != 'variable': - param.notraisepropertyerror = None - param_option_indexes = list(set(param_option_indexes)) - param_option_indexes.sort(reverse=True) - for idx in param_option_indexes: - check.param.pop(idx) - if check.param == []: - remove_indexes.append(check_idx) + 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_replace_text(self): - for check in self.objectspace.space.constraints.check: - namespace = check.namespace - if hasattr(check, 'param'): - for param in check.param: - if param.type == 'variable': - param.text = self.objectspace.paths.get_variable_path(param.text, namespace) - check.is_in_leadership = self.objectspace.paths.get_leader(check.target) is not None - # let's replace the target by the path - check.target = self.objectspace.paths.get_variable_path(check.target, namespace) - 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: - old_xmlfiles = self.objectspace.display_xmlfiles(self.valid_enums[check.target]['xmlfiles']) + check_xmlfiles = self.valid_enums[check.target]['xmlfiles'] + old_xmlfiles = self.objectspace.display_xmlfiles(check_xmlfiles) xmlfiles = self.objectspace.display_xmlfiles(check.xmlfiles) - raise DictConsistencyError(_(f'valid_enum define in {xmlfiles} but already set in {old_xmlfiles} for "{check.target}", did you forget remove_check?'), 3) + 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) - raise DictConsistencyError(_(f'param is mandatory for a valid_enum of variable "{check.target}" in {xmlfiles}'), 4) + 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) - self._set_valid_enum(variable, - check, - ) + 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 check_change_warning(self): - #convert level to "warnings_only" - for check in self.objectspace.space.constraints.check: - if check.level == 'warning': - check.warnings_only = True + 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: - check.warnings_only = False + 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, @@ -157,39 +280,49 @@ class ConstrainteAnnotator: variable = self.objectspace.paths.get_family_obj(target.name) return variable, list(variable.variable.values()) - def check_params_target(self): + 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) - raise DictConsistencyError(_(f'target is mandatory in a condition for source "{condition.source}" in {xmlfiles}'), 9) - for target in condition.target: - if target.type.endswith('list') and condition.name not in ['disabled_if_in', 'disabled_if_not_in']: - xmlfiles = self.objectspace.display_xmlfiles(target.xmlfiles) - raise DictConsistencyError(_(f'target "{target.type}" not allow in condition "{condition.name}" in {xmlfiles}'), 10) - - def filter_targets(self): # pylint: disable=C0111 - for condition in self.objectspace.space.constraints.condition: - namespace = condition.namespace + 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: - raise DictConsistencyError(_('target name and source name must be different: {}').format(condition.source), 11) + 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_names = [normalize_family(name) for name in target.name.split('.')] - target.name = self.objectspace.paths.get_variable_path('.'.join(target_names), namespace) + 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_names = '.'.join([normalize_family(name) \ + for name in target.name.split('.')]) try: - target_names = [normalize_family(name) for name in target.name.split('.')] - target.name = self.objectspace.paths.get_family_path('.'.join(target_names), namespace) + target.name = self.objectspace.paths.get_family_path(target_names, + condition.namespace, + ) except KeyError: - raise DictConsistencyError(_('cannot found family {}').format(target.name), 12) + raise DictConsistencyError(_(f'cannot found family {target.name}'), 12) + 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): # pylint: disable=C0111 - # transform *list to variable or family + def convert_xxxlist_to_variable(self): + """transform *list to variable or family + """ for condition in self.objectspace.space.constraints.condition: new_targets = [] remove_targets = [] @@ -214,33 +347,28 @@ class ConstrainteAnnotator: condition.target.extend(new_targets) def check_condition_fallback_optional(self): - # a condition with a fallback **and** the source variable doesn't exist + """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): + 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 - if apply_action: - actions = self._get_condition_actions(condition.name) - for target in condition.target: - leader_or_variable, variables = self._get_family_variables_from_target(target) - for action_idx, action in enumerate(actions): - if action_idx == 0: - setattr(leader_or_variable, action, True) - else: - for variable in variables: - setattr(variable, action, True) 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): + 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) @@ -251,18 +379,24 @@ class ConstrainteAnnotator: for idx in remove_conditions: self.objectspace.space.constraints.condition.pop(idx) - def _get_condition_actions(self, condition_name): - if condition_name.startswith('disabled_if_'): - return ['disabled'] - if condition_name.startswith('hidden_if_'): - return ['hidden', 'frozen', 'force_default_on_freeze'] - if condition_name.startswith('mandatory_if_'): - return ['mandatory'] - if condition_name == 'auto_hidden_if_not_in': - return ['auto_frozen'] + 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 condition for ChoiceOption that don't have param + """ remove_conditions = [] for condition_idx, condition in enumerate(self.objectspace.space.constraints.condition): namespace = condition.namespace @@ -273,54 +407,48 @@ class ConstrainteAnnotator: ) if suffix: xmlfiles = self.objectspace.display_xmlfiles(condition.xmlfiles) - raise DictConsistencyError(_(f'the source "{condition.source}" in condition cannot be a dynamic variable in {xmlfiles}'), 20) - valid_enum = None + 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': + if condition.source in self.valid_enums and \ + self.valid_enums[condition.source]['type'] == 'string': valid_enum = self.valid_enums[condition.source]['values'] - if valid_enum is not None: - remove_param = [] - for param_idx, param in enumerate(condition.param): - if param.text not in valid_enum: - remove_param.append(param_idx) + 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 condition.param == []: - for target in condition.target: - leader_or_variable, variables = self._get_family_variables_from_target(target) - if condition.name == 'disabled_if_not_in': - leader_or_variable.disabled = True - elif condition.name == 'hidden_if_not_in': - leader_or_variable.hidden = True - for variable in variables: - variable.frozen = True - variable.force_default_on_freeze = True - elif condition.name == 'mandatory_if_not_in': - leader_or_variable.mandatory = True + if not condition.param and condition.name.endswith('_if_not_in'): + self.force_actions_to_variable(condition) remove_conditions.append(condition_idx) - remove_conditions = list(set(remove_conditions)) 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 = [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 + """ for condition in self.objectspace.space.constraints.condition: - inverse = condition.name.endswith('_if_not_in') - actions = self._get_condition_actions(condition.name) + 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') + # 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 @@ -328,201 +456,155 @@ class ConstrainteAnnotator: (leader_or_variable.auto_save or leader_or_variable.auto_freeze) and \ 'force_default_on_freeze' in actions: xmlfiles = self.objectspace.display_xmlfiles(leader_or_variable.xmlfiles) - raise DictConsistencyError(_(f'cannot have auto_freeze or auto_store with the hidden_if_in or hidden_if_not_in variable "{leader_or_variable.name}" in {xmlfiles}'), 51) - for idx, action in enumerate(actions): - prop = self.objectspace.property_(leader_or_variable.xmlfiles) - prop.type = 'calculation' - prop.inverse = inverse - prop.source = condition.source - prop.expected = text - prop.name = action - if idx == 0: - # main action is for the variable or family - if not hasattr(leader_or_variable, 'property'): - leader_or_variable.property = [] - leader_or_variable.property.append(prop) - else: - # other actions are set to the variable or children of family - for variable in variables: - if not hasattr(variable, 'property'): - variable.property = [] - variable.property.append(prop) - del self.objectspace.space.constraints.condition + msg = _(f'cannot have auto_freeze or auto_store with the hidden_if_in or ' + f'hidden_if_not_in variable "{leader_or_variable.name}" ' + f'in {xmlfiles}') + raise DictConsistencyError(msg, 51) + self.build_property(leader_or_variable, + text, + condition, + main_action, + ) + 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 _set_valid_enum(self, - variable, - check, - ): - type_ = variable.type - target = check.target - # value for choice's variable is mandatory - variable.mandatory = True - # build choice - variable.choice = [] - variable.type = 'choice' + 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) - has_variable = False - values = [] - for param in check.param: - if has_variable: - xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles) - raise DictConsistencyError(_(f'only one "variable" parameter is allowed for valid_enum of variable "{variable.name}" in {xmlfiles}'), 5) - param_type = type_ - if param.type == 'variable': - has_variable = True - param_variable = self.objectspace.paths.get_variable_obj(param.text) - if param.optional is True: - xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles) - raise DictConsistencyError(_(f'optional parameter in valid_enum for variable "{variable.name}" is not allowed in {xmlfiles}'), 14) - if not param_variable.multi: - xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles) - raise DictConsistencyError(_(f'only multi "variable" parameter is allowed for valid_enum of variable "{variable.name}" in {xmlfiles}'), 6) - param_type = 'calculation' - value = param.text - else: - if 'type' in vars(param) and type_ != param.type: - xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles) - raise DictConsistencyError(_(f'parameter in valid_enum has incompatible type "{param.type}" with type of the variable "{variable.name}" ("{type_}") in {xmlfiles}'), 7) - if hasattr(param, 'text'): - try: - value = CONVERT_OPTION[type_].get('func', str)(param.text) - except: - raise DictConsistencyError(_(f'unable to change type of a valid_enum entry "{param.text}" is not a valid "{type_}" for "{variable.name}"'), 13) - else: - if param.type == 'number': - xmlfiles = self.objectspace.display_xmlfiles(param.xmlfiles) - raise DictConsistencyError(_(f'param type is number, so value is mandatory for valid_enum of variable "{variable.name}" in {xmlfiles}'), 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 - - # FIXME really? - if param_type != 'calculation': - self.valid_enums[target] = {'type': type_, - 'values': values, - 'xmlfiles': check.xmlfiles, - } - # check value or set first choice value has default value - if hasattr(variable, 'value'): - for value in variable.value: - if value.name not in values: - raise DictConsistencyError(_(f'value "{value.name}" of variable "{variable.name}" is not in list of all expected values ({values})'), 15) - else: - new_value = self.objectspace.value(check.xmlfiles) - new_value.name = values[0] - new_value.type = type_ - variable.value = [new_value] - - def convert_check(self): + def convert_check(self) -> None: + """valid and manage + """ for check in self.objectspace.space.constraints.check: variable = self.objectspace.paths.get_variable_obj(check.target) - name = check.name - if name == 'valid_entier': + if check.name == 'valid_entier': if not hasattr(check, 'param'): - raise DictConsistencyError(_('{} must have, at least, 1 param').format(name), 17) + 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) - raise DictConsistencyError(_(f'param in "valid_entier" must be an "integer", not "{param.type}" in {xmlfiles}'), 18) + 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) - raise DictConsistencyError(_(f'unknown parameter "{param.name}" in check "valid_entier" for variable "{check.target}" in {xmlfiles}'), 19) + msg = _(f'unknown parameter "{param.name}" in check "valid_entier" ' + f'for variable "{check.target}" in {xmlfiles}') + raise DictConsistencyError(msg, 19) else: - check_ = self.objectspace.check(variable.xmlfiles) - check_.name = name - check_.warnings_only = check.warnings_only - if hasattr(check, 'param'): - check_.param = check.param if not hasattr(variable, 'check'): variable.check = [] - variable.check.append(check_) - del self.objectspace.space.constraints.check + variable.check.append(check) - def convert_fill(self): # pylint: disable=C0111,R0912 - # sort fill/auto by index - fills = {fill.index: fill for idx, fill in enumerate(self.objectspace.space.constraints.fill)} - indexes = list(fills.keys()) - indexes.sort() + def convert_fill(self) -> None: + """valid and manage + """ targets = [] - for idx in indexes: - fill = fills[idx] + 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) - raise DictConsistencyError(_(f'A fill already exists for the target of "{fill.target}" created in {xmlfiles}'), 24) + 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) - raise DictConsistencyError(_(f'cannot find fill function "{fill.name}" in {xmlfiles}'), 25) + msg = _(f'cannot find fill function "{fill.name}" in {xmlfiles}') + raise DictConsistencyError(msg, 25) - namespace = fill.namespace # let's replace the target by the path fill.target, suffix = self.objectspace.paths.get_variable_path(fill.target, - namespace, + fill.namespace, with_suffix=True, ) if suffix is not None: xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles) - raise DictConsistencyError(_(f'Cannot add fill function to "{fill.target}" only for the suffix "{suffix}" in {xmlfiles}'), 26) + 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) - value = self.objectspace.value(variable.xmlfiles) + + # create an object value + value = self.objectspace.value(fill.xmlfiles) value.type = 'calculation' value.name = fill.name - if hasattr(fill, 'param'): - param_to_delete = [] - for fill_idx, param in enumerate(fill.param): - if param.type not in ['suffix', 'string'] and not hasattr(param, 'text'): - xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles) - raise DictConsistencyError(_(f"All '{param.type}' variables must have a value in order to calculate {fill.target} in {xmlfiles}"), 27) - if param.type == 'suffix': - if hasattr(param, 'text'): - xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles) - raise DictConsistencyError(_(f'"{param.type}" variables must not have a value in order to calculate "{fill.target}" in {xmlfiles}'), 28) - if not self.objectspace.paths.variable_is_dynamic(fill.target): - xmlfiles = self.objectspace.display_xmlfiles(fill.xmlfiles) - raise DictConsistencyError(_(f'Cannot set suffix target to the none dynamic variable "{fill.target}" in {xmlfiles}'), 53) - if param.type == 'string': - if not hasattr(param, 'text'): - param.text = None - if param.type == 'variable': - try: - param.text, suffix = self.objectspace.paths.get_variable_path(param.text, - namespace, - with_suffix=True, - ) - if suffix: - param.suffix = suffix - except DictConsistencyError as err: - if err.errno != 42: - raise err - if param.optional is False: - raise err - param_to_delete.append(fill_idx) - continue - else: - param.notraisepropertyerror = None - param_to_delete.sort(reverse=True) - for param_idx in param_to_delete: - fill.param.pop(param_idx) - value.param = fill.param variable.value = [value] - del self.objectspace.space.constraints.fill - def remove_constraints(self): - if hasattr(self.objectspace.space.constraints, 'index'): - del self.objectspace.space.constraints.index - del self.objectspace.space.constraints.namespace - del self.objectspace.space.constraints.xmlfiles - if vars(self.objectspace.space.constraints): # pragma: no cover - raise Exception('constraints again?') - del self.objectspace.space.constraints + # 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) diff --git a/src/rougail/data/rougail.dtd b/src/rougail/data/rougail.dtd index e41596ee..9985dc55 100644 --- a/src/rougail/data/rougail.dtd +++ b/src/rougail/data/rougail.dtd @@ -125,7 +125,7 @@ - + diff --git a/src/rougail/template.py b/src/rougail/template.py index 92b34531..67baebcf 100644 --- a/src/rougail/template.py +++ b/src/rougail/template.py @@ -89,12 +89,7 @@ class CheetahTemplate(ChtTemplate): return None -class CreoleValue: - def __str__(self): - return str(self._value) - - -class CreoleLeaderIndex(CreoleValue): +class CreoleLeaderIndex: def __init__(self, value, follower, @@ -112,6 +107,9 @@ class CreoleLeaderIndex(CreoleValue): raise AttributeError() return value + def __str__(self): + return str(self._value) + def __lt__(self, value): return self._value.__lt__(value) @@ -137,7 +135,7 @@ class CreoleLeaderIndex(CreoleValue): return value + self._value -class CreoleLeader(CreoleValue): +class CreoleLeader: def __init__(self, value, ) -> None: @@ -187,7 +185,8 @@ class CreoleExtra: self.suboption = suboption def __getattr__(self, - key: str) -> Any: + key: str, + ) -> Any: return self.suboption[key] def __iter__(self): diff --git a/tests/dictionaries/01fill_only_optional/00-base.xml b/tests/dictionaries/01fill_only_optional/00-base.xml new file mode 100644 index 00000000..531bcdec --- /dev/null +++ b/tests/dictionaries/01fill_only_optional/00-base.xml @@ -0,0 +1,21 @@ + + + + + + + non + + + + + + + mode_conteneur_actif4 + + + + diff --git a/tests/dictionaries/01fill_only_optional/__init__.py b/tests/dictionaries/01fill_only_optional/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/dictionaries/01fill_only_optional/makedict/base.json b/tests/dictionaries/01fill_only_optional/makedict/base.json new file mode 100644 index 00000000..86ba9458 --- /dev/null +++ b/tests/dictionaries/01fill_only_optional/makedict/base.json @@ -0,0 +1 @@ +{"rougail.general.mode_conteneur_actif": null, "rougail.general.mode_conteneur_actif1": "non"} diff --git a/tests/dictionaries/01fill_only_optional/tiramisu/__init__.py b/tests/dictionaries/01fill_only_optional/tiramisu/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/dictionaries/01fill_only_optional/tiramisu/base.py b/tests/dictionaries/01fill_only_optional/tiramisu/base.py new file mode 100644 index 00000000..982e80ee --- /dev/null +++ b/tests/dictionaries/01fill_only_optional/tiramisu/base.py @@ -0,0 +1,15 @@ +from importlib.machinery import SourceFileLoader +func = SourceFileLoader('func', 'tests/dictionaries/../eosfunc/test.py').load_module() +for key, value in dict(locals()).items(): + if key != ['SourceFileLoader', 'func']: + setattr(func, key, value) +try: + from tiramisu3 import * +except: + from tiramisu import * +from rougail.tiramisu import ConvertDynOptionDescription +option_3 = ChoiceOption(properties=frozenset({'force_default_on_freeze', 'frozen', 'hidden', 'mandatory', 'normal'}), name='mode_conteneur_actif', doc='No change', multi=False, default=Calculation(func.calc_val, Params((), kwargs={})), values=('oui', 'non')) +option_4 = ChoiceOption(properties=frozenset({'mandatory', 'normal'}), name='mode_conteneur_actif1', doc='No change', multi=False, default='non', values=('oui', 'non')) +option_2 = OptionDescription(name='general', doc='general', properties=frozenset({'normal'}), children=[option_3, option_4]) +option_1 = OptionDescription(name='rougail', doc='rougail', children=[option_2]) +option_0 = OptionDescription(name='baseoption', doc='baseoption', children=[option_1])