From 585313e4bc73719e564e22cc995680bc461b1a26 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Fri, 19 Dec 2025 19:02:47 +0100 Subject: [PATCH] feat: multi rougailcli.yml files --- src/rougail/cli/__main__.py | 68 +++++++++++---------- src/rougail/cli/config.py | 23 ++++++- tests/cli/result_user_data.txt | 8 +-- tests/cli/result_user_datas.txt | 8 +-- tests/cli/result_user_datas_layers.txt | 12 ++-- tests/cli/versions.txt | 2 +- tests/rougailcli_file/choice_console.txt | 1 + tests/rougailcli_file/choice_rougailcli.yml | 8 +++ tests/rougailcli_file/warnings.txt | 2 +- tests/rougailcli_file/warnings3.txt | 2 +- tests/rougailcli_file/yaml.txt | 2 +- tests/rougailcli_files/rougailcli1.yml | 5 ++ tests/rougailcli_files/rougailcli2.yml | 5 ++ tests/rougailcli_files/structures/file.yml | 4 ++ tests/rougailcli_files/yaml.txt | 1 + tests/second_step/yaml.txt | 2 +- tests/test_load.py | 32 +++++++++- 17 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 tests/rougailcli_file/choice_console.txt create mode 100644 tests/rougailcli_file/choice_rougailcli.yml create mode 100644 tests/rougailcli_files/rougailcli1.yml create mode 100644 tests/rougailcli_files/rougailcli2.yml create mode 100644 tests/rougailcli_files/structures/file.yml create mode 100644 tests/rougailcli_files/yaml.txt diff --git a/src/rougail/cli/__main__.py b/src/rougail/cli/__main__.py index ffb1590..63fd277 100644 --- a/src/rougail/cli/__main__.py +++ b/src/rougail/cli/__main__.py @@ -27,7 +27,7 @@ from tiramisu.error import PropertiesOptionError from tiramisu import MetaConfig from rougail import Rougail -from rougail.user_datas import UserDatas +from rougail.user_data import UserData from rougail.config import get_rougail_config from rougail.utils import load_modules from rougail.error import RougailWarning @@ -53,7 +53,7 @@ def _main(arguments, do_not_print): rougailconfig = get_rougail_config( backward_compatibility=False, add_extra_options=False ) - cmd_config = load_cmd_user_datas(rougailconfig, arguments) + cmd_config = load_cmd_user_data(rougailconfig, arguments) print_traceback = rougailconfig["cli.debug"] if rougailconfig["cli.versions"]: versions = display_version(cmd_config) @@ -62,7 +62,7 @@ def _main(arguments, do_not_print): for version in versions: print(version) exit() - layer_datas, metaconfig, config, err_warn = load_user_datas(rougailconfig) + layer_datas, metaconfig, config, err_warn = load_user_data(rougailconfig) output = get_output(rougailconfig, metaconfig, config, err_warn, layer_datas) if do_not_print: return output.run() @@ -71,7 +71,7 @@ def _main(arguments, do_not_print): exit(1) -def load_cmd_user_datas(rougailconfig, arguments): +def load_cmd_user_data(rougailconfig, arguments): rougailconfig.generate_config() cmd_config = rougailconfig.config origin_prop = cmd_config.property.default("read_write", "append") @@ -79,23 +79,28 @@ def load_cmd_user_datas(rougailconfig, arguments): frozenset(origin_prop | {"not_for_commandline"}), "read_write", "append" ) cmd_config.property.read_write() - config_file = None + config_files = None if RougailUserDataYaml: - _config_file = os.environ.pop(f"{ENV_PREFIX}_CLI.CONFIG_FILE", None) - if not _config_file: - _config_file = cmd_config.forcepermissive.option("cli.config_file").value.get() - if Path(_config_file).is_file(): - config_file = _config_file + _config_files = os.environ.pop(f"{ENV_PREFIX}_CLI.CONFIG_FILE", None) + if _config_files: + _config_files = _config_files.split(',') + else: + _config_files = cmd_config.forcepermissive.option("cli.config_file").value.get() + _config_files = [_config_file for _config_file in _config_files if Path(_config_file).is_file()] + if _config_files: + config_files = _config_files if RougailUserDataEnvironment: env_prefix = ENV_PREFIX else: env_prefix = None - user_data = rougailconfig_load(rougailconfig, config_file, env_prefix, True, _arguments=arguments, _generate=False, _add_help=False) + user_data = rougailconfig_load(rougailconfig, config_files, env_prefix, True, _arguments=arguments, _generate=False, _add_help=False) display_warnings = rougailconfig["cli.warnings"] manage_warnings(display_warnings) if not cmd_config.option("cli.versions").value.get(): if display_warnings and user_data["warnings"]: for warning in user_data["warnings"]: + if isinstance(warning, dict): + warning = next(iter(warning)) warn(warning) if manage_warnings: # replays to display errors if needed @@ -157,7 +162,7 @@ def manage_warnings(warnings): filterwarnings("default", category=RougailWarning) -def load_user_datas(rougailconfig): +def load_user_data(rougailconfig): layer_datas = {} if not rougailconfig["cli.load_config"]: return None, None, None, {"errors": [], "warnings": []} @@ -190,14 +195,15 @@ def load_user_datas(rougailconfig): subconfig.property.setdefault( frozenset(subconfig.property.default("read_only", "remove") | (read_write - read_only)), "read_only", "remove" ) - subconfig.property.read_write() if read_only: subconfig.property.setdefault( frozenset(subconfig.property.default("read_only", "append") | read_only), "read_only", "append" ) + if read_write or read_only: subconfig.property.read_write() except: pass + subconfig.information.set("description_type", rougailconfig["cli.description_type"]) metaconfig = subconfig if last_layers: for layer in layers[:-1]: @@ -206,9 +212,9 @@ def load_user_datas(rougailconfig): metaconfig.owner.set(metaconfig.path()) subconfig = metaconfig err_warn = {"errors": [], "warnings": []} - invalid_user_datas_error = rougailconfig["cli.invalid_user_datas_error"] - unknown_user_datas_error = rougailconfig["cli.unknown_user_datas_error"] - interactive_user_datas = {} + invalid_user_data_error = rougailconfig["cli.invalid_user_data_error"] + unknown_user_data_error = rougailconfig["cli.unknown_user_data_error"] + interactive_user_data = {} for idx, layer in enumerate(layers): if idx: subconfig = subconfig.config("_".join(layer)) @@ -217,7 +223,7 @@ def load_user_datas(rougailconfig): else: layer_name = None # data user - user_datas = [] + user_data = [] if has_layers: layer_datas[layer_name] = {} for user_data_name in layer: @@ -231,30 +237,30 @@ def load_user_datas(rougailconfig): _('cannot find "user_data" module "{0}"').format(user_data_name) ) module = load_modules("rougail.user_data_" + user_data_name, str(path)) - rougail_user_datas = module.RougailUserData - if hasattr(rougail_user_datas, 'interactive_user_datas') and rougail_user_datas.interactive_user_datas: - interactive_user_datas.setdefault(layer_name, {})[user_data_name] = rougail_user_datas + rougail_user_data = module.RougailUserData + if hasattr(rougail_user_data, 'interactive_user_data') and rougail_user_data.interactive_user_data: + interactive_user_data.setdefault(layer_name, {})[user_data_name] = rougail_user_data continue - elif interactive_user_datas: - raise Exception(_(f'interactive user datas "{0}" is loader before uninteractive user datas "{1}"').format(list(interactive_user_datas), user_data_name)) - for user_data in rougail_user_datas( + elif interactive_user_data: + raise Exception(_(f'interactive user datas "{0}" is loader before uninteractive user datas "{1}"').format(list(interactive_user_data), user_data_name)) + for ud in rougail_user_data( subconfig, rougailconfig=rougailconfig, ).run(): if has_layers: - layer_datas[layer_name].setdefault(user_data_name, []).append(user_data["source"]) - user_datas.append(user_data) - if user_datas: - new_err_warn = UserDatas(subconfig).user_datas(user_datas, invalid_user_datas_error=invalid_user_datas_error, unknown_user_datas_error=unknown_user_datas_error) + layer_datas[layer_name].setdefault(user_data_name, []).append(ud["source"]) + user_data.append(ud) + if user_data: + new_err_warn = UserData(subconfig).user_data(user_data, invalid_user_data_error=invalid_user_data_error, unknown_user_data_error=unknown_user_data_error) for level, datas in new_err_warn.items(): if datas: err_warn[level].extend(datas) - for layer_name, interactive_user_data in interactive_user_datas.items(): - for layer, rougail_user_datas in interactive_user_data.items(): + for layer_name, interactive_user_data in interactive_user_data.items(): + for layer, rougail_user_data in interactive_user_data.items(): if has_layers and len(layers) > 1: subconfig = subconfig.config("_".join(layer)) subconfig.owner.set(subconfig.path()) - for user_data in rougail_user_datas( + for user_data in rougail_user_data( subconfig, rougailconfig=rougailconfig, ).run(): @@ -281,7 +287,7 @@ def get_output(rougailconfig, metaconfig, config, err_warn, layer_datas): user_data_errors=err_warn["errors"], user_data_warnings=err_warn["warnings"], config_owner_is_path=True, - metaconfig=metaconfig, + root_config=metaconfig, layer_datas=layer_datas, ) return output diff --git a/src/rougail/cli/config.py b/src/rougail/cli/config.py index 3a26dd2..19f1c53 100644 --- a/src/rougail/cli/config.py +++ b/src/rougail/cli/config.py @@ -33,7 +33,8 @@ cli: commandline: false params: allow_relative: true - default: .rougailcli.yml + default: + - .rougailcli.yml debug: false # {_('Displays debug informations')} @@ -41,9 +42,9 @@ cli: versions: false # {_('Displays Rougail version and all its components')} - invalid_user_datas_error: false # {_("Invalid value in user datas is not allowed")} + invalid_user_data_error: false # {_("Invalid value in user datas is not allowed")} - unknown_user_datas_error: false # {_("Unknown variable in user datas is not allowed")} + unknown_user_data_error: false # {_("Unknown variable in user datas is not allowed")} layers: description: {_('Open each user datas in separate layers')} @@ -59,6 +60,22 @@ cli: read_write: false # {_('Configuration in output step is in read_write mode')} + description_type: + description: {_('Type of variables description')} + help: >- + {_("""To identify different variables, we use the variable description. There are four types of description: + + - name_and_description: which contains the variable name followed by its description + - path: the variable's path + - name: the variable name + - description: the variable's description""")} + choices: + - name_and_description + - path + - name + - description + default: description + inaccessible_read_write_modes: description: {_('Modes that should not be accessible in read_write mode')} multi: true diff --git a/tests/cli/result_user_data.txt b/tests/cli/result_user_data.txt index 98190bf..f038dbf 100644 --- a/tests/cli/result_user_data.txt +++ b/tests/cli/result_user_data.txt @@ -1,7 +1,7 @@ ╭────────────── Caption ───────────────╮ -│ Variable Modified value │ -│ (⏳ Original default value) │ +│ Variable Modified value │ +│ (⏳ Original default value) │ ╰──────────────────────────────────────╯ Variables: -┗━━ 📓 a description: a yaml value ◀ loaded from the YAML file "yaml/file.yml" - (⏳ my_value) +┗━━ 📓 a description: a yaml value ◀ loaded from the YAML file "yaml/file.yml" + (⏳ my_value) diff --git a/tests/cli/result_user_datas.txt b/tests/cli/result_user_datas.txt index 98190bf..f038dbf 100644 --- a/tests/cli/result_user_datas.txt +++ b/tests/cli/result_user_datas.txt @@ -1,7 +1,7 @@ ╭────────────── Caption ───────────────╮ -│ Variable Modified value │ -│ (⏳ Original default value) │ +│ Variable Modified value │ +│ (⏳ Original default value) │ ╰──────────────────────────────────────╯ Variables: -┗━━ 📓 a description: a yaml value ◀ loaded from the YAML file "yaml/file.yml" - (⏳ my_value) +┗━━ 📓 a description: a yaml value ◀ loaded from the YAML file "yaml/file.yml" + (⏳ my_value) diff --git a/tests/cli/result_user_datas_layers.txt b/tests/cli/result_user_datas_layers.txt index 8cc1133..e284499 100644 --- a/tests/cli/result_user_datas_layers.txt +++ b/tests/cli/result_user_datas_layers.txt @@ -1,11 +1,7 @@ ╭────────────── Caption ───────────────╮ -│ Variable Modified value │ -│ (⏳ Original default value) │ +│ Variable Modified value │ +│ (⏳ Original default value) │ ╰──────────────────────────────────────╯ -╭─────────── Layers ────────────╮ -│ environment variable │ -│ the YAML file "yaml/file.yml" │ -╰───────────────────────────────╯ Variables: -┗━━ 📓 a description: a yaml value ◀ loaded from the YAML file "yaml/file.yml" - (⏳ my env value ◀ loaded from environment variable ⏳ my env value) +┗━━ 📓 a description: a yaml value ◀ loaded from the YAML file "yaml/file.yml" + (⏳ my env value ◀ loaded from environment variable ⏳ my_value) diff --git a/tests/cli/versions.txt b/tests/cli/versions.txt index fa39f07..e64954a 100644 --- a/tests/cli/versions.txt +++ b/tests/cli/versions.txt @@ -1 +1 @@ -["tiramisu", "tiramisu-cmdline-parser", "rougail", "rougail-cli", "rougail-structural-bitwarden", "rougail-user-data-ansible", "rougail-user-data-bitwarden", "rougail-user-data-commandline", "rougail-user-data-environment", "rougail-user-data-questionary", "rougail-user-data-yaml", "rougail-output-ansible", "rougail-output-console", "rougail-output-doc", "rougail-output-json"] +["tiramisu", "tiramisu-cmdline-parser", "rougail", "rougail-cli", "rougail-structural-bitwarden", "rougail-user-data-ansible", "rougail-user-data-bitwarden", "rougail-user-data-commandline", "rougail-user-data-environment", "rougail-user-data-questionary", "rougail-user-data-yaml", "rougail-output-ansible", "rougail-output-display", "rougail-output-doc", "rougail-output-json"] diff --git a/tests/rougailcli_file/choice_console.txt b/tests/rougailcli_file/choice_console.txt new file mode 100644 index 0000000..6c21a64 --- /dev/null +++ b/tests/rougailcli_file/choice_console.txt @@ -0,0 +1 @@ +"\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Caption \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 Variable \u001b[38;5;220mDefault value\u001b[0m \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\nVariables:\n\u001b[94m\u2517\u2501\u2501 \u001b[0m\ud83d\udcd3 a description: \u001b[38;5;220mmy_value\u001b[0m\n" \ No newline at end of file diff --git a/tests/rougailcli_file/choice_rougailcli.yml b/tests/rougailcli_file/choice_rougailcli.yml new file mode 100644 index 0000000..1b10a1c --- /dev/null +++ b/tests/rougailcli_file/choice_rougailcli.yml @@ -0,0 +1,8 @@ +--- +main_structural_directories: + - structures + - structures_warnings +doc: + output_format: console +cli: + warnings: false diff --git a/tests/rougailcli_file/warnings.txt b/tests/rougailcli_file/warnings.txt index 14bc0cb..ec23967 100644 --- a/tests/rougailcli_file/warnings.txt +++ b/tests/rougailcli_file/warnings.txt @@ -1 +1 @@ -["family \"configuration rougail-json\" is disabled, \"test mandatories variables before display in json\" will be ignored when loading from the YAML file \"warnings.yml\""] \ No newline at end of file +["family \"configuration rougail-json\" is disabled, so cannot access to \"test mandatories variables before display in json\", it will be ignored when loading from the YAML file \"warnings.yml\""] \ No newline at end of file diff --git a/tests/rougailcli_file/warnings3.txt b/tests/rougailcli_file/warnings3.txt index 1176511..699a0ef 100644 --- a/tests/rougailcli_file/warnings3.txt +++ b/tests/rougailcli_file/warnings3.txt @@ -1 +1 @@ -["family \"configuration rougail-json\" is disabled, \"test mandatories variables before display in json\" will be ignored when loading from the YAML file \"warnings3.yml\"", "\"validators\" is a calculation for my_variable but has no description in \"structures/file.yml\" and \"structures_warnings/file.yml\""] \ No newline at end of file +["family \"configuration rougail-json\" is disabled, so cannot access to \"test mandatories variables before display in json\", it will be ignored when loading from the YAML file \"warnings3.yml\"", "\"validators\" is a calculation for my_variable but has no description in \"structures/file.yml\" and \"structures_warnings/file.yml\""] \ No newline at end of file diff --git a/tests/rougailcli_file/yaml.txt b/tests/rougailcli_file/yaml.txt index 93aeb83..acfd629 100644 --- a/tests/rougailcli_file/yaml.txt +++ b/tests/rougailcli_file/yaml.txt @@ -1 +1 @@ -"{\n \"my_variable\": {\n \"type\": \"variable\",\n \"default\": {\n \"name\": \"Default\",\n \"values\": \"my_value\"\n },\n \"properties\": [\n {\n \"type\": \"type\",\n \"name\": \"string\"\n },\n {\n \"type\": \"property\",\n \"name\": \"mandatory\"\n }\n ],\n \"path\": \"my_variable\",\n \"names\": [\n \"my_variable\"\n ],\n \"description\": \"A description.\",\n \"gen_examples\": [\n \"my_value\"\n ],\n \"mandatory_without_value\": false\n }\n}" \ No newline at end of file +"{\n \"my_variable\": {\n \"type\": \"variable\",\n \"default\": {\n \"name\": \"Default\",\n \"values\": \"my_value\"\n },\n \"variable_type\": \"string\",\n \"path\": \"my_variable\",\n \"names\": [\n \"my_variable\"\n ],\n \"description\": \"A description.\",\n \"properties\": [\n {\n \"type\": \"property\",\n \"name\": \"mandatory\",\n \"ori_name\": \"mandatory\",\n \"access_control\": false\n }\n ],\n \"gen_examples\": [\n \"my_value\"\n ],\n \"mandatory_without_value\": false\n }\n}" \ No newline at end of file diff --git a/tests/rougailcli_files/rougailcli1.yml b/tests/rougailcli_files/rougailcli1.yml new file mode 100644 index 0000000..b5f94a7 --- /dev/null +++ b/tests/rougailcli_files/rougailcli1.yml @@ -0,0 +1,5 @@ +--- +main_structural_directories: + - structures +step: + output: json diff --git a/tests/rougailcli_files/rougailcli2.yml b/tests/rougailcli_files/rougailcli2.yml new file mode 100644 index 0000000..eeeb7d3 --- /dev/null +++ b/tests/rougailcli_files/rougailcli2.yml @@ -0,0 +1,5 @@ +--- +step: + output: doc +doc: + output_format: json diff --git a/tests/rougailcli_files/structures/file.yml b/tests/rougailcli_files/structures/file.yml new file mode 100644 index 0000000..a211e78 --- /dev/null +++ b/tests/rougailcli_files/structures/file.yml @@ -0,0 +1,4 @@ +--- +version: 1.1 + +my_variable: my_value # a description diff --git a/tests/rougailcli_files/yaml.txt b/tests/rougailcli_files/yaml.txt new file mode 100644 index 0000000..acfd629 --- /dev/null +++ b/tests/rougailcli_files/yaml.txt @@ -0,0 +1 @@ +"{\n \"my_variable\": {\n \"type\": \"variable\",\n \"default\": {\n \"name\": \"Default\",\n \"values\": \"my_value\"\n },\n \"variable_type\": \"string\",\n \"path\": \"my_variable\",\n \"names\": [\n \"my_variable\"\n ],\n \"description\": \"A description.\",\n \"properties\": [\n {\n \"type\": \"property\",\n \"name\": \"mandatory\",\n \"ori_name\": \"mandatory\",\n \"access_control\": false\n }\n ],\n \"gen_examples\": [\n \"my_value\"\n ],\n \"mandatory_without_value\": false\n }\n}" \ No newline at end of file diff --git a/tests/second_step/yaml.txt b/tests/second_step/yaml.txt index 93aeb83..acfd629 100644 --- a/tests/second_step/yaml.txt +++ b/tests/second_step/yaml.txt @@ -1 +1 @@ -"{\n \"my_variable\": {\n \"type\": \"variable\",\n \"default\": {\n \"name\": \"Default\",\n \"values\": \"my_value\"\n },\n \"properties\": [\n {\n \"type\": \"type\",\n \"name\": \"string\"\n },\n {\n \"type\": \"property\",\n \"name\": \"mandatory\"\n }\n ],\n \"path\": \"my_variable\",\n \"names\": [\n \"my_variable\"\n ],\n \"description\": \"A description.\",\n \"gen_examples\": [\n \"my_value\"\n ],\n \"mandatory_without_value\": false\n }\n}" \ No newline at end of file +"{\n \"my_variable\": {\n \"type\": \"variable\",\n \"default\": {\n \"name\": \"Default\",\n \"values\": \"my_value\"\n },\n \"variable_type\": \"string\",\n \"path\": \"my_variable\",\n \"names\": [\n \"my_variable\"\n ],\n \"description\": \"A description.\",\n \"properties\": [\n {\n \"type\": \"property\",\n \"name\": \"mandatory\",\n \"ori_name\": \"mandatory\",\n \"access_control\": false\n }\n ],\n \"gen_examples\": [\n \"my_value\"\n ],\n \"mandatory_without_value\": false\n }\n}" \ No newline at end of file diff --git a/tests/test_load.py b/tests/test_load.py index 6dd1558..2ca89b1 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -25,7 +25,7 @@ def test_cli(): def test_cli_boolean(): with chdir(test_dir / 'cli'): - ret = main(['--main_structural_directories', 'structures', '--console.mandatory'], do_not_print=True) + ret = main(['--main_structural_directories', 'structures', '--display.mandatory'], do_not_print=True) filename = Path('result.txt') if not filename.is_file(): with filename.open('w') as fh: @@ -37,7 +37,7 @@ def test_cli_boolean(): def test_cli_boolean_no(): with chdir(test_dir / 'cli'): - ret = main(['--main_structural_directories', 'structures', '--console.no-mandatory'], do_not_print=True) + ret = main(['--main_structural_directories', 'structures', '--display.no-mandatory'], do_not_print=True) filename = Path('result.txt') if not filename.is_file(): with filename.open('w') as fh: @@ -139,6 +139,19 @@ def test_cli_rougailcli(): assert ret == (True, data), str(filename.absolute()) +def test_cli_rougailcli_files(): + with chdir(test_dir / 'rougailcli_files'): + os.environ["ROUGAILCLI_CLI.CONFIG_FILE"] = 'rougailcli1.yml,rougailcli2.yml' + ret = main([], do_not_print=True) + filename = Path('yaml.txt') + if not filename.is_file(): + with filename.open('w') as fh: + fh.write(dumps(ret[1])) + with filename.open() as fh: + data = loads(fh.read()) + assert ret == (True, data), str(filename.absolute()) + + def test_cli_alt_rougailcli(): save = os.environ.copy() with chdir(test_dir / 'rougailcli_file'): @@ -239,3 +252,18 @@ def test_cli_rougailcli_not_warning2(): data = loads(fh.read()) assert ret == data, str(filename.absolute()) os.environ = save + + +def test_cli_rougailcli_choice(): + save = os.environ.copy() + with chdir(test_dir / 'rougailcli_file'): + os.environ["ROUGAILCLI_CLI.CONFIG_FILE"] = 'choice_rougailcli.yml' + ret = main([], do_not_print=True) + filename = Path('choice_console.txt') + if not filename.is_file(): + with filename.open('w') as fh: + fh.write(dumps(ret[1])) + with filename.open() as fh: + data = loads(fh.read()) + assert ret == (True, data), str(filename.absolute()) + os.environ = save