392 lines
12 KiB
Python
392 lines
12 KiB
Python
"""
|
|
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 <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
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(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):
|
|
"""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, informations: dict, level: int) -> str:
|
|
"""Transform to string"""
|
|
msg = self.header()
|
|
if informations:
|
|
msg += self.dict_to_string(informations, level)
|
|
return msg
|
|
|
|
def dict_to_string(self, informations: dict, level: int) -> str:
|
|
"""Parse the dict to transform to dict"""
|
|
msg = ""
|
|
table_datas = []
|
|
ori_level = None
|
|
for value in informations.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, informations: dict, level: int) -> str:
|
|
"""manage namespace family"""
|
|
return self.title(
|
|
_('Variables for "{0}"').format(self.family_description(informations)),
|
|
level,
|
|
)
|
|
|
|
def family_to_string(self, informations: dict, level: int) -> str:
|
|
"""manage other family type"""
|
|
msg = self.title(self.family_description(informations), level)
|
|
calculated_properties = []
|
|
msg += self.property_to_string(informations, calculated_properties) + ENTER
|
|
if calculated_properties:
|
|
msg += self.join(calculated_properties) + ENTER
|
|
helps = informations.get("help")
|
|
if helps:
|
|
for help_ in helps:
|
|
msg += help_.strip() + ENTER
|
|
if "identifiers" in informations:
|
|
msg += self.section(_("Identifiers"), informations["identifiers"]) + ENTER
|
|
return msg
|
|
|
|
def family_description(self, informations: dict) -> str():
|
|
"""Get family name"""
|
|
if "description" in informations:
|
|
return informations["description"]
|
|
return display_list(
|
|
[
|
|
get_display_path(informations, index)
|
|
for index in range(len(informations["paths"]))
|
|
],
|
|
separator="or",
|
|
)
|
|
|
|
# VARIABLE
|
|
def variable_to_string(self, informations: dict, table_datas: dict) -> None:
|
|
"""Manage variable"""
|
|
calculated_properties = []
|
|
table_datas.append(
|
|
[
|
|
self.join(
|
|
self.variable_first_column(informations, calculated_properties)
|
|
),
|
|
self.join(
|
|
self.variable_second_column(informations, calculated_properties)
|
|
),
|
|
]
|
|
)
|
|
|
|
def variable_first_column(
|
|
self, informations: dict, calculated_properties: list
|
|
) -> list:
|
|
"""Collect string for the first column"""
|
|
first_col = [
|
|
self.join(
|
|
[
|
|
self.bold(get_display_path(informations, index))
|
|
for index in range(len(informations["paths"]))
|
|
]
|
|
),
|
|
self.property_to_string(informations, calculated_properties),
|
|
]
|
|
self.columns(first_col)
|
|
return first_col
|
|
|
|
def variable_second_column(
|
|
self, informations: dict, calculated_properties: list
|
|
) -> list:
|
|
"""Collect string for the second column"""
|
|
if "descriptions" in informations:
|
|
description = self.join(list(dict.fromkeys(informations["descriptions"])))
|
|
else:
|
|
description = to_phrase(
|
|
display_list(
|
|
list(dict.fromkeys(informations["names"])),
|
|
separator="or",
|
|
)
|
|
)
|
|
second_col = [self.stripped(description)]
|
|
for help_ in informations.get("help", []):
|
|
second_col.append(self.stripped(help_))
|
|
if "validators" in informations:
|
|
validators = informations["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 informations:
|
|
second_col.append(self.section(_("Choices"), informations["choices"]))
|
|
if "default" in informations and informations.get("display_default", True):
|
|
second_col.append(self.section(_("Default"), informations["default"]))
|
|
if "examples" in informations:
|
|
examples = informations["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, informations: dict, calculated_properties: list
|
|
) -> str:
|
|
"""Transform properties to string"""
|
|
properties = []
|
|
for prop in informations.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)
|
|
|
|
|
|
def get_display_path(informations: dict, index: int) -> str:
|
|
if "display_paths" in informations and index in informations["display_paths"]:
|
|
return informations["display_paths"][index]
|
|
return informations["paths"][index]
|