From 84a89133fa8298e663021c973be04ed226edd950 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 26 Nov 2025 20:44:59 +0100 Subject: [PATCH] feat: add frozen variable support --- src/rougail/user_data_questionary/__init__.py | 23 ++- .../user_data_questionary/__version__.py | 21 +++ src/rougail/user_data_questionary/cli.py | 0 src/rougail/user_data_questionary/config.py | 14 +- src/rougail/user_data_questionary/data.py | 143 +++++++++++------- src/rougail/user_data_questionary/i18n.py | 4 +- 6 files changed, 140 insertions(+), 65 deletions(-) delete mode 100644 src/rougail/user_data_questionary/cli.py diff --git a/src/rougail/user_data_questionary/__init__.py b/src/rougail/user_data_questionary/__init__.py index 52c9e77..4920dd8 100644 --- a/src/rougail/user_data_questionary/__init__.py +++ b/src/rougail/user_data_questionary/__init__.py @@ -1,5 +1,26 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2024-2025 + +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 .data import RougailUserDataQuestionary from .__version__ import __version__ RougailUserData = RougailUserDataQuestionary -__all__ = ('RougailUserDataQuestionary',) +__all__ = ("RougailUserDataQuestionary",) diff --git a/src/rougail/user_data_questionary/__version__.py b/src/rougail/user_data_questionary/__version__.py index 1ac008e..ddadd60 100644 --- a/src/rougail/user_data_questionary/__version__.py +++ b/src/rougail/user_data_questionary/__version__.py @@ -1 +1,22 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2024-2025 + +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 +""" + __version__ = "0.1.0a1" diff --git a/src/rougail/user_data_questionary/cli.py b/src/rougail/user_data_questionary/cli.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/rougail/user_data_questionary/config.py b/src/rougail/user_data_questionary/config.py index 24ab5ab..b36a3f7 100644 --- a/src/rougail/user_data_questionary/config.py +++ b/src/rougail/user_data_questionary/config.py @@ -26,6 +26,7 @@ def get_rougail_config( *, backward_compatibility=True, ) -> dict: + """generate rougail config""" options = f""" questionary: description: {_("Define values interactivly")} @@ -46,11 +47,12 @@ questionary: type: boolean default: false """ - return {'name': 'questionary', - 'process': 'user data', - 'options': options, - 'level': 60, - } + return { + "name": "questionary", + "process": "user data", + "options": options, + "level": 60, + } -__all__ = ('get_rougail_config',) +__all__ = ("get_rougail_config",) diff --git a/src/rougail/user_data_questionary/data.py b/src/rougail/user_data_questionary/data.py index f381047..fe6d213 100644 --- a/src/rougail/user_data_questionary/data.py +++ b/src/rougail/user_data_questionary/data.py @@ -18,13 +18,23 @@ 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 """ -import warnings -from questionary import text, select, confirm, password, Validator, ValidationError, print as qprint +import warnings +from questionary import ( + text, + select, + confirm, + password, + Validator, + ValidationError, + print as qprint, +) +from tiramisu.error import ValueOptionError, ValueErrorWarning, ValueWarning from rougail.tiramisu import CONVERT_OPTION from rougail.config import RougailConfig from rougail.error import ExtensionError -from tiramisu.error import ValueOptionError, ValueErrorWarning, ValueWarning + +from .i18n import _ class RougailUserDataQuestionary: @@ -40,29 +50,28 @@ class RougailUserDataQuestionary: self.config = config if rougailconfig is None: rougailconfig = RougailConfig - user_data = rougailconfig['step.user_data'] - if 'questionary' not in user_data: - user_data.append('questionary') - rougailconfig['step.user_data'] = user_data - user_data = rougailconfig['step.user_data'] - if 'questionary' not in user_data: - raise ExtensionError('questionary is not set in step.user_data') + user_data = rougailconfig["step.user_data"] + if "questionary" not in user_data: + user_data.append("questionary") + rougailconfig["step.user_data"] = user_data + user_data = rougailconfig["step.user_data"] + if "questionary" not in user_data: + raise ExtensionError("questionary is not set in step.user_data") self.rougailconfig = rougailconfig - self.errors = [] - self.warnings = [] warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueWarning) def run( self, ) -> None: -# self.config.property.read_write() - if 'demoting_error_warning' not in self.config.property.get(): + self.errors = [] + self.warnings = [] + if "demoting_error_warning" not in self.config.property.get(): add_demoting = True - self.config.property.add('demoting_error_warning') + self.config.property.add("demoting_error_warning") else: add_demoting = False - if self.rougailconfig['questionary.mandatory']: + if self.rougailconfig["questionary.mandatory"]: current_titles = [] while True: mandatories = self.config.value.mandatory() @@ -70,9 +79,9 @@ class RougailUserDataQuestionary: break mandatory = mandatories[0] path = mandatory.path() - if '.' in path: + if "." in path: current_config = self.config - for idx, p in enumerate(path.split('.')[0:-1]): + for idx, p in enumerate(path.split(".")[0:-1]): current_config = current_config.option(p) if idx < len(current_titles): if current_titles[idx] == p: @@ -80,14 +89,19 @@ class RougailUserDataQuestionary: current_titles = current_titles[0:idx] current_titles.append(p) self.print(current_config.description(), idx) - self.display_questionary(mandatory) + self.display_questionary(mandatory, title_level=0) else: + old_path_in_description = self.config.information.get( + "path_in_description", True + ) + self.config.information.set("path_in_description", False) self.parse(self.config) + self.config.information.set("path_in_description", old_path_in_description) if add_demoting: - self.config.property.remove('demoting_error_warning') + self.config.property.remove("demoting_error_warning") return [ { - "source": 'Questionary', + "source": "Questionary", "errors": self.errors, "warnings": self.warnings, "values": [], @@ -95,19 +109,22 @@ class RougailUserDataQuestionary: ] def parse(self, config, title_level=0): + display_title = True for option in config: + if title_level and display_title: + self.print(config.description(), title_level) + display_title = False if option.isoptiondescription(): - self.print(option.description(), title_level) self.parse(option, title_level + 1) else: - self.display_questionary(option) + self.display_questionary(option, title_level) def print(self, title, title_level): - qprint(' ' * title_level + '📂 ' + title, 'bold') + qprint(" " * title_level + "📂 " + title, "bold") - def display_questionary(self, option): + def display_questionary(self, option, title_level): kwargs = {} - option_type = option.information.get('type') + option_type = option.information.get("type") isdefault = option.owner.isdefault() with warnings.catch_warnings(): warnings.simplefilter("ignore") @@ -116,32 +133,43 @@ class RougailUserDataQuestionary: type_obj = None type_obj = CONVERT_OPTION.get(option_type, {}).get("func") RougailValidator.option = option - RougailValidator.option_type = {'type': option_type, - 'func': type_obj, - } + RougailValidator.option_type = { + "type": option_type, + "func": type_obj, + } RougailValidator.ismulti = ismulti ori_default = default - if option_type == 'choice': + args = [" " * title_level + "📓 " + option.description()] + if "frozen" in option.property.get(): + args[0] += " " + str(default) + question_funtion = qprint + qprint(" " + args[0]) + return + if option_type == "choice": question_funtion = select RougailValidator.default = default - kwargs['choices'] = option.value.list() - elif option_type == 'boolean': + kwargs["choices"] = option.value.list() + elif option_type == "boolean": question_funtion = confirm - elif option_type == 'secret' and not self.rougailconfig['questionary.show_secrets']: + elif ( + option_type == "secret" + and not self.rougailconfig["questionary.show_secrets"] + ): question_funtion = password else: question_funtion = text - kwargs['validate'] = RougailValidator - args = ['📓 ' + option.description()] + kwargs["validate"] = RougailValidator if ismulti: - kwargs['multiline'] = True + kwargs["multiline"] = True if default: - kwargs['default'] = "\n".join([str(d) for d in default]) + kwargs["default"] = "\n".join([str(d) for d in default]) elif default is not None: if isinstance(default, (int, float)): default = str(default) - kwargs['default'] = default - value = RougailValidator().convert_value(question_funtion(*args, **kwargs).ask(), False) + kwargs["default"] = default + value = RougailValidator().convert_value( + question_funtion(*args, **kwargs).ask(), False + ) if isdefault and value == ori_default: option.value.reset() else: @@ -152,16 +180,17 @@ class RougailValidator(Validator): def validate(self, document): return self.convert_value(document.text) - def convert_value(self, - document, - validate=True, - ): + def convert_value( + self, + document, + validate=True, + ): if not self.ismulti: value = self._convert_a_value(document, document, validate) else: value = [] if document is not None: - for val in document.strip().split('\n'): + for val in document.strip().split("\n"): val = self._convert_a_value(val, document, validate) if val is not None: value.append(val) @@ -171,39 +200,39 @@ class RougailValidator(Validator): self.option.value.set(value) for warn in warns: if isinstance(warn.message, ValueErrorWarning): - warn.message.prefix = '' + warn.message.prefix = "" raise ValidationError( message=str(warn.message), cursor_position=len(document), ) except ValueOptionError as err: - err.prefix = '' + err.prefix = "" raise ValidationError( message=str(err), cursor_position=len(document), - ) + ) from err return value def _convert_a_value(self, value, document, validate): if value is None: - if self.option_type['type'] == 'choice': + if self.option_type["type"] == "choice": return self.default - return + return None if isinstance(value, str): value = value.strip() - if value == '': + if value == "": if validate and "mandatory" in self.option.property.get(): raise ValidationError( - message=f"Value must not be empty", + message=_("Value must not be empty"), cursor_position=len(document), ) - return - if self.option_type['func']: + return None + if self.option_type["func"]: try: - return self.option_type['func'](value) - except: + return self.option_type["func"](value) + except Exception as err: raise ValidationError( - message=f"Not a valid {self.option_type['type']}", + message=_("Not a valid {0}").format(self.option_type["type"]), cursor_position=len(document), - ) + ) from err return value diff --git a/src/rougail/user_data_questionary/i18n.py b/src/rougail/user_data_questionary/i18n.py index 38be47a..3c9d64b 100644 --- a/src/rougail/user_data_questionary/i18n.py +++ b/src/rougail/user_data_questionary/i18n.py @@ -20,7 +20,9 @@ from gettext import translation from pathlib import Path t = translation( - "rougail_user_data_questionary", str(Path(__file__).parent / "locale"), fallback=True + "rougail_user_data_questionary", + str(Path(__file__).parent / "locale"), + fallback=True, ) _ = t.gettext