feat: multi rougailcli.yml files

This commit is contained in:
egarette@silique.fr 2025-12-19 19:02:47 +01:00
parent 1f537963df
commit 585313e4bc
17 changed files with 128 additions and 57 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
---
main_structural_directories:
- structures
- structures_warnings
doc:
output_format: console
cli:
warnings: false

View file

@ -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\""]
["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\""]

View file

@ -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\""]
["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\""]

View file

@ -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}"
"{\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}"

View file

@ -0,0 +1,5 @@
---
main_structural_directories:
- structures
step:
output: json

View file

@ -0,0 +1,5 @@
---
step:
output: doc
doc:
output_format: json

View file

@ -0,0 +1,4 @@
---
version: 1.1
my_variable: my_value # a description

View file

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

View file

@ -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}"
"{\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}"

View file

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