""" 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 . """ 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)