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

1448 lines
52 KiB
Python

"""
Silique (https://www.silique.fr)
Copyright (C) 2024-2026
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/>.
"""
from typing import Tuple, List, Optional
from io import BytesIO
from ruamel.yaml import YAML
import tabulate as tabulate_module
from tabulate import tabulate
from rougail.tiramisu import normalize_family
from tiramisu import undefined
from tiramisu.error import PropertiesOptionError, display_list
try:
from tiramisu_cmdline_parser.api import gen_argument_name
except:
gen_argument_name = None
from .i18n import _
from .config import Tabulars, ROUGAIL_VARIABLE_TYPE
ENTER = "\n\n"
_yaml = YAML()
_yaml.indent(mapping=2, sequence=4, offset=2)
def dump(informations):
"""Dump variable, means transform bool, ... to yaml string"""
with BytesIO() as ymlfh:
_yaml.dump(informations, ymlfh)
ret = ymlfh.getvalue().decode("utf-8").strip()
if ret.endswith("..."):
ret = ret[:-3].strip()
return ret
def to_phrase(msg, type_="variable"):
"""Add maj for the first character and ends with dot"""
msg = str(msg).strip()
if not msg:
# replace None to empty string
return ""
# a phrase must ends with a dot
if type_ == "variable":
msg = add_dot(msg)
elif type_ in ["family", "description"]:
if msg.endswith("."):
msg = msg[:-1]
else:
raise Exception("unknown type")
# and start with a maj
return msg[0].upper() + msg[1:]
def add_dot(msg):
msg = str(msg).strip()
last_char = msg[-1]
if last_char not in [".", "?", "!", ",", ":", ";"]:
msg += "."
return msg
class CommonTabular:
"""Class with common function for tabular"""
def __init__(self, formatter: "CommonFormatter") -> None:
self.formatter = formatter
self.clear()
def clear(self):
self.columns = []
def get(self):
columns = list(self.get_columns())
self.clear()
return columns
def add(
self,
informations: dict,
modified_attributes: dict,
force_identifiers: Optional[str],
) -> tuple:
self.informations = informations
self.modified_attributes = modified_attributes
self.force_identifiers = force_identifiers
self.calculated_properties = []
self.set_description()
self.set_type()
self.set_choices()
self.set_properties()
self.set_default()
self.set_examples()
self.set_paths()
self.set_commandline()
self.set_environment()
self.set_validators()
self.set_tags()
self.columns.append(self._add())
def set_description(self):
if "description" in self.informations:
self.description = self.formatter.get_description(
self.informations,
self.modified_attributes,
force_identifiers=self.force_identifiers,
)
else:
self.description = None
self.help_ = self.formatter.convert_list_to_string(
"help", self.informations, self.modified_attributes
)
def set_choices(self):
self.default_is_already_set, self.choices = (
self.formatter.convert_choices_to_string(
self.informations, self.modified_attributes
)
)
def set_type(self):
self.multi = self.informations.get("multiple", True)
self.type = self.informations["variable_type"]
def set_default(self):
if "default" in self.informations:
self.default = self.formatter.convert_section_to_string(
"default", self.informations, self.modified_attributes, multi=self.multi
)
else:
self.default = None
def set_examples(self):
self.examples = self.formatter.convert_section_to_string(
"examples", self.informations, self.modified_attributes, multi=True
)
def set_paths(self):
self.paths = self.formatter.join(
self.formatter.display_paths(
self.informations,
self.modified_attributes,
self.force_identifiers,
is_variable=True,
)
)
def set_commandline(self):
if self.formatter.with_commandline and not self.informations.get(
"not_for_commandline", False
):
commandlines = self.formatter.display_paths(
self.informations,
self.modified_attributes,
self.force_identifiers,
is_variable=True,
is_bold=False,
variable_prefix="--",
with_anchor=False,
)
if self.type == "boolean":
for path in self.formatter.display_paths(
self.informations,
self.modified_attributes,
self.force_identifiers,
is_variable=True,
is_bold=False,
is_upper=False,
with_anchor=False,
variable_prefix="--",
):
commandlines.append(
"--" + gen_argument_name(path[2:], False, True, False)
)
if "alternative_name" in self.informations:
alternative_name = self.informations["alternative_name"]
commandlines[0] = f"-{alternative_name}, {commandlines[0]}"
if self.type == "boolean":
commandlines[1] = (
"-"
+ gen_argument_name(alternative_name, True, True, False)
+ f", {commandlines[1]}"
)
if "full_path" in self.informations:
full_path = self.informations["full_path"]
else:
full_path = self.informations["path"]
self.commandlines = self.formatter.section(
full_path, _("Command line"), commandlines, force_enter=True
)
else:
self.commandlines = None
def set_environment(self):
if self.formatter.with_environment:
environments = self.formatter.display_paths(
self.informations,
self.modified_attributes,
self.force_identifiers,
is_variable=True,
is_bold=False,
is_upper=True,
variable_prefix=self.formatter.prefix,
with_anchor=False,
)
if "full_path" in self.informations:
full_path = self.informations["full_path"]
else:
full_path = self.informations["path"]
self.environments = self.formatter.section(
full_path, _("Environment variable"), environments
)
else:
self.environments = None
def set_properties(self):
self.properties = self.formatter.property_to_string(
self.informations,
self.calculated_properties,
self.modified_attributes,
)
def set_validators(self):
self.validators = self.formatter.convert_section_to_string(
"validators", self.informations, self.modified_attributes, multi=True
)
def set_tags(self):
self.tags = self.formatter.convert_section_to_string(
"tags", self.informations, self.modified_attributes, multi=True
)
def get_column(self, *args):
contents = [arg for arg in args if arg]
self.formatter.columns(contents)
return self.formatter.join(contents)
class CommonFormatter:
"""Class with common function for formatter"""
enter_tabular = "\n"
format_in_title = True
# tabulate module name
name = None
def __init__(self, rougailconfig, support_namespace, document_a_type, **kwargs):
tabulate_module.PRESERVE_WHITESPACE = True
self.header_setted = False
self.rougailconfig = rougailconfig
self.support_namespace = support_namespace
self.document_a_type = document_a_type
def run(self, informations: dict, *, dico_is_already_treated=False) -> str:
"""Transform to string"""
if informations:
level = self.rougailconfig["doc.title_level"]
self.options()
return self._run(informations, level, dico_is_already_treated)
return ""
def options(self):
self.with_commandline = self.rougailconfig["doc.tabulars.with_commandline"]
self.with_environment = self.rougailconfig["doc.tabulars.with_environment"]
if self.with_environment and not gen_argument_name:
raise Exception("please install tiramisu_cmdline_parser")
if not self.rougailconfig["main_namespace"] and self.with_environment:
environment_prefix = self.rougailconfig["doc.tabulars.environment_prefix"]
if environment_prefix:
self.prefix = environment_prefix + "_"
self.with_family = not self.rougailconfig["doc.tabulars.without_family"]
self.other_root_filenames = dict(
self.rougailconfig["doc.other_root_filenames"].items()
)
tabular_template = self.rougailconfig["doc.tabular_template"]
self.tabular_datas = Tabulars().get()[tabular_template](self)
def compute(self, data):
return ENTER.join([d for d in data if d])
# Class you needs implement to your Formatter
def title(
self,
title: str,
level: int,
collapse: bool = True,
) -> str:
"""Display family name as a title"""
raise NotImplementedError()
def join(
self,
lst: List[str],
) -> str:
"""Display line in tabular from a list"""
raise NotImplementedError()
def bold(
self,
msg: str,
) -> str:
"""Set a text to bold"""
raise NotImplementedError()
def underline(
self,
msg: str,
) -> str:
"""Set a text to underline"""
raise NotImplementedError()
def anchor(
self,
path: str,
true_path: str,
) -> str:
"""Set a text to a link anchor"""
return path
def link_variable(
self,
path: str,
true_path: str,
description: str,
filename: Optional[str],
) -> str:
"""Set a text link to variable anchor"""
if not description:
return f'"{path}"'
return f'"{description}" ({path})'
def stripped(
self,
text: str,
) -> str:
"""Return stripped text (as help)"""
raise NotImplementedError()
def list(
self,
choices: list,
*,
inside_tabular: bool = True,
type_: str = "variable",
with_enter: bool = False,
) -> str:
"""Display a liste of element"""
raise NotImplementedError()
def prop(
self,
prop: str,
italic: bool,
delete: bool,
underline: bool,
) -> str:
"""Display property"""
raise NotImplementedError()
def link(
self,
comment: str,
link: str,
underline: bool,
) -> str:
"""Add a link"""
raise NotImplementedError()
def yaml(self, _dump: str, yaml_version: str = "1.1"):
output = f"---\n{_dump}"
if yaml_version == "1.2":
output = f"%YAML 1.2\n{output}\n..."
return self._yaml(output)
##################
def end_family_informations(self) -> str:
return None
def display_paths(
self,
informations: dict,
modified_attributes: dict,
force_identifiers: Optional[str],
*,
is_variable=False,
variable_prefix: str = "",
is_bold: bool = True,
is_upper: bool = False,
with_anchor: bool = True,
) -> str:
if with_anchor:
anchor = self.anchor
else:
def anchor(path, true_path):
return path
ret_paths = []
path = informations["path"]
if not path:
return None
if is_bold:
bold = self.bold
else:
def bold(value):
return value
if is_upper:
def upper(value):
return value.upper()
else:
def upper(value):
return value
if "identifiers" in modified_attributes:
name, previous, new = modified_attributes["identifiers"]
ret_paths.extend(
[
bold(
self.delete(
upper(
variable_prefix
+ calc_path(
path, formatter=self, identifiers=identifier
)
)
)
)
for identifier in previous
]
)
else:
new = []
if "full_path" in informations:
full_path = informations["full_path"]
else:
full_path = informations["path"]
if "identifiers" in informations:
for idx, identifier in enumerate(informations["identifiers"]):
if force_identifiers and identifier != force_identifiers:
continue
path_ = calc_path(path, formatter=self, identifiers=identifier)
if variable_prefix:
path_ = variable_prefix + upper(path_)
if not idx:
path_ = anchor(path_, full_path)
if identifier in new:
path_ = self.underline(path_)
ret_paths.append(bold(path_))
else:
ret_paths.append(bold(anchor(variable_prefix + upper(path), full_path)))
return ret_paths
def tabular_header(
self,
lst: list,
) -> tuple:
"""Manage the header of a tabular"""
return lst
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 ENTER.join(dico)
return ENTER.join([msg for msg in self.dict_to_dict(dico, level, init=True)])
def dict_to_dict(
self,
dico: dict,
level: int,
*,
init: bool = False,
) -> str:
"""Parse the dict to transform to dict"""
msg = []
for value in dico.values():
if value["type"] == "variable":
self.variable_to_string(value)
elif self.with_family:
if init and value["type"] == "namespace":
namespace = True
else:
namespace = False
if self.tabular_datas.columns:
msg.append(self.tabular())
msg.extend(
self.family_to_string(value["informations"], level, namespace)
)
msg.extend(self.dict_to_dict(value["children"], level + 1))
end = self.end_family(level)
if end:
msg.append(end)
else:
self.dict_to_dict(
value["children"],
level + 1,
)
if self.tabular_datas.columns and (init or self.with_family):
msg.append(self.tabular())
return msg
# FAMILY
def namespace_to_title(self, informations: dict, level: int) -> str:
"""manage namespace family"""
return self.title(
self.get_description(informations, {}, title=True),
level,
)
def family_to_string(self, informations: dict, level: int, namespace: bool) -> str:
"""manage other family type"""
if namespace:
ret = [self.namespace_to_title(informations, level)]
else:
ret = [
self.title(
self.get_description(informations, {}, title=True),
level,
)
]
msg = []
helps = informations.get("help")
if helps:
for help_ in helps:
msg.extend([to_phrase(h) for h in help_.strip().split("\n")])
path = self.display_paths(
informations, {}, None, with_anchor=False, is_bold=False
)
if path:
if "full_path" in informations:
full_path = informations["full_path"]
else:
full_path = informations["path"]
msg.append(self.section(full_path, _("Path"), path, type_="family"))
calculated_properties = []
property_str = self.property_to_string(informations, calculated_properties, {})
if property_str:
msg.append(property_str)
if calculated_properties:
msg.append(self.join(calculated_properties))
if "identifier" in informations:
if "full_path" in informations:
full_path = informations["full_path"]
else:
full_path = informations["path"]
msg.append(
self.section(
full_path,
_("Identifiers"),
informations["identifier"],
type_="family",
)
)
if msg:
ret.append(self.display_family_informations(msg))
end = self.end_family_informations()
if end:
ret.append(end)
return ret
def display_family_informations(self, msg) -> str:
fam_info = self.family_informations()
msg = self.family_informations_ends_line().join(msg)
if fam_info:
msg = fam_info + msg
return msg
def family_informations(self) -> str:
return None
def family_informations_starts_line(self) -> str:
return None
def family_informations_ends_line(self) -> str:
return ""
def end_family(self, level: int, collapse: bool = True) -> str:
return None
def convert_list_to_string(
self, attribute: str, informations: dict, modified_attributes: dict
) -> str():
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):
if attribute.endswith("s"):
attr = attribute[:-1]
else:
attr = attribute
data = data[attr].replace(
"{{ identifier }}", self.italic(data["identifier"])
)
data = self.to_phrase(data)
if data in new:
data = self.underline(data)
datas.append(data)
return self.stripped(self.join(datas))
def get_description(
self,
informations: dict,
modified_attributes: dict,
*,
force_identifiers: Optional[list] = None,
with_to_phrase: bool = True,
title: bool = False,
) -> str:
add_new_description = True
def _get_description(
description,
identifiers,
delete=False,
new=[],
previous_identifiers=[],
new_identifiers=[],
its_a_name=False,
):
if identifiers and "{{ identifier }}" in description:
if its_a_name:
information_type = "name"
else:
information_type = "description"
if isinstance(informations["path"], dict):
info = informations["path"]
else:
info = informations
path = informations["path"]
if isinstance(path, str):
identifier_type = "many"
else:
identifier_type = path["identifier_type"]
if previous_identifiers:
pass
if not title or self.format_in_title:
formatter = self
else:
formatter = None
description = get_path_from_identifiers(
description,
identifiers,
previous_identifiers,
new_identifiers,
identifier_type,
formatter=formatter,
information_type=information_type,
)
elif with_to_phrase:
description = self.to_phrase(description)
if description in new:
description = self.underline(description)
if delete:
description = self.delete(description)
return description
if force_identifiers:
all_identifiers = force_identifiers
else:
all_identifiers = informations.get("identifiers", [])
if "description" in modified_attributes:
name, previous, new = modified_attributes["description"]
if previous:
identifiers = modified_attributes.get("identifiers", [])
# if modified_attributes has no description
if not identifiers:
identifiers = all_identifiers
modified_description = _get_description(
previous[0], identifiers, delete=True
)
else:
modified_description = None
elif (
"identifiers" in modified_attributes
and "{{ identifier }}" in informations["description"]
):
# FIXME aussi au dessus !
name, previous, new = modified_attributes["identifiers"]
previous_identifiers = previous[-1] if previous else []
new_identifiers = new[-1] if new else []
if new_identifiers:
all_identifiers = [
identifier
for identifier in all_identifiers
if identifier != new_identifiers
]
modified_description = _get_description(
informations["description"],
all_identifiers,
previous_identifiers=previous_identifiers,
new_identifiers=new_identifiers,
)
add_new_description = False
else:
modified_description = None
new = []
if add_new_description:
if "description" in informations:
description = _get_description(
informations["description"], all_identifiers, new=new
)
else:
description = _get_description(
informations["name"],
all_identifiers,
new=new,
its_a_name=True,
)
else:
description = None
if modified_description:
if description:
description = self.join([modified_description, description])
else:
description = modified_description
if not description:
return None
return self.stripped(description)
# VARIABLE
def variable_to_string(
self,
informations: dict,
modified_attributes: dict = {},
force_identifiers: Optional[str] = None,
) -> None:
"""Manage variable"""
self.tabular_datas.add(informations, modified_attributes, force_identifiers)
def convert_section_to_string(
self,
attribute: str,
informations: dict,
modified_attributes: dict,
multi: bool,
*,
section_name: bool = True,
with_to_phrase=False,
) -> str():
values = []
submessage = ""
if "full_path" in informations:
full_path = informations["full_path"]
else:
full_path = informations["path"]
if modified_attributes and attribute in modified_attributes:
name, previous, new = modified_attributes[attribute]
if isinstance(previous, list):
for p in previous:
submessage, m = self.message_to_string(full_path, p, submessage)
values.append(self.delete(m))
else:
submessage, old_values = self.message_to_string(
full_path, previous, submessage
)
values.append(self.delete(old_values))
else:
new = []
if attribute in informations:
old = informations[attribute]
name = old["name"]
if isinstance(old["values"], list):
for value in old["values"]:
if "identifiers" in informations and (
not isinstance(value, dict) or "identifiers" not in value
):
identifiers = informations["identifiers"]
else:
identifiers = []
submessage, old_value = self.message_to_string(
full_path, value, submessage, force_identifiers=identifiers
)
if value in new:
old_value = self.underline(old_value)
values.append(old_value)
if multi:
values = self.list(values, with_enter=section_name)
else:
values = self.join(values)
elif values:
old_values = old["values"]
submessage, old_values = self.message_to_string(
full_path, old_values, submessage
)
if old["values"] in new:
old_values = self.underline(old_values)
values.append(old_values)
values = self.join(values)
else:
old_values = old["values"]
if "identifiers" in informations and (
not isinstance(old_values, dict) or "identifiers" not in old_values
):
identifiers = informations["identifiers"]
else:
identifiers = []
submessage, values = self.message_to_string(
full_path, old_values, submessage, force_identifiers=identifiers
)
if old["values"] in new:
values = self.underline(values)
if values != []:
return self.section(
full_path,
name,
values,
submessage=submessage,
section_name=section_name,
with_to_phrase=with_to_phrase,
)
def convert_choices_to_string(
self,
informations: dict,
modified_attributes: dict,
with_default: bool = True,
) -> str():
default_is_already_set = False
if "choices" in informations:
choices = informations["choices"]
choices_values = choices["values"]
if not isinstance(choices_values, list):
choices_values = [choices_values]
default_is_a_list = False
else:
default_is_a_list = True
if "full_path" in informations:
full_path = informations["full_path"]
else:
full_path = informations["path"]
for idx, choice in enumerate(choices_values.copy()):
if isinstance(choice, dict):
choices_values[idx] = self.message_to_string(
full_path, choice, None
)[1]
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():
if (
isinstance(value, str)
and value.endswith(".")
and value not in choices_values
):
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 = [None]
if not isinstance(default, list):
default = [default]
for idx, value in enumerate(default.copy()):
if isinstance(value, dict):
default[idx] = self.message_to_string(full_path, value, None)[1]
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():
if (
isinstance(val, str)
and val.endswith(".")
and val[:-1] in choices_values
):
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 isinstance(choice, dict):
choice = self.message_to_string(full_path, choice, None)[1]
if with_default and choice in old_default:
choices_values.insert(
0, self.delete(dump(choice) + "" + _("(default)"))
)
else:
choices_values.insert(0, self.delete(dump(choice)))
else:
new = []
for idx, val in enumerate(choices_values):
if with_default and val in old_default:
choices_values[idx] = (
dump(val) + " " + self.delete("" + _("(default)"))
)
elif with_default and val in default:
if val in new_default:
if val in new:
choices_values[idx] = self.underline(
dump(val) + " " + self.bold("" + _("(default)"))
)
else:
choices_values[idx] = (
dump(val)
+ " "
+ self.underline(self.bold("" + _("(default)")))
)
else:
choices_values[idx] = (
dump(val) + " " + self.bold("" + _("(default)"))
)
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(
full_path, choices["name"], choices_values
)
return default_is_already_set, None
# OTHERs
def to_phrase(self, text: str) -> str:
return text
def property_to_string(
self,
informations: dict,
calculated_properties: list,
modified_attributes: dict,
contents: list = ["type", "properties", "mode", "access_control", "validator"],
) -> str:
"""Transform properties to string"""
properties = []
modified_properties = []
if "type" in contents:
if "type" in modified_attributes:
properties.append(
self.prop(
modified_attributes["type"],
italic=False,
delete=True,
underline=False,
)
)
if "multiple" in modified_attributes:
if (
modified_attributes["multiple"][1]
and modified_attributes["multiple"][1][0]
):
properties.append(
self.prop(
"multiple", italic=False, delete=True, underline=False
)
)
else:
properties.append(
self.prop(
"multiple", italic=False, delete=False, underline=True
)
)
if "properties" not in contents and "properties" in modified_attributes:
for prop in modified_attributes["properties"]:
if prop["ori_name"] == "mandatory":
properties.append(
self.prop(
prop["ori_name"],
italic=False,
delete=True,
underline=False,
)
)
break
if "mode" in contents and "mode" in modified_attributes:
name, previous, new = modified_attributes["mode"]
if previous:
properties.append(
self.prop(previous[0], italic=False, delete=True, underline=False)
)
if new:
properties.append(
self.prop(new[0], italic=False, delete=False, underline=True)
)
if "properties" in modified_attributes:
if "properties" in contents:
for props in modified_attributes["properties"]:
if not props:
continue
for prop in props:
if prop.get("access_control"):
if "access_control" in contents:
modified_properties.append(prop)
elif prop["ori_name"] != "unique" or "unique" in contents:
modified_properties.append(prop)
elif "access_control" in contents:
for prop in modified_attributes["properties"]:
if prop.get("access_control"):
modified_properties.append(prop)
elif "validator" in contents:
for prop in modified_attributes["properties"]:
if prop["name"] == "unique":
modified_properties.append(prop)
local_calculated_properties = {}
if "properties" in contents and "properties" in modified_attributes:
previous, new = self.get_modified_properties(
*modified_attributes["properties"][1:]
)
for p, data in previous.items():
if "type" not in contents and data[0] == "mandatory":
continue
if "validator" not in contents and data[0] == "unique":
continue
if p not in new:
properties.append(
self.prop(p, italic=False, delete=True, underline=False)
)
if data[1] is not None:
local_calculated_properties[p] = [
{"annotation": data[1], "delete": True}
]
else:
previous = new = []
if "type" in contents and "variable_type" in modified_attributes:
previous, new = modified_attributes["variable_type"][1:]
properties.append(
self.prop(previous[0], italic=False, delete=True, underline=False)
)
others = []
if "type" in contents and "variable_type" in informations:
others.append({"name": informations["variable_type"], "type": "type"})
if informations.get("multiple") and not "multiple" in modified_attributes:
others.append({"name": "multiple", "type": "multiple"})
if "properties" not in contents and "properties" in informations:
for prop in informations["properties"]:
if prop["ori_name"] == "mandatory":
others.append(prop)
break
if "mode" in contents and "mode" in informations:
others.append({"name": informations["mode"], "type": "mode"})
if "properties" in informations:
if "properties" in contents:
for prop in informations["properties"]:
if prop.get("access_control"):
if "access_control" in contents:
others.append(prop)
elif prop["ori_name"] != "unique" or "validator" in contents:
others.append(prop)
elif "access_control" in contents:
for prop in informations["properties"]:
if prop.get("access_control"):
others.append(prop)
elif "validator" in contents:
for prop in informations["properties"]:
if prop["name"] == "unique":
others.append(prop)
for prop in others:
prop_name = prop["name"]
if (
"type" not in contents
and prop.get("ori_name", prop_name) == "mandatory"
):
continue
if prop_name not in previous and prop_name in new:
underline = True
else:
underline = False
if prop["type"] == "type":
properties.append(
self.link(prop_name, ROUGAIL_VARIABLE_TYPE, underline=underline)
)
else:
if "annotation" in prop:
italic = True
prop_annotation = prop["annotation"]
if prop_name in new and (
prop_name not in previous
or new[prop_name] != previous[prop_name]
):
underline_ = True
else:
underline_ = False
local_calculated_properties.setdefault(prop["name"], []).append(
{"annotation": prop_annotation, "underline": underline_}
)
else:
italic = False
properties.append(
self.prop(
prop_name, italic=italic, delete=False, underline=underline
)
)
if local_calculated_properties:
if "full_path" in informations:
full_path = informations["full_path"]
else:
full_path = informations["path"]
for (
calculated_property_name,
calculated_property,
) in local_calculated_properties.items():
data = []
for calc in calculated_property:
annotation = self.message_to_string(
full_path, calc["annotation"], None
)[1]
if calc.get("underline", False):
annotation = self.underline(annotation)
if calc.get("delete", False):
annotation = self.delete(annotation)
data.append(annotation)
if len(calculated_property) > 1:
calculated_property = self.join(data)
else:
calculated_property = data[0]
calculated_properties.append(
self.section(
full_path,
calculated_property_name.capitalize(),
calculated_property,
)
)
if not properties:
return ""
return " ".join(properties)
def get_modified_properties(
self, previous: List[dict], new: List[dict]
) -> Tuple[dict, dict]:
def modified_properties_parser(dico):
return {d["name"]: (d["ori_name"], d.get("annotation")) for d in dico}
return modified_properties_parser(previous), modified_properties_parser(new)
def columns(
self,
col: List[str], # pylint: disable=unused-argument
) -> None:
"""Manage column"""
return
def tabular(self, with_header: bool = True) -> str:
"""Transform list to a tabular in string format"""
if with_header:
headers = self.tabular_header(self.tabular_datas.headers())
else:
headers = ()
msg = tabulate(
self.tabular_datas.get(),
headers=headers,
tablefmt=self._tabular_name,
)
return msg
def message_to_string(self, full_path, msg, ret, *, force_identifiers=[]):
if isinstance(msg, dict):
if "submessage" in msg:
ret += msg["submessage"]
msg = msg["values"]
elif "message" in msg:
filename = None
if self.other_root_filenames:
path = msg["path"]["path"]
for root in self.other_root_filenames:
if path == root or path.startswith(f"{root}."):
filename = self.other_root_filenames[root]
break
else:
if "." in self.other_root_filenames:
filename = self.other_root_filenames["."]
if "identifiers" in msg["path"]:
msg["identifiers"] = msg["path"]["identifiers"]
calculated_paths = calc_path(
msg["path"], formatter=self, identifiers=force_identifiers
)
if self.support_namespace and self.document_a_type:
namespace = full_path.split(".", 1)[0]
else:
namespace = None
if isinstance(calculated_paths, list):
msgs = [
msg["message"].format(
self.link_variable(
doc_path(
calculated_path, self.document_a_type, namespace
),
msg["path"]["path"],
self.get_description(
msg,
{},
force_identifiers=[msg["path"]["identifiers"][idx]],
with_to_phrase=False,
),
filename=filename,
)
)
for idx, calculated_path in enumerate(calculated_paths)
]
msg = self.list(msgs)
else:
path = self.link_variable(
doc_path(calculated_paths, self.document_a_type, namespace),
msg["path"]["path"],
self.get_description(msg, {}, with_to_phrase=False),
filename=filename,
)
msg = msg["message"].format(path)
elif "description" in msg:
if "variables" in msg:
paths = []
for variable in msg["variables"]:
filename = None
if self.other_root_filenames:
path = variable["path"]
for root in self.other_root_filenames:
if path == root or path.startswith(f"{root}."):
filename = self.other_root_filenames[root]
break
else:
if "." in self.other_root_filenames:
filename = self.other_root_filenames["."]
identifiers = variable.get("identifiers")
if identifiers is None and force_identifiers:
identifiers = force_identifiers
if self.format_in_title:
formatter = self
else:
formatter = None
path = calc_path(
variable, formatter=self, identifiers=identifiers
)
paths.append(
self.link_variable(
path,
variable["path"],
self.get_description(
variable,
{},
force_identifiers=identifiers,
with_to_phrase=False,
),
filename=filename,
)
)
msg = msg["description"].format(*paths)
else:
msg = msg["description"]
return ret, msg
def section(
self,
full_path: str,
name: str,
msg: str,
submessage: str = "",
type_="variable",
section_name=True,
with_to_phrase=False,
force_enter=False,
) -> str:
"""Return something like Name: msg"""
submessage, msg = self.message_to_string(full_path, msg, submessage)
if isinstance(msg, list):
if len(msg) == 1:
submessage, elt = self.message_to_string(full_path, msg[0], submessage)
if isinstance(elt, list):
submessage += self.list(elt, type_=type_, with_enter=section_name)
elif force_enter:
submessage += self.enter_tabular + elt
else:
submessage += elt
else:
lst = []
for p in msg:
submessage, elt = self.message_to_string(full_path, p, submessage)
lst.append(elt)
submessage += self.list(lst, type_=type_, with_enter=section_name)
msg = ""
if not isinstance(msg, str):
submessage += dump(msg)
else:
submessage += msg
if section_name:
return _("{0}: {1}").format(self.bold(name), submessage)
if with_to_phrase:
return to_phrase(submessage)
return submessage
def calc_path(path, *, formatter=None, identifiers: List[str] = None) -> str:
def _path_with_identifier(path, identifier):
if identifier is None:
identifier = "{{ __identifier__ }}"
else:
identifier = normalize_family(str(identifier))
if formatter:
identifier = formatter.italic(identifier)
return path.replace("{{ identifier }}", identifier, 1)
if isinstance(path, dict):
path_ = path["path"]
if "identifiers" in path:
path_ = get_path_from_identifiers(
path["path"],
path["identifiers"],
[],
[],
path["identifier_type"],
formatter,
)
elif identifiers:
for identifier in identifiers[0]:
path_ = _path_with_identifier(path_, identifier)
elif identifiers:
path_ = path
for identifier in identifiers:
path_ = _path_with_identifier(path_, identifier)
path_ = path_.replace("{{ __identifier__ }}", "{{ identifier }}")
else:
path_ = path
return path_
def doc_path(path, document_a_type, namespace: Optional[str] = None) -> str:
if document_a_type:
if "." not in path:
return None
if not namespace or path.startswith(namespace + "."):
return path.split(".", 1)[-1]
return path
def get_path_from_identifiers(
text: str,
all_identifiers: list,
previous_identifiers: list,
new_identifiers: list,
identifier_type: str,
formatter: Optional[object] = None,
information_type="path",
) -> str:
if not isinstance(all_identifiers, list):
raise Exception("hu1?")
if all_identifiers:
for i in all_identifiers:
if not isinstance(i, list):
raise Exception("hu2?")
for j in i:
if isinstance(j, list):
raise Exception("hu3?")
if not isinstance(new_identifiers, list):
raise Exception("hu?")
def _text_with_identifier(information, identifier, delete=False, underline=False):
if identifier is None:
identifier = "{{ __identifier__ }}"
elif information_type == "path":
identifier = normalize_family(str(identifier))
else:
identifier = str(identifier)
if formatter:
if delete:
identifier = formatter.delete(identifier)
if underline:
identifier = formatter.underline(identifier)
identifier = formatter.italic(identifier)
return information.replace("{{ identifier }}", identifier, 1)
if identifier_type == "outside":
separator = "and"
else:
separator = "or"
if information_type == "description":
ori_text = "{{ identifier }}"
else:
ori_text = text
paths = []
identifiers_done = []
for identifiers in all_identifiers:
if information_type != "path":
identifier = identifiers[-1]
if identifier in identifiers_done:
continue
text_ = _text_with_identifier(ori_text, identifiers[-1])
identifiers_done.append(identifier)
else:
text_ = ori_text
for identifier in identifiers:
text_ = _text_with_identifier(text_, identifier)
paths.append(text_)
if formatter:
for identifier in previous_identifiers:
paths.append(_text_with_identifier(ori_text, identifier, delete=True))
for identifier in new_identifiers:
paths.append(_text_with_identifier(ori_text, identifier, underline=True))
if information_type == "description":
if identifier_type == "outside":
paths = [text.replace("{{ identifier }}", path) for path in paths]
return display_list(
paths,
separator=separator,
sort=False,
)
identifiers_text = display_list(
paths,
separator=separator,
sort=False,
)
return text.replace("{{ identifier }}", identifiers_text)
if identifier_type == "outside":
return paths
return display_list(paths, separator=separator, sort=False)