From 1701d317d44baa0253546cf8582f863df408d96d Mon Sep 17 00:00:00 2001 From: Emmanuel Garette <egarette@silique.fr> Date: Fri, 1 Nov 2024 11:17:14 +0100 Subject: [PATCH] feat: black + improvement --- pyproject.toml | 40 ++ src/rougail/output_doc/__init__.py | 535 +++++++++++++--------- src/rougail/output_doc/annotator.py | 220 +++++---- src/rougail/output_doc/cli.py | 36 -- src/rougail/output_doc/config.py | 66 +-- src/rougail/output_doc/output/__init__.py | 28 +- src/rougail/output_doc/output/asciidoc.py | 125 +++-- src/rougail/output_doc/output/github.py | 119 +++-- 8 files changed, 691 insertions(+), 478 deletions(-) create mode 100644 pyproject.toml delete mode 100644 src/rougail/output_doc/cli.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..89998d7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +build-backend = "flit_core.buildapi" +requires = ["flit_core >=3.8.0,<4"] + +[project] +name = "rougail.output_doc" +version = "0.1.0rc0" +authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] +readme = "README.md" +description = "Rougail output doc" +requires-python = ">=3.8" +license = {file = "LICENSE"} +classifiers = [ + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "Natural Language :: English", + "Natural Language :: French", + +] +dependencies = [ + "rougail ~= 1.1.0", +] + +[project.urls] +Home = "https://forge.cloud.silique.fr/stove/rougail-output-exporter" + +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "$version" +version_scheme = "pep440" +version_provider = "pep621" +update_changelog_on_bump = true +changelog_merge_prerelease = true diff --git a/src/rougail/output_doc/__init__.py b/src/rougail/output_doc/__init__.py index 38c352c..ed6da20 100644 --- a/src/rougail/output_doc/__init__.py +++ b/src/rougail/output_doc/__init__.py @@ -1,25 +1,22 @@ #!/usr/bin/env python3 """ Silique (https://www.silique.fr) -Copyright (C) 2022-2024 +Copyright (C) 2024 -distribued with GPL-2 or later license - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +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/>. """ -#FIXME si plusieurs example dont le 1er est none tester les autres : tests/dictionaries/00_8test_none +# FIXME si plusieurs example dont le 1er est none tester les autres : tests/dictionaries/00_8test_none from tiramisu import Calculation from tiramisu.error import display_list import tabulate as tabulate_module @@ -27,62 +24,62 @@ from tabulate import tabulate from warnings import warn from typing import Optional -from gettext import gettext as _ - from rougail.error import display_xmlfiles from rougail import RougailConfig, Rougail, CONVERT_OPTION from rougail.object_model import PROPERTY_ATTRIBUTE from .config import OutPuts +from .i18n import _ 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 only a hostname'), - 'allow_ip': _('the domain name can be an IP'), - 'allow_cidr_network': _('the domain name can be network in CIDR format'), + "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 {value}'), - 'max_number': _('the maximum value is {value}'), + "number": { + "params": { + "min_number": _("the minimum value is {value}"), + "max_number": _("the maximum value is {value}"), }, }, - 'ip': { - 'msg': 'IP', - 'params': { - 'cidr': _('IP must be in CIDR format'), - 'private_only': _('private IP are allowed'), - 'allow_reserved': _('reserved IP are allowed'), + "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'), + "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'), + "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'), + "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"), }, }, - } @@ -92,36 +89,48 @@ ROUGAIL_VARIABLE_TYPE = ( class RougailOutputDoc: - def __init__(self, - *, - config: 'Config'=None, - rougailconfig: RougailConfig=None, - ): + def __init__( + self, + *, + config: "Config" = None, + rougailconfig: RougailConfig = None, + **kwarg, + ): if rougailconfig is None: rougailconfig = RougailConfig + if rougailconfig["step.output"] != "doc": + rougailconfig["step.output"] = "doc" + if rougailconfig["step.output"] != "doc": + raise Exception("doc is not set as step.output") self.rougailconfig = rougailconfig outputs = OutPuts().get() - output = self.rougailconfig['doc.output_format'] + output = self.rougailconfig["doc.output_format"] if output not in outputs: - raise Exception(f'cannot find output "{output}", available outputs: {list(outputs)}') + raise Exception( + f'cannot find output "{output}", available outputs: {list(outputs)}' + ) if config is None: rougail = Rougail(self.rougailconfig) - rougail.converted.plugins.append('output_doc') + rougail.converted.plugins.append("output_doc") config = rougail.get_config() self.conf = config - self.conf.property.setdefault(frozenset({'advanced'}), 'read_write', 'append') + self.conf.property.setdefault(frozenset({"advanced"}), "read_write", "append") self.conf.property.read_write() self.conf.property.remove("cache") self.dynamic_paths = {} self.formater = outputs[output]() - self.level = self.rougailconfig['doc.title_level'] - #self.property_to_string = [('mandatory', 'obligatoire'), ('hidden', 'cachée'), ('disabled', 'désactivée'), ('unique', 'unique'), ('force_store_value', 'modifié automatiquement')] - self.property_to_string = [('mandatory', _('mandatory')), - ('hidden', _('hidden')), - ('disabled', _('disabled')), - ('unique', _('unique')), - ('force_store_value', _('auto modified')), - ] + self.level = self.rougailconfig["doc.title_level"] + # self.property_to_string = [('mandatory', 'obligatoire'), ('hidden', 'cachée'), ('disabled', 'désactivée'), ('unique', 'unique'), ('force_store_value', 'modifié automatiquement')] + self.property_to_string = [ + ("mandatory", _("mandatory")), + ("hidden", _("hidden")), + ("disabled", _("disabled")), + ("unique", _("unique")), + ("force_store_value", _("auto modified")), + ] + + def run(self): + print(self.gen_doc()) def gen_doc(self): tabulate_module.PRESERVE_WHITESPACE = True @@ -133,51 +142,62 @@ class RougailOutputDoc: name = namespace.name() examples_mini[name] = {} examples_all[name] = {} - doc = self._display_doc( - self.display_families( - namespace, - self.level + 1, - examples_mini[name], - examples_all[name], - ), - [], - ) + '\n' + doc = ( + self._display_doc( + self.display_families( + namespace, + self.level + 1, + examples_mini[name], + examples_all[name], + ), + [], + ) + + "\n" + ) if not examples_mini[name]: del examples_mini[name] if not examples_all[name]: del examples_all[name] else: - return_string += self.formater.title(_(f'Variables for "{namespace.name()}"'), self.level) + return_string += self.formater.title( + _(f'Variables for "{namespace.name()}"'), self.level + ) return_string += doc else: - doc = self._display_doc( - self.display_families( - self.conf.unrestraint, - self.level + 1, - examples_mini, - examples_all, - ), - [], - ) + '\n' + doc = ( + self._display_doc( + self.display_families( + self.conf.unrestraint, + self.level + 1, + examples_mini, + examples_all, + ), + [], + ) + + "\n" + ) if examples_all: - return_string += self.formater.title(_(f'Variables'), self.level) + return_string += self.formater.title(_(f"Variables"), self.level) return_string += doc if not examples_all: - return '' - if examples_mini: - #"Exemple avec les variables obligatoires non renseignées" - return_string += self.formater.title( - _("Example with mandatory variables not filled in"), self.level - ) - return_string += self.formater.yaml(examples_mini) - if examples_all: - #"Exemple avec tous les variables modifiables" - return_string += self.formater.title("Example with all variables modifiable", self.level) - return_string += self.formater.yaml(examples_all) + return "" + if self.rougailconfig["doc.with_example"]: + if examples_mini: + # "Exemple avec les variables obligatoires non renseignées" + return_string += self.formater.title( + _("Example with mandatory variables not filled in"), self.level + ) + return_string += self.formater.yaml(examples_mini) + if examples_all: + # "Exemple avec tous les variables modifiables" + return_string += self.formater.title( + "Example with all variables modifiable", self.level + ) + return_string += self.formater.yaml(examples_all) return return_string def _display_doc(self, variables, add_paths): - return_string = '' + return_string = "" for variable in variables: typ = variable["type"] path = variable["path"] @@ -189,21 +209,50 @@ class RougailOutputDoc: else: for idx, path in enumerate(variable["paths"]): if path in self.dynamic_paths: - paths_msg = display_list([self.formater.bold(path_) for path_ in self.dynamic_paths[path]['paths']], separator='or') - variable["objects"][idx][0] = variable["objects"][idx][0].replace('{{ ROUGAIL_PATH }}', paths_msg) - suffixes = self.dynamic_paths[path]['suffixes'] + paths_msg = display_list( + [ + self.formater.bold(path_) + for path_ in self.dynamic_paths[path]["paths"] + ], + separator="or", + ) + variable["objects"][idx][0] = variable["objects"][idx][ + 0 + ].replace("{{ ROUGAIL_PATH }}", paths_msg) + identifiers = self.dynamic_paths[path]["identifiers"] description = variable["objects"][idx][1][0] - if "{{ suffix }}" in description: - if description.endswith('.'): + if "{{ identifier }}" in description: + if description.endswith("."): description = description[:-1] - comment_msg = self.to_phrase(display_list([description.replace('{{ suffix }}', self.formater.italic(suffix)) for suffix in suffixes], separator='or', add_quote=True)) + comment_msg = self.to_phrase( + display_list( + [ + description.replace( + "{{ identifier }}", + self.formater.italic(identifier), + ) + for identifier in identifiers + ], + separator="or", + add_quote=True, + ) + ) variable["objects"][idx][1][0] = comment_msg - variable["objects"][idx][1] = self.formater.join(variable["objects"][idx][1]) - return_string += self.formater.table(tabulate( - variable["objects"], - headers=self.formater.table_header(['Variable', 'Description']), - tablefmt=self.formater.name, - )) + '\n\n' + variable["objects"][idx][1] = self.formater.join( + variable["objects"][idx][1] + ) + return_string += ( + self.formater.table( + tabulate( + variable["objects"], + headers=self.formater.table_header( + ["Variable", "Description"] + ), + tablefmt=self.formater.name, + ) + ) + + "\n\n" + ) add_paths.append(path) return return_string @@ -236,8 +285,12 @@ class RougailOutputDoc: continue path = child.path(uncalculated=True) if child.isdynamic(): - self.dynamic_paths.setdefault(path, {'paths': [], 'suffixes': []})['paths'].append(child.path()) - self.dynamic_paths[path]['suffixes'].append(child.suffixes()[-1]) + self.dynamic_paths.setdefault( + path, {"paths": [], "identifiers": []} + )["paths"].append(child.path()) + self.dynamic_paths[path]["identifiers"].append( + child.identifiers()[-1] + ) if not variables or variables[-1]["type"] != "variables": variables.append( { @@ -298,9 +351,18 @@ class RougailOutputDoc: title = f"{family.path()}" isdynamic = family.isdynamic(only_self=True) if isdynamic: - suffixes = family.suffixes(only_self=True) - if '{{ suffix }}' in title: - title = display_list([title.replace('{{ suffix }}', self.formater.italic(suffix)) for suffix in suffixes], separator='or', add_quote=True) + identifiers = family.identifiers(only_self=True) + if "{{ identifier }}" in title: + title = display_list( + [ + title.replace( + "{{ identifier }}", self.formater.italic(identifier) + ) + for identifier in identifiers + ], + separator="or", + add_quote=True, + ) msg = self.formater.title(title, level) subparameter = [] self.manage_properties(family, subparameter) @@ -309,8 +371,8 @@ class RougailOutputDoc: comment = [] self.subparameter_to_parameter(subparameter, comment) if comment: - msg += '\n'.join(comment) + ENTER - help = self.to_phrase(family.information.get('help', "")) + msg += "\n".join(comment) + ENTER + help = self.to_phrase(family.information.get("help", "")) if help: msg += "\n" + help + ENTER if family.isleadership(): @@ -318,39 +380,43 @@ class RougailOutputDoc: help = "This family contains lists of variable blocks." msg += "\n" + help + ENTER if isdynamic: - suffixes = family.suffixes(only_self=True , uncalculated=True) - if isinstance(suffixes, Calculation): - suffixes = self.to_string(family, 'dynamic') - if isinstance(suffixes, list): - for idx, val in enumerate(suffixes): + identifiers = family.identifiers(only_self=True, uncalculated=True) + if isinstance(identifiers, Calculation): + identifiers = self.to_string(family, "dynamic") + if isinstance(identifiers, list): + for idx, val in enumerate(identifiers): if not isinstance(val, Calculation): continue - suffixes[idx] = self.to_string(family, 'dynamic', f'_{idx}') - suffixes = self.formater.list(suffixes) - #help = f"Cette famille construit des familles dynamiquement.\n\n{self.formater.bold('Suffixes')}: {suffixes}" - help = f"This family builds families dynamically.\n\n{self.formater.bold('Suffixes')}: {suffixes}" + identifiers[idx] = self.to_string(family, "dynamic", f"_{idx}") + identifiers = self.formater.list(identifiers) + # help = f"Cette famille construit des familles dynamiquement.\n\n{self.formater.bold('Identifiers')}: {identifiers}" + help = f"This family builds families dynamically.\n\n{self.formater.bold('Identifiers')}: {identifiers}" msg += "\n" + help + ENTER return msg - def manage_properties(self, - variable, - subparameter, - ): + def manage_properties( + self, + variable, + subparameter, + ): properties = variable.property.get(uncalculated=True) - for mode in self.rougailconfig['modes_level']: + for mode in self.rougailconfig["modes_level"]: if mode in properties: subparameter.append((self.formater.prop(mode), None, None)) break for prop, msg in self.property_to_string: if prop in properties: subparameter.append((self.formater.prop(msg), None, None)) - elif variable.information.get(f'{prop}_calculation', False): - subparameter.append((self.formater.prop(msg), msg, self.to_string(variable, prop))) + elif variable.information.get(f"{prop}_calculation", False): + subparameter.append( + (self.formater.prop(msg), msg, self.to_string(variable, prop)) + ) - def subparameter_to_string(self, - subparameter, - ): - subparameter_str = '' + def subparameter_to_string( + self, + subparameter, + ): + subparameter_str = "" for param in subparameter: if param[1]: subparameter_str += f"_{param[0]}_ " @@ -358,10 +424,11 @@ class RougailOutputDoc: subparameter_str += f"{param[0]} " return subparameter_str[:-1] - def subparameter_to_parameter(self, - subparameter, - comment, - ): + def subparameter_to_parameter( + self, + subparameter, + comment, + ): for param in subparameter: if not param[1]: continue @@ -370,10 +437,10 @@ class RougailOutputDoc: def to_phrase(self, msg): if not msg: - return '' + return "" msg = str(msg).strip() - if not msg.endswith('.'): - msg += '.' + if not msg.endswith("."): + msg += "." return msg[0].upper() + msg[1:] def display_variable( @@ -389,16 +456,18 @@ class RougailOutputDoc: subparameter = [] description = variable.description(uncalculated=True) comment = [self.to_phrase(description)] - help_ = self.to_phrase(variable.information.get("help", '')) + help_ = self.to_phrase(variable.information.get("help", "")) if help_: comment.append(help_) - self.type_to_string(variable, - subparameter, - comment, - ) - self.manage_properties(variable, - subparameter, - ) + self.type_to_string( + variable, + subparameter, + comment, + ) + self.manage_properties( + variable, + subparameter, + ) if variable.ismulti(): multi = not variable.isfollower() or variable.issubmulti() else: @@ -410,127 +479,161 @@ class RougailOutputDoc: if variable.name() == description: warning = f'No attribute "description" for variable "{variable.path()}" in {display_xmlfiles(variable.information.get("dictionaries"))}' warn(warning) - default = self.get_default(variable, - comment, - ) + default = self.get_default( + variable, + comment, + ) default_in_choices = False - if variable.information.get("type") == 'choice': + if variable.information.get("type") == "choice": choices = variable.value.list(uncalculated=True) if isinstance(choices, Calculation): - choices = self.to_string(variable, 'choice') + choices = self.to_string(variable, "choice") if isinstance(choices, list): for idx, val in enumerate(choices): if not isinstance(val, Calculation): if default is not None and val == default: - choices[idx] = str(val) + ' ← ' + _("(default)") + choices[idx] = str(val) + " ← " + _("(default)") default_in_choices = True continue - choices[idx] = self.to_string(variable, 'choice', f'_{idx}') + choices[idx] = self.to_string(variable, "choice", f"_{idx}") choices = self.formater.list(choices) comment.append(f'{self.formater.bold(_("Choices"))}: {choices}') # choice if default is not None and not default_in_choices: comment.append(f"{self.formater.bold(_('Default'))}: {default}") - self.manage_exemples(multi, - variable, - examples_all, - examples_mini, - comment, - ) + self.manage_exemples( + multi, + variable, + examples_all, + examples_mini, + comment, + ) self.subparameter_to_parameter(subparameter, comment) self.formater.columns(parameter, comment) return [self.formater.join(parameter), comment] - def get_default(self, - variable, - comment, - ): - if variable.information.get('fake_default', False): + def get_default( + self, + variable, + comment, + ): + if variable.information.get("fake_default", False): default = None else: default = variable.value.get(uncalculated=True) if default in [None, []]: return if isinstance(default, Calculation): - default = self.to_string(variable, 'default') + default = self.to_string(variable, "default") if isinstance(default, list): for idx, val in enumerate(default): if not isinstance(val, Calculation): continue - default[idx] = self.to_string(variable, 'default', f'_{idx}') + default[idx] = self.to_string(variable, "default", f"_{idx}") default = self.formater.list(default) return default - def to_string(self, - variable, - prop, - suffix='', - ): - calculation_type = variable.information.get(f'{prop}_calculation_type{suffix}', None) + def to_string( + self, + variable, + prop, + identifier="", + ): + calculation_type = variable.information.get( + f"{prop}_calculation_type{identifier}", None + ) if not calculation_type: - raise Exception(f'cannot find {prop}_calculation_type{suffix} information, do you have declare doc has a plugins?') - calculation = variable.information.get(f'{prop}_calculation{suffix}') - if calculation_type == 'jinja': + raise Exception( + f"cannot find {prop}_calculation_type{identifier} information, do you have declare doc has a plugins?" + ) + calculation = variable.information.get(f"{prop}_calculation{identifier}") + if calculation_type == "jinja": if calculation is not True: values = self.formater.to_string(calculation) else: - values = "issu d'un calcul" + values = "depends on a calculation" warning = f'"{prop}" is a calculation for {variable.path()} but has no description in {display_xmlfiles(variable.information.get("dictionaries"))}' warn(warning) - elif calculation_type == 'variable': + elif calculation_type == "variable": if prop in PROPERTY_ATTRIBUTE: values = self.formater.to_string(calculation) else: values = _(f'the value of the variable "{calculation}"') + elif calculation_type == "identifier": + if prop in PROPERTY_ATTRIBUTE: + values = self.formater.to_string(calculation) + else: + values = _(f"value of the {calculation_type}") else: values = _(f"value of the {calculation_type}") - if not values.endswith('.'): - values += '.' + if not values.endswith("."): + values += "." return values - def type_to_string(self, - variable, - subparameter, - comment, - ): + def type_to_string( + self, + variable, + subparameter, + comment, + ): variable_type = variable.information.get("type") - doc_type = DocTypes.get(variable_type, {'params': {}}) - subparameter.append((self.formater.link(doc_type.get('msg', variable_type), ROUGAIL_VARIABLE_TYPE), None)) + doc_type = DocTypes.get(variable_type, {"params": {}}) + subparameter.append( + ( + self.formater.link( + doc_type.get("msg", variable_type), ROUGAIL_VARIABLE_TYPE + ), + None, + ) + ) option = variable.get() validators = [] - for param, msg in doc_type['params'].items(): - value = option.impl_get_extra(f'_{param}') + for param, msg in doc_type["params"].items(): + value = option.impl_get_extra(f"_{param}") if value is None: value = option.impl_get_extra(param) if value is not None and value is not False: validators.append(msg.format(value=value)) - valids = [name for name in variable.information.list() if name.startswith('validators_calculation_type_')] + valids = [ + name + for name in variable.information.list() + if name.startswith("validators_calculation_type_") + ] if valids: for idx in range(len(valids)): - validators.append(self.to_string(variable, - 'validators', - f'_{idx}', - )) + validators.append( + self.to_string( + variable, + "validators", + f"_{idx}", + ) + ) if validators: if len(validators) == 1: comment.append(f'{self.formater.bold("Validator")}: ' + validators[0]) else: - comment.append(f'{self.formater.bold("Validators")}:' + self.formater.list(validators)) + comment.append( + f'{self.formater.bold("Validators")}:' + + self.formater.list(validators) + ) - def manage_exemples(self, - multi, - variable, - examples_all, - examples_mini, - comment, - ): + def manage_exemples( + self, + multi, + variable, + examples_all, + examples_mini, + comment, + ): example_mini = None example_all = None - example = variable.information.get("test", None) + example = variable.information.get("examples", None) + if example is None: + example = variable.information.get("test", None) default = variable.value.get() if isinstance(example, tuple): example = list(example) - mandatory = 'mandatory' in variable.property.get(uncalculated=True) + mandatory = "mandatory" in variable.property.get(uncalculated=True) if example: if not multi: example = example[0] @@ -552,9 +655,11 @@ class RougailOutputDoc: elif default not in [None, []]: example_all = default else: - example = CONVERT_OPTION.get(variable.information.get("type"), {}).get('example', None) + example = CONVERT_OPTION.get(variable.information.get("type"), {}).get( + "example", None + ) if example is None: - example = 'xxx' + example = "xxx" if multi: example = [example] if mandatory: @@ -578,3 +683,7 @@ class RougailOutputDoc: if example_mini is not None: examples_mini[variable.name()] = example_mini examples_all[variable.name()] = example_all + + +RougailOutput = RougailOutputDoc +__all__ = ("RougailOutputDoc",) diff --git a/src/rougail/output_doc/annotator.py b/src/rougail/output_doc/annotator.py index c70468a..9b4afb4 100644 --- a/src/rougail/output_doc/annotator.py +++ b/src/rougail/output_doc/annotator.py @@ -19,14 +19,24 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ + from tiramisu import undefined from rougail.annotator.variable import Walk from rougail.i18n import _ from rougail.error import DictConsistencyError -from rougail.object_model import Calculation, JinjaCalculation, VariableCalculation, \ - InformationCalculation, IndexCalculation, SuffixCalculation, CONVERT_OPTION, \ - PROPERTY_ATTRIBUTE +from rougail.object_model import ( + Calculation, + JinjaCalculation, + VariableCalculation, + VariablePropertyCalculation, + IdentifierCalculation, + IdentifierPropertyCalculation, + InformationCalculation, + IndexCalculation, + CONVERT_OPTION, + PROPERTY_ATTRIBUTE, +) class Annotator(Walk): @@ -45,21 +55,33 @@ class Annotator(Walk): self.populate_family() self.populate_variable() - def add_default_value(self, - family, - value, - *, - inside_list=False, - ) -> None: + def get_examples_values(self, variable): + values = self.objectspace.informations.get(variable.path).get("examples", None) + if not values: + values = self.objectspace.informations.get(variable.path).get("test", None) + return values + + def add_default_value( + self, + family, + value, + *, + inside_list=False, + ) -> None: if isinstance(value, Calculation): - default_values ='example' + default_values = "example" if not inside_list: default_values = [default_values] - if isinstance(value, VariableCalculation): - variable, suffix = self.objectspace.paths.get_with_dynamic( - value.variable, value.path_prefix, family.path, value.version, value.namespace, value.xmlfiles + if isinstance(value, (VariableCalculation, VariablePropertyCalculation)): + variable, identifier = self.objectspace.paths.get_with_dynamic( + value.variable, + value.path_prefix, + family.path, + value.version, + value.namespace, + value.xmlfiles, ) - values = self.objectspace.informations.get(variable.path).get('test', None) + values = self.get_examples_values(variable) if values: if inside_list: default_values = list(values) @@ -81,11 +103,12 @@ class Annotator(Walk): else: for value in family.dynamic: self.add_default_value(family, value, inside_list=True) - self.calculation_to_information(family.path, - 'dynamic', - family.dynamic, - family.version, - ) + self.calculation_to_information( + family.path, + "dynamic", + family.dynamic, + family.version, + ) def populate_variable(self) -> None: """convert variables""" @@ -93,29 +116,31 @@ class Annotator(Walk): if variable.type == "symlink": continue if variable.type == "choice": - self.calculation_to_information(variable.path, - 'choice', - variable.choices, - variable.version, - ) - self.calculation_to_information(variable.path, - 'default', - variable.default, - variable.version, - ) - self.calculation_to_information(variable.path, - 'validators', - variable.validators, - variable.version, - ) - if variable.path in self.objectspace.leaders and \ - not variable.default: - values = self.objectspace.informations.get(variable.path).get('test', None) + self.calculation_to_information( + variable.path, + "choice", + variable.choices, + variable.version, + ) + self.calculation_to_information( + variable.path, + "default", + variable.default, + variable.version, + ) + self.calculation_to_information( + variable.path, + "validators", + variable.validators, + variable.version, + ) + if variable.path in self.objectspace.leaders and not variable.default: + values = self.get_examples_values(variable) if values: variable.default = list(values) else: - variable.default = [CONVERT_OPTION[variable.type]['example']] - self.objectspace.informations.add(variable.path, 'fake_default', True) + variable.default = [CONVERT_OPTION[variable.type]["example"]] + self.objectspace.informations.add(variable.path, "fake_default", True) self.objectspace.informations.add( variable.path, "dictionaries", variable.xmlfiles ) @@ -126,58 +151,64 @@ class Annotator(Walk): variable: dict, ) -> None: """convert properties""" - for prop in ['hidden', 'disabled', 'mandatory']: + for prop in ["hidden", "disabled", "mandatory"]: prop_value = getattr(variable, prop, None) if not prop_value: continue - self.calculation_to_information(variable.path, - prop, - prop_value, - variable.version, - ) + self.calculation_to_information( + variable.path, + prop, + prop_value, + variable.version, + ) - def calculation_to_information(self, - path: str, - prop: str, - values, - version: str, - ): - self._calculation_to_information(path, - prop, - values, - version, - ) + def calculation_to_information( + self, + path: str, + prop: str, + values, + version: str, + ): + self._calculation_to_information( + path, + prop, + values, + version, + ) if isinstance(values, list): for idx, val in enumerate(values): - self._calculation_to_information(path, - prop, - val, - version, - suffix=f'_{idx}', - ) + self._calculation_to_information( + path, + prop, + val, + version, + identifier=f"_{idx}", + ) - def _calculation_to_information(self, - path: str, - prop: str, - values, - version: str, - *, - suffix: str='', - ): - if not isinstance(values, Calculation): + def _calculation_to_information( + self, + path: str, + prop: str, + values, + version: str, + *, + identifier: str = "", + ): + if not isinstance(values, Calculation): return values_calculation = True if isinstance(values, JinjaCalculation): if values.description: values_calculation = values.description - values_calculation_type = 'jinja' - elif isinstance(values, VariableCalculation): + values_calculation_type = "jinja" + elif isinstance(values, (VariableCalculation, VariablePropertyCalculation)): values_calculation = values.variable paths = self.objectspace.paths - if version != '1.0' and paths.regexp_relative.search(values_calculation): - calculation_path = paths.get_relative_path(values_calculation, - path, - ) + if version != "1.0" and paths.regexp_relative.search(values_calculation): + calculation_path = paths.get_full_path( + values_calculation, + path, + ) if prop in PROPERTY_ATTRIBUTE: if values.when is not undefined: values_calculation = f'when the variable "{calculation_path}" has the value "{values.when}"' @@ -187,18 +218,27 @@ class Annotator(Walk): values_calculation = f'when the variable "{calculation_path}" has the value "True"' else: values_calculation = calculation_path - values_calculation_type = 'variable' + values_calculation_type = "variable" elif isinstance(values, InformationCalculation): - values_calculation_type = 'information' - elif isinstance(values, SuffixCalculation): - values_calculation_type = 'suffix' + values_calculation_type = "information" + elif isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)): + if version != "1.0" and prop in PROPERTY_ATTRIBUTE: + if values.when is not undefined: + values_calculation = f'when the identifier is "{values.when}"' + elif values.when_not is not undefined: + values_calculation = ( + f'when the identifier is not "{values.when_not}"' + ) + values_calculation_type = "identifier" elif isinstance(values, IndexCalculation): - values_calculation_type = 'index' - self.objectspace.informations.add(path, - f'{prop}_calculation_type{suffix}', - values_calculation_type, - ) - self.objectspace.informations.add(path, - f'{prop}_calculation{suffix}', - values_calculation, - ) + values_calculation_type = "index" + self.objectspace.informations.add( + path, + f"{prop}_calculation_type{identifier}", + values_calculation_type, + ) + self.objectspace.informations.add( + path, + f"{prop}_calculation{identifier}", + values_calculation, + ) diff --git a/src/rougail/output_doc/cli.py b/src/rougail/output_doc/cli.py deleted file mode 100644 index 9a52072..0000000 --- a/src/rougail/output_doc/cli.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Cli code for Rougail-output-doc - -Silique (https://www.silique.fr) -Copyright (C) 2024 - -distribued with GPL-2 or later license - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -from . import RougailOutputDoc - - -def run(rougailconfig, - config, - user_data, - ): - inventory = RougailOutputDoc(config=config, - rougailconfig=rougailconfig, - ) - print(inventory.gen_doc()) - - -__all__ = ('run',) diff --git a/src/rougail/output_doc/config.py b/src/rougail/output_doc/config.py index 93d659c..0491c54 100644 --- a/src/rougail/output_doc/config.py +++ b/src/rougail/output_doc/config.py @@ -4,43 +4,42 @@ Config file for Rougail-doc Silique (https://www.silique.fr) Copyright (C) 2024 -distribued with GPL-2 or later license +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 free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 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. -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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +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 pathlib import Path from rougail.utils import load_modules -# from .utils import _ OUTPUTS = None def get_outputs() -> None: - module_name = 'rougail.doc.output' + module_name = "rougail.doc.output" outputs = {} - for path in (Path(__file__).parent / 'output').iterdir(): + for path in (Path(__file__).parent / "output").iterdir(): name = path.name if not name.endswith(".py") or name.endswith("__.py"): continue - module = load_modules(module_name + '.' + name, str(path)) + module = load_modules(module_name + "." + name, str(path)) if "Formater" not in dir(module): continue level = module.Formater.level if level in outputs: - raise Exception(f'duplicated level rougail-doc for output "{level}": {module.Formater.name} and {outputs[level].name}') + raise Exception( + f'duplicated level rougail-doc for output "{level}": {module.Formater.name} and {outputs[level].name}' + ) outputs[module.Formater.level] = module.Formater return {outputs[level].name: outputs[level] for level in sorted(outputs)} @@ -59,9 +58,10 @@ class OutPuts: # pylint: disable=R0903 return OUTPUTS -def get_rougail_config(*, - backward_compatibility=True, - ) -> dict: +def get_rougail_config( + *, + backward_compatibility=True, +) -> dict: outputs = list(OutPuts().get()) output_format_default = outputs[0] rougail_options = """ @@ -77,20 +77,28 @@ doc: description: Start title level alternative_name: dt default: 1 + with_example: + description: Display example in documentation + negative_description: Hide example in documentation + alternative_name: de + default: false output_format: description: Generate document in format alternative_name: do default: output_format_default choices: -""".replace('output_format_default', output_format_default) +""".replace( + "output_format_default", output_format_default + ) for output in outputs: rougail_options += f" - {output}\n" - return {'name': 'doc', - 'process': 'output', - 'options': rougail_options, - 'allow_user_data': False, - 'level': 50, - } + return { + "name": "doc", + "process": "output", + "options": rougail_options, + "allow_user_data": False, + "level": 50, + } -__all__ = ("OutPuts", 'get_rougail_config') +__all__ = ("OutPuts", "get_rougail_config") diff --git a/src/rougail/output_doc/output/__init__.py b/src/rougail/output_doc/output/__init__.py index e4581b1..490a0a4 100644 --- a/src/rougail/output_doc/output/__init__.py +++ b/src/rougail/output_doc/output/__init__.py @@ -2,24 +2,16 @@ Silique (https://www.silique.fr) Copyright (C) 2024 -distribued with GPL-2 or later license +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 free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 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. -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 General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +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/>. """ - - -def echo(msg): - return msg -_ = echo diff --git a/src/rougail/output_doc/output/asciidoc.py b/src/rougail/output_doc/output/asciidoc.py index 0bdbbea..bd7b9c9 100644 --- a/src/rougail/output_doc/output/asciidoc.py +++ b/src/rougail/output_doc/output/asciidoc.py @@ -1,3 +1,21 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2024 + +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 io import BytesIO from typing import List from itertools import chain @@ -5,7 +23,7 @@ from ruamel.yaml import YAML class Formater: - name = 'asciidoc' + name = "asciidoc" level = 40 def __init__(self): @@ -13,12 +31,13 @@ class Formater: self._yaml.indent(mapping=2, sequence=4, offset=2) def header(self): - return '' + return "" - def title(self, - title: str, - level: int, - ) -> str: + def title( + self, + title: str, + level: int, + ) -> str: char = "=" return f"{char * (level + 1)} {title}\n\n" @@ -30,79 +49,91 @@ class Formater: stable = table.split("\n", 1) return stable[0].replace("<", "a") + "\n" + stable[1] - def link(self, - comment: str, - link: str, - ) -> str: + def link( + self, + comment: str, + link: str, + ) -> str: return f"`{link}[{comment}]`" - def prop(self, - prop: str, - ) -> str: - return f'`{prop}`' + def prop( + self, + prop: str, + ) -> str: + return f"`{prop}`" - def list(self, - choices: list, - ) -> str: + def list( + self, + choices: list, + ) -> str: prefix = "\n\n* " char = "\n* " return prefix + char.join([self.dump(choice) for choice in choices]) - def is_list(self, - txt: str, - ) -> str: - return txt.startswith('* ') + def is_list( + self, + txt: str, + ) -> str: + return txt.startswith("* ") - def columns(self, - col1: List[str], - col2: List[str], - ) -> None: + def columns( + self, + col1: List[str], + col2: List[str], + ) -> None: self.max_line = 0 for params in chain(col1, col2): - for param in params.split('\n'): + for param in params.split("\n"): self.max_line = max(self.max_line, len(param)) self.max_line += 1 - def join(self, - lst: List[str], - ) -> str: + def join( + self, + lst: List[str], + ) -> str: string = "" - previous = '' + previous = "" for line in lst: if string: - if self.is_list(previous.split('\n')[-1]): + if self.is_list(previous.split("\n")[-1]): string += "\n\n" else: string += " +\n" string += line - + previous = line return "\n" + string - def to_string(self, - text: str, - ) -> str: + def to_string( + self, + text: str, + ) -> str: return text - def table_header(self, - lst, - ): - return lst[0] + " " * (self.max_line - len(lst[0])), lst[1] + " " * (self.max_line - len(lst[1])) + def table_header( + self, + lst, + ): + return lst[0] + " " * (self.max_line - len(lst[0])), lst[1] + " " * ( + self.max_line - len(lst[1]) + ) - def bold(self, - msg: str, - ) -> str: + def bold( + self, + msg: str, + ) -> str: return f"**{msg}**" - def italic(self, - msg: str, - ) -> str: + def italic( + self, + msg: str, + ) -> str: return f"_{msg}_" def dump(self, dico): with BytesIO() as ymlfh: self._yaml.dump(dico, ymlfh) - ret = ymlfh.getvalue().decode('utf-8').strip() - if ret.endswith('...'): + ret = ymlfh.getvalue().decode("utf-8").strip() + if ret.endswith("..."): ret = ret[:-3].strip() return ret diff --git a/src/rougail/output_doc/output/github.py b/src/rougail/output_doc/output/github.py index ef5ca0b..47fe0ab 100644 --- a/src/rougail/output_doc/output/github.py +++ b/src/rougail/output_doc/output/github.py @@ -1,3 +1,21 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2024 + +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 io import BytesIO from typing import List from itertools import chain @@ -5,7 +23,7 @@ from ruamel.yaml import YAML class Formater: - name = 'github' + name = "github" level = 50 def __init__(self): @@ -15,14 +33,15 @@ class Formater: def header(self): if self.header_setted: - return '' + return "" self.header_setted = True return "---\ngitea: none\ninclude_toc: true\n---\n" - def title(self, - title: str, - level: int, - ) -> str: + def title( + self, + title: str, + level: int, + ) -> str: char = "#" return f"{char * level} {title}\n\n" @@ -32,67 +51,77 @@ class Formater: def table(self, table): return table - def link(self, - comment: str, - link: str, - ) -> str: + def link( + self, + comment: str, + link: str, + ) -> str: return f"[`{comment}`]({link})" - def prop(self, - prop: str, - ) -> str: - return f'`{prop}`' + def prop( + self, + prop: str, + ) -> str: + return f"`{prop}`" - def list(self, - choices, - ): + def list( + self, + choices, + ): prefix = "<br/>- " char = "<br/>- " return prefix + char.join([self.dump(choice) for choice in choices]) - def is_list(self, - txt: str, - ) -> str: - return txt.startswith('* ') - - def columns(self, - col1: List[str], - col2: List[str], - ) -> None: + def is_list( + self, + txt: str, + ) -> str: + return txt.startswith("* ") + + def columns( + self, + col1: List[str], + col2: List[str], + ) -> None: self.max_line = 0 for params in chain(col1, col2): - for param in params.split('\n'): + for param in params.split("\n"): self.max_line = max(self.max_line, len(param)) self.max_line += 1 - def join(self, - lst: List[str], - ) -> str: + def join( + self, + lst: List[str], + ) -> str: return "<br/>".join(lst) - def to_string(self, - text: str, - ) -> str: - return text.strip().replace('\n', '<br/>') + def to_string( + self, + text: str, + ) -> str: + return text.strip().replace("\n", "<br/>") - def table_header(self, - lst): - return lst[0] + " " * (self.max_line - len(lst[0])), lst[1] + " " * (self.max_line - len(lst[1])) + def table_header(self, lst): + return lst[0] + " " * (self.max_line - len(lst[0])), lst[1] + " " * ( + self.max_line - len(lst[1]) + ) - def bold(self, - msg: str, - ) -> str: + def bold( + self, + msg: str, + ) -> str: return f"**{msg}**" - def italic(self, - msg: str, - ) -> str: + def italic( + self, + msg: str, + ) -> str: return f"*{msg}*" def dump(self, dico): with BytesIO() as ymlfh: self._yaml.dump(dico, ymlfh) - ret = ymlfh.getvalue().decode('utf-8').strip() - if ret.endswith('...'): + ret = ymlfh.getvalue().decode("utf-8").strip() + if ret.endswith("..."): ret = ret[:-3].strip() return ret