rougail/src/rougail/annotator/family.py

208 lines
8.3 KiB
Python

"""Annotate family
"""
from ..i18n import _
from ..error import DictConsistencyError
from ..utils import normalize_family
from .variable import RENAME_ATTIBUTES
#mode order is important
modes_level = ('basic', 'normal', 'expert')
class Mode: # pylint: disable=R0903
"""Class to manage mode level
"""
def __init__(self,
name: str,
level: int,
) -> None:
self.name = name
self.level = level
def __gt__(self,
other: int,
) -> bool:
return other.level < self.level
modes = {name: Mode(name, idx) for idx, name in enumerate(modes_level)}
class FamilyAnnotator:
"""Annotate family
"""
def __init__(self,
objectspace,
):
self.objectspace = objectspace
if not hasattr(self.objectspace.space, 'variables'):
return
self.family_names()
self.remove_empty_families()
self.change_variable_mode()
self.change_family_mode()
self.dynamic_families()
self.convert_help()
def family_names(self) -> None:
"""Set doc, path, ... to family
"""
for families in self.objectspace.space.variables.values():
families.doc = families.name
families.path = families.name
for family in families.family.values():
if not hasattr(family, 'description'):
family.description = family.name
for key, value in RENAME_ATTIBUTES.items():
setattr(family, value, getattr(family, key))
setattr(family, key, None)
family.name = normalize_family(family.name)
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.family.items():
if not hasattr(family, 'variable') or len(family.variable) == 0:
removed_families.append(family_name)
for family_name in removed_families:
del families.family[family_name]
def change_variable_mode(self):
"""change the mode of variables
"""
for variables in self.objectspace.space.variables.values():
for family in variables.family.values():
family_mode = family.mode
for variable in family.variable.values():
if not isinstance(variable, self.objectspace.leadership):
func = self._change_variabe_mode
else:
func = self._change_variable_mode_leader
func(variable,
family_mode,
)
def _change_variabe_mode(self,
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 'mode' not in vars(variable) and \
(variable.auto_save is True or variable.auto_freeze is True):
variable.mode = modes_level[0]
self._annotate_variable(variable,
family_mode,
)
def _change_variable_mode_leader(self,
leadership,
family_mode: str,
) -> None:
is_follower = False
leader_mode = None
for follower in leadership.variable:
if follower.auto_save is True:
xmlfiles = self.objectspace.display_xmlfiles(leadership.xmlfiles)
msg = _(f'leader/followers "{follower.name}" could not be auto_save in {xmlfiles}')
raise DictConsistencyError(msg, 29)
if follower.auto_freeze is True:
xmlfiles = self.objectspace.display_xmlfiles(leadership.xmlfiles)
msg = f'leader/followers "{follower.name}" could not be auto_freeze in {xmlfiles}'
raise DictConsistencyError(_(msg), 30)
self._annotate_variable(follower,
family_mode,
is_follower,
)
if leader_mode is None:
leader_mode = leadership.variable[0].mode
leadership.variable[0].mode = None
else:
# leader's mode is minimum level
if modes[leader_mode] > modes[follower.mode]:
follower.mode = leader_mode
is_follower = True
leadership.mode = leader_mode
def _annotate_variable(self,
variable,
family_mode: str,
is_follower=False,
) -> None:
"""if the variable is mandatory and doesn't have any value
then the variable's mode is set to 'basic'
"""
# a boolean must have value, the default value is "True"
if not hasattr(variable, 'value') and variable.type == 'boolean':
new_value = self.objectspace.value(variable.xmlfiles)
new_value.name = True
new_value.type = 'boolean'
variable.value = [new_value]
# variable with default value is mandatory
if hasattr(variable, 'value') and variable.value:
has_value = True
for value in variable.value:
if value.type == 'calculation':
has_value = False
break
if has_value:
# if has value without any calculation
variable.mandatory = True
# mandatory variable without value is a basic variable
if variable.mandatory is True and (not hasattr(variable, 'value') or is_follower):
variable.mode = modes_level[0]
# none basic variable in high level family has to be in high level
if modes[variable.mode] < modes[family_mode] and \
(not is_follower or variable.mode != modes_level[0]):
variable.mode = family_mode
# hidden variable is also frozen
if variable.hidden is True:
variable.frozen = True
if not variable.auto_save and \
not variable.auto_freeze and \
'force_default_on_freeze' not in vars(variable):
variable.force_default_on_freeze = True
def change_family_mode(self):
"""change mode of a family
"""
for families in self.objectspace.space.variables.values():
for family in families.family.values():
# default is high level
mode = modes_level[-1]
# get de lower sub variable mode
for variable in family.variable.values():
variable_mode = variable.mode
if modes[mode] > modes[variable_mode]:
mode = variable_mode
# set the lower variable mode to family
family.mode = mode
def dynamic_families(self):
"""link dynamic families to object
"""
for families in self.objectspace.space.variables.values():
for family in families.family.values():
if 'dynamic' not in vars(family):
continue
family.dynamic = self.objectspace.paths.get_variable(family.dynamic)
if not family.dynamic.multi:
xmlfiles = self.objectspace.display_xmlfiles(family.xmlfiles)
msg = _(f'dynamic family "{family.name}" must be linked '
f'to multi variable in {xmlfiles}')
raise DictConsistencyError(msg, 16)
def convert_help(self):
"""Convert variable help
"""
for families in self.objectspace.space.variables.values():
for family in families.family.values():
if hasattr(family, 'help'):
if not hasattr(family, 'information'):
family.information = self.objectspace.information(family.xmlfiles)
family.information.help = family.help
del family.help