rougail-output-doc/src/rougail/output_doc/utils.py

848 lines
30 KiB
Python
Raw Normal View History

2024-11-15 08:13:45 +01:00
"""
Silique (https://www.silique.fr)
2025-02-10 09:52:12 +01:00
Copyright (C) 2024-2025
2024-11-15 08:13:45 +01:00
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 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 Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
2025-10-14 12:58:39 +02:00
from typing import Tuple, List, Optional
2024-11-15 08:13:45 +01:00
from io import BytesIO
from ruamel.yaml import YAML
import tabulate as tabulate_module
from tiramisu.error import display_list
from tabulate import tabulate
2025-10-14 12:58:39 +02:00
from rougail.tiramisu import normalize_family
from tiramisu import undefined
2024-11-15 08:13:45 +01:00
from .i18n import _
ROUGAIL_VARIABLE_TYPE = (
"https://rougail.readthedocs.io/en/latest/variable.html#variables-types"
)
ENTER = "\n\n"
DocTypes = {
"domainname": {
"params": {
"allow_startswith_dot": _("the domain name can starts by a dot"),
"allow_without_dot": _("the domain name can be a hostname"),
"allow_ip": _("the domain name can be an IP"),
"allow_cidr_network": _("the domain name can be network in CIDR format"),
},
},
"number": {
"params": {
"min_number": _("the minimum value is {0}"),
"max_number": _("the maximum value is {0}"),
},
},
"integer": {
"params": {
"min_integer": _("the minimum value is {0}"),
"max_integer": _("the maximum value is {0}"),
},
},
2024-11-15 08:13:45 +01:00
"ip": {
"msg": "IP",
"params": {
"cidr": _("IP must be in CIDR format"),
"private_only": _("private IP are allowed"),
"allow_reserved": _("reserved IP are allowed"),
},
2025-09-29 21:26:12 +02:00
},
"network": {
"params": {
"cidr": _("network must be in CIDR format"),
},
2024-11-15 08:13:45 +01:00
},
"hostname": {
"params": {
"allow_ip": _("the host name can be an IP"),
},
},
"web_address": {
"params": {
"allow_ip": _("the domain name in web address can be an IP"),
"allow_without_dot": _(
"the domain name in web address can be only a hostname"
),
},
},
"port": {
"params": {
"allow_range": _("can be range of port"),
"allow_protocol": _("can have the protocol"),
"allow_zero": _("port 0 is allowed"),
2025-03-29 14:37:45 +01:00
"allow_wellknown": _("well-known ports (1 to 1023) are allowed"),
"allow_registred": _("registred ports (1024 to 49151) are allowed"),
"allow_private": _("private ports (greater than 49152) are allowed"),
2024-11-15 08:13:45 +01:00
},
},
"secret": {
"params": {
2025-03-29 14:37:45 +01:00
"min_len": _("minimum length for the secret is {0} characters"),
"max_len": _("maximum length for the secret is {0} characters"),
"forbidden_char": _("forbidden characters: {0}"),
2024-11-15 08:13:45 +01:00
},
},
"unix_filename": {
"params": {
"allow_relative": _("this filename could be a relative path"),
"test_existence": _("this file must exists"),
"types": _("file type allowed: {0}"),
},
},
2024-11-15 08:13:45 +01:00
}
_yaml = YAML()
_yaml.indent(mapping=2, sequence=4, offset=2)
def dump(informations):
2024-11-15 08:13:45 +01:00
"""Dump variable, means transform bool, ... to yaml string"""
with BytesIO() as ymlfh:
_yaml.dump(informations, ymlfh)
2024-11-15 08:13:45 +01:00
ret = ymlfh.getvalue().decode("utf-8").strip()
if ret.endswith("..."):
ret = ret[:-3].strip()
return ret
2025-10-14 12:58:39 +02:00
def to_phrase(msg, type_="variable"):
2025-10-15 09:44:22 +02:00
"""Add maj for the first character and ends with dot"""
2024-11-15 08:13:45 +01:00
if not msg:
# replace None to empty string
return ""
msg = str(msg).strip()
# a phrase must ends with a dot
2025-10-15 09:44:22 +02:00
if type_ == "variable":
2025-10-14 12:58:39 +02:00
if not msg.endswith("."):
msg += "."
2025-10-15 09:44:22 +02:00
elif type_ == "family":
2025-10-14 12:58:39 +02:00
if msg.endswith("."):
msg = msg[:-1]
else:
2025-10-15 09:44:22 +02:00
raise Exception("unknown type")
2024-11-15 08:13:45 +01:00
# and start with a maj
return msg[0].upper() + msg[1:]
class CommonFormater:
"""Class with common function for formater"""
enter_table = "\n"
# tabulate module name
name = None
2025-10-14 12:58:39 +02:00
def __init__(self, with_family: bool):
2024-11-15 08:13:45 +01:00
tabulate_module.PRESERVE_WHITESPACE = True
self.header_setted = False
2025-10-14 12:58:39 +02:00
self.with_family = with_family
2024-11-15 08:13:45 +01:00
# Class you needs implement to your Formater
def title(
self,
title: str,
level: int,
) -> str:
"""Display family name as a title"""
raise NotImplementedError()
def join(
self,
lst: List[str],
) -> str:
"""Display line in table from a list"""
raise NotImplementedError()
def bold(
self,
msg: str,
) -> str:
"""Set a text to bold"""
raise NotImplementedError()
def stripped(
self,
text: str,
) -> str:
"""Return stripped text (as help)"""
raise NotImplementedError()
def list(
self,
choices: list,
) -> str:
"""Display a liste of element"""
raise NotImplementedError()
def prop(
self,
prop: str,
italic: bool,
) -> str:
"""Display property"""
raise NotImplementedError()
def link(
self,
comment: str,
link: str,
) -> str:
"""Add a link"""
raise NotImplementedError()
##################
2025-10-02 08:19:18 +02:00
def family_informations(self) -> str:
2025-10-15 09:44:22 +02:00
return ""
2025-10-02 08:19:18 +02:00
def end_family_informations(self) -> str:
2025-10-15 09:44:22 +02:00
return ""
2025-10-02 08:19:18 +02:00
2025-10-14 12:58:39 +02:00
def display_paths(
2025-10-02 08:19:18 +02:00
self,
2025-10-14 12:58:39 +02:00
informations: dict,
modified_attributes: dict,
force_identifiers: Optional[str],
2025-10-02 08:19:18 +02:00
) -> str:
2025-10-14 12:58:39 +02:00
ret_paths = []
path = informations["path"]
if "identifiers" in modified_attributes:
name, previous, new = modified_attributes["identifiers"]
2025-10-15 09:44:22 +02:00
ret_paths.extend(
[
self.bold(self.delete(calc_path(path, self, identifier)))
for identifier in previous
]
)
2025-10-14 12:58:39 +02:00
else:
new = []
if "identifiers" in informations:
for identifier in informations["identifiers"]:
if force_identifiers and identifier != force_identifiers:
continue
2025-10-14 12:58:39 +02:00
path_ = calc_path(path, self, identifier)
if identifier in new:
path_ = self.underline(path_)
ret_paths.append(self.bold(path_))
else:
ret_paths.append(self.bold(path))
return ret_paths
2025-10-02 08:19:18 +02:00
def after_family_paths(self) -> str:
return ENTER
def after_family_properties(self) -> str:
return ENTER
2024-11-15 08:13:45 +01:00
def table_header(
self,
lst: list,
) -> tuple:
"""Manage the header of a table"""
return lst
2025-10-15 09:44:22 +02:00
def run(
self, informations: dict, level: int, *, dico_is_already_treated=False
) -> str:
2024-11-15 08:13:45 +01:00
"""Transform to string"""
if informations:
2025-10-14 12:58:39 +02:00
return self._run(informations, level, dico_is_already_treated)
2025-03-29 15:10:03 +01:00
return ""
2024-11-15 08:13:45 +01:00
2025-10-14 12:58:39 +02:00
def _run(self, dico: dict, level: int, dico_is_already_treated: bool) -> str:
"""Parse the dict to transform to dict"""
if dico_is_already_treated:
return "".join(dico)
return "".join([msg for msg in self.dict_to_dict(dico, level, init=True)])
2025-10-15 09:44:22 +02:00
def dict_to_dict(
self,
dico: dict,
level: int,
*,
ori_table_datas: list = None,
init: bool = False,
) -> str:
2024-11-15 08:13:45 +01:00
"""Parse the dict to transform to dict"""
2025-03-29 15:10:03 +01:00
msg = []
2025-10-14 12:58:39 +02:00
if ori_table_datas is not None:
table_datas = ori_table_datas
else:
table_datas = []
ori_level = None
2025-03-29 15:10:03 +01:00
for value in dico.values():
2025-10-14 12:58:39 +02:00
if value["type"] == "variable":
self.variable_to_string(value, table_datas)
2024-11-15 08:13:45 +01:00
else:
2025-10-14 12:58:39 +02:00
if self.with_family:
if value["type"] == "namespace":
if ori_level is None:
ori_level = level
level += 1
informations = value["informations"]
msg.append(self.namespace_to_title(informations, ori_level))
msg.append(self.family_informations())
msg.extend(self.display_paths(informations, {}, None))
2025-10-14 12:58:39 +02:00
msg.append(self.after_family_paths())
2025-10-15 09:44:22 +02:00
msg.append(
self.property_to_string(informations, {}, {})[1] + ENTER
)
2025-10-14 12:58:39 +02:00
msg.append(self.end_family_informations())
msg.extend(self.dict_to_dict(value["children"], level))
msg.append(self.end_namespace(ori_level))
else:
if table_datas:
msg.append(self.table(table_datas))
table_datas = []
msg.extend(self.family_to_string(value["informations"], level))
msg.extend(self.dict_to_dict(value["children"], level + 1))
msg.append(self.end_family(level))
2024-11-15 08:13:45 +01:00
else:
2025-10-15 09:44:22 +02:00
self.dict_to_dict(
value["children"], level + 1, ori_table_datas=table_datas
)
2025-10-14 12:58:39 +02:00
if (init or ori_table_datas is None) and table_datas:
2025-03-29 15:10:03 +01:00
msg.append(self.table(table_datas))
2024-11-15 08:13:45 +01:00
return msg
# FAMILY
2025-03-29 15:10:03 +01:00
def namespace_to_title(self, informations: dict, level: int) -> str:
2024-11-15 08:13:45 +01:00
"""manage namespace family"""
return self.title(
2025-10-15 09:44:22 +02:00
_('Variables for "{0}"').format(
self.get_description("family", informations, {}, None)
2025-10-15 09:44:22 +02:00
),
level,
2024-11-15 08:13:45 +01:00
)
def end_namespace(self, level: int) -> str:
return self.end_family(level)
2025-10-02 08:19:18 +02:00
def family_to_string(self, informations: dict, level: int) -> str:
2024-11-15 08:13:45 +01:00
"""manage other family type"""
msg = [self.title(self.get_description("family", informations, {}, None), level)]
helps = informations.get("help")
2024-11-15 08:13:45 +01:00
if helps:
for help_ in helps:
2025-10-02 08:19:18 +02:00
msg.append(self.display_family_help(help_.strip()))
msg.append(self.family_informations())
2025-10-15 09:44:22 +02:00
msg.append(
self.join(self.display_paths(informations, {}, None)) + self.after_family_paths()
2025-10-02 08:19:18 +02:00
)
calculated_properties = []
2025-10-15 09:44:22 +02:00
msg.append(
self.property_to_string(informations, calculated_properties, {})[1] + ENTER
)
2025-10-02 08:19:18 +02:00
if calculated_properties:
2025-10-15 09:44:22 +02:00
msg.append(
self.join(calculated_properties) + self.after_family_properties()
)
2025-10-14 12:58:39 +02:00
if "identifier" in informations:
2025-05-11 19:13:12 +02:00
msg.append(
2025-10-15 09:44:22 +02:00
self.section(_("Identifiers"), informations["identifier"])
+ self.after_family_properties()
2025-05-11 19:13:12 +02:00
)
2025-10-02 08:19:18 +02:00
msg.append(self.end_family_informations())
2024-11-15 08:13:45 +01:00
return msg
def end_family(self, level: int) -> str:
2025-10-15 09:44:22 +02:00
return ""
2025-10-02 08:19:18 +02:00
2025-10-15 09:44:22 +02:00
def convert_list_to_string(
self, attribute: str, informations: dict, modified_attributes: dict
) -> str():
2025-10-14 12:58:39 +02:00
datas = []
if attribute in modified_attributes:
name, previous, new = modified_attributes[attribute]
for data in previous:
datas.append(self.delete(self.to_phrase(data)))
else:
new = []
if attribute in informations:
for data in informations[attribute]:
if isinstance(data, dict):
2025-10-15 09:44:22 +02:00
if attribute.endswith("s"):
2025-10-14 12:58:39 +02:00
attr = attribute[:-1]
else:
attr = attribute
2025-10-15 09:44:22 +02:00
data = data[attr].replace(
"{{ identifier }}", self.italic(data["identifier"])
)
2025-10-14 13:50:02 +02:00
data = self.to_phrase(data)
2025-10-14 12:58:39 +02:00
if data in new:
data = self.underline(data)
2025-10-14 13:50:02 +02:00
datas.append(data)
2025-10-14 12:58:39 +02:00
return self.stripped(self.join(datas))
2025-10-15 09:44:22 +02:00
def get_description(
self, type_: str, informations: dict, modified_attributes: dict, force_identifiers: Optional[str]
2025-10-15 09:44:22 +02:00
) -> str():
2025-10-14 12:58:39 +02:00
def _get_description(description, identifiers, delete=False, new=[]):
if "{{ identifier }}" in description:
if type_ == "variable":
2025-10-15 09:44:22 +02:00
identifiers_text = display_list(
[self.italic(i[-1]) for i in identifiers if not force_identifiers or i == force_identifiers], separator="or"
2025-10-15 09:44:22 +02:00
)
description = description.replace(
"{{ identifier }}", identifiers_text
)
2025-10-14 12:58:39 +02:00
else:
d = []
for i in identifiers:
if force_identifiers and i != force_identifiers:
continue
2025-10-15 09:44:22 +02:00
new_description = description.replace(
"{{ identifier }}", self.italic(i[-1])
)
2025-10-14 12:58:39 +02:00
if new_description not in d:
d.append(self.to_phrase(new_description))
description = display_list(d, separator="or")
else:
description = self.to_phrase(description)
if description in new:
description = self.underline(description)
if delete:
description = self.delete(description)
return description
2025-10-15 09:44:22 +02:00
2025-10-14 12:58:39 +02:00
if "description" in modified_attributes:
name, previous, new = modified_attributes["description"]
2025-10-15 09:44:22 +02:00
modified_description = _get_description(
previous, modified_attributes.get("identifiers", []), delete=True
)
2025-10-14 12:58:39 +02:00
else:
modified_description = None
new = []
2025-10-15 09:44:22 +02:00
description = _get_description(
informations["description"], informations.get("identifiers"), new=new
)
2025-10-14 12:58:39 +02:00
if modified_description:
if description:
description = self.join([modified_description, description])
else:
description = modified_description
if not description:
return None
return self.stripped(description)
2024-11-15 08:13:45 +01:00
# VARIABLE
2025-10-15 09:44:22 +02:00
def variable_to_string(
self, informations: dict, table_datas: list, modified_attributes: dict = {}, force_identifiers: Optional[str]=None
2025-10-15 09:44:22 +02:00
) -> None:
2024-11-15 08:13:45 +01:00
"""Manage variable"""
calculated_properties = []
2025-10-15 09:44:22 +02:00
multi, first_column = self.variable_first_column(
informations, calculated_properties, modified_attributes, force_identifiers
2025-10-15 09:44:22 +02:00
)
2024-11-15 08:13:45 +01:00
table_datas.append(
[
2025-10-14 12:58:39 +02:00
self.join(first_column),
self.join(
2025-10-15 09:44:22 +02:00
self.variable_second_column(
informations,
calculated_properties,
modified_attributes,
multi,
force_identifiers,
2025-10-15 09:44:22 +02:00
)
),
2024-11-15 08:13:45 +01:00
]
)
def variable_first_column(
2025-10-15 09:44:22 +02:00
self,
informations: dict,
calculated_properties: list,
modified_attributes: Optional[dict],
force_identifiers: Optional[str],
) -> list:
2024-11-15 08:13:45 +01:00
"""Collect string for the first column"""
2025-10-15 09:44:22 +02:00
multi, properties = self.property_to_string(
informations, calculated_properties, modified_attributes
)
2024-11-15 08:13:45 +01:00
first_col = [
self.join(self.display_paths(informations, modified_attributes, force_identifiers)),
2025-10-15 09:44:22 +02:00
properties,
2024-11-15 08:13:45 +01:00
]
self.columns(first_col)
2025-10-14 12:58:39 +02:00
return multi, first_col
2024-11-15 08:13:45 +01:00
def variable_second_column(
2025-10-15 09:44:22 +02:00
self,
informations: dict,
calculated_properties: list,
modified_attributes: dict,
multi: bool,
force_identifiers: Optional[str],
) -> list:
2024-11-15 08:13:45 +01:00
"""Collect string for the second column"""
2025-10-14 12:58:39 +02:00
second_col = []
#
if "description" in informations:
2025-10-15 09:44:22 +02:00
description = self.get_description(
"variable", informations, modified_attributes, force_identifiers,
2025-10-15 09:44:22 +02:00
)
2025-10-14 12:58:39 +02:00
second_col.append(description)
#
help_ = self.convert_list_to_string("help", informations, modified_attributes)
if help_:
second_col.append(help_)
#
2025-10-15 09:44:22 +02:00
validators = self.convert_section_to_string(
"validators", informations, modified_attributes, multi=True
)
2025-10-14 12:58:39 +02:00
if validators:
second_col.append(validators)
2025-10-15 09:44:22 +02:00
default_is_already_set, choices = self.convert_choices_to_string(
informations, modified_attributes
)
2025-10-14 12:58:39 +02:00
if choices:
second_col.append(choices)
if not default_is_already_set and "default" in informations:
2025-10-15 09:44:22 +02:00
self.convert_section_to_string(
"default", informations, modified_attributes, multi=multi
)
second_col.append(
self.convert_section_to_string(
"default", informations, modified_attributes, multi=multi
)
)
examples = self.convert_section_to_string(
"examples", informations, modified_attributes, multi=True
)
2025-10-14 12:58:39 +02:00
if examples:
second_col.append(examples)
second_col.extend(calculated_properties)
self.columns(second_col)
return second_col
2025-10-15 09:44:22 +02:00
def convert_section_to_string(
self, attribute: str, informations: dict, modified_attributes: dict, multi: bool
) -> str():
2025-10-14 12:58:39 +02:00
values = []
submessage = ""
if modified_attributes and attribute in modified_attributes:
name, previous, new = modified_attributes[attribute]
2025-10-15 09:44:22 +02:00
# if "identifiers" in modified_attributes:
# iname, iprevious, inew = modified_attributes["identifiers"]
# identifiers = iprevious.copy()
# for identifier in informations.get("identifiers", []):
# if identifier not in inew:
# identifiers.append(identifier)
#
# else:
# identifiers = informations.get("identifiers", [])
2025-10-14 12:58:39 +02:00
if isinstance(previous, list):
for p in previous:
submessage, m = self.message_to_string(p, submessage)
values.append(self.delete(m))
else:
submessage, old_values = self.message_to_string(previous, submessage)
values.append(self.delete(old_values))
2024-11-15 08:13:45 +01:00
else:
2025-10-14 12:58:39 +02:00
new = []
if attribute in informations:
old = informations[attribute]
name = old["name"]
if isinstance(old["values"], list):
for value in old["values"]:
submessage, old_value = self.message_to_string(value, submessage)
if value in new:
old_value = self.underline(old_value)
values.append(old_value)
if multi:
values = self.list(values)
else:
values = self.join(values)
elif values:
old_values = old["values"]
submessage, old_values = self.message_to_string(old_values, submessage)
if old["values"] in new:
old_values = self.underline(old_values)
values.append(old_values)
values = self.join(values)
2024-11-15 08:13:45 +01:00
else:
2025-10-14 12:58:39 +02:00
submessage, values = self.message_to_string(old["values"], submessage)
if old["values"] in new:
values = self.underline(values)
if values != []:
return self.section(name, values, submessage=submessage)
2025-10-15 09:44:22 +02:00
def convert_choices_to_string(
self, informations: dict, modified_attributes: dict
) -> str():
2025-10-14 12:58:39 +02:00
default_is_already_set = False
if "choices" in informations:
2025-09-22 09:42:46 +02:00
choices = informations["choices"]
2025-10-14 12:58:39 +02:00
choices_values = choices["values"]
if not isinstance(choices_values, list):
choices_values = [choices_values]
default_is_a_list = False
2024-11-15 08:13:45 +01:00
else:
2025-10-14 12:58:39 +02:00
default_is_a_list = True
if "default" in modified_attributes:
name, old_default, new_default = modified_attributes["default"]
if not old_default:
old_default = [None]
if not isinstance(old_default, list):
old_default = [old_default]
for value in old_default.copy():
2025-10-15 09:44:22 +02:00
if (
isinstance(value, str)
and value.endswith(".")
and value not in choices_values
):
2025-10-14 12:58:39 +02:00
old_default.remove(value)
old_default.append(value[:-1])
else:
old_default = new_default = []
# check if all default values are in choices (could be from a calculation)
if "default" in informations:
default = informations["default"]["values"]
else:
default = []
if not isinstance(default, list):
default = [default]
default_value_not_in_choices = set(default) - set(choices_values)
if default_value_not_in_choices:
default_is_changed = False
for val in default_value_not_in_choices.copy():
2025-10-15 09:44:22 +02:00
if (
isinstance(val, str)
and val.endswith(".")
and val[:-1] in choices_values
):
2025-10-14 12:58:39 +02:00
default.remove(val)
default.append(val[:-1])
default_is_changed = True
if val in new_default:
new_default.remove(val)
new_default.append(val[:-1])
if default_is_changed:
default_value_not_in_choices = set(default) - set(choices_values)
if default_value_not_in_choices:
old_default = []
new_default = []
default = []
else:
default_is_already_set = True
if "choices" in modified_attributes:
name, previous, new = modified_attributes["choices"]
for choice in reversed(previous):
if choice in old_default:
2025-10-15 09:44:22 +02:00
choices_values.insert(
0, self.delete(dump(choice) + "" + _("(default)"))
)
2025-10-14 12:58:39 +02:00
else:
choices_values.insert(0, self.delete(dump(choice)))
else:
new = []
for idx, val in enumerate(choices_values):
if val in old_default:
2025-10-15 09:44:22 +02:00
choices_values[idx] = (
dump(val) + " " + self.delete("" + _("(default)"))
)
2025-10-14 12:58:39 +02:00
elif val in default:
if val in new_default:
if val in new:
2025-10-15 09:44:22 +02:00
choices_values[idx] = self.underline(
dump(val) + " " + self.bold("" + _("(default)"))
)
2025-10-14 12:58:39 +02:00
else:
2025-10-15 09:44:22 +02:00
choices_values[idx] = (
dump(val)
+ " "
+ self.underline(self.bold("" + _("(default)")))
)
2025-10-14 12:58:39 +02:00
else:
2025-10-15 09:44:22 +02:00
choices_values[idx] = (
dump(val) + " " + self.bold("" + _("(default)"))
)
2025-10-14 12:58:39 +02:00
elif val in new:
choices_values[idx] = self.underline(dump(val))
# if old value and new value is a list, display a list
if not default_is_a_list and len(choices_values) == 1:
choices_values = choices_values[0]
return default_is_already_set, self.section(choices["name"], choices_values)
return default_is_already_set, None
2024-11-15 08:13:45 +01:00
# OTHERs
2025-10-14 12:58:39 +02:00
def to_phrase(self, text: str) -> str:
return text
2025-10-02 08:19:18 +02:00
def display_family_help(self, help_):
2025-10-14 12:58:39 +02:00
return self.to_phrase(help_) + ENTER
2025-10-02 08:19:18 +02:00
def property_to_string(
2025-10-15 09:44:22 +02:00
self,
informations: dict,
calculated_properties: list,
modified_attributes: dict,
) -> str:
2024-11-15 08:13:45 +01:00
"""Transform properties to string"""
properties = []
2025-10-14 12:58:39 +02:00
local_calculated_properties = {}
multi = False
if "properties" in modified_attributes:
2025-10-15 09:44:22 +02:00
previous, new = self.get_modified_properties(
*modified_attributes["properties"][1:]
)
2025-10-14 12:58:39 +02:00
for p, annotation in previous.items():
if p not in new:
properties.append(self.prop(self.delete(p), italic=False))
if annotation is not None:
local_calculated_properties[p] = [self.delete(annotation)]
else:
previous = new = []
for prop in informations.get("properties", []):
2024-11-20 21:12:56 +01:00
if prop["type"] == "type":
2024-11-15 08:13:45 +01:00
properties.append(self.link(prop["name"], ROUGAIL_VARIABLE_TYPE))
else:
2025-10-14 12:58:39 +02:00
if prop["type"] == "multiple":
multi = True
prop_name = prop["name"]
2024-11-15 08:13:45 +01:00
if "annotation" in prop:
italic = True
2025-10-14 12:58:39 +02:00
prop_annotation = prop["annotation"]
2025-10-15 09:44:22 +02:00
if prop_name in new and (
prop_name not in previous
or new[prop_name] != previous[prop_name]
):
2025-10-14 12:58:39 +02:00
prop_annotation = self.underline(prop_annotation)
2025-10-15 09:44:22 +02:00
local_calculated_properties.setdefault(prop["name"], []).append(
prop_annotation
)
2024-11-15 08:13:45 +01:00
else:
italic = False
2025-10-14 12:58:39 +02:00
if prop_name not in previous and prop_name in new:
prop_name = self.underline(prop_name)
properties.append(self.prop(prop_name, italic=italic))
if local_calculated_properties:
2025-10-15 09:44:22 +02:00
for (
calculated_property_name,
calculated_property,
) in local_calculated_properties.items():
2025-10-14 12:58:39 +02:00
if len(calculated_property) > 1:
calculated_property = self.join(calculated_property)
else:
calculated_property = calculated_property[0]
calculated_properties.append(
2025-10-15 09:44:22 +02:00
self.section(
calculated_property_name.capitalize(), calculated_property
)
2025-10-14 12:58:39 +02:00
)
2024-11-15 08:13:45 +01:00
if not properties:
2025-10-14 12:58:39 +02:00
return multi, ""
return multi, " ".join(properties)
2025-10-15 09:44:22 +02:00
def get_modified_properties(
self, previous: List[dict], new: List[dict]
) -> Tuple[dict, dict]:
2025-10-14 12:58:39 +02:00
def modified_properties_parser(dico):
return {d["name"]: d.get("annotation") for d in dico}
2025-10-15 09:44:22 +02:00
2025-10-14 12:58:39 +02:00
return modified_properties_parser(previous), modified_properties_parser(new)
2024-11-15 08:13:45 +01:00
def columns(
self,
col: List[str], # pylint: disable=unused-argument
) -> None:
"""Manage column"""
return
2025-10-15 09:44:22 +02:00
def table(self, datas: list, with_header: bool = True) -> str:
2024-11-15 08:13:45 +01:00
"""Transform list to a table in string format"""
if with_header:
headers = self.table_header([_("Variable"), _("Description")])
else:
headers = ()
2024-11-15 08:13:45 +01:00
msg = (
tabulate(
datas,
headers=headers,
2024-11-20 21:12:56 +01:00
tablefmt=self._table_name,
2024-11-15 08:13:45 +01:00
)
+ "\n\n"
)
datas.clear()
return msg
2025-10-14 12:58:39 +02:00
def message_to_string(self, msg, ret, identifiers=[]):
if isinstance(msg, dict):
if "submessage" in msg:
ret += msg["submessage"]
msg = msg["values"]
elif "message" in msg:
path = calc_path(msg["path"], self, identifiers)
msg = msg["message"].format(path)
return ret, msg
2024-11-15 08:13:45 +01:00
def section(
self,
name: str,
msg: str,
2025-10-14 12:58:39 +02:00
submessage: str = "",
2024-11-15 08:13:45 +01:00
) -> str:
"""Return something like Name: msg"""
2025-10-14 12:58:39 +02:00
submessage, msg = self.message_to_string(msg, submessage)
2024-11-15 08:13:45 +01:00
if isinstance(msg, list):
if len(msg) == 1:
2025-10-16 20:58:39 +02:00
submessage, elt = self.message_to_string(msg[0], submessage)
2025-10-18 06:40:23 +02:00
if isinstance(elt, list):
submessage += self.list(elt)
else:
submessage += elt
2024-11-15 08:13:45 +01:00
else:
2025-10-14 12:58:39 +02:00
lst = []
for p in msg:
submessage, elt = self.message_to_string(p, submessage)
lst.append(elt)
submessage += self.list(lst)
2025-10-16 20:58:39 +02:00
msg = ""
2024-11-15 08:13:45 +01:00
if not isinstance(msg, str):
2025-10-14 12:58:39 +02:00
submessage += dump(msg)
else:
submessage += msg
return _("{0}: {1}").format(self.bold(name), submessage)
2025-10-15 09:44:22 +02:00
def calc_path(path, formater=None, identifiers: List[str] = None) -> str:
2025-10-14 12:58:39 +02:00
def _path_with_identifier(path, identifier):
identifier = normalize_family(str(identifier))
if formater:
identifier = formater.italic(identifier)
2025-10-15 09:44:22 +02:00
return path.replace("{{ identifier }}", identifier, 1)
2025-10-14 12:58:39 +02:00
if isinstance(path, dict):
path_ = path["path"]
for identifier in path["identifiers"]:
path_ = _path_with_identifier(path_, identifier)
elif identifiers:
path_ = path
for identifier in identifiers:
path_ = _path_with_identifier(path_, identifier)
else:
path_ = path
return path_