rougail/src/rougail/annotator/family.py

303 lines
13 KiB
Python

"""Annotate family
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from ..i18n import _
from ..error import DictConsistencyError
from ..utils import normalize_family
from .variable import Walk
class Mode: # pylint: disable=R0903
"""Class to manage mode level
"""
def __init__(self,
level: int,
) -> None:
self.level = level
def __gt__(self,
other: int,
) -> bool:
return other.level < self.level
class FamilyAnnotator(Walk):
"""Annotate family
"""
def __init__(self,
objectspace,
):
self.objectspace = objectspace
if not hasattr(self.objectspace.space, 'variables'):
return
self.modes = {name: Mode(idx) for idx, name in enumerate(self.objectspace.rougailconfig['modes_level'])}
self.remove_empty_families()
self.family_names()
self.change_modes()
self.dynamic_families()
self.convert_help()
def _has_variable(self,
family: 'self.objectspace.family',
) -> bool:
if hasattr(family, 'variable'):
for variable in family.variable.values():
if isinstance(variable, self.objectspace.family):
if self._has_variable(variable):
return True
else:
return True
return False
def remove_empty_families(self) -> None:
"""Remove all families without any variable
"""
for families in self.objectspace.space.variables.values():
removed_families = []
for family_name, family in families.variable.items():
if isinstance(family, self.objectspace.family) and not self._has_variable(family):
removed_families.append(family_name)
for family_name in removed_families:
del families.variable[family_name]
def family_names(self) -> None:
"""Set doc, path, ... to family
"""
for family in self.get_families():
if not hasattr(family, 'description'):
family.description = family.name
family.doc = family.description
del family.description
family.name = normalize_family(family.name)
def change_modes(self):
"""change the mode of variables
"""
modes_level = self.objectspace.rougailconfig['modes_level']
default_variable_mode = self.objectspace.rougailconfig['default_variable_mode']
if default_variable_mode not in modes_level:
msg = _(f'default variable mode "{default_variable_mode}" is not a valid mode, '
f'valid modes are {modes_level}')
raise DictConsistencyError(msg, 72, None)
default_family_mode = self.objectspace.rougailconfig['default_family_mode']
if default_family_mode not in modes_level:
msg = _(f'default family mode "{default_family_mode}" is not a valid mode, '
f'valid modes are {modes_level}')
raise DictConsistencyError(msg, 73, None)
families = list(self.get_families())
for family in families:
self.valid_mode(family)
self._set_default_mode(family)
families.reverse()
for family in families:
self._change_family_mode(family)
def valid_mode(self,
obj,
) -> None:
modes_level = self.objectspace.rougailconfig['modes_level']
if hasattr(obj, 'mode') and obj.mode not in modes_level:
msg = _(f'mode "{obj.mode}" for "{obj.name}" is not a valid mode, '
f'valid modes are {modes_level}')
raise DictConsistencyError(msg, 71, obj.xmlfiles)
def _set_default_mode(self,
family: 'self.objectspace.family',
) -> None:
if hasattr(family, 'mode') and 'mode' in vars(family):
family_mode = family.mode
else:
family_mode = None
if not hasattr(family, 'variable'):
return
for variable in family.variable.values():
self.valid_mode(variable)
if isinstance(variable, self.objectspace.family):
if family_mode and not self._has_mode(variable):
self._set_auto_mode(variable, family_mode)
continue
if isinstance(variable, self.objectspace.leadership):
func = self._set_default_mode_leader
else:
func = self._set_default_mode_variable
func(variable, family_mode)
@staticmethod
def _has_mode(obj) -> bool:
return 'mode' in vars(obj) and not hasattr(obj, 'mode_auto')
def _set_default_mode_variable(self,
variable: 'self.objectspace.variable',
family_mode: str,
) -> None:
# auto_save or auto_freeze variable is set to 'basic' mode
# if its mode is not defined by the user
if not self._has_mode(variable) and \
(variable.auto_save is True or variable.auto_freeze is True):
variable.mode = self.objectspace.rougailconfig['modes_level'][0]
# mandatory variable without value is a basic variable
elif not self._has_mode(variable) and \
variable.mandatory is True and \
not hasattr(variable, 'default') and \
not hasattr(variable, 'default_multi'):
variable.mode = self.objectspace.rougailconfig['modes_level'][0]
elif family_mode and not self._has_mode(variable):
self._set_auto_mode(variable, family_mode)
@staticmethod
def _set_auto_mode(obj, mode: str) -> None:
obj.mode = mode
obj.mode_auto = True
def _set_default_mode_leader(self,
leadership: 'self.objectspace.leadership',
family_mode: str,
) -> None:
leader_mode = None
for follower in leadership.variable:
self.valid_mode(follower)
if follower.auto_save is True:
msg = _(f'leader/followers "{follower.name}" could not be auto_save')
raise DictConsistencyError(msg, 29, leadership.xmlfiles)
if follower.auto_freeze is True:
msg = f'leader/followers "{follower.name}" could not be auto_freeze'
raise DictConsistencyError(_(msg), 30, leadership.xmlfiles)
if leader_mode is not None:
if hasattr(follower, 'mode'):
follower_mode = follower.mode
else:
follower_mode = self.objectspace.rougailconfig['default_variable_mode']
if self.modes[leader_mode] > self.modes[follower_mode]:
if self._has_mode(follower) and not self._has_mode(leadership.variable[0]):
# if follower has mode but not the leader
self._set_auto_mode(leadership.variable[0], follower_mode)
else:
# leader's mode is minimum level
if self._has_mode(follower):
msg = _(f'the follower "{follower.name}" is in "{follower_mode}" mode '
f'but leader have the higher mode "{leader_mode}"')
raise DictConsistencyError(msg, 63, follower.xmlfiles)
self._set_auto_mode(follower, leader_mode)
self._set_default_mode_variable(follower,
family_mode,
)
if leader_mode is None:
if hasattr(leadership.variable[0], 'mode'):
leader_mode = leadership.variable[0].mode
else:
leader_mode = self.objectspace.rougailconfig['default_variable_mode']
if hasattr(leadership.variable[0], 'mode'):
leader_mode = leadership.variable[0].mode
self._set_auto_mode(leadership, leader_mode)
def _change_family_mode(self,
family: 'self.objectspace.family',
) -> None:
if hasattr(family, 'mode'):
family_mode = family.mode
else:
family_mode = self.objectspace.rougailconfig['default_family_mode']
min_variable_mode = self.objectspace.rougailconfig['modes_level'][-1]
# change variable mode, but not if variables are not in a family
if hasattr(family, 'variable'):
for variable in family.variable.values():
if not isinstance(variable, self.objectspace.family):
if isinstance(variable, self.objectspace.leadership):
func = self._change_variable_mode_leader
else:
func = self._change_variable_mode
func(variable,
family_mode,
)
elif not hasattr(variable, 'mode'):
variable.mode = self.objectspace.rougailconfig['default_family_mode']
if self.modes[min_variable_mode] > self.modes[variable.mode]:
min_variable_mode = variable.mode
if isinstance(family, self.objectspace.family) and \
(not hasattr(family, 'mode') or family.mode != min_variable_mode):
# set the lower variable mode to family
if self._has_mode(family):
msg = _(f'the family "{family.name}" is in "{family.mode}" mode but variables and '
f'families inside have the higher modes "{min_variable_mode}"')
raise DictConsistencyError(msg, 62, family.xmlfiles)
self._set_auto_mode(family, min_variable_mode)
def _change_variable_mode(self,
variable,
family_mode: str,
) -> None:
if hasattr(variable, 'mode'):
variable_mode = variable.mode
else:
variable_mode = self.objectspace.rougailconfig['default_variable_mode']
# none basic variable in high level family has to be in high level
if self.modes[variable_mode] < self.modes[family_mode]:
if self._has_mode(variable):
msg = _(f'the variable "{variable.name}" is in "{variable_mode}" mode '
f'but family has the higher family mode "{family_mode}"')
raise DictConsistencyError(msg, 61, variable.xmlfiles)
self._set_auto_mode(variable, family_mode)
if not hasattr(variable, 'mode'):
variable.mode = variable_mode
def _change_variable_mode_leader(self,
leadership,
family_mode: str,
) -> None:
for follower in leadership.variable:
self._change_variable_mode(follower,
family_mode,
)
leadership.variable[0].mode = None
def dynamic_families(self):
"""link dynamic families to object
"""
for family in self.get_families():
if 'dynamic' not in vars(family):
continue
family.suffixes = self.objectspace.paths.get_variable(family.dynamic)
del family.dynamic
if not family.suffixes.multi:
msg = _(f'dynamic family "{family.name}" must be linked '
f'to multi variable')
raise DictConsistencyError(msg, 16, family.xmlfiles)
for variable in family.variable.values():
if isinstance(variable, self.objectspace.family):
msg = _(f'dynamic family "{family.name}" cannot contains another family')
raise DictConsistencyError(msg, 22, family.xmlfiles)
def convert_help(self):
"""Convert variable help
"""
for family in self.get_families():
if not hasattr(family, 'help'):
continue
if not hasattr(family, 'information'):
family.information = self.objectspace.information(family.xmlfiles)
family.information.help = family.help
del family.help