2025-12-02 13:59:33 +01:00
|
|
|
"""
|
|
|
|
|
Silique (https://www.silique.fr)
|
2026-01-02 17:20:34 +01:00
|
|
|
Copyright (C) 2022-2026
|
2026-01-17 10:18:44 +01:00
|
|
|
|
2025-12-02 13:59:33 +01:00
|
|
|
This program is free software: you can redistribute it and/or modify it
|
|
|
|
|
under the terms of the GNU Lesser General Public License as published by the
|
|
|
|
|
Free Software Foundation, either version 3 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 Lesser General Public License for more
|
|
|
|
|
details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
"""
|
|
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
from typing import Optional, Any
|
2025-12-02 13:59:33 +01:00
|
|
|
from ruamel.yaml import YAML
|
2025-12-03 21:49:02 +01:00
|
|
|
from io import BytesIO
|
2025-12-02 13:59:33 +01:00
|
|
|
|
|
|
|
|
from tiramisu.error import PropertiesOptionError, ConfigError
|
2025-12-03 21:49:02 +01:00
|
|
|
from tiramisu import owners, groups
|
2025-12-02 13:59:33 +01:00
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
from .config import OutPuts
|
2025-12-02 13:59:33 +01:00
|
|
|
from .i18n import _
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RougailOutputDisplay:
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
config: "Config",
|
|
|
|
|
*,
|
|
|
|
|
rougailconfig: "RougailConfig" = None,
|
2025-12-03 21:49:02 +01:00
|
|
|
root_config=None,
|
2025-12-02 13:59:33 +01:00
|
|
|
user_data_errors: Optional[list] = None,
|
|
|
|
|
user_data_warnings: Optional[list] = None,
|
|
|
|
|
config_owner_is_path: bool = False,
|
|
|
|
|
layer_datas=None,
|
2026-01-15 21:20:14 +01:00
|
|
|
true_config=None,
|
2025-12-02 13:59:33 +01:00
|
|
|
**kwargs,
|
|
|
|
|
) -> None:
|
|
|
|
|
if rougailconfig is None:
|
|
|
|
|
from rougail import RougailConfig
|
|
|
|
|
rougailconfig = RougailConfig
|
|
|
|
|
self.rougailconfig = rougailconfig
|
|
|
|
|
self.config = config
|
2026-01-15 21:20:14 +01:00
|
|
|
if true_config is None:
|
|
|
|
|
self.true_config = config
|
|
|
|
|
else:
|
|
|
|
|
self.true_config = true_config
|
2025-12-03 21:49:02 +01:00
|
|
|
self.root_config = root_config
|
2025-12-02 13:59:33 +01:00
|
|
|
self.layer_datas = layer_datas
|
|
|
|
|
self.config_owner_is_path = config_owner_is_path
|
|
|
|
|
self.errors = []
|
|
|
|
|
self.warnings = []
|
|
|
|
|
if user_data_errors is None:
|
|
|
|
|
user_data_errors = []
|
|
|
|
|
self.user_data_errors = user_data_errors
|
|
|
|
|
if user_data_warnings is None:
|
|
|
|
|
user_data_warnings = []
|
|
|
|
|
self.user_data_warnings = user_data_warnings
|
2025-12-03 21:49:02 +01:00
|
|
|
# self.out = []
|
|
|
|
|
self.nodes = None
|
|
|
|
|
self.yaml = YAML()
|
|
|
|
|
self.yaml.indent(mapping=2, sequence=4, offset=2)
|
2025-12-02 13:59:33 +01:00
|
|
|
|
|
|
|
|
def run(self) -> str:
|
|
|
|
|
self.is_mandatory = self.rougailconfig["display.mandatory"]
|
|
|
|
|
if self.is_mandatory:
|
2026-01-15 21:20:14 +01:00
|
|
|
ori_properties = self.true_config.property.exportation()
|
|
|
|
|
self.true_config.property.read_write()
|
2025-12-02 13:59:33 +01:00
|
|
|
if not self.user_data_errors and not self.errors:
|
|
|
|
|
self.mandatories()
|
2026-01-15 21:20:14 +01:00
|
|
|
self.true_config.property.importation(ori_properties)
|
2025-12-03 21:49:02 +01:00
|
|
|
output_format = self.rougailconfig["display.output_format"]
|
|
|
|
|
output = OutPuts().get()[output_format](self.rougailconfig)
|
2025-12-02 13:59:33 +01:00
|
|
|
warnings = self.user_data_warnings + self.warnings
|
|
|
|
|
errors = self.user_data_errors + self.errors
|
2025-12-03 21:49:02 +01:00
|
|
|
level = None
|
|
|
|
|
errors_warnings_dict = {}
|
2026-01-01 09:22:58 +01:00
|
|
|
if not errors and self.nodes is None:
|
|
|
|
|
show_secrets = self.rougailconfig["display.show_secrets"]
|
|
|
|
|
self.nodes = Node(self.yaml, show_secrets, self.config, self.root_config, self.config_owner_is_path, errors)
|
2025-12-03 21:49:02 +01:00
|
|
|
if warnings:
|
|
|
|
|
level = "warnings"
|
|
|
|
|
#output.display_warnings(errors_warnings_dict, warnings)
|
|
|
|
|
for warning in warnings:
|
|
|
|
|
output.error_warn_to_dict(warning, errors_warnings_dict, 'warning')
|
2025-12-02 13:59:33 +01:00
|
|
|
if errors:
|
2025-12-03 21:49:02 +01:00
|
|
|
level = "errors"
|
|
|
|
|
for error in errors:
|
|
|
|
|
output.error_warn_to_dict(error, errors_warnings_dict, 'error')
|
|
|
|
|
if level:
|
|
|
|
|
if level == "errors":
|
|
|
|
|
tree = output.error_header()
|
|
|
|
|
else:
|
|
|
|
|
tree = output.warning_header()
|
|
|
|
|
ret = output.parse_error_warning(tree, errors_warnings_dict, output.display_error, level)
|
|
|
|
|
if errors:
|
|
|
|
|
return False, ret
|
|
|
|
|
ret += "\n"
|
|
|
|
|
else:
|
|
|
|
|
ret = ''
|
2026-01-01 09:22:58 +01:00
|
|
|
code, run = output.run(self.nodes, self.layer_datas)
|
2025-12-03 21:49:02 +01:00
|
|
|
return code, ret + run
|
|
|
|
|
|
|
|
|
|
def print(self) -> None:
|
|
|
|
|
status, ret = self.run()
|
|
|
|
|
print(ret)
|
|
|
|
|
return status
|
|
|
|
|
#
|
|
|
|
|
# def exporter(self) -> bool:
|
|
|
|
|
# if self.is_mandatory:
|
|
|
|
|
# ori_properties = self.config.property.exportation()
|
|
|
|
|
# self.config.property.read_write()
|
|
|
|
|
# if not self.user_data_errors and not self.errors:
|
|
|
|
|
# self.mandatories()
|
|
|
|
|
# self.config.property.importation(ori_properties)
|
|
|
|
|
# warnings = self.user_data_warnings + self.warnings
|
|
|
|
|
# if warnings:
|
|
|
|
|
# self.output.display_warnings(warnings)
|
|
|
|
|
#
|
|
|
|
|
# errors = self.user_data_errors + self.errors
|
|
|
|
|
# if errors:
|
|
|
|
|
# self.output.display_errors(errors)
|
|
|
|
|
# return False
|
|
|
|
|
# self.output.out = []
|
|
|
|
|
# if self.output.has_variable():
|
|
|
|
|
# self.output.header()
|
|
|
|
|
# self.output.end()
|
|
|
|
|
# return True
|
2025-12-02 13:59:33 +01:00
|
|
|
|
|
|
|
|
def mandatories(self) -> None:
|
|
|
|
|
try:
|
|
|
|
|
mandatories = self.config.value.mandatory()
|
2025-12-03 21:49:02 +01:00
|
|
|
except (ConfigError, PropertiesOptionError, ValueError) as err:
|
|
|
|
|
try:
|
|
|
|
|
subconfig = err.subconfig
|
|
|
|
|
except AttributeError:
|
|
|
|
|
subconfig = None
|
|
|
|
|
if subconfig:
|
|
|
|
|
err.prefix = ""
|
|
|
|
|
self.errors.append({str(err): subconfig})
|
|
|
|
|
else:
|
|
|
|
|
self.errors.append(str(err))
|
2025-12-02 13:59:33 +01:00
|
|
|
else:
|
2025-12-03 21:49:02 +01:00
|
|
|
self.populate_mandatories(mandatories)
|
2025-12-02 13:59:33 +01:00
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
def populate_mandatories(self, mandatories: list) -> None:
|
2025-12-02 13:59:33 +01:00
|
|
|
for option in mandatories:
|
|
|
|
|
try:
|
|
|
|
|
option.value.get()
|
|
|
|
|
except PropertiesOptionError:
|
2025-12-03 21:49:02 +01:00
|
|
|
self.errors.append(
|
|
|
|
|
{
|
|
|
|
|
_(
|
|
|
|
|
"mandatory variable but is inaccessible and has no value"
|
|
|
|
|
): option._subconfig,
|
|
|
|
|
}
|
|
|
|
|
)
|
2025-12-02 13:59:33 +01:00
|
|
|
else:
|
2025-12-03 21:49:02 +01:00
|
|
|
self.errors.append({_("mandatory variable but has no value"): option._subconfig})
|
2025-12-02 13:59:33 +01:00
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
|
|
|
|
|
class Node:
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
yaml,
|
|
|
|
|
show_secrets,
|
|
|
|
|
config,
|
|
|
|
|
root_config,
|
|
|
|
|
config_owner_is_path,
|
2026-01-01 09:22:58 +01:00
|
|
|
errors,
|
2025-12-03 21:49:02 +01:00
|
|
|
*,
|
|
|
|
|
node=None,
|
|
|
|
|
values: Optional[dict]=None,
|
|
|
|
|
level: int=0,
|
|
|
|
|
leader_index: Optional[int]=None,
|
|
|
|
|
) -> None:
|
|
|
|
|
self.yaml = yaml
|
|
|
|
|
self.show_secrets = show_secrets
|
|
|
|
|
self.config = config
|
|
|
|
|
self.root_config = root_config
|
|
|
|
|
self.config_owner_is_path = config_owner_is_path
|
2026-01-01 09:22:58 +01:00
|
|
|
self.errors = errors
|
2025-12-03 21:49:02 +01:00
|
|
|
if node is None:
|
|
|
|
|
values = self.config.value.get()
|
|
|
|
|
self.level = level
|
|
|
|
|
if node:
|
|
|
|
|
self.hidden = "hidden" in node.property.get()
|
|
|
|
|
self.description = node.description()
|
|
|
|
|
else:
|
|
|
|
|
self.hidden = None
|
|
|
|
|
self.description = _("Variables:")
|
|
|
|
|
self.level = level
|
|
|
|
|
self.children = []
|
|
|
|
|
|
|
|
|
|
if node and node.isoptiondescription() and node.isleadership():
|
|
|
|
|
values_iter = iter(values.items())
|
|
|
|
|
leader, leader_values = next(values_iter)
|
|
|
|
|
followers_values = {idx: {leader: value} for idx, value in enumerate(leader_values)}
|
|
|
|
|
for follower, follower_value in values_iter:
|
|
|
|
|
followers_values[follower.index()][follower] = follower_value
|
|
|
|
|
for idx, fvalues in enumerate(followers_values.values()):
|
|
|
|
|
self.add_node(leader, fvalues, leader_index=idx)
|
|
|
|
|
else:
|
|
|
|
|
for option, value in values.items():
|
|
|
|
|
if option.isoptiondescription():
|
|
|
|
|
if option.group_type() == groups.namespace and not value:
|
|
|
|
|
continue
|
|
|
|
|
if option.isleadership():
|
|
|
|
|
leader, leader_values = next(iter(value.items()))
|
|
|
|
|
if not leader_values:
|
|
|
|
|
self.add_leaf(leader, [], description=option.description())
|
|
|
|
|
else:
|
|
|
|
|
self.add_node(option, value)
|
|
|
|
|
else:
|
|
|
|
|
if value == {}:
|
|
|
|
|
self.add_leaf(option, "{}")
|
|
|
|
|
else:
|
|
|
|
|
self.add_node(option, value)
|
2025-12-02 13:59:33 +01:00
|
|
|
else:
|
2025-12-03 21:49:02 +01:00
|
|
|
if option.isleader():
|
|
|
|
|
index = leader_index
|
|
|
|
|
else:
|
|
|
|
|
index = None
|
|
|
|
|
self.add_leaf(option, value, leader_index=index)
|
2025-12-02 13:59:33 +01:00
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
def add_node(
|
2025-12-02 13:59:33 +01:00
|
|
|
self,
|
2025-12-03 21:49:02 +01:00
|
|
|
option,
|
|
|
|
|
values,
|
|
|
|
|
*,
|
|
|
|
|
leader_index: Optional[int]=None,
|
|
|
|
|
) -> 'Node':
|
|
|
|
|
self.children.append({"type": "node",
|
2026-01-01 09:22:58 +01:00
|
|
|
"node": Node(
|
2025-12-03 21:49:02 +01:00
|
|
|
self.yaml,
|
|
|
|
|
self.show_secrets,
|
|
|
|
|
self.config,
|
|
|
|
|
self.root_config,
|
|
|
|
|
self.config_owner_is_path,
|
2026-01-01 09:22:58 +01:00
|
|
|
self.errors,
|
2025-12-03 21:49:02 +01:00
|
|
|
node=option,
|
|
|
|
|
values=values,
|
|
|
|
|
level=self.level + 1,
|
|
|
|
|
leader_index=leader_index,
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def add_leaf(
|
|
|
|
|
self, option, value: Any, *, leader_index: Optional[int] = None, description: Optional[str] = None
|
2025-12-02 13:59:33 +01:00
|
|
|
):
|
2025-12-03 21:49:02 +01:00
|
|
|
properties = option.property.get()
|
|
|
|
|
if description is None:
|
|
|
|
|
description = option.description()
|
|
|
|
|
icon = "leaf"
|
|
|
|
|
else:
|
|
|
|
|
icon = "node"
|
|
|
|
|
self.children.append({"type": "leaf",
|
|
|
|
|
"description": description,
|
|
|
|
|
"values": self.get_values(option, value, leader_index, properties),
|
|
|
|
|
"icon": icon,
|
|
|
|
|
"hidden": "hidden" in properties,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_values(self, option: "Option", value: Any, leader_index: Optional[int], properties: list[str]) -> None:
|
|
|
|
|
if option.isoptiondescription():
|
|
|
|
|
return [{"is_default": True, "value": value, "loaded_from": None}]
|
|
|
|
|
values = []
|
|
|
|
|
meta_config = self.config
|
|
|
|
|
meta_option = option
|
|
|
|
|
index = option.index()
|
|
|
|
|
if index is not None:
|
|
|
|
|
loaded_from_key = f"loaded_from_{index}"
|
|
|
|
|
else:
|
|
|
|
|
loaded_from_key = "loaded_from"
|
|
|
|
|
#default = option.owner.isdefault() or not option.information.get("default_value_makes_sense", True)
|
|
|
|
|
force_store_value = "force_store_value" in properties
|
|
|
|
|
is_default = option.owner.isdefault()
|
|
|
|
|
option_path = option.path()
|
2026-01-01 21:39:06 +01:00
|
|
|
default_owner = owners.default
|
2026-01-13 10:12:31 +01:00
|
|
|
if self.root_config == meta_config:
|
|
|
|
|
added = True
|
|
|
|
|
true_default = is_default
|
|
|
|
|
else:
|
|
|
|
|
true_default = is_default and meta_option.owner.get() == default_owner
|
|
|
|
|
added = not is_default or true_default
|
2026-01-17 10:18:44 +01:00
|
|
|
secret_manager = option.information.get('secret_manager', False)
|
2025-12-03 21:49:02 +01:00
|
|
|
while True:
|
2026-01-17 10:18:44 +01:00
|
|
|
if values and true_default and (value in [None, []] or secret_manager):
|
2025-12-03 21:49:02 +01:00
|
|
|
break
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
value = [self.convert_value(meta_option, val) for val in value]
|
|
|
|
|
else:
|
|
|
|
|
value = self.convert_value(meta_option, value)
|
2026-01-01 21:39:06 +01:00
|
|
|
if true_default:
|
|
|
|
|
loaded_from = None
|
|
|
|
|
else:
|
|
|
|
|
loaded_from = meta_option.information.get(loaded_from_key, None)
|
2026-01-13 10:12:31 +01:00
|
|
|
if added or force_store_value:
|
2026-01-01 21:39:06 +01:00
|
|
|
values.append(
|
|
|
|
|
{
|
|
|
|
|
"value": value,
|
|
|
|
|
"is_default": true_default,
|
|
|
|
|
"loaded_from": loaded_from,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
if true_default or force_store_value:
|
|
|
|
|
break
|
2025-12-03 21:49:02 +01:00
|
|
|
meta_config_path = meta_config.path()
|
2026-01-01 21:39:06 +01:00
|
|
|
if is_default and (not meta_config_path or "." not in meta_config_path):
|
|
|
|
|
# we are in root metaconfig and we have default value
|
2025-12-03 21:49:02 +01:00
|
|
|
break
|
|
|
|
|
new_meta_config = self.get_metaconfig_with_default_value(meta_config, meta_option)
|
2026-01-01 21:39:06 +01:00
|
|
|
if not new_meta_config:
|
2025-12-03 21:49:02 +01:00
|
|
|
break
|
|
|
|
|
meta_option = new_meta_config.option(option_path, index)
|
|
|
|
|
if new_meta_config == meta_config:
|
2026-01-01 21:39:06 +01:00
|
|
|
# we already have current value, so we search default's one
|
2025-12-03 21:49:02 +01:00
|
|
|
is_default = True
|
2026-01-01 21:39:06 +01:00
|
|
|
true_default = True
|
2025-12-03 21:49:02 +01:00
|
|
|
value = meta_option.value.default()
|
|
|
|
|
else:
|
|
|
|
|
is_default = meta_option.owner.isdefault()
|
2026-01-01 09:22:58 +01:00
|
|
|
try:
|
|
|
|
|
value = meta_option.value.get()
|
|
|
|
|
except ValueError as err:
|
2026-01-01 21:39:06 +01:00
|
|
|
if not is_default:
|
2026-01-01 09:28:58 +01:00
|
|
|
meta_option._set_subconfig()
|
|
|
|
|
self.errors.append({str(err): meta_option._subconfig})
|
2026-01-01 09:22:58 +01:00
|
|
|
break
|
|
|
|
|
except Exception as err:
|
|
|
|
|
break
|
2026-01-01 21:39:06 +01:00
|
|
|
true_default = is_default and meta_option.owner.get() == default_owner
|
2026-01-13 10:12:31 +01:00
|
|
|
added = True
|
2025-12-03 21:49:02 +01:00
|
|
|
meta_config = new_meta_config
|
|
|
|
|
if leader_index is not None:
|
|
|
|
|
if len(value) > leader_index:
|
|
|
|
|
value = value[leader_index]
|
|
|
|
|
else:
|
|
|
|
|
value = None
|
|
|
|
|
return values
|
2025-12-02 13:59:33 +01:00
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
def convert_value(
|
2025-12-02 13:59:33 +01:00
|
|
|
self,
|
2025-12-03 21:49:02 +01:00
|
|
|
option,
|
|
|
|
|
value: Any,
|
|
|
|
|
) -> str:
|
|
|
|
|
"""Dump variable, means transform bool, ... to yaml string"""
|
|
|
|
|
if (
|
|
|
|
|
value is not None
|
|
|
|
|
and not self.show_secrets
|
|
|
|
|
and option.type() == "password"
|
|
|
|
|
):
|
|
|
|
|
return "*" * 10
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
return value
|
|
|
|
|
with BytesIO() as ymlfh:
|
|
|
|
|
self.yaml.dump(value, ymlfh)
|
|
|
|
|
ret = ymlfh.getvalue().decode("utf-8").strip()
|
|
|
|
|
if ret.endswith("..."):
|
|
|
|
|
ret = ret[:-3].strip()
|
|
|
|
|
return ret
|
2025-12-02 13:59:33 +01:00
|
|
|
|
2025-12-03 21:49:02 +01:00
|
|
|
def get_metaconfig_with_default_value(self, meta_config, option):
|
|
|
|
|
default_owner = option.owner.default()
|
|
|
|
|
if default_owner == owners.default:
|
2026-01-01 21:39:06 +01:00
|
|
|
if option.owner.get() == default_owner:
|
|
|
|
|
return None
|
2025-12-03 21:49:02 +01:00
|
|
|
if self.root_config:
|
|
|
|
|
return self.root_config
|
|
|
|
|
return self.config
|
|
|
|
|
if not self.config_owner_is_path:
|
|
|
|
|
while True:
|
2026-01-01 21:39:06 +01:00
|
|
|
if meta_config.type() != 'metaconfig':
|
|
|
|
|
return None
|
2025-12-03 21:49:02 +01:00
|
|
|
meta_config = meta_config.parent()
|
|
|
|
|
if not meta_config.owner.isdefault():
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
meta_config = self.root_config
|
|
|
|
|
for child in default_owner.split(".")[1:]:
|
|
|
|
|
meta_config = meta_config.config(child)
|
|
|
|
|
return meta_config
|