diff --git a/README.md b/README.md index 5599a42..bd012ab 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# rougail-exporter +# rougail-output-console diff --git a/locale/fr/LC_MESSAGES/rougail_output_exporter.po b/locale/fr/LC_MESSAGES/rougail_output_console.po similarity index 100% rename from locale/fr/LC_MESSAGES/rougail_output_exporter.po rename to locale/fr/LC_MESSAGES/rougail_output_console.po diff --git a/locale/rougail_output_exporter.pot b/locale/rougail_output_console.pot similarity index 61% rename from locale/rougail_output_exporter.pot rename to locale/rougail_output_console.pot index 6fd3c0a..8ed76b1 100644 --- a/locale/rougail_output_exporter.pot +++ b/locale/rougail_output_console.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-11-01 11:03+0100\n" +"POT-Creation-Date: 2024-11-27 16:46+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,43 +15,43 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: src/rougail/output_exporter/__init__.py:73 +#: src/rougail/output_console/__init__.py:82 msgid "The following variables are mandatory but have no value:" msgstr "" -#: src/rougail/output_exporter/__init__.py:84 +#: src/rougail/output_console/__init__.py:93 msgid "The following variables are inaccessible but are empty and mandatory :" msgstr "" -#: src/rougail/output_exporter/output/console.py:53 +#: src/rougail/output_console/__init__.py:170 msgid "Undocumented variable" msgstr "" -#: src/rougail/output_exporter/output/console.py:54 +#: src/rougail/output_console/__init__.py:171 msgid "Undocumented but modified variable" msgstr "" -#: src/rougail/output_exporter/output/console.py:57 +#: src/rougail/output_console/__init__.py:174 msgid "Unmodifiable variable" msgstr "" -#: src/rougail/output_exporter/output/console.py:61 +#: src/rougail/output_console/__init__.py:178 msgid "Default value" msgstr "" -#: src/rougail/output_exporter/output/console.py:62 +#: src/rougail/output_console/__init__.py:179 msgid "Modified value" msgstr "" -#: src/rougail/output_exporter/output/console.py:63 +#: src/rougail/output_console/__init__.py:180 msgid "Original default value" msgstr "" -#: src/rougail/output_exporter/output/console.py:67 +#: src/rougail/output_console/__init__.py:184 msgid "Caption" msgstr "" -#: src/rougail/output_exporter/output/console.py:92 +#: src/rougail/output_console/__init__.py:209 msgid "Variables:" msgstr "" diff --git a/pyproject.toml b/pyproject.toml index d10b94d..51faf33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,11 +3,11 @@ build-backend = "flit_core.buildapi" requires = ["flit_core >=3.8.0,<4"] [project] -name = "rougail.output_exporter" +name = "rougail.output_console" version = "0.1.0" authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] readme = "README.md" -description = "Rougail output exporter" +description = "Rougail output console" requires-python = ">=3.8" license = {file = "LICENSE"} classifiers = [ @@ -30,12 +30,12 @@ dependencies = [ ] [project.urls] -Home = "https://forge.cloud.silique.fr/stove/rougail-output-exporter" +Home = "https://forge.cloud.silique.fr/stove/rougail-output-console" [tool.commitizen] name = "cz_conventional_commits" tag_format = "$version" version_scheme = "pep440" version_provider = "pep621" -#update_changelog_on_bump = true +update_changelog_on_bump = true changelog_merge_prerelease = true diff --git a/src/rougail/output_exporter/output/console.py b/src/rougail/output_console/__init__.py similarity index 65% rename from src/rougail/output_exporter/output/console.py rename to src/rougail/output_console/__init__.py index 2a054f4..810dbe1 100644 --- a/src/rougail/output_exporter/output/console.py +++ b/src/rougail/output_console/__init__.py @@ -17,19 +17,18 @@ along with this program. If not, see . """ from typing import Any, List, Optional - from rich.tree import Tree from rich.console import Console from rich.table import Table from rich.panel import Panel from tiramisu import undefined -from ...i18n import _ +from tiramisu.error import PropertiesOptionError, ConfigError + +from .i18n import _ -class Formater: - name = "console" - level = 10 +class RougailOutputConsole: variable_hidden_color = "orange1" variable_advanced_color = "bright_blue" variable_advanced_and_modified_color = "red1" @@ -38,13 +37,131 @@ class Formater: def __init__( self, - rougailconfig: "RougailConfig", + config: "Config", + rougailconfig: "RougailConfig" = None, + user_data_errors: Optional[list] = None, + user_data_warnings: Optional[list] = None, ) -> None: - self.console = Console(force_terminal=True) + if rougailconfig is None: + from rougail import RougailConfig + + rougailconfig = RougailConfig self.rougailconfig = rougailconfig - self.read_write = self.rougailconfig["exporter.read_write"] - self.show_secrets = self.rougailconfig["exporter.show_secrets"] + self.config = config + self.read_write = self.rougailconfig["console.read_write"] + self.is_mandatory = self.rougailconfig["console.mandatory"] + self.show_secrets = self.rougailconfig["console.show_secrets"] + self.errors = [] + self.warnings = [] + if user_data_errors is None: + user_data_errors = [] + self.user_data_errors = user_data_errors + if user_data_warnings is None: + user_data_warnings = [] + self.user_data_warnings = user_data_warnings + self.console = Console(force_terminal=True) self.out = [] + self.root = self.get_root() + + def mandatory(self): + if not self.is_mandatory: + return + title = False + options_with_error = [] + try: + mandatories = self.config.value.mandatory() + except (ConfigError, PropertiesOptionError) as err: + self.errors.append(f"Error in config: {err}") + return + for option in mandatories: + try: + option.value.get() + if not title: + # self.errors.append("Les variables suivantes sont obligatoires mais n'ont pas de valeur :") + self.errors.append( + _("The following variables are mandatory but have no value:") + ) + title = True + self.errors.append(f" - {option.description()}") + except PropertiesOptionError: + options_with_error.append(option) + if not title: + for idx, option in enumerate(options_with_error): + if not idx: + # self.errors.append("Les variables suivantes sont inaccessibles mais sont vides et obligatoires :") + self.errors.append( + _( + "The following variables are inaccessible but are empty and mandatory :" + ) + ) + self.errors.append(f" - {option.description()}") + + def exporter(self) -> bool: + self.config.property.read_write() + self.mandatory() + if self.read_write: + self.config.property.read_write() + else: + self.config.property.read_only() + errors = self.user_data_errors + self.errors + if errors: + self.display_errors(errors) + if self.errors: + return False + warnings = self.user_data_warnings + self.warnings + if warnings: + self.display_warnings(warnings) + self.header() + self.parse_options( + self.config, + self.root, + ) + self.end() + return True + + def run(self) -> None: + self.exporter() + return self.print() + + def parse_options( + self, + conf, + parent, + ): + for option in conf: + if option.isoptiondescription(): + family = parent.add_family(option) + if option.isleadership(): + self.parse_leadership( + option, + family, + ) + else: + self.parse_options( + option, + family, + ) + else: + parent.add_variable(option) + + def parse_leadership( + self, + conf, + parent, + ): + leader, *followers = list(conf) + leader_values = leader.value.get() + for idx, leader_value in enumerate(leader_values): + leader_obj = parent.add_family(leader) + leader_obj.add_variable( + leader, + value=leader_value, + leader_index=idx, + ) + for follower in followers: + if follower.index() != idx: + continue + leader_obj.add_variable(follower) def header(self): header_variable = "Variable\n" @@ -66,7 +183,7 @@ class Formater: header.add_row(header_variable, header_value) self.out.append(Panel.fit(header, title=_("Caption"))) - def errors( + def display_errors( self, errors, ) -> None: @@ -78,7 +195,7 @@ class Formater: tree.add(error) self.out.append(tree) - def warnings( + def display_warnings( self, warnings: list, ) -> None: @@ -87,7 +204,7 @@ class Formater: tree.add(warning) self.out.append(tree) - def root(self) -> None: + def get_root(self) -> None: self.output = OutputFamily( _("Variables:"), None, @@ -129,68 +246,6 @@ class OutputFamily: ) self.root = root - # - # def parse_option(self, - # option, - # value, - # variables, - # ): - # if '.' in line: - # # it's a dict - # family, variable = line.split('.', 1) - # current_path = parent_path - # if current_path: - # current_path += '.' - # current_path += family - # if for_doc: - # if 'hidden' in self.conf.option(current_path).property.get() or family_hidden: - # family_hidden = True - # family = f'[orange1]{family}[/orange1]' - # elif 'advanced' in self.conf.option(current_path).property.get(): - # family = f'[bright_blue]{family}[/bright_blue]' - # if '.' not in variable and self.conf.option(full_path.rsplit('.', 1)[0]).isleadership(): - # dico.setdefault(family, []) - # leadership = True - # else: - # dico.setdefault(family, {}) - # leadership = False - # self.parse_option(full_path, - # variable, - # value, - # ) - # elif leadership: - # # it's a leadership - # for idx, val in enumerate(value): - # dic = {k.rsplit('.', 1)[-1]: v for k, v in val.items()} - # if for_doc: - # leader = True - # for k, v in val.items(): - # if leader: - # is_default = self.conf.option(k).owner.isdefault() - # properties = self.conf.option(k).property.get() - # else: - # is_default = self.conf.option(k, idx).owner.isdefault() - # properties = self.conf.option(k, idx).property.get() - # if self.conf.option(k).type() == _('password') and not self.args.show_secrets: - # v = "*" * 10 - # subpath = k.rsplit('.', 1)[-1] - # if 'hidden' in properties or family_hidden: - # subpath = f'[orange1]{subpath}[/orange1]' - # elif 'advanced' in properties: - # if isdefault: - # subpath = f'[bright_blue]{subpath}[/bright_blue]' - # else: - # subpath = f'[red1]{subpath}[/red1]' - # if is_default: - # v = '[gold1]' + str(v) + '[/gold1]' - # dico.append(f'{subpath}: {v}') - # leader = False - # else: - # dico.append(dic) - # else: - # # it's a variable - # self.parse_variable(option, value) - # def add_family( self, option, @@ -316,3 +371,9 @@ class OutputFamily: if not self.root.show_secrets and option.type() == "password": return "*" * 10 return str(value) + + +RougailOutput = RougailOutputConsole + + +__all__ = ("RougailOutputConsole",) diff --git a/src/rougail/output_console/config.py b/src/rougail/output_console/config.py new file mode 100644 index 0000000..f59a4d7 --- /dev/null +++ b/src/rougail/output_console/config.py @@ -0,0 +1,56 @@ +""" +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 . +""" + +from pathlib import Path + + +def get_rougail_config( + *, + backward_compatibility=True, +) -> dict: + options = """ +console: + description: Configuration rougail-console + disabled: + type: jinja + jinja: | + {% if step.output != 'console' %} + disabled + {% endif %} + read_write: + description: Display variables available in read_write mode + negative_description: Display variables available in read_only mode + default: false + show_secrets: + description: Show secrets instead of obscuring them + negative_description: Obscuring secrets instead of show them + default: false + mandatory: + description: Test mandatories variable before display in console + negative_description: Do not test mandatories variable before display in console + default: true +""" + return { + "name": "console", + "process": "output", + "options": options, + "level": 40, + } + + +__all__ = ("get_rougail_config",) diff --git a/src/rougail/output_exporter/i18n.py b/src/rougail/output_console/i18n.py similarity index 89% rename from src/rougail/output_exporter/i18n.py rename to src/rougail/output_console/i18n.py index 635691a..6f3b7d3 100644 --- a/src/rougail/output_exporter/i18n.py +++ b/src/rougail/output_console/i18n.py @@ -19,6 +19,6 @@ along with this program. If not, see . from gettext import translation from pathlib import Path -t = translation("rougail_output_exporter", str(Path(__file__).parent / "locale"), fallback=True) +t = translation("rougail_output_console", str(Path(__file__).parent / "locale"), fallback=True) _ = t.gettext diff --git a/locale/fr/LC_MESSAGES/rougail_output_exporter.mo b/src/rougail/output_console/locale/fr/LC_MESSAGES/rougail_output_console.mo similarity index 100% rename from locale/fr/LC_MESSAGES/rougail_output_exporter.mo rename to src/rougail/output_console/locale/fr/LC_MESSAGES/rougail_output_console.mo diff --git a/src/rougail/output_exporter/__init__.py b/src/rougail/output_exporter/__init__.py deleted file mode 100644 index 18fe583..0000000 --- a/src/rougail/output_exporter/__init__.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -Silique (https://www.silique.fr) -Copyright (C) 2022-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 . -""" - -from typing import Optional -from tiramisu.error import PropertiesOptionError, ConfigError -from .config import OutPuts -from .i18n import _ - - -class RougailOutputExporter: - def __init__( - self, - config: "Config", - rougailconfig: "RougailConfig" = None, - user_data_errors: Optional[list] = None, - user_data_warnings: Optional[list] = None, - ) -> None: - if rougailconfig is None: - from rougail import RougailConfig - - rougailconfig = RougailConfig - outputs = OutPuts().get() - output = rougailconfig["exporter.output_format"] - if output not in outputs: - raise Exception( - f'cannot find output "{output}", available outputs: {list(outputs)}' - ) - self.rougailconfig = rougailconfig - self.config = config - self.read_write = self.rougailconfig["exporter.read_write"] - self.errors = [] - self.warnings = [] - if user_data_errors is None: - user_data_errors = [] - self.user_data_errors = user_data_errors - if user_data_warnings is None: - user_data_warnings = [] - self.user_data_warnings = user_data_warnings - self.formater = outputs[output](self.rougailconfig) - self.root = self.formater.root() - - def mandatory(self): - if not self.rougailconfig["exporter.mandatory"]: - return - title = False - options_with_error = [] - try: - mandatories = self.config.value.mandatory() - except (ConfigError, PropertiesOptionError) as err: - self.errors.append(f"Error in config: {err}") - return - for option in mandatories: - try: - option.value.get() - if not title: - # self.errors.append("Les variables suivantes sont obligatoires mais n'ont pas de valeur :") - self.errors.append( - _("The following variables are mandatory but have no value:") - ) - title = True - self.errors.append(f" - {option.description()}") - except PropertiesOptionError: - options_with_error.append(option) - if not title: - for idx, option in enumerate(options_with_error): - if not idx: - # self.errors.append("Les variables suivantes sont inaccessibles mais sont vides et obligatoires :") - self.errors.append( - _( - "The following variables are inaccessible but are empty and mandatory :" - ) - ) - self.errors.append(f" - {option.description()}") - - def exporter(self) -> bool: - self.config.property.read_write() - self.mandatory() - if self.read_write: - self.config.property.read_write() - else: - self.config.property.read_only() - errors = self.user_data_errors + self.errors - if errors: - self.formater.errors(errors) - if self.errors: - return False - warnings = self.user_data_warnings + self.warnings - if warnings: - self.formater.warnings(warnings) - self.formater.header() - self.parse_options( - self.config, - self.root, - ) - self.formater.end() - return True - - def print(self) -> None: - return self.formater.print() - - def run(self) -> None: - self.exporter() - return self.print() - - def parse_options( - self, - conf, - parent, - ): - for option in conf: - if option.isoptiondescription(): - family = parent.add_family(option) - if option.isleadership(): - self.parse_leadership( - option, - family, - ) - else: - self.parse_options( - option, - family, - ) - else: - parent.add_variable(option) - - def parse_leadership( - self, - conf, - parent, - ): - leader, *followers = list(conf) - leader_values = leader.value.get() - for idx, leader_value in enumerate(leader_values): - leader_obj = parent.add_family(leader) - leader_obj.add_variable( - leader, - value=leader_value, - leader_index=idx, - ) - for follower in followers: - if follower.index() != idx: - continue - leader_obj.add_variable(follower) - - -RougailOutput = RougailOutputExporter - - -__all__ = ("RougailOutputExporter",) diff --git a/src/rougail/output_exporter/config.py b/src/rougail/output_exporter/config.py deleted file mode 100644 index 68d3e60..0000000 --- a/src/rougail/output_exporter/config.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Config file for Rougail-exporter - -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 . -""" - -from pathlib import Path -from rougail.utils import load_modules - - -OUTPUTS = None - - -def get_outputs() -> None: - module_name = "rougail.output_exporter.output" - outputs = {} - 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)) - if "Formater" not in dir(module): - continue - level = module.Formater.level - if level in outputs: - raise Exception( - f'duplicated level rougail-exporter 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)} - - -class OutPuts: # pylint: disable=R0903 - """Transformations applied on a object instance""" - - def __init__( - self, - ) -> None: - global OUTPUTS - if OUTPUTS is None: - OUTPUTS = get_outputs() - - def get(self) -> dict: - return OUTPUTS - - -def get_rougail_config( - *, - backward_compatibility=True, -) -> dict: - outputs = tuple(OutPuts().get()) - options = """ -exporter: - description: Configuration rougail-exporter - disabled: - type: jinja - jinja: | - {% if step.output != 'exporter' %} - disabled - {% endif %} - read_write: - description: Display variables available in read_write mode - negative_description: Display variables available in read_only mode - alternative_name: er - default: false - show_secrets: - description: Show secrets instead of obscuring them - negative_description: Obscuring secrets instead of show them - alternative_name: es - default: false - mandatory: - description: Test mandatories variable before export - negative_description: Do not test mandatories variable before export - alternative_name: em - default: true - output_format: - description: Generate document in format - alternative_name: eo - default: DEFAULT - choices: -""".replace( - "DEFAULT", outputs[0] - ) - for output in outputs: - options += f" - {output}\n" - return { - "name": "exporter", - "process": "output", - "options": options, - "level": 40, - } - - -__all__ = ("OutPuts", "get_rougail_config") diff --git a/src/rougail/output_exporter/locale/fr/LC_MESSAGES/rougail_output_exporter.mo b/src/rougail/output_exporter/locale/fr/LC_MESSAGES/rougail_output_exporter.mo deleted file mode 100644 index fb7e0ae..0000000 Binary files a/src/rougail/output_exporter/locale/fr/LC_MESSAGES/rougail_output_exporter.mo and /dev/null differ diff --git a/src/rougail/output_exporter/output/__init__.py b/src/rougail/output_exporter/output/__init__.py deleted file mode 100644 index 6e215fd..0000000 --- a/src/rougail/output_exporter/output/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -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 . -"""