281 lines
9.3 KiB
Python
281 lines
9.3 KiB
Python
|
|
"""
|
||
|
|
Silique (https://www.silique.fr)
|
||
|
|
Copyright (C) 2022-2025
|
||
|
|
|
||
|
|
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/>.
|
||
|
|
"""
|
||
|
|
from typing import Any, Optional
|
||
|
|
from io import BytesIO
|
||
|
|
|
||
|
|
from tiramisu import owners
|
||
|
|
|
||
|
|
from rougail.utils import undefined
|
||
|
|
|
||
|
|
|
||
|
|
class CommonOutput:
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
family,
|
||
|
|
parent,
|
||
|
|
root,
|
||
|
|
_yaml,
|
||
|
|
out,
|
||
|
|
*,
|
||
|
|
# is_leader: bool = False,
|
||
|
|
root_family: Optional["OutputFamily"] = None,
|
||
|
|
no_icon: bool = False,
|
||
|
|
) -> None:
|
||
|
|
self.tree = None
|
||
|
|
self.root = root
|
||
|
|
self.parent = parent
|
||
|
|
if isinstance(family, tuple):
|
||
|
|
raise Exception()
|
||
|
|
self.family = family
|
||
|
|
# self.is_leader = is_leader
|
||
|
|
self.no_icon = no_icon
|
||
|
|
self._yaml = _yaml
|
||
|
|
self.out = out
|
||
|
|
if root_family is None:
|
||
|
|
root_family = self
|
||
|
|
self.variable_default_enable = False
|
||
|
|
self.variable_hidden_enable = False
|
||
|
|
self.value_modified_enable = False
|
||
|
|
self.value_unmodified_enable = False
|
||
|
|
self.value_default_enable = False
|
||
|
|
self.root_family = root_family
|
||
|
|
|
||
|
|
def header(self):
|
||
|
|
raise NotImplementedError()
|
||
|
|
|
||
|
|
def display_errors(
|
||
|
|
self,
|
||
|
|
errors,
|
||
|
|
) -> None:
|
||
|
|
tree = self.error_header()
|
||
|
|
for error in errors:
|
||
|
|
self.parse_error(tree, error)
|
||
|
|
self.out.append(tree)
|
||
|
|
|
||
|
|
def parse_error(self, tree, error):
|
||
|
|
if isinstance(error, list):
|
||
|
|
for err in error:
|
||
|
|
self.parse_error(tree, err)
|
||
|
|
return tree
|
||
|
|
elif isinstance(error, dict):
|
||
|
|
for key, value in error.items():
|
||
|
|
if key is None:
|
||
|
|
# it's variables, no more families
|
||
|
|
self.parse_error(tree, value)
|
||
|
|
else:
|
||
|
|
sub_tree = self.parse_error(tree, key)
|
||
|
|
self.parse_error(sub_tree, value)
|
||
|
|
else:
|
||
|
|
return self.display_error(tree, error)
|
||
|
|
|
||
|
|
def add_family(
|
||
|
|
self,
|
||
|
|
family,
|
||
|
|
) -> 'OutputFamily':
|
||
|
|
properties = family.property.get()
|
||
|
|
if "hidden" in properties:
|
||
|
|
self.root_family.variable_hidden_enable = True
|
||
|
|
color = self.variable_hidden_color
|
||
|
|
else:
|
||
|
|
self.root_family.variable_default_enable = True
|
||
|
|
color = None
|
||
|
|
family_output = self.colorize(
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"value": family.description(),
|
||
|
|
"color": color,
|
||
|
|
"loaded_from": None,
|
||
|
|
}
|
||
|
|
]
|
||
|
|
)
|
||
|
|
return self.__class__(
|
||
|
|
family_output,
|
||
|
|
self.get_tree(),
|
||
|
|
self.root,
|
||
|
|
self._yaml,
|
||
|
|
self.out,
|
||
|
|
root_family = self.root_family,
|
||
|
|
)
|
||
|
|
|
||
|
|
def add_variable(
|
||
|
|
self, option, value: Any = undefined, leader_index: Optional[int] = None
|
||
|
|
):
|
||
|
|
values = []
|
||
|
|
properties = option.property.get()
|
||
|
|
if not option.owner.isdefault() and option.information.get(
|
||
|
|
"default_value_makes_sense", True
|
||
|
|
):
|
||
|
|
self.get_modified_value(option, value, values)
|
||
|
|
if "force_store_value" not in properties or not values:
|
||
|
|
self.get_default_values(option, values, leader_index)
|
||
|
|
#
|
||
|
|
if "hidden" in properties:
|
||
|
|
self.root_family.variable_hidden_enable = True
|
||
|
|
variable_color = self.variable_hidden_color
|
||
|
|
else:
|
||
|
|
self.root_family.variable_default_enable = True
|
||
|
|
variable_color = self.variable_normal_color
|
||
|
|
key = self.colorize(
|
||
|
|
[
|
||
|
|
{
|
||
|
|
"value": option.description(),
|
||
|
|
"color": variable_color,
|
||
|
|
"loaded_from": None,
|
||
|
|
}
|
||
|
|
]
|
||
|
|
)
|
||
|
|
value = self.colorize(
|
||
|
|
values,
|
||
|
|
option,
|
||
|
|
)
|
||
|
|
return key, value
|
||
|
|
|
||
|
|
def get_modified_value(self, option: "Option", value: Any, values: list) -> None:
|
||
|
|
self.root_family.value_modified_enable = True
|
||
|
|
follower_index = option.index()
|
||
|
|
if follower_index is not None:
|
||
|
|
loaded_from = option.information.get(
|
||
|
|
f"loaded_from_{follower_index}", None
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
loaded_from = option.information.get("loaded_from", None)
|
||
|
|
if value is undefined:
|
||
|
|
value = option.value.get()
|
||
|
|
values.append(
|
||
|
|
{
|
||
|
|
"value": value,
|
||
|
|
"color": self.value_modified_color,
|
||
|
|
"loaded_from": loaded_from,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
def get_default_values(self, option: "Option", values: list, leader_index: Optional[int]) -> None:
|
||
|
|
meta_config = self.root.config
|
||
|
|
meta_option = option
|
||
|
|
index = option.index()
|
||
|
|
while True:
|
||
|
|
default_value = meta_option.value.default()
|
||
|
|
if leader_index is not None:
|
||
|
|
if len(default_value) > leader_index:
|
||
|
|
default_value = default_value[leader_index]
|
||
|
|
else:
|
||
|
|
default_value = None
|
||
|
|
is_root_metaconfig = False
|
||
|
|
if (
|
||
|
|
meta_config is None
|
||
|
|
or not meta_config.path()
|
||
|
|
or "." not in meta_config.path()
|
||
|
|
):
|
||
|
|
is_root_metaconfig = True
|
||
|
|
if (
|
||
|
|
(values and default_value is None) or default_value == []
|
||
|
|
) and is_root_metaconfig:
|
||
|
|
break
|
||
|
|
if not values:
|
||
|
|
self.root_family.value_unmodified_enable = True
|
||
|
|
color = self.value_unmodified_color
|
||
|
|
else:
|
||
|
|
self.root_family.value_default_enable = True
|
||
|
|
color = self.value_default_color
|
||
|
|
meta_config = self.get_subconfig_with_default_value(meta_option)
|
||
|
|
meta_option = meta_config.option(option.path(), index)
|
||
|
|
if is_root_metaconfig:
|
||
|
|
loaded_from = None
|
||
|
|
else:
|
||
|
|
if index is not None:
|
||
|
|
key = f"loaded_from_{index}"
|
||
|
|
else:
|
||
|
|
key = "loaded_from"
|
||
|
|
loaded_from = meta_option.information.get(key, None)
|
||
|
|
values.append(
|
||
|
|
{
|
||
|
|
"value": default_value,
|
||
|
|
"color": color,
|
||
|
|
"loaded_from": loaded_from,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
if is_root_metaconfig:
|
||
|
|
break
|
||
|
|
#
|
||
|
|
# def get_default_value(self, option: "Option", values: list, leader_index: Optional[int]) -> None:
|
||
|
|
# follower_index = option.index()
|
||
|
|
## if leader_index is None and follower_index is not None:
|
||
|
|
## if not option.isfollower() or not option.issubmulti():
|
||
|
|
## default_value = None
|
||
|
|
## else:
|
||
|
|
## default_value = []
|
||
|
|
## else:
|
||
|
|
## default_value = None
|
||
|
|
# if not values:
|
||
|
|
# self.root_family.value_unmodified_enable = True
|
||
|
|
# color = self.value_unmodified_color
|
||
|
|
# else:
|
||
|
|
# self.root_family.value_default_enable = True
|
||
|
|
# color = self.value_default_color
|
||
|
|
# if follower_index is not None:
|
||
|
|
# loaded_from = option.information.get(
|
||
|
|
# f"loaded_from_{follower_index}", None
|
||
|
|
# )
|
||
|
|
# else:
|
||
|
|
# loaded_from = option.information.get("loaded_from", None)
|
||
|
|
# values.append(
|
||
|
|
# {
|
||
|
|
# "value": default_value,
|
||
|
|
# "color": color,
|
||
|
|
# "loaded_from": loaded_from,
|
||
|
|
# }
|
||
|
|
# )
|
||
|
|
|
||
|
|
def get_subconfig_with_default_value(self, config):
|
||
|
|
default_owner = config.owner.default()
|
||
|
|
if default_owner == owners.default:
|
||
|
|
return self.root.config
|
||
|
|
if not self.root.config_owner_is_path:
|
||
|
|
meta_config = self.root.config
|
||
|
|
while True:
|
||
|
|
meta_config = meta_config.parent()
|
||
|
|
if not meta_config.owner.isdefault():
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
meta_config = self.root.metaconfig
|
||
|
|
for child in default_owner.split(".")[1:]:
|
||
|
|
meta_config = meta_config.config(child)
|
||
|
|
return meta_config
|
||
|
|
|
||
|
|
def convert_value(
|
||
|
|
self,
|
||
|
|
option,
|
||
|
|
value: Any,
|
||
|
|
) -> str:
|
||
|
|
"""Dump variable, means transform bool, ... to yaml string"""
|
||
|
|
if (
|
||
|
|
value is not None
|
||
|
|
and not self.root.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
|