diff --git a/src/rougail/annotator.py b/src/rougail/annotator.py index 08d9e502..7e2bfacc 100644 --- a/src/rougail/annotator.py +++ b/src/rougail/annotator.py @@ -1,5 +1,6 @@ # coding: utf-8 from copy import copy +from typing import List from collections import OrderedDict from os.path import join, basename @@ -184,7 +185,10 @@ class ServiceAnnotator: family.name = '{}{}'.format(name, index) family.variable = variables family.mode = None - self.paths.append('family', subpath, 'services', creoleobj=family) + self.paths.add_family('services', + subpath, + family, + ) families.append(family) return families @@ -230,7 +234,11 @@ class ServiceAnnotator: choices.append(choice) variable.choice = choices path = '{}.{}'.format(subpath, name) - self.paths.append('variable', path, 'services', 'service', variable) + self.paths.add_variable('services', + path, + 'service', + variable, + ) return variable def _update_file(self, file_, index, service_path): @@ -327,7 +335,10 @@ class ServiceAnnotator: family.name = '{}{}'.format(name, index) family.variable = variables family.mode = None - self.paths.append('family', subpath, 'services', creoleobj=family) + self.paths.add_family('services', + subpath, + family, + ) families.append(family) return families @@ -440,64 +451,108 @@ class SpaceAnnotator(object): variable.help = hlp.text del self.space.help + def manage_leader(self, + leader_space: 'Leadership', + leader_family_name: str, + leader_name: str, + namespace: str, + variable: 'Variable', + group: 'Group', + leader_fullname: str, + ) -> None: + # manage leader's variable + if variable.multi is not True: + raise CreoleDictConsistencyError(_('the variable {} in a group must be multi').format(variable.name)) + leader_space.variable = [] + leader_space.name = leader_name + leader_space.hidden = variable.hidden + variable.hidden = None + if hasattr(group, 'description'): + leader_space.doc = group.description + else: + leader_space.doc = variable.description + 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] + leader_family.variable[leader_name] = leader_space + leader_space.variable.append(variable) + self.paths.set_leader(namespace, + leader_family_name, + leader_name, + leader_name, + ) + leader_space.path = leader_fullname + + + def manage_slave(self, + namespace: str, + leader_family_name: str, + variable: 'Variable', + leader_name: str, + follower_names: List[str], + leader_space: 'Leadership', + ) -> None: + if variable.name != follower_names[0]: + raise CreoleDictConsistencyError(_('cannot found this follower {}').format(follower_names[0])) + follower_names.remove(variable.name) + # followers are multi + if not variable.multi: + raise CreoleDictConsistencyError(_('the variable {} in a group must be multi or submulti').format(variable.name)) + leader_space.variable.append(variable) # pylint: disable=E1101 + self.paths.set_leader(namespace, + leader_family_name, + variable.name, + leader_name, + ) + def convert_groups(self): # pylint: disable=C0111 - if hasattr(self.space, 'constraints'): - if hasattr(self.space.constraints, 'group'): - for group in self.space.constraints.group: - leader_fullname = group.master - follower_names = list(group.slave.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) - leader_family = self.space.variables[namespace].family[leader_family_name] - leader_path = namespace + '.' + leader_family_name - is_leader = False - for variable in list(leader_family.variable.values()): - if isinstance(variable, self.objectspace.Leadership): - # append follower to an existed leadership - if variable.name == leader_name: - leader_space = variable - is_leader = True - else: - if is_leader: - if variable.name == follower_names[0]: - # followers are multi - if not variable.multi: - raise CreoleDictConsistencyError(_('the variable {} in a group must be multi or submulti').format(variable.name)) - follower_names.remove(variable.name) - leader_family.variable.pop(variable.name) - leader_space.variable.append(variable) # pylint: disable=E1101 - if namespace == 'creole': - variable_fullpath = variable.name - else: - variable_fullpath = leader_path + '.' + variable.name - self.paths.set_leader(variable_fullpath, leader_name) - if follower_names == []: - break - else: - raise CreoleDictConsistencyError(_('cannot found this follower {}').format(follower_names[0])) - if is_leader is False and variable.name == leader_name: - leader_space = self.objectspace.Leadership() - leader_space.variable = [] - leader_space.name = leader_name - leader_space.hidden = variable.hidden - if hasattr(group, 'description'): - leader_space.doc = group.description - else: - leader_space.doc = variable.description - variable.hidden = None - self.paths.append('family', leader_path + '.' + leader_name, namespace, creoleobj=leader_space) - # manage leader's variable - if variable.multi is not True: - raise CreoleDictConsistencyError(_('the variable {} in a group must be multi').format(variable.name)) - leader_family.variable[leader_name] = leader_space - leader_space.variable.append(variable) # pylint: disable=E1101 - self.paths.set_leader(leader_fullname, leader_name) - leader_space.path = leader_fullname - is_leader = True + if hasattr(self.space, 'constraints') and hasattr(self.space.constraints, 'group'): + for group in self.space.constraints.group: + leader_fullname = group.master + follower_names = list(group.slave.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: - raise CreoleDictConsistencyError(_('cannot found followers {}').format(follower_names)) - del self.space.constraints.group + if has_a_leader: + # it's a slave + self.manage_slave(namespace, + leader_family_name, + variable, + leader_name, + follower_names, + leader_space, + ) + ori_leader_family.variable.pop(variable.name) + if follower_names == []: + # no more slave + break + elif variable.name == leader_name: + # it's a leader + leader_space = self.objectspace.Leadership() + self.manage_leader(leader_space, + leader_family_name, + leader_name, + namespace, + variable, + group, + leader_fullname, + ) + has_a_leader = True + else: + raise CreoleDictConsistencyError(_('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'): diff --git a/src/rougail/objspace.py b/src/rougail/objspace.py index 75d30dbb..306be9fd 100644 --- a/src/rougail/objspace.py +++ b/src/rougail/objspace.py @@ -194,7 +194,11 @@ class CreoleObjSpace(object): if 'variable' not in vars(family): family.variable = OrderedDict() family.variable[name] = variable_obj - self.paths.append('variable', name, namespace, family.name, variable_obj) + self.paths.add_variable(namespace, + name, + family.name, + variable_obj, + ) return variable_obj def remove_check(self, name): # pylint: disable=C0111 @@ -425,8 +429,10 @@ class CreoleObjSpace(object): if not isinstance(space, self.help): # pylint: disable=E1101 if child.tag == 'variable': family_name = normalize_family(document.attrib['name']) - self.paths.append('variable', child.attrib['name'], namespace, family_name, - creoleobj) + self.paths.add_variable(namespace, + child.attrib['name'], + family_name, + creoleobj) if child.attrib.get('redefine', 'False') == 'True': if namespace == 'creole': self.redefine_variables.append(child.attrib['name']) @@ -438,7 +444,10 @@ class CreoleObjSpace(object): family_name = normalize_family(child.attrib['name']) if namespace != 'creole': family_name = namespace + '.' + family_name - self.paths.append('family', family_name, namespace, creoleobj=creoleobj) + self.paths.add_family(namespace, + family_name, + creoleobj, + ) creoleobj.path = self.paths.get_family_path(family_name, namespace) def create_or_populate_from_xml(self, namespace, xmlfolders, from_zephir=None): @@ -575,7 +584,7 @@ class CreoleObjSpace(object): self._sub_xml_export(name, node, node_name, subspace, space) -class Path(object): +class Path: """Helper class to handle the `path` attribute of a CreoleObjSpace instance. @@ -585,28 +594,27 @@ class Path(object): self.variables = {} self.families = {} - def append(self, pathtype, name, namespace, family=None, creoleobj=None): # pylint: disable=C0111 - if pathtype == 'family': - self.families[name] = dict(name=name, namespace=namespace, creoleobj=creoleobj) - elif pathtype == 'variable': - if namespace == 'creole': - varname = name - else: - if '.' in name: - varname = name - else: - varname = '.'.join([namespace, family, name]) - self.variables[varname] = dict(name=name, family=family, namespace=namespace, - leader=None, creoleobj=creoleobj) - else: # pragma: no cover - raise Exception('unknown pathtype {}'.format(pathtype)) + # Family + def add_family(self, + namespace: str, + name: str, + creoleobj: str, + ) -> str: # pylint: disable=C0111 + self.families[name] = dict(name=name, + namespace=namespace, + creoleobj=creoleobj, + ) - def get_family_path(self, name, current_namespace): # pylint: disable=C0111 + def get_family_path(self, + name: str, + current_namespace: str, + ) -> str: # pylint: disable=C0111 if current_namespace is None: # pragma: no cover raise CreoleOperationError('current_namespace must not be None') dico = self.families[normalize_family(name, check_name=False, - allow_dot=True)] + allow_dot=True, + )] if dico['namespace'] != 'creole' and current_namespace != dico['namespace']: raise CreoleDictConsistencyError(_('A family located in the {} namespace ' 'shall not be used in the {} namespace').format( @@ -616,41 +624,100 @@ class Path(object): path = '.'.join([dico['namespace'], path]) return path - def get_family_namespace(self, name): # pylint: disable=C0111 + def get_family_namespace(self, + name: str, + ) -> str: # pylint: disable=C0111 dico = self.families[name] if dico['namespace'] is None: return dico['name'] return dico['namespace'] - def get_family_obj(self, name): # pylint: disable=C0111 + def get_family_obj(self, + name: str, + ) -> 'Family': # pylint: disable=C0111 if name not in self.families: raise CreoleDictConsistencyError(_('unknown family {}').format(name)) dico = self.families[name] return dico['creoleobj'] - def get_variable_name(self, name): # pylint: disable=C0111 + # Leadership + def set_leader(self, + namespace: str, + leader_family_name: str, + name: str, + leader_name: str, + ) -> None: # pylint: disable=C0111 + if namespace != 'creole': + # 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, + family=dico['family'], + creoleobj=dico['creoleobj']) + name = new_path dico = self._get_variable(name) - return dico['name'] + if dico['leader'] != None: + raise CreoleDictConsistencyError(_('Already defined leader {} for variable' + ' {}'.format(dico['leader'], name))) + dico['leader'] = leader_name - def get_variable_obj(self, name): # pylint: disable=C0111 - dico = self._get_variable(name) - return dico['creoleobj'] + def get_leader(self, name): # pylint: disable=C0111 + return self._get_variable(name)['leader'] - def get_variable_family_name(self, name): # pylint: disable=C0111 - dico = self._get_variable(name) - return dico['family'] + # Variable + def add_variable(self, + namespace: str, + name: str, + family: str=None, + creoleobj=None, + ) -> str: # pylint: disable=C0111 + if namespace == 'creole' or '.' in name: + varname = name + else: + varname = '.'.join([namespace, family, name]) + self.variables[varname] = dict(name=name, + family=family, + namespace=namespace, + leader=None, + creoleobj=creoleobj) - def get_variable_family_path(self, name): # pylint: disable=C0111 + def get_variable_name(self, + name, + ): # pylint: disable=C0111 + return self._get_variable(name)['name'] + + def get_variable_obj(self, + name:str, + ) -> 'Variable': # pylint: disable=C0111 + return self._get_variable(name)['creoleobj'] + + def get_variable_family_name(self, + name: str, + ) -> str: # pylint: disable=C0111 + return self._get_variable(name)['family'] + + def get_variable_family_path(self, + name: str, + ) -> str: # pylint: disable=C0111 dico = self._get_variable(name) list_path = [dico['namespace'], dico['family']] if dico['leader'] is not None: list_path.append(dico['leader']) return '.'.join(list_path) - def get_variable_namespace(self, name): # pylint: disable=C0111 + def get_variable_namespace(self, + name: str, + ) -> str: # pylint: disable=C0111 return self._get_variable(name)['namespace'] - def get_variable_path(self, name, current_namespace, allow_source=False): # pylint: disable=C0111 + def get_variable_path(self, + name: str, + current_namespace: str, + allow_source: str=False, + ) -> str: # pylint: disable=C0111 if current_namespace is None: # pragma: no cover raise CreoleOperationError('current_namespace must not be None') dico = self._get_variable(name) @@ -667,30 +734,17 @@ class Path(object): list_path.append(dico['name']) return '.'.join(list_path) - def path_is_defined(self, name): # pylint: disable=C0111 + def path_is_defined(self, + name: str, + ) -> str: # pylint: disable=C0111 return name in self.variables - def set_leader(self, name, leader): # pylint: disable=C0111 - dico = self._get_variable(name) - namespace = dico['namespace'] - if dico['leader'] != None: - raise CreoleDictConsistencyError(_('Already defined leader {} for variable' - ' {}'.format(dico['leader'], name))) - dico['leader'] = leader - if namespace != 'creole': - new_path = self.get_variable_path(name, namespace) - self.append('variable', new_path, namespace, family=dico['family'], creoleobj=dico['creoleobj']) - self.variables[new_path]['leader'] = leader - del self.variables[name] - - def _get_variable(self, name): + def _get_variable(self, + name: str, + ) -> str: if name not in self.variables: if name.startswith('creole.'): name = name.split('.')[-1] if name not in self.variables: raise CreoleDictConsistencyError(_('unknown option {}').format(name)) return self.variables[name] - - def get_leader(self, name): # pylint: disable=C0111 - dico = self._get_variable(name) - return dico['leader']