From d18248544c8345b3d40cfbf9f180fa636f122b26 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Fri, 24 Jul 2020 14:59:09 +0200 Subject: [PATCH] refactor --- src/rougail/annotator.py | 1420 ++++++++--------- src/rougail/data/rougail.dtd | 4 +- src/rougail/objspace.py | 9 +- src/rougail/path.py | 95 +- src/rougail/xmlreflector.py | 1 - .../__init__.py | 0 .../10autosave_hidden_frozenifin/00-base.xml | 2 +- .../result/00-base.xml | 2 +- .../tiramisu/base.py | 2 +- .../00-base.xml | 4 +- .../result/00-base.xml | 7 - .../tiramisu/base.py | 8 +- .../10leadership_leader_hidden/00-base.xml | 31 + .../__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 40 + .../tiramisu}/__init__.py | 0 .../tiramisu/base.py | 13 + .../00-base.xml | 32 + .../__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 36 + .../tiramisu}/__init__.py | 0 .../tiramisu/base.py | 13 + .../00-base.xml | 0 .../__init__.py | 0 .../makedict/base.json | 0 .../result/00-base.xml | 0 .../tiramisu}/__init__.py | 0 .../tiramisu/base.py | 0 .../00-base.xml | 38 + .../__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 31 + .../tiramisu}/__init__.py | 0 .../tiramisu/base.py | 10 + .../00-base.xml | 38 + .../__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 31 + .../tiramisu}/__init__.py | 0 .../tiramisu/base.py | 10 + .../result/00-base.xml | 1 - .../tiramisu/base.py | 2 +- .../00-base.xml | 38 + .../__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 31 + .../tiramisu/__init__.py | 0 .../tiramisu/base.py | 10 + .../10load_frozenifin/00-base.xml | 4 +- .../10load_frozenifin/result/00-base.xml | 4 +- .../10load_frozenifin/tiramisu/base.py | 4 +- .../10load_frozenifin_auto/00-base.xml | 4 +- .../10load_frozenifin_auto/result/00-base.xml | 4 +- .../10load_frozenifin_auto/tiramisu/base.py | 4 +- .../10load_frozenifin_multiparam/00-base.xml | 4 +- .../result/00-base.xml | 8 +- .../tiramisu/base.py | 4 +- .../10load_hidden_if_family/00-base.xml | 39 + .../10load_hidden_if_family/__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 51 + .../tiramisu/__init__.py | 0 .../10load_hidden_if_family/tiramisu/base.py | 12 + .../10load_mandatoryifin/00-base.xml | 4 +- .../10load_mandatoryifin/result/00-base.xml | 8 +- .../10load_mandatoryifin/tiramisu/base.py | 4 +- .../10valid_enum_checkval_true/00-base.xml | 24 - .../makedict/base.json | 1 - .../result/00-base.xml | 16 - .../tiramisu/base.py | 8 - .../result/00-base.xml | 1 - .../tiramisu/base.py | 2 +- .../makedict/base.json | 2 +- .../result/00-base.xml | 3 +- .../tiramisu/base.py | 2 +- .../40condition_fallback/00-base.xml | 6 + .../40condition_fallback/result/00-base.xml | 11 + .../40condition_fallback/tiramisu/base.py | 4 +- .../40hidden_if_in_group_fallback/00-base.xml | 35 + .../40hidden_if_in_group_fallback/__init__.py | 0 .../makedict/base.json | 1 + .../result/00-base.xml | 36 + .../tiramisu/__init__.py | 0 .../tiramisu/base.py | 11 + .../extra_dirs/extra1/00-base.xml | 2 +- .../extra_dirs/extra1/00-base.xml | 2 +- .../80action_onlyone/00-base.xml | 22 - .../extra_dirs/extra/00-base.xml | 26 - .../extra_dirs/extra/01-base.xml | 22 - .../80auto_autosave/00-base.xml | 25 - .../00-base.xml | 10 +- .../00-base.xml | 8 +- .../01-base.xml | 8 +- .../80empty_typeeole_container/00_base.xml | 24 - .../80empty_typeeole_eole/00_base.xml | 4 +- .../80empty_typeeole_number/00_base.xml | 4 +- .../00-base.xml | 11 +- .../80fill_autofreeze/__init__.py | 0 .../80fill_container/00-base.xml | 29 - .../00-base.xml | 10 +- .../flattener_dicos/80fill_error/__init__.py | 0 .../00-base.xml | 8 +- .../flattener_dicos/80fill_multi/__init__.py | 0 .../00-base.xml | 9 +- .../01-base.xml | 13 +- .../80redefine_fillerror/__init__.py | 0 tests/test_1_flattener.py | 4 +- tests/test_3_makedict.py | 17 +- 110 files changed, 1451 insertions(+), 1093 deletions(-) rename tests/{flattener_dicos/10load_disabled_if_inaccent => }/__init__.py (100%) create mode 100644 tests/flattener_dicos/10leadership_leader_hidden/00-base.xml rename tests/flattener_dicos/{10load_disabled_if_inaccent/tiramisu => 10leadership_leader_hidden}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10leadership_leader_hidden/makedict/base.json create mode 100644 tests/flattener_dicos/10leadership_leader_hidden/result/00-base.xml rename tests/flattener_dicos/{10valid_enum_checkval_true => 10leadership_leader_hidden/tiramisu}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10leadership_leader_hidden/tiramisu/base.py create mode 100644 tests/flattener_dicos/10leadership_leader_hidden_if_in/00-base.xml rename tests/flattener_dicos/{10valid_enum_checkval_true/tiramisu => 10leadership_leader_hidden_if_in}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10leadership_leader_hidden_if_in/makedict/base.json create mode 100644 tests/flattener_dicos/10leadership_leader_hidden_if_in/result/00-base.xml rename tests/flattener_dicos/{80action_onlyone => 10leadership_leader_hidden_if_in/tiramisu}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10leadership_leader_hidden_if_in/tiramisu/base.py rename tests/flattener_dicos/{10load_disabled_if_inaccent => 10load_disabled_if_inaccent_family}/00-base.xml (100%) rename tests/flattener_dicos/{80auto_autofreeze => 10load_disabled_if_inaccent_family}/__init__.py (100%) rename tests/flattener_dicos/{10load_disabled_if_inaccent => 10load_disabled_if_inaccent_family}/makedict/base.json (100%) rename tests/flattener_dicos/{10load_disabled_if_inaccent => 10load_disabled_if_inaccent_family}/result/00-base.xml (100%) rename tests/flattener_dicos/{80auto_autosave => 10load_disabled_if_inaccent_family/tiramisu}/__init__.py (100%) rename tests/flattener_dicos/{10load_disabled_if_inaccent => 10load_disabled_if_inaccent_family}/tiramisu/base.py (100%) create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback/00-base.xml rename tests/flattener_dicos/{80auto_error => 10load_disabled_if_not_in_fallback}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback/makedict/base.json create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback/result/00-base.xml rename tests/flattener_dicos/{80auto_multi => 10load_disabled_if_not_in_fallback/tiramisu}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback/tiramisu/base.py create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback_force/00-base.xml rename tests/flattener_dicos/{80empty_typeeole_container => 10load_disabled_if_not_in_fallback_force}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback_force/makedict/base.json create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback_force/result/00-base.xml rename tests/flattener_dicos/{80fill_container => 10load_disabled_if_not_in_fallback_force/tiramisu}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10load_disabled_if_not_in_fallback_force/tiramisu/base.py create mode 100644 tests/flattener_dicos/10load_disabledifin_fallback_force/00-base.xml rename tests/flattener_dicos/{80redefine_autoerror => 10load_disabledifin_fallback_force}/__init__.py (100%) create mode 100644 tests/flattener_dicos/10load_disabledifin_fallback_force/makedict/base.json create mode 100644 tests/flattener_dicos/10load_disabledifin_fallback_force/result/00-base.xml create mode 100644 tests/flattener_dicos/10load_disabledifin_fallback_force/tiramisu/__init__.py create mode 100644 tests/flattener_dicos/10load_disabledifin_fallback_force/tiramisu/base.py create mode 100644 tests/flattener_dicos/10load_hidden_if_family/00-base.xml create mode 100644 tests/flattener_dicos/10load_hidden_if_family/__init__.py create mode 100644 tests/flattener_dicos/10load_hidden_if_family/makedict/base.json create mode 100644 tests/flattener_dicos/10load_hidden_if_family/result/00-base.xml create mode 100644 tests/flattener_dicos/10load_hidden_if_family/tiramisu/__init__.py create mode 100644 tests/flattener_dicos/10load_hidden_if_family/tiramisu/base.py delete mode 100644 tests/flattener_dicos/10valid_enum_checkval_true/00-base.xml delete mode 100644 tests/flattener_dicos/10valid_enum_checkval_true/makedict/base.json delete mode 100644 tests/flattener_dicos/10valid_enum_checkval_true/result/00-base.xml delete mode 100644 tests/flattener_dicos/10valid_enum_checkval_true/tiramisu/base.py create mode 100644 tests/flattener_dicos/40hidden_if_in_group_fallback/00-base.xml create mode 100644 tests/flattener_dicos/40hidden_if_in_group_fallback/__init__.py create mode 100644 tests/flattener_dicos/40hidden_if_in_group_fallback/makedict/base.json create mode 100644 tests/flattener_dicos/40hidden_if_in_group_fallback/result/00-base.xml create mode 100644 tests/flattener_dicos/40hidden_if_in_group_fallback/tiramisu/__init__.py create mode 100644 tests/flattener_dicos/40hidden_if_in_group_fallback/tiramisu/base.py delete mode 100644 tests/flattener_dicos/80action_onlyone/00-base.xml delete mode 100644 tests/flattener_dicos/80action_onlyone/extra_dirs/extra/00-base.xml delete mode 100644 tests/flattener_dicos/80action_onlyone/extra_dirs/extra/01-base.xml delete mode 100644 tests/flattener_dicos/80auto_autosave/00-base.xml delete mode 100644 tests/flattener_dicos/80empty_typeeole_container/00_base.xml rename tests/flattener_dicos/{80auto_autofreeze => 80fill_autofreeze}/00-base.xml (79%) create mode 100644 tests/flattener_dicos/80fill_autofreeze/__init__.py delete mode 100644 tests/flattener_dicos/80fill_container/00-base.xml rename tests/flattener_dicos/{80auto_error => 80fill_error}/00-base.xml (74%) create mode 100644 tests/flattener_dicos/80fill_error/__init__.py rename tests/flattener_dicos/{80auto_multi => 80fill_multi}/00-base.xml (81%) create mode 100644 tests/flattener_dicos/80fill_multi/__init__.py rename tests/flattener_dicos/{80redefine_autoerror => 80redefine_fillerror}/00-base.xml (80%) rename tests/flattener_dicos/{80redefine_autoerror => 80redefine_fillerror}/01-base.xml (50%) create mode 100644 tests/flattener_dicos/80redefine_fillerror/__init__.py diff --git a/src/rougail/annotator.py b/src/rougail/annotator.py index fd962c5b..ee5106fc 100644 --- a/src/rougail/annotator.py +++ b/src/rougail/annotator.py @@ -11,8 +11,6 @@ import imp from .i18n import _ from .utils import normalize_family from .error import DictConsistencyError -from .xmlreflector import HIGH_COMPATIBILITY -from .config import variable_namespace #mode order is important modes_level = ('basic', 'normal', 'expert') @@ -66,6 +64,24 @@ CONVERSION = {'number': int} FREEZE_AUTOFREEZE_VARIABLE = 'module_instancie' +class SpaceAnnotator: + """Transformations applied on a CreoleObjSpace instance + """ + def __init__(self, objectspace, eosfunc_file): + self.objectspace = objectspace + self.force_not_mandatory = [] + GroupAnnotator(objectspace) + ServiceAnnotator(objectspace) + VariableAnnotator(objectspace) + ConstraintAnnotator(objectspace, + eosfunc_file, + self.force_not_mandatory, + ) + FamilyAnnotator(objectspace, + self.force_not_mandatory, + ) + + class ServiceAnnotator: """Manage service's object for example:: @@ -78,38 +94,43 @@ class ServiceAnnotator: """ def __init__(self, objectspace): - self.space = objectspace.space - self.paths = objectspace.paths self.objectspace = objectspace - self.grouplist_conditions = {} self.convert_services() def gen_family(self, name, + path, ): family = self.objectspace.family() family.name = name family.doc = name family.mode = None + self.objectspace.paths.add_family('services', + path, + family, + ) return family def convert_services(self): - if not hasattr(self.space, 'services'): + if not hasattr(self.objectspace.space, 'services'): return - if not hasattr(self.space.services, 'service'): - del self.space.services + if not hasattr(self.objectspace.space.services, 'service'): + del self.objectspace.space.services return - self.space.services.hidden = True + self.objectspace.space.services.hidden = True families = {} - for idx, service_name in enumerate(self.space.services.service.keys()): - service = self.space.services.service[service_name] + for idx, service_name in enumerate(self.objectspace.space.services.service.keys()): + service = self.objectspace.space.services.service[service_name] new_service = self.objectspace.service() for elttype, values in vars(service).items(): if elttype == 'name' or elttype in ERASED_ATTRIBUTES: setattr(new_service, elttype, values) continue eltname = elttype + 's' - family = self.gen_family(eltname) + path = '.'.join(['services', eltname]) + family = self.gen_family(eltname, + path, + ) if isinstance(values, dict): values = list(values.values()) family.family = self.make_group_from_elts(service_name, @@ -119,7 +140,7 @@ class ServiceAnnotator: ) setattr(new_service, elttype, family) families[service_name] = new_service - self.space.services.service = families + self.objectspace.space.services.service = families def make_group_from_elts(self, service_name, @@ -151,16 +172,11 @@ class ServiceAnnotator: c_name = elt.source else: c_name = elt.name - family = self.gen_family(c_name) - family.variable = [] subpath = '{}.{}'.format(path, c_name) + family = self.gen_family(c_name, subpath) + family.variable = [] listname = '{}list'.format(name) activate_path = '.'.join([subpath, 'activate']) - if elt in self.grouplist_conditions: - # FIXME transformer le activate qui disparait en boolean - self.objectspace.list_conditions.setdefault(listname, - {}).setdefault(self.grouplist_conditions[elt], - []).append(activate_path) for key in dir(elt): if key.startswith('_') or key.endswith('_type') or key in ERASED_ATTRIBUTES: continue @@ -213,7 +229,10 @@ class ServiceAnnotator: type_ = 'string' variable.type = type_ if type_ == 'symlink': - variable.opt = value + variable.opt = self.objectspace.paths.get_variable_path(value, + 'services', + ) +# variable.opt = value variable.multi = None else: variable.doc = key @@ -221,12 +240,12 @@ class ServiceAnnotator: val.type = type_ val.name = value variable.value = [val] - self.paths.add_variable('services', - path, - 'service', - False, - variable, - ) + self.objectspace.paths.add_variable('services', + path, + 'service', + False, + variable, + ) return variable def _reorder_elts(self, @@ -279,75 +298,62 @@ class ServiceAnnotator: 'for {}').format(file_.name)) -class SpaceAnnotator(object): - """Transformations applied on a CreoleObjSpace instance - """ - def __init__(self, objectspace, eosfunc_file): - self.paths = objectspace.paths - self.space = objectspace.space +class GroupAnnotator: + def __init__(self, + objectspace, + ): self.objectspace = objectspace - self.valid_enums = {} - self.force_value = {} - self.force_not_mandatory = [] - if eosfunc_file: - self.eosfunc = imp.load_source('eosfunc', eosfunc_file) - else: - self.eosfunc = None - if HIGH_COMPATIBILITY: - self.has_hidden_if_in_condition = [] - self.convert_variable() - self.convert_auto_freeze() + if not hasattr(self.objectspace.space, 'constraints') or not hasattr(self.objectspace.space.constraints, 'group'): + return self.convert_groups() - self.filter_check() - self.filter_condition() - self.convert_valid_enums() - self.convert_check() - self.convert_fill() - self.remove_empty_families() - self.change_variable_mode() - self.change_family_mode() - self.dynamic_families() - self.filter_separators() - self.absolute_path_for_symlink_in_services() - self.convert_helps() - if hasattr(self.space, 'constraints'): - del self.space.constraints.index - del self.space.constraints.namespace - if vars(self.space.constraints): - raise Exception('constraints again?') - del self.space.constraints - def absolute_path_for_symlink_in_services(self): - if not hasattr(self.space, 'services'): - return - for family_name, family in vars(self.space.services).items(): - if not isinstance(family, dict): - continue - for fam in family.values(): - for fam1_name, fam1 in vars(fam).items(): - if fam1_name == 'name' or fam1_name in ERASED_ATTRIBUTES: - continue - for fam2 in fam1.family: - for variable in fam2.variable: - if variable.type == 'symlink' and '.' not in variable.name: - variable.opt = self.paths.get_variable_path(variable.opt, - variable_namespace, - ) - - def convert_helps(self): - # FIXME l'aide doit etre dans la variable! - if not hasattr(self.space, 'help'): - return - helps = self.space.help - if hasattr(helps, 'variable'): - for hlp in helps.variable.values(): - variable = self.paths.get_variable_obj(hlp.name) - variable.help = hlp.text - if hasattr(helps, 'family'): - for hlp in helps.family.values(): - variable = self.paths.get_family_obj(hlp.name) - variable.help = hlp.text - del self.space.help + def convert_groups(self): # pylint: disable=C0111 + for group in self.objectspace.space.constraints.group: + leader_fullname = group.leader + leader_family_name = self.objectspace.paths.get_variable_family_name(leader_fullname) + leader_name = self.objectspace.paths.get_variable_name(leader_fullname) + namespace = self.objectspace.paths.get_variable_namespace(leader_fullname) + if '.' not in leader_fullname: + leader_fullname = '.'.join([namespace, leader_family_name, leader_fullname]) + follower_names = list(group.follower.keys()) + has_a_leader = False + ori_leader_family = self.objectspace.paths.get_family_obj(leader_fullname.rsplit('.', 1)[0]) + for variable in list(ori_leader_family.variable.values()): + if has_a_leader: + # it's a follower + self.manage_follower(namespace, + leader_family_name, + variable, + leader_name, + follower_names, + leader_space, + leader_is_hidden, + ) + ori_leader_family.variable.pop(variable.name) + if follower_names == []: + # no more follower + break + elif variable.name == leader_name: + # it's a leader + if isinstance(variable, self.objectspace.Leadership): + # append follower to an existed leadership + leader_space = variable + # if variable.hidden: + # leader_is_hidden = True + else: + leader_space = self.objectspace.Leadership() + leader_is_hidden = self.manage_leader(leader_space, + leader_family_name, + leader_name, + namespace, + variable, + group, + leader_fullname, + ) + has_a_leader = True + else: + raise DictConsistencyError(_('cannot found followers {}').format(follower_names)) + del self.objectspace.space.constraints.group def manage_leader(self, leader_space: 'Leadership', @@ -373,21 +379,23 @@ class SpaceAnnotator(object): variable.hidden = None if hasattr(group, 'description'): leader_space.doc = group.description - else: + elif hasattr(variable, 'description'): leader_space.doc = variable.description + else: + leader_space.doc = variable.name leader_path = namespace + '.' + leader_family_name + '.' + leader_name - self.paths.add_family(namespace, - leader_path, - leader_space, - ) - leader_family = self.space.variables[namespace].family[leader_family_name] + self.objectspace.paths.add_family(namespace, + leader_path, + leader_space, + ) + leader_family = self.objectspace.space.variables[namespace].family[leader_family_name] leader_family.variable[leader_name] = leader_space leader_space.variable.append(variable) - self.paths.set_leader(namespace, - leader_family_name, - leader_name, - leader_name, - ) + self.objectspace.paths.set_leader(namespace, + leader_family_name, + leader_name, + leader_name, + ) leader_space.path = leader_fullname return leader_is_hidden @@ -407,377 +415,176 @@ class SpaceAnnotator(object): variable.frozen = True variable.force_default_on_freeze = True leader_space.variable.append(variable) # pylint: disable=E1101 - self.paths.set_leader(namespace, - leader_family_name, - variable.name, - leader_name, - ) + self.objectspace.paths.set_leader(namespace, + leader_family_name, + variable.name, + leader_name, + ) - def convert_groups(self): # pylint: disable=C0111 - if hasattr(self.space, 'constraints') and hasattr(self.space.constraints, 'group'): - for group in self.space.constraints.group: - leader_fullname = group.leader - follower_names = list(group.follower.keys()) - leader_family_name = self.paths.get_variable_family_name(leader_fullname) - namespace = self.paths.get_variable_namespace(leader_fullname) - leader_name = self.paths.get_variable_name(leader_fullname) - ori_leader_family = self.space.variables[namespace].family[leader_family_name] - has_a_leader = False - for variable in list(ori_leader_family.variable.values()): - if isinstance(variable, self.objectspace.Leadership): - # append follower to an existed leadership - if variable.name == leader_name: - leader_space = variable - has_a_leader = True - else: - if has_a_leader: - # it's a follower - self.manage_follower(namespace, - leader_family_name, - variable, - leader_name, - follower_names, - leader_space, - leader_is_hidden, - ) - ori_leader_family.variable.pop(variable.name) - if follower_names == []: - # no more follower - break - elif variable.name == leader_name: - # it's a leader - leader_space = self.objectspace.Leadership() - leader_is_hidden = self.manage_leader(leader_space, - leader_family_name, - leader_name, - namespace, - variable, - group, - leader_fullname, - ) - has_a_leader = True - else: - raise DictConsistencyError(_('cannot found followers {}').format(follower_names)) - del self.space.constraints.group - def remove_empty_families(self): # pylint: disable=C0111,R0201 - if hasattr(self.space, 'variables'): - for family in self.space.variables.values(): - if hasattr(family, 'family'): - space = family.family - removed_families = [] - for family_name, family in space.items(): - if not hasattr(family, 'variable') or len(family.variable) == 0: - removed_families.append(family_name) - del space[family_name] - # remove help too - if hasattr(self.space, 'help') and hasattr(self.space.help, 'family'): - for family in self.space.help.family.keys(): - if family in removed_families: - del self.space.help.family[family] - - def change_family_mode(self): # pylint: disable=C0111 - if not hasattr(self.space, 'variables'): - return - for family in self.space.variables.values(): - if hasattr(family, 'family'): - for family in family.family.values(): - mode = modes_level[-1] - for variable in family.variable.values(): - if isinstance(variable, self.objectspace.Leadership): - variable_mode = variable.variable[0].mode - variable.variable[0].mode = None - variable.mode = variable_mode - else: - variable_mode = variable.mode - if variable_mode is not None and modes[mode] > modes[variable_mode]: - mode = variable_mode - family.mode = mode - - def dynamic_families(self): # pylint: disable=C0111 - if not hasattr(self.space, 'variables'): - return - for family in self.space.variables.values(): - if hasattr(family, 'family'): - for family in family.family.values(): - if 'dynamic' in vars(family): - namespace = self.paths.get_variable_namespace(family.dynamic) - varpath = self.paths.get_variable_path(family.dynamic, namespace) - family.dynamic = varpath - - def annotate_variable(self, variable, family_mode, path, is_follower=False): - # if the variable is mandatory and doesn't have any value - # then the variable's mode is set to 'basic' - has_value = hasattr(variable, 'value') - if variable.mandatory is True and (not has_value or is_follower): - variable.mode = modes_level[0] - if variable.mode != None and modes[variable.mode] < modes[family_mode] and (not is_follower or variable.mode != modes_level[0]): - variable.mode = family_mode - if has_value and path not in self.force_not_mandatory: - variable.mandatory = True - if variable.hidden is True: - variable.frozen = True - if not variable.auto_save is True and 'force_default_on_freeze' not in vars(variable): - variable.force_default_on_freeze = True +class VariableAnnotator: + def __init__(self, + objectspace, + ): + self.objectspace = objectspace + self.convert_variable() + self.convert_helps() + self.convert_auto_freeze() + self.convert_separators() def convert_variable(self): - if not hasattr(self.space, 'variables'): + def _convert_variable(variable): + if not hasattr(variable, 'type'): + variable.type = 'string' + if variable.type != 'symlink' and not hasattr(variable, 'description'): + variable.description = variable.name + if hasattr(variable, 'value'): + for value in variable.value: + if not hasattr(value, 'type'): + value.type = variable.type + + def _convert_valid_enum(namespace, + variable, + path, + ): + if variable.type in FORCE_CHOICE: + check = self.objectspace.check() + check.name = 'valid_enum' + check.target = path + check.namespace = namespace + param = self.objectspace.param() + param.text = str(FORCE_CHOICE[variable.type]) + check.param = [param] + if not hasattr(self.objectspace.space, 'constraints'): + self.objectspace.space.constraints = self.objectspace.constraints() + self.objectspace.space.constraints.namespace = namespace + if not hasattr(self.objectspace.space.constraints, 'check'): + self.objectspace.space.constraints.check = [] + self.objectspace.space.constraints.check.append(check) + variable.type = 'string' + + if not hasattr(self.objectspace.space, 'variables'): return - for families in self.space.variables.values(): + for families in self.objectspace.space.variables.values(): + namespace = families.name if hasattr(families, 'family'): for family in families.family.values(): if hasattr(family, 'variable'): for variable in family.variable.values(): - if not hasattr(variable, 'type'): - variable.type = 'string' - if variable.type != 'symlink' and not hasattr(variable, 'description'): - variable.description = variable.name - if hasattr(variable, 'value'): - for value in variable.value: - if not hasattr(value, 'type'): - value.type = variable.type + if isinstance(variable, self.objectspace.Leadership): + for follower in variable.variable: + path = '{}.{}.{}.{}'.format(namespace, normalize_family(family.name), variable.name, follower.name) + _convert_variable(follower) + _convert_valid_enum(namespace, + follower, + path, + ) + else: + path = '{}.{}.{}'.format(namespace, normalize_family(family.name), variable.name) + _convert_variable(variable) + _convert_valid_enum(namespace, + variable, + path, + ) + + def convert_helps(self): + if not hasattr(self.objectspace.space, 'help'): + return + helps = self.objectspace.space.help + if hasattr(helps, 'variable'): + for hlp in helps.variable.values(): + variable = self.objectspace.paths.get_variable_obj(hlp.name) + variable.help = hlp.text + if hasattr(helps, 'family'): + for hlp in helps.family.values(): + variable = self.objectspace.paths.get_family_obj(hlp.name) + variable.help = hlp.text + del self.objectspace.space.help def convert_auto_freeze(self): # pylint: disable=C0111 - if hasattr(self.space, 'variables'): - for variables in self.space.variables.values(): + def _convert_auto_freeze(variable, namespace): + if variable.auto_freeze: + new_condition = self.objectspace.condition() + new_condition.name = 'auto_hidden_if_not_in' + new_condition.namespace = namespace + new_condition.source = FREEZE_AUTOFREEZE_VARIABLE + new_param = self.objectspace.param() + new_param.text = 'oui' + new_condition.param = [new_param] + new_target = self.objectspace.target() + new_target.type = 'variable' + path = variable.namespace + '.' + normalize_family(family.name) + '.' + variable.name + new_target.name = path + new_condition.target = [new_target] + if not hasattr(self.objectspace.space.constraints, 'condition'): + self.objectspace.space.constraints.condition = [] + self.objectspace.space.constraints.condition.append(new_condition) + if hasattr(self.objectspace.space, 'variables'): + 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 variable.auto_freeze: - new_condition = self.objectspace.condition() - new_condition.name = 'auto_hidden_if_not_in' - new_condition.namespace = variables.name - new_condition.source = FREEZE_AUTOFREEZE_VARIABLE - new_param = self.objectspace.param() - new_param.text = 'oui' - new_condition.param = [new_param] - new_target = self.objectspace.target() - new_target.type = 'variable' - if variables.name == variable_namespace: - path = variable.name - else: - path = variable.namespace + '.' + family.name + '.' + variable.name - new_target.name = path - new_condition.target = [new_target] - if not hasattr(self.space.constraints, 'condition'): - self.space.constraints.condition = [] - self.space.constraints.condition.append(new_condition) + if isinstance(variable, self.objectspace.Leadership): + for follower in variable.variable: + _convert_auto_freeze(follower, namespace) + else: + _convert_auto_freeze(variable, namespace) - def _set_valid_enum(self, variable, values, type_): - variable.mandatory = True - variable.choice = [] - choices = [] - for value in values: - choice = self.objectspace.choice() - try: - if type_ in CONVERSION: - choice.name = CONVERSION[type_](value) - else: - choice.name = str(value) - except: - raise DictConsistencyError(_(f'unable to change type of a valid_enum entry "{value}" is not a valid "{type_}" for "{variable.name}"')) - choices.append(choice.name) - choice.type = type_ - variable.choice.append(choice) - if not variable.choice: - raise DictConsistencyError(_('empty valid enum is not allowed for variable {}').format(variable.name)) - if hasattr(variable, 'value'): - for value in variable.value: - value.type = type_ - if type_ in CONVERSION: - cvalue = CONVERSION[type_](value.name) - else: - cvalue = value.name - if cvalue not in choices: - raise DictConsistencyError(_('value "{}" of variable "{}" is not in list of all expected values ({})').format(value.name, variable.name, choices)) - else: - new_value = self.objectspace.value() - new_value.name = values[0] - new_value.type = type_ - variable.value = [new_value] - variable.type = 'choice' - - def _convert_valid_enum(self, variable, path): - if variable.type in FORCE_CHOICE: - if path in self.valid_enums: - raise DictConsistencyError(_('cannot set valid enum for variable with type {}').format(variable.type)) - self._set_valid_enum(variable, FORCE_CHOICE[variable.type], 'string') - if path in self.valid_enums: - values = self.valid_enums[path]['values'] - self._set_valid_enum(variable, values, variable.type) - del self.valid_enums[path] - if path in self.force_value: - new_value = self.objectspace.value() - new_value.name = self.force_value[path] - new_value.type = variable.type - variable.value = [new_value] - del self.force_value[path] - - def convert_valid_enums(self): # pylint: disable=C0111 - if not hasattr(self.space, 'variables'): + def convert_separators(self): # pylint: disable=C0111,R0201 + if not hasattr(self.objectspace.space, 'variables'): return - for variables in self.space.variables.values(): - namespace = variables.name - if hasattr(variables, 'family'): - 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: - path = '{}.{}.{}.{}'.format(namespace, family.name, variable.name, follower.name) - self._convert_valid_enum(follower, path) - else: - path = '{}.{}.{}'.format(namespace, family.name, variable.name) - self._convert_valid_enum(variable, path) - # valid_enums must be empty now (all information are store in objects) - if self.valid_enums: - raise DictConsistencyError(_('valid_enum sets for unknown variables {}').format(self.valid_enums.keys())) - - def change_variable_mode(self): # pylint: disable=C0111 - if not hasattr(self.space, 'variables'): - return - for variables in self.space.variables.values(): - if hasattr(variables, 'family'): - for family in variables.family.values(): - family_mode = family.mode - if hasattr(family, 'variable'): - for variable in family.variable.values(): - - if isinstance(variable, self.objectspace.Leadership): - mode = modes_level[-1] - for follower in variable.variable: - if follower.auto_save is True: - raise DictConsistencyError(_('leader/followers {} ' - 'could not be ' - 'auto_save').format(follower.name)) - if follower.auto_freeze is True: - raise DictConsistencyError(_('leader/followers {} ' - 'could not be ' - 'auto_freeze').format(follower.name)) - if HIGH_COMPATIBILITY and variable.name != follower.name: # and variable.variable[0].mode != modes_level[0]: - is_follower = True - else: - is_follower = False - path = '{}.{}.{}'.format(family.path, variable.name, follower.name) - self.annotate_variable(follower, family_mode, path, is_follower) - # leader's mode is minimum level - if modes[variable.variable[0].mode] > modes[follower.mode]: - follower.mode = variable.variable[0].mode - variable.mode = variable.variable[0].mode - else: - # auto_save's variable is set in 'basic' mode if its mode is 'normal' - if variable.auto_save is True and variable.mode != modes_level[-1]: - variable.mode = modes_level[0] - # auto_freeze's variable is set in 'basic' mode if its mode is 'normal' - if variable.auto_freeze is True and variable.mode != modes_level[-1]: - variable.mode = modes_level[0] - path = '{}.{}'.format(family.path, variable.name) - self.annotate_variable(variable, family_mode, path) - - def convert_fill(self): # pylint: disable=C0111,R0912 - if not hasattr(self.space, 'constraints') or not hasattr(self.space.constraints, 'fill'): - return - # sort fill/auto by index - fills = {fill.index: fill for idx, fill in enumerate(self.space.constraints.fill)} - indexes = list(fills.keys()) - indexes.sort() - targets = [] - eosfunc = dir(self.eosfunc) - for idx in indexes: - fill = fills[idx] - # test if it's redefined calculation - if fill.target in targets and not fill.redefine: - raise DictConsistencyError(_(f"A fill already exists for the target: {fill.target}")) - targets.append(fill.target) - # - if not fill.name in eosfunc: - raise DictConsistencyError(_('cannot find fill function {}').format(fill.name)) - - namespace = fill.namespace - # let's replace the target by the path - fill.target = self.paths.get_variable_path(fill.target, - namespace) - - value = self.objectspace.value() - 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 TYPE_PARAM_FILL: - raise DictConsistencyError(_(f'cannot use {param.type} type as a param in a fill/auto')) - if param.type != 'string' and not hasattr(param, 'text'): - raise DictConsistencyError(_(f"All '{param.type}' variables shall have a value in order to calculate {fill.target}")) - if param.type == 'variable': - try: - param.text, suffix = self.paths.get_variable_path(param.text, - namespace, - with_suffix=True) - if suffix: - param.suffix = suffix - except DictConsistencyError as 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 = self.paths.get_variable_obj(fill.target) - variable.value = [value] - del self.space.constraints.fill - - def filter_separators(self): # pylint: disable=C0111,R0201 - if not hasattr(self.space, 'variables'): - return - for family in self.space.variables.values(): + for family in self.objectspace.space.variables.values(): if not hasattr(family, 'separators'): continue if hasattr(family.separators, 'separator'): for idx, separator in enumerate(family.separators.separator): - option = self.paths.get_variable_obj(separator.name) + option = self.objectspace.paths.get_variable_obj(separator.name) if hasattr(option, 'separator'): - subpath = self.paths.get_variable_path(separator.name, - separator.namespace, - ) + subpath = self.objectspace.paths.get_variable_path(separator.name, + separator.namespace, + ) raise DictConsistencyError(_('{} already has a separator').format(subpath)) option.separator = separator.text del family.separators - def load_params_in_validenum(self, param): - if param.type in ['string', 'python', 'number']: - if not hasattr(param, 'text') and (param.type == 'python' or param.type == 'number'): - raise DictConsistencyError(_("All '{}' variables shall be set in order to calculate {}").format(param.type, 'valid_enum')) - if param.type in ['string', 'number']: - try: - values = literal_eval(param.text) - except ValueError: - raise DictConsistencyError(_('Cannot load {}').format(param.text)) - elif param.type == 'python': - try: - #values = eval(param.text, {'eosfunc': self.eosfunc, '__builtins__': {'range': range, 'str': str}}) - values = eval(param.text, {'eosfunc': self.eosfunc, '__builtins__': {'range': range, 'str': str}}) - except NameError: - raise DictConsistencyError(_('The function {} is unknown').format(param.text)) - if not isinstance(values, list): - raise DictConsistencyError(_('Function {} shall return a list').format(param.text)) - new_values = [] - for val in values: - new_values.append(val) - values = new_values - else: - values = param.text - return values + +class ConstraintAnnotator: + def __init__(self, + objectspace, + eosfunc_file, + force_not_mandatory, + ): + if not hasattr(objectspace.space, 'constraints'): + return + self.objectspace = objectspace + self.eosfunc = imp.load_source('eosfunc', eosfunc_file) + self.valid_enums = {} + self.force_not_mandatory = force_not_mandatory + 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_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() + self.remove_constraints() def check_check(self): remove_indexes = [] functions = dir(self.eosfunc) functions.extend(['valid_enum', 'valid_in_network', 'valid_differ']) - for check_idx, check in enumerate(self.space.constraints.check): + for check_idx, check in enumerate(self.objectspace.space.constraints.check): if not check.name in functions: raise DictConsistencyError(_('cannot find check function {}').format(check.name)) if hasattr(check, 'param'): @@ -785,7 +592,7 @@ class SpaceAnnotator(object): for idx, param in enumerate(check.param): if param.type not in TYPE_PARAM_CHECK: raise DictConsistencyError(_('cannot use {} type as a param in check for {}').format(param.type, check.target)) - if param.type == 'variable' and not self.paths.path_is_defined(param.text): + 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: @@ -800,90 +607,318 @@ class SpaceAnnotator(object): remove_indexes.append(check_idx) remove_indexes.sort(reverse=True) for idx in remove_indexes: - del self.space.constraints.check[idx] + del self.objectspace.space.constraints.check[idx] def check_replace_text(self): - for check_idx, check in enumerate(self.space.constraints.check): + for check_idx, check in enumerate(self.objectspace.space.constraints.check): if hasattr(check, 'param'): namespace = check.namespace for idx, param in enumerate(check.param): if param.type == 'variable': - param.text = self.paths.get_variable_path(param.text, namespace) - check.is_in_leadership = self.paths.get_leader(check.target) != None + param.text = self.objectspace.paths.get_variable_path(param.text, namespace) + check.is_in_leadership = self.objectspace.paths.get_leader(check.target) != None # let's replace the target by the path - check.target = self.paths.get_variable_path(check.target, namespace) + check.target = self.objectspace.paths.get_variable_path(check.target, namespace) def check_valid_enum(self): remove_indexes = [] - for idx, check in enumerate(self.space.constraints.check): + for idx, check in enumerate(self.objectspace.space.constraints.check): if check.name == 'valid_enum': - proposed_value_type = False - remove_params = [] - for param_idx, param in enumerate(check.param): - if hasattr(param, 'name') and param.name == 'checkval': - try: - proposed_value_type = self.objectspace.convert_boolean(param.text) == False - remove_params.append(param_idx) - except TypeError as err: - raise DictConsistencyError(_('cannot load checkval value for variable {}: {}').format(check.target, err)) - if proposed_value_type: - # no more supported - raise DictConsistencyError(_('cannot load checkval value for variable {}, no more supported').format(check.target)) - remove_params.sort(reverse=True) - for param_idx in remove_params: - del check.param[param_idx] if len(check.param) != 1: - raise DictConsistencyError(_('cannot set more than one param ' - 'for valid_enum for variable {}' - '').format(check.target)) + raise DictConsistencyError(_(f'cannot set more than one param for valid_enum for variable {check.target}')) param = check.param[0] if check.target in self.valid_enums: - raise DictConsistencyError(_('valid_enum already set for {}' - '').format(check.target)) - if proposed_value_type: - if param.type == 'variable': - try: - values = self.load_params_in_validenum(param) - except NameError as err: - raise DictConsistencyError(_('cannot load value for variable {}: {}').format(check.target, err)) - add_default_value = not check.is_in_leadership - if add_default_value and values: - self.force_value[check.target] = values[0] - else: - values = self.load_params_in_validenum(param) - self.valid_enums[check.target] = {'type': param.type, - 'values': values} - remove_indexes.append(idx) + raise DictConsistencyError(_(f'valid_enum already set for {check.target}')) + if param.type not in ['string', 'python', 'number']: + raise DictConsistencyError(_(f'unknown type {param.type} for param in valid_enum for {check.target}')) + variable = self.objectspace.paths.get_variable_obj(check.target) + values = self.load_params_in_validenum(param, + variable.name, + variable.type, + ) + self.valid_enums[check.target] = {'type': param.type, + 'values': values} + self._set_valid_enum(variable, + values, + variable.type, + ) + remove_indexes.append(idx) remove_indexes.sort(reverse=True) for idx in remove_indexes: - del self.space.constraints.check[idx] + del self.objectspace.space.constraints.check[idx] + + def load_params_in_validenum(self, + param, + variable_name, + variable_type, + ): + if not hasattr(param, 'text') and (param.type == 'python' or param.type == 'number'): + raise DictConsistencyError(_(f"All '{param.type}' variables shall be set in order to calculate valid_enum for variable {variable_name}")) + if variable_type == 'string' and param.type == 'number': + raise DictConsistencyError(_(f'Unconsistency valid_enum type ({param.type}), for variable {variable_name}')) + if param.type == 'python': + try: + values = eval(param.text, {'eosfunc': self.eosfunc, '__builtins__': {'range': range, 'str': str}}) + except NameError: + raise DictConsistencyError(_('The function {} is unknown').format(param.text)) + else: + try: + values = literal_eval(param.text) + except ValueError: + raise DictConsistencyError(_(f'Cannot load {param.text} in valid_enum')) + if not isinstance(values, list): + raise DictConsistencyError(_('Function {} shall return a list').format(param.text)) + for value in values: + if variable_type == 'string' and not isinstance(value, str): + raise DictConsistencyError(_(f'Cannot load "{param.text}", "{value}" is not a string')) + if variable_type == 'number' and not isinstance(value, int): + raise DictConsistencyError(_(f'Cannot load "{param.text}", "{value}" is not a number')) + return values def check_change_warning(self): #convert level to "warnings_only" - for check in self.space.constraints.check: + for check in self.objectspace.space.constraints.check: if check.level == 'warning': check.warnings_only = True else: check.warnings_only = False check.level = None - def filter_check(self): # pylint: disable=C0111 - # valid param in check - if not hasattr(self.space, 'constraints') or not hasattr(self.space.constraints, 'check'): - return - self.check_check() - self.check_replace_text() - self.check_valid_enum() - self.check_change_warning() - if not self.space.constraints.check: - del self.space.constraints.check + 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_obj(target.name.rsplit('.', 1)[0]) + if isinstance(family, self.objectspace.Leadership) and family.name == variable.name: + return family, family.variable + return variable, [variable] + # it's a family + variable = self.objectspace.paths.get_family_obj(target.name) + return variable, list(variable.variable.values()) + def check_params_target(self): + for condition in self.objectspace.space.constraints.condition: + for param in condition.param: + if param.type not in TYPE_PARAM_CONDITION: + raise DictConsistencyError(_(f'cannot use {param.type} type as a param in a condition')) + if not hasattr(condition, 'target'): + raise DictConsistencyError(_('target is mandatory in condition')) + for target in condition.target: + if target.type.endswith('list') and condition.name not in ['disabled_if_in', 'disabled_if_not_in']: + raise DictConsistencyError(_(f'target in condition for {target.type} not allow in {condition.name}')) + + def filter_targets(self): # pylint: disable=C0111 + for condition_idx, condition in enumerate(self.objectspace.space.constraints.condition): + namespace = condition.namespace + for idx, target in enumerate(condition.target): + if target.type == 'variable': + if condition.source == target.name: + raise DictConsistencyError(_('target name and source name must be different: {}').format(condition.source)) + try: + target.name = self.objectspace.paths.get_variable_path(target.name, namespace) + except DictConsistencyError: + # for optional variable + pass + elif target.type == 'family': + try: + target.name = self.objectspace.paths.get_family_path(target.name, namespace) + except KeyError: + raise DictConsistencyError(_('cannot found family {}').format(target.name)) + + def convert_xxxlist_to_variable(self): # pylint: disable=C0111 + # transform *list to variable or family + for condition_idx, condition in enumerate(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() + new_target.type = type_ + new_target.name = listvar + new_target.index = target.index + 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 + 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) + continue + + remove_targets = [] + # optional + for idx, target in enumerate(condition.target): + if target.optional is True and not self.objectspace.paths.path_is_defined(target.name): + remove_targets.append(idx) + remove_targets = list(set(remove_targets)) + remove_targets.sort(reverse=True) + for idx in remove_targets: + condition.target.pop(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 _get_condition_actions(self, condition_name): + if condition_name.startswith('disabled_if_'): + return ['disabled'] + elif condition_name.startswith('hidden_if_'): + return ['hidden', 'frozen', 'force_default_on_freeze'] + elif condition_name.startswith('mandatory_if_'): + return ['mandatory'] + elif condition_name == 'auto_hidden_if_not_in': + return ['auto_frozen'] + + 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 = self.objectspace.paths.get_variable_path(condition.source, namespace, allow_source=True) + src_variable = self.objectspace.paths.get_variable_obj(condition.source) + valid_enum = None + 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.sort(reverse=True) + for idx in remove_param: + del condition.param[idx] + if condition.param == []: + remove_targets = [] + 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': + variable.mandatory = True + remove_targets = list(set(remove_targets)) + remove_targets.sort(reverse=True) + for target_idx in remove_targets: + condition.target.pop(target_idx) + 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_conditions = [] + for condition_idx, condition in enumerate(self.objectspace.space.constraints.condition): + if not condition.target: + 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 convert_condition(self): + for condition in self.objectspace.space.constraints.condition: + inverse = condition.name.endswith('_if_not_in') + actions = self._get_condition_actions(condition.name) + for param in condition.param: + if hasattr(param, 'text'): + param = param.text + else: + param = 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 + if hasattr(leader_or_variable, actions[0]) and getattr(leader_or_variable, actions[0]) is True: + continue + for idx, action in enumerate(actions): + prop = self.objectspace.property_() + prop.type = 'calculation' + prop.inverse = inverse + prop.source = condition.source + prop.expected = param + prop.name = action + if idx == 0: + if not hasattr(leader_or_variable, 'property'): + leader_or_variable.property = [] + leader_or_variable.property.append(prop) + else: + for variable in variables: + if not hasattr(variable, 'property'): + variable.property = [] + variable.property.append(prop) + del self.objectspace.space.constraints.condition + + def _set_valid_enum(self, variable, values, type_): + # value for choice's variable is mandatory + variable.mandatory = True + # build choice + variable.choice = [] + choices = [] + for value in values: + choice = self.objectspace.choice() + try: + choice.name = CONVERSION.get(type_, str)(value) + except: + raise DictConsistencyError(_(f'unable to change type of a valid_enum entry "{value}" is not a valid "{type_}" for "{variable.name}"')) + choices.append(choice.name) + choice.type = type_ + variable.choice.append(choice) + if not variable.choice: + raise DictConsistencyError(_('empty valid enum is not allowed for variable {}').format(variable.name)) + # check value or set first choice value has default value + if hasattr(variable, 'value'): + for value in variable.value: + value.type = type_ + try: + cvalue = CONVERSION.get(type_, str)(value.name) + except: + raise DictConsistencyError(_(f'unable to change type of value "{value}" is not a valid "{type_}" for "{variable.name}"')) + if cvalue not in choices: + raise DictConsistencyError(_('value "{}" of variable "{}" is not in list of all expected values ({})').format(value.name, variable.name, choices)) + else: + new_value = self.objectspace.value() + new_value.name = values[0] + new_value.type = type_ + variable.value = [new_value] + variable.type = 'choice' def convert_check(self): - if not hasattr(self.space, 'constraints') or not hasattr(self.space.constraints, 'check'): - return - for check in self.space.constraints.check: - variable = self.paths.get_variable_obj(check.target) + for check in self.objectspace.space.constraints.check: + variable = self.objectspace.paths.get_variable_obj(check.target) check_ = self.objectspace.check() name = check.name if name == 'valid_differ': @@ -912,239 +947,168 @@ class SpaceAnnotator(object): if not hasattr(variable, 'check'): variable.check = [] variable.check.append(check_) - del self.space.constraints.check + del self.objectspace.space.constraints.check - def filter_targets(self): # pylint: disable=C0111 - for condition_idx, condition in enumerate(self.space.constraints.condition): - namespace = condition.namespace - for idx, target in enumerate(condition.target): - if target.type == 'variable': - if condition.source == target.name: - raise DictConsistencyError(_('target name and source name must be different: {}').format(condition.source)) - target.name = self.paths.get_variable_path(target.name, namespace) - elif target.type == 'family': - try: - target.name = self.paths.get_family_path(target.name, namespace) - except KeyError: - raise DictConsistencyError(_('cannot found family {}').format(target.name)) + 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() + targets = [] + eosfunc = dir(self.eosfunc) + for idx in indexes: + fill = fills[idx] + # test if it's redefined calculation + if fill.target in targets and not fill.redefine: + raise DictConsistencyError(_(f"A fill already exists for the target: {fill.target}")) + targets.append(fill.target) + # + if fill.name not in eosfunc: + raise DictConsistencyError(_('cannot find fill function {}').format(fill.name)) - def convert_xxxlist_to_variable(self): # pylint: disable=C0111 - # transform *list to variable or family - for condition_idx, condition in enumerate(self.space.constraints.condition): - new_targets = [] - remove_targets = [] - for target_idx, target in enumerate(condition.target): - if target.type not in ['variable', 'family']: - listname = target.type - if not listname.endswith('list'): - raise Exception('not yet implemented') - listvars = self.objectspace.list_conditions.get(listname, - {}).get(target.name) - if listvars: - for listvar in listvars: - variable = self.paths.get_variable_obj(listvar) - type_ = 'variable' - new_target = self.objectspace.target() - new_target.type = type_ - new_target.name = listvar - new_target.index = target.index - new_targets.append(new_target) - remove_targets.append(target_idx) - remove_targets = list(set(remove_targets)) - remove_targets.sort(reverse=True) - for target_idx in remove_targets: - condition.target.pop(target_idx) - condition.target.extend(new_targets) + namespace = fill.namespace + # let's replace the target by the path + fill.target = self.objectspace.paths.get_variable_path(fill.target, + namespace, + ) - def check_condition(self): - for condition in self.space.constraints.condition: - if condition.name not in ['disabled_if_in', 'disabled_if_not_in', 'hidden_if_in', 'auto_hidden_if_not_in', - 'hidden_if_not_in', 'mandatory_if_in', 'mandatory_if_not_in']: - raise DictConsistencyError(_(f'unknown condition {condition.name}')) - - def check_params(self): - for condition in self.space.constraints.condition: - for param in condition.param: - if param.type not in TYPE_PARAM_CONDITION: - raise DictConsistencyError(_(f'cannot use {param.type} type as a param in a condition')) - - def check_target(self): - for condition in self.space.constraints.condition: - if not hasattr(condition, 'target'): - raise DictConsistencyError(_('target is mandatory in condition')) - for target in condition.target: - if target.type.endswith('list') and condition.name not in ['disabled_if_in', 'disabled_if_not_in']: - raise DictConsistencyError(_(f'target in condition for {target.type} not allow in {condition.name}')) - - 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.space.constraints.condition): - remove_targets = [] - if condition.fallback is True and not self.paths.path_is_defined(condition.source): - for target in condition.target: - if target.type in ['variable', 'family']: - if target.name.startswith(variable_namespace + '.'): - name = target.name.split('.')[-1] - else: - name = target.name - if target.type == 'variable': - variable = self.paths.get_variable_obj(name) - else: - variable = self.paths.get_family_obj(name) - if condition.name == 'disabled_if_in': - variable.disabled = True - if condition.name == 'mandatory_if_in': - variable.mandatory = True - if condition.name == 'hidden_if_in': - variable.hidden = True + value = self.objectspace.value() + 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 TYPE_PARAM_FILL: + raise DictConsistencyError(_(f'cannot use {param.type} type as a param in a fill/auto')) + if param.type != 'string' and not hasattr(param, 'text'): + raise DictConsistencyError(_(f"All '{param.type}' variables shall have a value in order to calculate {fill.target}")) + 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 param.optional is False: + raise err + param_to_delete.append(fill_idx) + continue else: - listname = target.type - listvars = self.objectspace.list_conditions.get(listname, - {}).get(target.name, None) - if listvars is not None: - for listvar in listvars: - variable = self.paths.get_variable_obj(listvar) - if condition.name in ['disabled_if_in']: - variable.value[0].name = False - del self.objectspace.list_conditions[listname][target.name] - remove_conditions.append(idx) - for idx, target in enumerate(condition.target): - if target.optional is True and not self.paths.path_is_defined(target.name): - remove_targets.append(idx) - remove_targets = list(set(remove_targets)) - remove_targets.sort(reverse=True) - for idx in remove_targets: - condition.target.pop(idx) - remove_conditions = list(set(remove_conditions)) - remove_conditions.sort(reverse=True) - for idx in remove_conditions: - self.space.constraints.condition.pop(idx) + 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 = self.objectspace.paths.get_variable_obj(fill.target) + variable.value = [value] + del self.objectspace.space.constraints.fill - def check_choice_option_condition(self): - # remove condition for ChoiceOption that don't have param - remove_conditions = [] - for condition_idx, condition in enumerate(self.space.constraints.condition): - namespace = condition.namespace - src_variable = self.paths.get_variable_obj(condition.source) - condition.source = self.paths.get_variable_path(condition.source, namespace, allow_source=True) - valid_enum = None - if condition.source in self.valid_enums and \ - self.valid_enums[condition.source]['type'] == 'string': - valid_enum = self.valid_enums[condition.source]['values'] - if src_variable.type in FORCE_CHOICE: - valid_enum = FORCE_CHOICE[src_variable.type] - 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.sort(reverse=True) - for idx in remove_param: - del condition.param[idx] - if condition.param == []: - remove_targets = [] - for target in condition.target: - if target.name.startswith(f'{variable_namespace}.'): - name = target.name.split('.')[-1] - else: - name = target.name - if target.type == 'variable': - variable = self.paths.get_variable_obj(name) - else: - variable = self.paths.get_family_obj(name) - if condition.name == 'disabled_if_not_in': - variable.disabled = True - elif condition.name == 'hidden_if_not_in': - variable.hidden = True - elif condition.name == 'mandatory_if_not_in': - variable.mandatory = True - remove_targets = list(set(remove_targets)) - remove_targets.sort(reverse=True) - for target_idx in remove_targets: - condition.target.pop(target_idx) - remove_conditions.append(condition_idx) - remove_conditions = list(set(remove_conditions)) - remove_conditions.sort(reverse=True) - for idx in remove_conditions: - self.space.constraints.condition.pop(idx) + def remove_constraints(self): + if hasattr(self.objectspace.space.constraints, 'index'): + del self.objectspace.space.constraints.index + del self.objectspace.space.constraints.namespace + if vars(self.objectspace.space.constraints): + raise Exception('constraints again?') + del self.objectspace.space.constraints - def manage_variable_property(self): - for condition in self.space.constraints.condition: - #parse each variable and family - for target_idx, target in enumerate(condition.target): - if target.name.startswith(f'{variable_namespace}.'): - name = target.name.split('.')[-1] - else: - name = target.name - if target.type == 'variable': - variable = self.paths.get_variable_obj(name) - else: - variable = self.paths.get_family_obj(name) - if condition.name in ['hidden_if_in', 'hidden_if_not_in']: - variable.hidden = False - if condition.name in ['mandatory_if_in', 'mandatory_if_not_in']: - variable.mandatory = False - if HIGH_COMPATIBILITY and condition.name in ['hidden_if_in', - 'hidden_if_not_in']: - self.has_hidden_if_in_condition.append(name) - if condition.name in ['mandatory_if_in', 'mandatory_if_not_in']: - self.force_not_mandatory.append(target.name) - def remove_condition_with_empty_target(self): - remove_conditions = [] - for condition_idx, condition in enumerate(self.space.constraints.condition): - if not condition.target: - remove_conditions.append(condition_idx) - remove_conditions = list(set(remove_conditions)) - remove_conditions.sort(reverse=True) - for idx in remove_conditions: - self.space.constraints.condition.pop(idx) +class FamilyAnnotator: + def __init__(self, objectspace, force_not_mandatory): + self.objectspace = objectspace + self.force_not_mandatory = force_not_mandatory + self.remove_empty_families() + self.change_variable_mode() + self.change_family_mode() + self.dynamic_families() - def filter_condition(self): # pylint: disable=C0111 - if not hasattr(self.space, 'constraints') or not hasattr(self.space.constraints, 'condition'): + def remove_empty_families(self): # pylint: disable=C0111,R0201 + if hasattr(self.objectspace.space, 'variables'): + for family in self.objectspace.space.variables.values(): + if hasattr(family, 'family'): + space = family.family + removed_families = [] + for family_name, family in space.items(): + if not hasattr(family, 'variable') or len(family.variable) == 0: + removed_families.append(family_name) + del space[family_name] + + def change_family_mode(self): # pylint: disable=C0111 + if not hasattr(self.objectspace.space, 'variables'): return - self.check_condition() - self.check_params() - self.check_target() - self.check_condition_fallback_optional() - self.filter_targets() - self.convert_xxxlist_to_variable() - self.check_choice_option_condition() - self.manage_variable_property() - self.remove_condition_with_empty_target() - for condition in self.space.constraints.condition: - inverse = condition.name.endswith('_if_not_in') - if condition.name.startswith('disabled_if_'): - actions = ['disabled'] - elif condition.name.startswith('hidden_if_'): - actions = ['frozen', 'hidden', 'force_default_on_freeze'] - elif condition.name.startswith('mandatory_if_'): - actions = ['mandatory'] - elif condition.name == 'auto_hidden_if_not_in': - actions = ['auto_frozen'] - for param in condition.param: - if hasattr(param, 'text'): - param = param.text - else: - param = None - for target in condition.target: - if target.name.startswith(f'{variable_namespace}.'): - name = target.name.split('.')[-1] - else: - name = target.name - if target.type == 'variable': - variable = self.paths.get_variable_obj(name) - else: - variable = self.paths.get_family_obj(name) - if not hasattr(variable, 'property'): - variable.property = [] - for action in actions: - prop = self.objectspace.property_() - prop.type = 'calculation' - prop.inverse = inverse - prop.source = condition.source - prop.expected = param - prop.name = action - variable.property.append(prop) - del self.space.constraints.condition + for family in self.objectspace.space.variables.values(): + if hasattr(family, 'family'): + for family in family.family.values(): + mode = modes_level[-1] + for variable in family.variable.values(): + if isinstance(variable, self.objectspace.Leadership): + variable_mode = variable.variable[0].mode + variable.variable[0].mode = None + variable.mode = variable_mode + else: + variable_mode = variable.mode + if variable_mode is not None and modes[mode] > modes[variable_mode]: + mode = variable_mode + family.mode = mode + + def dynamic_families(self): # pylint: disable=C0111 + if not hasattr(self.objectspace.space, 'variables'): + return + for family in self.objectspace.space.variables.values(): + if hasattr(family, 'family'): + for family in family.family.values(): + if 'dynamic' in vars(family): + namespace = self.objectspace.paths.get_variable_namespace(family.dynamic) + varpath = self.objectspace.paths.get_variable_path(family.dynamic, namespace) + family.dynamic = varpath + + def annotate_variable(self, variable, family_mode, path, is_follower=False): + # if the variable is mandatory and doesn't have any value + # then the variable's mode is set to 'basic' + has_value = hasattr(variable, 'value') + if variable.mandatory is True and (not has_value or is_follower): + variable.mode = modes_level[0] + if variable.mode != None and modes[variable.mode] < modes[family_mode] and (not is_follower or variable.mode != modes_level[0]): + variable.mode = family_mode + if has_value and path not in self.force_not_mandatory: + variable.mandatory = True + if variable.hidden is True: + variable.frozen = True + if not variable.auto_save is True and 'force_default_on_freeze' not in vars(variable): + variable.force_default_on_freeze = True + + def change_variable_mode(self): # pylint: disable=C0111 + if not hasattr(self.objectspace.space, 'variables'): + return + for variables in self.objectspace.space.variables.values(): + namespace = variables.name + if hasattr(variables, 'family'): + for family in variables.family.values(): + family_mode = family.mode + if hasattr(family, 'variable'): + for variable in family.variable.values(): + + if isinstance(variable, self.objectspace.Leadership): + mode = modes_level[-1] + for idx, follower in enumerate(variable.variable): + if follower.auto_save is True: + raise DictConsistencyError(_(f'leader/followers {follower.name} could not be auto_save')) + if follower.auto_freeze is True: + raise DictConsistencyError(_('leader/followers {follower.name} could not be auto_freeze')) + is_follower = idx != 0 + path = '{}.{}.{}'.format(family.path, variable.name, follower.name) + self.annotate_variable(follower, family_mode, path, is_follower) + # leader's mode is minimum level + if modes[variable.variable[0].mode] > modes[follower.mode]: + follower.mode = variable.variable[0].mode + variable.mode = variable.variable[0].mode + else: + # auto_save's variable is set in 'basic' mode if its mode is 'normal' + if variable.auto_save is True and variable.mode != modes_level[-1]: + variable.mode = modes_level[0] + # auto_freeze's variable is set in 'basic' mode if its mode is 'normal' + if variable.auto_freeze is True and variable.mode != modes_level[-1]: + variable.mode = modes_level[0] + path = '{}.{}'.format(family.path, variable.name) + self.annotate_variable(variable, family_mode, path) diff --git a/src/rougail/data/rougail.dtd b/src/rougail/data/rougail.dtd index 1f50c910..51eb9a47 100644 --- a/src/rougail/data/rougail.dtd +++ b/src/rougail/data/rougail.dtd @@ -129,9 +129,11 @@ - + + + diff --git a/src/rougail/objspace.py b/src/rougail/objspace.py index c21a0690..955229ef 100644 --- a/src/rougail/objspace.py +++ b/src/rougail/objspace.py @@ -27,8 +27,8 @@ from collections import OrderedDict from lxml.etree import Element, SubElement # pylint: disable=E0611 from .i18n import _ -from .xmlreflector import XMLReflector, HIGH_COMPATIBILITY -from .annotator import ERASED_ATTRIBUTES, ServiceAnnotator, SpaceAnnotator +from .xmlreflector import XMLReflector +from .annotator import ERASED_ATTRIBUTES, SpaceAnnotator from .utils import normalize_family from .error import OperationError, SpaceObjShallNotBeUpdated, DictConsistencyError from .path import Path @@ -183,7 +183,7 @@ class CreoleObjSpace: family_names.append(child.attrib['name']) if child.tag == 'variables': child.attrib['name'] = namespace - if HIGH_COMPATIBILITY and child.tag == 'value' and child.text == None: + if child.tag == 'value' and child.text == None: # FIXME should not be here continue # variable objects creation @@ -444,8 +444,6 @@ class CreoleObjSpace: ): redefine = self.convert_boolean(child.attrib.get('redefine', False)) has_value = hasattr(variableobj, 'value') - if HIGH_COMPATIBILITY and has_value: - has_value = len(child) != 1 or child[0].text != None if redefine is True and child.tag == 'variable' and has_value and len(child) != 0: del variableobj.value for attr, val in child.attrib.items(): @@ -522,7 +520,6 @@ class CreoleObjSpace: variableobj.path = self.paths.get_family_path(family_name, namespace) def space_visitor(self, eosfunc_file): # pylint: disable=C0111 - ServiceAnnotator(self) SpaceAnnotator(self, eosfunc_file) def save(self, filename, force_no_save=False): diff --git a/src/rougail/path.py b/src/rougail/path.py index abcf4f84..b3796a57 100644 --- a/src/rougail/path.py +++ b/src/rougail/path.py @@ -13,6 +13,7 @@ class Path: def __init__(self): self.variables = {} self.families = {} + self.full_paths = {} # Family def add_family(self, @@ -20,33 +21,40 @@ class Path: name: str, variableobj: str, ) -> str: # pylint: disable=C0111 - self.families[name] = dict(name=name, - namespace=namespace, - variableobj=variableobj, - ) + if '.' not in name and namespace == variable_namespace: + full_name = '.'.join([namespace, name]) + self.full_paths[name] = full_name + else: + full_name = name + self.families[full_name] = dict(name=name, + namespace=namespace, + variableobj=variableobj, + ) def get_family_path(self, name: str, current_namespace: str, ) -> str: # pylint: disable=C0111 + name = normalize_family(name, + check_name=False, + allow_dot=True, + ) + if '.' not in name and current_namespace == variable_namespace and name in self.full_paths: + name = self.full_paths[name] if current_namespace is None: # pragma: no cover raise OperationError('current_namespace must not be None') - dico = self.families[normalize_family(name, - check_name=False, - allow_dot=True, - )] + dico = self.families[name] if dico['namespace'] != variable_namespace and current_namespace != dico['namespace']: raise DictConsistencyError(_('A family located in the {} namespace ' 'shall not be used in the {} namespace').format( dico['namespace'], current_namespace)) - path = dico['name'] - if dico['namespace'] is not None and '.' not in dico['name']: - path = '.'.join([dico['namespace'], path]) - return path + return dico['name'] def get_family_obj(self, name: str, ) -> 'Family': # pylint: disable=C0111 + if '.' not in name and name in self.full_paths: + name = self.full_paths[name] if name not in self.families: raise DictConsistencyError(_('unknown family {}').format(name)) dico = self.families[name] @@ -59,23 +67,25 @@ class Path: name: str, leader_name: str, ) -> None: # pylint: disable=C0111 - if namespace != variable_namespace: - # need rebuild path and move object in new path - old_path = namespace + '.' + leader_family_name + '.' + name - dico = self._get_variable(old_path) - del self.variables[old_path] - new_path = namespace + '.' + leader_family_name + '.' + leader_name + '.' + name - self.add_variable(namespace, - new_path, - dico['family'], - False, - dico['variableobj'], - ) + # need rebuild path and move object in new path + old_path = namespace + '.' + leader_family_name + '.' + name + dico = self._get_variable(old_path) + del self.variables[old_path] + new_path = namespace + '.' + leader_family_name + '.' + leader_name + '.' + name + self.add_variable(namespace, + new_path, + dico['family'], + False, + dico['variableobj'], + ) + if namespace == variable_namespace: + self.full_paths[name] = new_path + else: name = new_path dico = self._get_variable(name) if dico['leader'] != None: raise DictConsistencyError(_('Already defined leader {} for variable' - ' {}'.format(dico['leader'], name))) + ' {}'.format(dico['leader'], name))) dico['leader'] = leader_name def get_leader(self, name): # pylint: disable=C0111 @@ -89,16 +99,19 @@ class Path: is_dynamic: bool, variableobj, ) -> str: # pylint: disable=C0111 - if namespace == variable_namespace or '.' in name: - varname = name + if '.' not in name: + full_name = '.'.join([namespace, family, name]) + self.full_paths[name] = full_name else: - varname = '.'.join([namespace, family, name]) - self.variables[varname] = dict(name=name, - family=family, - namespace=namespace, - leader=None, - is_dynamic=is_dynamic, - variableobj=variableobj) + full_name = name + if namespace == variable_namespace: + name = name.rsplit('.', 1)[1] + self.variables[full_name] = dict(name=name, + family=family, + namespace=namespace, + leader=None, + is_dynamic=is_dynamic, + variableobj=variableobj) def get_variable_name(self, name, @@ -154,6 +167,8 @@ class Path: def path_is_defined(self, name: str, ) -> str: # pylint: disable=C0111 + if '.' not in name and name not in self.variables and name in self.full_paths: + return True return name in self.variables def _get_variable(self, @@ -161,14 +176,20 @@ class Path: with_suffix: bool=False, ) -> str: if name not in self.variables: - if name.startswith(f'{variable_namespace}.'): - name = name.split('.')[-1] + if name not in self.variables: + if '.' not in name and name in self.full_paths: + name = self.full_paths[name] if name not in self.variables: for var_name, variable in self.variables.items(): if variable['is_dynamic'] and name.startswith(var_name): return variable, name[len(var_name):] + if '.' not in name: + for var_name, path in self.full_paths.items(): + if name.startswith(var_name): + variable = self.variables[self.full_paths[var_name]] + if variable['is_dynamic']: + return variable, name[len(var_name):] raise DictConsistencyError(_('unknown option {}').format(name)) if with_suffix: return self.variables[name], None return self.variables[name] - diff --git a/src/rougail/xmlreflector.py b/src/rougail/xmlreflector.py index 368fcfa8..81cc5d75 100644 --- a/src/rougail/xmlreflector.py +++ b/src/rougail/xmlreflector.py @@ -8,7 +8,6 @@ from lxml.etree import DTD, parse, tostring # , XMLParser from .i18n import _ from .error import DictConsistencyError -HIGH_COMPATIBILITY = True class XMLReflector(object): """Helper class for loading the Creole XML file, diff --git a/tests/flattener_dicos/10load_disabled_if_inaccent/__init__.py b/tests/__init__.py similarity index 100% rename from tests/flattener_dicos/10load_disabled_if_inaccent/__init__.py rename to tests/__init__.py diff --git a/tests/flattener_dicos/10autosave_hidden_frozenifin/00-base.xml b/tests/flattener_dicos/10autosave_hidden_frozenifin/00-base.xml index 825fc8eb..3a48d4d7 100644 --- a/tests/flattener_dicos/10autosave_hidden_frozenifin/00-base.xml +++ b/tests/flattener_dicos/10autosave_hidden_frozenifin/00-base.xml @@ -8,7 +8,7 @@ - @@ -25,8 +25,8 @@ non mandatory normal - frozen hidden + frozen force_default_on_freeze non diff --git a/tests/flattener_dicos/10load_frozenifin/tiramisu/base.py b/tests/flattener_dicos/10load_frozenifin/tiramisu/base.py index 5a4e9b0f..2ce0a2f3 100644 --- a/tests/flattener_dicos/10load_frozenifin/tiramisu/base.py +++ b/tests/flattener_dicos/10load_frozenifin/tiramisu/base.py @@ -3,8 +3,8 @@ from rougail.tiramisu import ConvertDynOptionDescription import imp func = imp.load_source('func', 'tests/flattener_dicos/../eosfunc/test.py') option_3 = ChoiceOption(properties=frozenset(['mandatory', 'normal']), doc='No change', multi=False, name='condition', default='non', values=('oui', 'non')) -option_4 = ChoiceOption(properties=frozenset(['mandatory', 'normal', Calculation(calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('hidden'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('force_default_on_freeze'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')}))]), doc='No change', multi=False, name='mode_conteneur_actif', default='non', values=('oui', 'non')) -option_5 = ChoiceOption(properties=frozenset(['mandatory', 'normal', Calculation(calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('hidden'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('force_default_on_freeze'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')}))]), doc='No change', multi=False, name='mode_conteneur_actif2', default='non', values=('oui', 'non')) +option_4 = ChoiceOption(properties=frozenset(['mandatory', 'normal', Calculation(calc_value, Params(ParamValue('hidden'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('force_default_on_freeze'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')}))]), doc='No change', multi=False, name='mode_conteneur_actif', default='non', values=('oui', 'non')) +option_5 = ChoiceOption(properties=frozenset(['mandatory', 'normal', Calculation(calc_value, Params(ParamValue('hidden'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')})), Calculation(calc_value, Params(ParamValue('force_default_on_freeze'), kwargs={'condition': ParamOption(option_3, todict=True), 'expected': ParamValue('oui')}))]), doc='No change', multi=False, name='mode_conteneur_actif2', default='non', values=('oui', 'non')) option_2 = OptionDescription(doc='general', name='general', properties=frozenset(['normal']), children=[option_3, option_4, option_5]) option_1 = OptionDescription(doc='rougail', name='rougail', children=[option_2]) option_0 = OptionDescription(name='baseoption', doc='baseoption', children=[option_1]) diff --git a/tests/flattener_dicos/10load_frozenifin_auto/00-base.xml b/tests/flattener_dicos/10load_frozenifin_auto/00-base.xml index 19e1647f..3a89b4b8 100644 --- a/tests/flattener_dicos/10load_frozenifin_auto/00-base.xml +++ b/tests/flattener_dicos/10load_frozenifin_auto/00-base.xml @@ -8,10 +8,10 @@ non -