""" Silique (https://www.silique.fr) Copyright (C) 2024-2025 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 List from io import BytesIO from ruamel.yaml import YAML import tabulate as tabulate_module from tiramisu.error import display_list from tabulate import tabulate 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}"), }, }, "ip": { "msg": "IP", "params": { "cidr": _("IP must be in CIDR format"), "private_only": _("private IP are allowed"), "allow_reserved": _("reserved IP are allowed"), }, }, "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"), "allow_wellknown": _("ports 1 to 1023 are allowed"), "allow_registred": _("ports 1024 to 49151 are allowed"), "allow_private": _("ports greater than 49152 are allowed"), }, }, "secret": { "params": { "min_len": _("minimum length for the secret"), "max_len": _("maximum length for the secret"), }, }, } _yaml = YAML() _yaml.indent(mapping=2, sequence=4, offset=2) def dump(dico): """Dump variable, means transform bool, ... to yaml string""" with BytesIO() as ymlfh: _yaml.dump(dico, ymlfh) ret = ymlfh.getvalue().decode("utf-8").strip() if ret.endswith("..."): ret = ret[:-3].strip() return ret def to_phrase(msg): """Add maj for the first character and ends with dot""" if not msg: # replace None to empty string return "" msg = str(msg).strip() # a phrase must ends with a dot if not msg.endswith("."): msg += "." # 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 def __init__(self): tabulate_module.PRESERVE_WHITESPACE = True self.header_setted = False # Class you needs implement to your Formater def header(self): """Header of the documentation""" raise NotImplementedError() 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() ################## def table_header( self, lst: list, ) -> tuple: """Manage the header of a table""" return lst def run(self, dico: dict, level: int) -> str: """Transform to string""" msg = self.header() if dico: msg += self.dict_to_string(dico, level) return msg def dict_to_string(self, dico: dict, level: int) -> str: """Parse the dict to transform to dict""" msg = "" table_datas = [] ori_level = None for value in dico.values(): if value["type"] == "namespace": if ori_level is None: ori_level = level level += 1 msg += self.namespace_to_string(value["informations"], ori_level) msg += self.dict_to_string(value["children"], level) else: if value["type"] == "variable": self.variable_to_string(value, table_datas) else: if table_datas: msg += self.table(table_datas) msg += self.family_to_string(value["informations"], level) msg += self.dict_to_string(value["children"], level + 1) if table_datas: msg += self.table(table_datas) return msg # FAMILY def namespace_to_string(self, dico: dict, level: int) -> str: """manage namespace family""" return self.title( _('Variables for "{0}"').format(self.family_description(dico)), level ) def family_to_string(self, dico: dict, level: int) -> str: """manage other family type""" msg = self.title(self.family_description(dico), level) calculated_properties = [] msg += self.property_to_string(dico, calculated_properties) + ENTER if calculated_properties: msg += self.join(calculated_properties) + ENTER helps = dico.get("help") if helps: for help_ in helps: msg += help_.strip() + ENTER if "identifiers" in dico: msg += self.section(_("Identifiers"), dico["identifiers"]) + ENTER return msg def family_description(self, dico: dict) -> str(): """Get family name""" if "description" in dico: return dico["description"] return display_list( dico["paths"], separator="or", ) # VARIABLE def variable_to_string(self, dico: dict, table_datas: dict) -> None: """Manage variable""" calculated_properties = [] table_datas.append( [ self.join(self.variable_first_column(dico, calculated_properties)), self.join(self.variable_second_column(dico, calculated_properties)), ] ) def variable_first_column(self, dico: dict, calculated_properties: list) -> list: """Collect string for the first column""" first_col = [ self.join([self.bold(path_) for path_ in dico["paths"]]), self.property_to_string(dico, calculated_properties), ] self.columns(first_col) return first_col def variable_second_column(self, dico: dict, calculated_properties: list) -> list: """Collect string for the second column""" if "descriptions" in dico: description = self.join(list(dict.fromkeys(dico["descriptions"]))) else: description = to_phrase( display_list( list(dict.fromkeys(dico["names"])), separator="or", ) ) second_col = [self.stripped(description)] for help_ in dico.get("help", []): second_col.append(self.stripped(help_)) if "validators" in dico: validators = dico["validators"] if len(validators) == 1: second_col.append(self.section(_("Validator"), validators[0])) else: second_col.append(self.section(_("Validators"), self.list(validators))) if "choices" in dico: second_col.append(self.section(_("Choices"), dico["choices"])) if "default" in dico and dico.get("display_default", True): second_col.append(self.section(_("Default"), dico["default"])) if "examples" in dico: examples = dico["examples"] if len(examples) == 1: second_col.append(self.section(_("Example"), examples[0])) else: second_col.append(self.section(_("Examples"), examples)) second_col.extend(calculated_properties) self.columns(second_col) return second_col # OTHERs def property_to_string(self, dico: dict, calculated_properties: list) -> str: """Transform properties to string""" properties = [] for prop in dico.get("properties", []): if prop["type"] == "type": properties.append(self.link(prop["name"], ROUGAIL_VARIABLE_TYPE)) else: if "annotation" in prop: italic = True calculated_properties.append( self.section(prop["name"].capitalize(), prop["annotation"]) ) else: italic = False prop_str = self.prop(prop["name"], italic=italic) properties.append(prop_str) if not properties: return "" return " ".join(properties) def columns( self, col: List[str], # pylint: disable=unused-argument ) -> None: """Manage column""" return def table(self, datas: list) -> str: """Transform list to a table in string format""" msg = ( tabulate( datas, headers=self.table_header([_("Variable"), _("Description")]), tablefmt=self._table_name, ) + "\n\n" ) datas.clear() return msg def section( self, name: str, msg: str, ) -> str: """Return something like Name: msg""" if isinstance(msg, list): if len(msg) == 1: msg = msg[0] else: msg = self.list(msg) if not isinstance(msg, str): msg = dump(msg) return _("{0}: {1}").format(self.bold(name), msg)