feat: exporter to console

This commit is contained in:
egarette@silique.fr 2024-11-27 16:50:48 +01:00
parent a1113ddff2
commit 3283d85829
12 changed files with 208 additions and 377 deletions

View file

@ -1,2 +1,2 @@
# rougail-exporter
# rougail-output-console

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View file

@ -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

View file

@ -17,19 +17,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
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",)

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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",)

View file

@ -19,6 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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",)

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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")

View file

@ -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 <http://www.gnu.org/licenses/>.
"""