diff --git a/src/rougail/user_data_questionary/cli.py b/src/rougail/user_data_questionary/cli.py new file mode 100644 index 0000000..14ac605 --- /dev/null +++ b/src/rougail/user_data_questionary/cli.py @@ -0,0 +1,35 @@ +""" +Cli code for Rougail-user-data-questionary + +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 .data import RougailUserDataQuestionary + + +def run(rougailconfig, + config, + user_datas, + ): + RougailUserDataQuestionary(config, + rougailconfig=rougailconfig, + ).run() + + +__all__ = ('run',) diff --git a/src/rougail/user_data_questionary/config.py b/src/rougail/user_data_questionary/config.py new file mode 100644 index 0000000..89e122a --- /dev/null +++ b/src/rougail/user_data_questionary/config.py @@ -0,0 +1,47 @@ +""" +Config file for Rougail-user-data-questionary + +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 +""" +def get_rougail_config(*, + backward_compatibility=True, + ) -> dict: + options = """ +questionary: + description: Define value interactivly + disabled: + type: jinja + jinja: | + {% if 'questionary' not in step.user_data %} + disabled + {% endif %} + mandatory: + description: Ask values only for mandatories variables without any value + alternative_name: qm + default: false +""" + return {'name': 'questionary', + 'process': 'user data', + 'options': options, + 'level': 60, + } + + +__all__ = ('get_rougail_config',) diff --git a/src/rougail/user_data_questionary/data.py b/src/rougail/user_data_questionary/data.py new file mode 100644 index 0000000..7843116 --- /dev/null +++ b/src/rougail/user_data_questionary/data.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +""" +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 rougail.object_model import CONVERT_OPTION +from rougail.config import RougailConfig +from tiramisu.error import ValueOptionError +from questionary import text, select, confirm, Validator, ValidationError, print as qprint + +class RougailUserDataQuestionary: + def __init__(self, + config: 'Config', + *, + rougailconfig: RougailConfig=None, + ): + self.config = config + if rougailconfig is None: + rougailconfig = RougailConfig + self.rougailconfig = rougailconfig + self.errors = [] + self.warnings = [] + + def run(self): + self.config.property.read_write() + 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) + else: + self.parse(self.config) + self.config.property.read_only() + + def parse(self, config, title_level=0): + for option in config: + if option.isoptiondescription(): + self.print(option.description(), title_level) + self.parse(option, title_level + 1) + else: + self.display_questionary(option) + + def print(self, title, title_level): + qprint(' ' * title_level + '📂 ' + title, 'bold') + + def display_questionary(self, option): + kwargs = {} + option_type = option.information.get('type') + isdefault = option.owner.isdefault() + 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.ismulti = ismulti + if option_type == 'choice': + question_funtion = select + RougailValidator.default = default + kwargs['choices'] = option.value.list() + elif option_type == 'boolean': + question_funtion = confirm + else: + question_funtion = text + kwargs['validate'] = RougailValidator + args = ['📓 ' + option.description()] + if ismulti: + kwargs['multiline'] = True + if default: + kwargs['default'] = "\n".join(default) + elif default is not None: + kwargs['default'] = default + value = RougailValidator().convert_value(question_funtion(*args, **kwargs).ask(), False) + if isdefault and value == default: + option.value.reset() + else: + option.value.set(value) + + +class RougailValidator(Validator): + def validate(self, document): + 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: + self.option.value.set(value) + except ValueOptionError as err: + err.prefix = '' + raise ValidationError( + message=str(err), + cursor_position=len(document), + ) + return value + + def _convert_a_value(self, value, document, validate): + if value is None: + if self.option_type['type'] == 'choice': + return self.default + return + if isinstance(value, str): + value = value.strip() + if value == '': + if validate and "mandatory" in self.option.property.get(): + raise ValidationError( + message=f"Value must not be empty", + cursor_position=len(document), + ) + return + if self.option_type['func']: + try: + return self.option_type['func'](value) + except: + raise ValidationError( + message=f"Not a valid {self.option_type['type']}", + cursor_position=len(document), + ) + return value