From a71b476307b2effcfe6e63663483d21393361ac5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 26 Nov 2025 22:05:09 +0100 Subject: [PATCH] fix: refactoring --- src/rougail/user_data_questionary/data.py | 206 ++++++++++++---------- 1 file changed, 113 insertions(+), 93 deletions(-) diff --git a/src/rougail/user_data_questionary/data.py b/src/rougail/user_data_questionary/data.py index f2ae394..9392335 100644 --- a/src/rougail/user_data_questionary/data.py +++ b/src/rougail/user_data_questionary/data.py @@ -38,6 +38,8 @@ from .i18n import _ class RougailUserDataQuestionary: + """Rougail userdata for Questionary""" + interactive_user_datas = True def __init__( @@ -66,30 +68,14 @@ class RougailUserDataQuestionary: ) -> None: self.errors = [] self.warnings = [] + self.questionary_show_secrets = self.rougailconfig["questionary.show_secrets"] if "demoting_error_warning" not in self.config.property.get(): add_demoting = True self.config.property.add("demoting_error_warning") else: add_demoting = False if self.rougailconfig["questionary.mandatory"]: - current_titles = [] - while True: - mandatories = self.config.value.mandatory() - if not mandatories: - break - mandatory = mandatories[0] - path = mandatory.path() - if "." in path: - current_config = self.config - 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: - continue - current_titles = current_titles[0:idx] - current_titles.append(p) - self.print(current_config.description(), idx) - self.display_questionary(mandatory, title_level=0) + self.parse_mandatories() else: old_path_in_description = self.config.information.get( "path_in_description", True @@ -108,6 +94,26 @@ class RougailUserDataQuestionary: } ] + def parse_mandatories(self): + current_titles = [] + while True: + mandatories = self.config.value.mandatory() + if not mandatories: + break + mandatory = mandatories[0] + path = mandatory.path() + if "." in path: + current_config = self.config + 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: + continue + current_titles = current_titles[0:idx] + current_titles.append(p) + self.print(current_config.description(), idx) + self.display_questionary(mandatory, title_level=0) + def parse(self, config, title_level=0): display_title = True for option in config: @@ -125,40 +131,21 @@ class RougailUserDataQuestionary: def display_questionary(self, option, title_level): kwargs = {} option_type = option.information.get("type") - isdefault = option.owner.isdefault() with warnings.catch_warnings(): warnings.simplefilter("ignore") default = option.value.get() ismulti = option.ismulti() - 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 = option_type RougailValidator.ismulti = ismulti - ori_default = default args = [" " * title_level + "📓 " + option.description()] if "frozen" in option.property.get(): - args[0] += " " + str(default) - question_funtion = qprint - qprint(" " + args[0]) + self._display_questionary_frozen(args[0], default) return - if option_type == "choice": - question_funtion = select - RougailValidator.default = default - kwargs["choices"] = option.value.list() - elif option_type == "boolean": - question_funtion = confirm - elif ( - option_type == "secret" - and not self.rougailconfig["questionary.show_secrets"] - ): - question_funtion = password - else: - question_funtion = text - kwargs["validate"] = RougailValidator + question_funtion = self._dispatcher_questionary( + option_type, RougailValidator, kwargs, option, default + ) + ori_default = default if ismulti: kwargs["multiline"] = True if default: @@ -170,69 +157,102 @@ class RougailUserDataQuestionary: value = RougailValidator().convert_value( question_funtion(*args, **kwargs).ask(), False ) - if isdefault and value == ori_default: + if option.owner.isdefault() and value == ori_default: option.value.reset() else: option.value.set(value) + def _dispatcher_questionary( + self, option_type: str, RougailValidator, kwargs: dict, option, default: any + ) -> callable: + if option_type == "choice": + RougailValidator.default = default + kwargs["choices"] = option.value.list() + return select + if option_type == "boolean": + return confirm + if option_type == "secret" and not self.questionary_show_secrets: + return password + kwargs["validate"] = RougailValidator + return text + + def _display_questionary_frozen(self, args: str, default: any) -> None: + qprint(" " + args + str(default)) + class RougailValidator(Validator): + """Extend Questionary Validator to Rougail needs + Be careful it's a singleton + """ + def validate(self, document): + """patch Questionary validate""" return self.convert_value(document.text) 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"): - val = self._convert_a_value(val, document, validate) - if val is not None: - value.append(val) - if validate: - try: - with warnings.catch_warnings(record=True) as warns: - self.option.value.set(value) - for warn in warns: - if isinstance(warn.message, ValueErrorWarning): - warn.message.prefix = "" - raise ValidationError( - message=str(warn.message), - cursor_position=len(document), - ) - except ValueOptionError as err: - err.prefix = "" - raise ValidationError( - message=str(err), - cursor_position=len(document), - ) from err + set_value=True, + ) -> any: + """Valid value""" + self.set_value = set_value + self.document = document + self.func = CONVERT_OPTION.get(self.option_type, {}).get("func") + convert_func = ( + self._convert_multi_value if self.ismulti else self._convert_a_value + ) + value = convert_func(document) + if self.set_value: + self._set_value(value) return value - def _convert_a_value(self, value, document, validate): - if value is None: - if self.option_type["type"] == "choice": - return self.default - return None + def _convert_multi_value(self, value: any) -> list[any]: + if self.document is None: + return [] + value = [] + for val in self.document.strip().split("\n"): + val = self._convert_a_value(val) + if val is not None: + value.append(val) + return value + + def _convert_a_value(self, value: any) -> any: if isinstance(value, str): value = value.strip() - if value == "": - if validate and "mandatory" in self.option.property.get(): - raise ValidationError( - message=_("Value must not be empty"), - cursor_position=len(document), - ) - return None - if self.option_type["func"]: - try: - return self.option_type["func"](value) - except Exception as err: - raise ValidationError( - message=_("Not a valid {0}").format(self.option_type["type"]), - cursor_position=len(document), - ) from err - return value + if value in [None, ""]: + return self._convert_empty_value() + if not self.func: + return value + try: + return self.func(value) + except: + msg = _("Not a valid {0}").format(self.option_type) + self._raise_validation_error(msg) + + def _convert_empty_value(self) -> any: + if self.option_type == "choice": + return self.default + self._validate_empty_value() + return None + + def _set_value(self, value: any) -> None: + try: + with warnings.catch_warnings(record=True) as warns: + self.option.value.set(value) + except ValueOptionError as err: + self._raise_validation_error(err) + for warn in warns: + if isinstance(warn.message, ValueErrorWarning): + self._raise_validation_error(warn.message) + + def _validate_empty_value(self) -> None: + if self.set_value and "mandatory" in self.option.property.get(): + self._raise_validation_error(_("Value must not be empty")) + + def _raise_validation_error(self, err) -> None: + if not isinstance(err, str): + err.prefix = "" + raise ValidationError( + message=str(err), + cursor_position=len(self.document), + )