740 lines
28 KiB
Python
740 lines
28 KiB
Python
|
|
"""
|
||
|
|
Silique (https://www.silique.fr)
|
||
|
|
Copyright (C) 2024-2026
|
||
|
|
|
||
|
|
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 warnings import warn
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
from tiramisu import Calculation, groups
|
||
|
|
from tiramisu.error import display_list, PropertiesOptionError
|
||
|
|
from rougail.tiramisu import display_xmlfiles
|
||
|
|
from rougail.utils import PROPERTY_ATTRIBUTE
|
||
|
|
from rougail.error import VariableCalculationDependencyError, RougailWarning, ExtensionError
|
||
|
|
|
||
|
|
from .utils import dump, to_phrase, calc_path
|
||
|
|
from .i18n import _
|
||
|
|
|
||
|
|
|
||
|
|
HIDDEN_PROPERTIES = [
|
||
|
|
"hidden",
|
||
|
|
"disabled",
|
||
|
|
]
|
||
|
|
|
||
|
|
DISABLED_PROPERTIES = ["disabled"]
|
||
|
|
|
||
|
|
|
||
|
|
class _ToString:
|
||
|
|
def _to_string(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
prop,
|
||
|
|
do_not_raise=False,
|
||
|
|
):
|
||
|
|
calculation = child.information.get(f"{prop}_calculation", None)
|
||
|
|
if not calculation:
|
||
|
|
if do_not_raise:
|
||
|
|
return None
|
||
|
|
raise ExtensionError(
|
||
|
|
_(
|
||
|
|
'cannot find "{0}_calculation" information, '
|
||
|
|
"do you have annotate this configuration?"
|
||
|
|
).format(prop)
|
||
|
|
)
|
||
|
|
if isinstance(calculation, list):
|
||
|
|
values = []
|
||
|
|
for cal in calculation:
|
||
|
|
value = self._calculation_to_string(child, cal, prop)
|
||
|
|
if value is not None:
|
||
|
|
values.append(value)
|
||
|
|
return values
|
||
|
|
ret = self._calculation_to_string(child, calculation, prop)
|
||
|
|
if isinstance(ret, list) and len(ret) == 1:
|
||
|
|
return ret[0]
|
||
|
|
return ret
|
||
|
|
|
||
|
|
def _calculation_to_string(self, child, calculation, attribute_type):
|
||
|
|
if "description" in calculation:
|
||
|
|
values = calculation
|
||
|
|
if self.document_a_type and "variables" in values:
|
||
|
|
for variable in list(values["variables"]):
|
||
|
|
variable["path"] = self.doc_path(variable["path"])
|
||
|
|
|
||
|
|
elif "type" not in calculation:
|
||
|
|
values = calculation["value"]
|
||
|
|
elif calculation["type"] == "jinja":
|
||
|
|
values = self._calculation_jinja_to_string(
|
||
|
|
child, calculation, attribute_type
|
||
|
|
)
|
||
|
|
elif calculation["type"] == "variable":
|
||
|
|
values = self._calculation_variable_to_string(
|
||
|
|
child, calculation, attribute_type
|
||
|
|
)
|
||
|
|
elif calculation["type"] == "identifier":
|
||
|
|
values = self._calculation_identifier_to_string(
|
||
|
|
child, calculation, attribute_type
|
||
|
|
)
|
||
|
|
elif calculation["type"] == "information":
|
||
|
|
if "path" in calculation:
|
||
|
|
variable_path = self.doc_path(calculation["path"])
|
||
|
|
values = _(
|
||
|
|
'the value of the information "{0}" of the variable "{1}"'
|
||
|
|
).format(calculation["information"], variable_path)
|
||
|
|
else:
|
||
|
|
values = _('the value of the global information "{0}"').format(
|
||
|
|
calculation["information"]
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
values = _("the value of the {0}").format(calculation["type"])
|
||
|
|
return values
|
||
|
|
|
||
|
|
def _calculation_jinja_to_string(self, child, calculation, attribute_type):
|
||
|
|
if calculation["value"] is not True:
|
||
|
|
values = calculation["value"]
|
||
|
|
else:
|
||
|
|
values = _("depends on a calculation")
|
||
|
|
warning = _(
|
||
|
|
'"{0}" is a calculation for {1} but has no description in {2}'
|
||
|
|
).format(
|
||
|
|
attribute_type,
|
||
|
|
self.doc_path(child.path(uncalculated=True)),
|
||
|
|
display_xmlfiles(child.information.get("ymlfiles")),
|
||
|
|
)
|
||
|
|
warn(
|
||
|
|
warning,
|
||
|
|
RougailWarning,
|
||
|
|
)
|
||
|
|
return values
|
||
|
|
|
||
|
|
def _calculation_variable_to_string(self, child, calculation, attribute_type):
|
||
|
|
if attribute_type in PROPERTY_ATTRIBUTE:
|
||
|
|
func = self._calculation_variable_to_string_known_property
|
||
|
|
else:
|
||
|
|
func = self._calculation_variable_to_string_not_properties
|
||
|
|
return func(child, calculation, attribute_type)
|
||
|
|
|
||
|
|
def _calculation_variable_to_string_known_property(self, child, informations, prop):
|
||
|
|
condition = informations["value"]
|
||
|
|
variable_path = condition["path"]
|
||
|
|
values = []
|
||
|
|
option = self.true_config.option(variable_path)
|
||
|
|
if option.isdynamic():
|
||
|
|
variables = self._get_annotation_variable(
|
||
|
|
option, condition.get("identifiers")
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
option = self.true_config.option(variable_path)
|
||
|
|
try:
|
||
|
|
is_inaccessible = self.is_inaccessible_user_data(option)
|
||
|
|
except AttributeError as err:
|
||
|
|
if err.code != "option-not-found":
|
||
|
|
raise err from err
|
||
|
|
is_inaccessible = True
|
||
|
|
if is_inaccessible:
|
||
|
|
variables = [[None, None, None]]
|
||
|
|
else:
|
||
|
|
description = self._convert_description(
|
||
|
|
option.description(uncalculated=True),
|
||
|
|
"description",
|
||
|
|
its_a_path=False,
|
||
|
|
)
|
||
|
|
variables = [[variable_path, description, None]]
|
||
|
|
for cpath, description, identifiers in variables:
|
||
|
|
if not cpath:
|
||
|
|
# we cannot access to this variable, so try with permissive
|
||
|
|
if condition["type"] == "transitive":
|
||
|
|
value = None
|
||
|
|
else:
|
||
|
|
value = condition["value"]
|
||
|
|
option = self.true_config.forcepermissive.option(variable_path)
|
||
|
|
try:
|
||
|
|
variable_value = self._get_unmodified_default_value(option)
|
||
|
|
except PropertiesOptionError as err:
|
||
|
|
if informations["propertyerror"] is True:
|
||
|
|
raise err from err
|
||
|
|
if informations["propertyerror"] == "transitive":
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
except VariableCalculationDependencyError:
|
||
|
|
values.append(_("depends on an undocumented variable"))
|
||
|
|
continue
|
||
|
|
except AttributeError as err:
|
||
|
|
return informations.get("default", False)
|
||
|
|
if condition["type"] == "transitive":
|
||
|
|
return True
|
||
|
|
if prop in HIDDEN_PROPERTIES:
|
||
|
|
condition_type = condition["condition"]
|
||
|
|
if (
|
||
|
|
condition_type == "when"
|
||
|
|
and value == variable_value
|
||
|
|
or condition_type == "when_not"
|
||
|
|
and value != variable_value
|
||
|
|
):
|
||
|
|
# always "prop"
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
if condition["type"] == "transitive":
|
||
|
|
submsg = _('is "{0}"').format(prop)
|
||
|
|
else:
|
||
|
|
condition_type = condition["condition"]
|
||
|
|
conditions = []
|
||
|
|
if not informations["propertyerror"]:
|
||
|
|
conditions.append(_("is accessible"))
|
||
|
|
if informations["optional"]:
|
||
|
|
conditions.append(_("is defined"))
|
||
|
|
value = condition["value"]
|
||
|
|
if not isinstance(value, str):
|
||
|
|
value = dump(value)
|
||
|
|
if condition_type == "when_not":
|
||
|
|
conditions.append(_('hasn\'t the value "{0}"').format(value))
|
||
|
|
else:
|
||
|
|
conditions.append(_('has the value "{0}"').format(value))
|
||
|
|
submsg = display_list(conditions, sort=False)
|
||
|
|
if informations["propertyerror"] == "transitive":
|
||
|
|
submsg = display_list(
|
||
|
|
[_("is {0}").format(prop), submsg], separator="or", sort=False
|
||
|
|
)
|
||
|
|
msg = _('when the variable "{{0}}" {0}').format(submsg)
|
||
|
|
path_obj = {
|
||
|
|
"path": self.doc_path(variable_path),
|
||
|
|
}
|
||
|
|
if identifiers:
|
||
|
|
path_obj["identifiers"] = identifiers
|
||
|
|
|
||
|
|
values.append(
|
||
|
|
{
|
||
|
|
"message": msg,
|
||
|
|
"path": path_obj,
|
||
|
|
"description": description,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
if len(values) == 1:
|
||
|
|
return values[0]
|
||
|
|
return values
|
||
|
|
|
||
|
|
def _calculation_variable_to_string_not_properties(
|
||
|
|
self, child, calculation, attribute_type
|
||
|
|
):
|
||
|
|
variable = self.true_config.unrestraint.option(calculation["value"]["path"])
|
||
|
|
if calculation["optional"]:
|
||
|
|
try:
|
||
|
|
variable.get()
|
||
|
|
except AttributeError:
|
||
|
|
return None
|
||
|
|
true_msg = _('the value of the variable "{0}" if it is defined')
|
||
|
|
else:
|
||
|
|
true_msg = _('the value of the variable "{0}"')
|
||
|
|
if not variable.isdynamic():
|
||
|
|
func = self._calculation_normal_variable_to_string_not_properties
|
||
|
|
else:
|
||
|
|
func = self._calculation_dynamic_variable_to_string_not_properties
|
||
|
|
return func(variable, calculation["value"], true_msg)
|
||
|
|
|
||
|
|
def _calculation_normal_variable_to_string_not_properties(
|
||
|
|
self, child, obj, true_msg
|
||
|
|
):
|
||
|
|
isfollower = child.isfollower()
|
||
|
|
if not isfollower and self.is_inaccessible_user_data(child):
|
||
|
|
uncalculated = child.value.default(uncalculated=True)
|
||
|
|
if uncalculated and not isinstance(uncalculated, Calculation):
|
||
|
|
if isinstance(uncalculated, list):
|
||
|
|
return {
|
||
|
|
"submessage": _("(from an undocumented variable)"),
|
||
|
|
"values": uncalculated,
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
if not isinstance(uncalculated, str):
|
||
|
|
uncalculated = dump(uncalculated)
|
||
|
|
true_msg = _("{0} (from an undocumented variable)").format(
|
||
|
|
uncalculated
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
true_msg = _("the value of an undocumented variable")
|
||
|
|
try:
|
||
|
|
description = self._convert_description(
|
||
|
|
child.description(uncalculated=True), "description", its_a_path=False
|
||
|
|
)
|
||
|
|
except AttributeError:
|
||
|
|
description = path
|
||
|
|
return {
|
||
|
|
"message": true_msg,
|
||
|
|
"path": obj,
|
||
|
|
"description": description,
|
||
|
|
}
|
||
|
|
|
||
|
|
def _calculation_dynamic_variable_to_string_not_properties(
|
||
|
|
self, child, obj, true_msg
|
||
|
|
):
|
||
|
|
values = []
|
||
|
|
for path, description, identifiers in self._get_annotation_variable(
|
||
|
|
child, obj.get("identifiers")
|
||
|
|
):
|
||
|
|
cpath = calc_path(path, identifiers=identifiers)
|
||
|
|
variable = self.true_config.option(cpath)
|
||
|
|
path_obj = {
|
||
|
|
"path": self.doc_path(path),
|
||
|
|
}
|
||
|
|
if identifiers:
|
||
|
|
path_obj["identifiers"] = identifiers
|
||
|
|
values.append(
|
||
|
|
self._calculation_normal_variable_to_string_not_properties(
|
||
|
|
variable, path_obj, true_msg
|
||
|
|
)
|
||
|
|
)
|
||
|
|
return values
|
||
|
|
|
||
|
|
def _calculation_identifier_to_string(self, child, calculation, attribute_type):
|
||
|
|
if attribute_type in PROPERTY_ATTRIBUTE:
|
||
|
|
if calculation["condition"] == "when":
|
||
|
|
return _('when the identifier is "{0}"').format(calculation["value"])
|
||
|
|
if calculation["condition"] == "when_not":
|
||
|
|
return _('when the identifier is not "{0}"').format(
|
||
|
|
calculation["value"]
|
||
|
|
)
|
||
|
|
return True
|
||
|
|
else:
|
||
|
|
return _("the value of the identifier")
|
||
|
|
|
||
|
|
def doc_path(self, path):
|
||
|
|
if self.document_a_type:
|
||
|
|
if not "." in path:
|
||
|
|
return None
|
||
|
|
return path.split(".", 1)[-1]
|
||
|
|
return path
|
||
|
|
|
||
|
|
def _get_annotation_variable(self, child, ori_identifiers):
|
||
|
|
path = child.path(uncalculated=True)
|
||
|
|
if ori_identifiers:
|
||
|
|
if None in ori_identifiers:
|
||
|
|
all_identifiers = self.true_config.option(
|
||
|
|
calc_path(path, identifiers=ori_identifiers)
|
||
|
|
).identifiers()
|
||
|
|
else:
|
||
|
|
all_identifiers = [ori_identifiers]
|
||
|
|
else:
|
||
|
|
all_identifiers = child.identifiers()
|
||
|
|
for identifiers in all_identifiers:
|
||
|
|
cpath = calc_path(path, identifiers=identifiers)
|
||
|
|
description = self._convert_description(
|
||
|
|
child.description(uncalculated=True), "description", its_a_path=False
|
||
|
|
)
|
||
|
|
if child.isdynamic():
|
||
|
|
yield path, description, identifiers.copy()
|
||
|
|
else:
|
||
|
|
yield path, description, None
|
||
|
|
|
||
|
|
|
||
|
|
class Collect(_ToString):
|
||
|
|
def collect_families(self, family) -> dict:
|
||
|
|
informations = {}
|
||
|
|
for child in family.list(uncalculated=True):
|
||
|
|
if child.type(only_self=True) == "symlink" or (
|
||
|
|
not child.isdynamic() and self.is_inaccessible_user_data(child)
|
||
|
|
):
|
||
|
|
# do not document symlink option or inacesssible variable
|
||
|
|
# (dynamic variable could be accessible only in one context)
|
||
|
|
continue
|
||
|
|
if child.isoptiondescription():
|
||
|
|
func = self.collect_family
|
||
|
|
else:
|
||
|
|
func = self.collect_variable
|
||
|
|
func(child, informations)
|
||
|
|
return informations
|
||
|
|
|
||
|
|
def collect_family(self, family, informations: dict) -> None:
|
||
|
|
family_type = self._get_family_type(family)
|
||
|
|
family_informations = self._collect_family(family, family_type)
|
||
|
|
if family_informations is False:
|
||
|
|
return
|
||
|
|
sub_informations = self.collect_families(family)
|
||
|
|
if not sub_informations:
|
||
|
|
# a family without subfamily/subvariable
|
||
|
|
return
|
||
|
|
name = family.name(uncalculated=True)
|
||
|
|
informations[name] = {
|
||
|
|
"type": family_type,
|
||
|
|
"informations": family_informations,
|
||
|
|
"children": sub_informations,
|
||
|
|
}
|
||
|
|
|
||
|
|
def _get_family_type(self, family) -> str:
|
||
|
|
if self.support_namespace and family.group_type() is groups.namespace:
|
||
|
|
return "namespace"
|
||
|
|
if family.isleadership():
|
||
|
|
return "leadership"
|
||
|
|
if family.isdynamic(only_self=True):
|
||
|
|
return "dynamic"
|
||
|
|
return "family"
|
||
|
|
|
||
|
|
def _collect_family(
|
||
|
|
self,
|
||
|
|
family,
|
||
|
|
family_type,
|
||
|
|
*,
|
||
|
|
with_identifier: bool = True,
|
||
|
|
current_identifier_only: bool = False,
|
||
|
|
) -> dict:
|
||
|
|
path = family.path(uncalculated=True)
|
||
|
|
informations = {}
|
||
|
|
if not self._collect(family, informations, family_type=family_type):
|
||
|
|
return False, []
|
||
|
|
if family_type == "leadership":
|
||
|
|
informations.setdefault("help", []).append(
|
||
|
|
_("This family contains lists of variable blocks")
|
||
|
|
)
|
||
|
|
elif family_type == "dynamic":
|
||
|
|
informations.setdefault("help", []).append(
|
||
|
|
_("This family builds families dynamically")
|
||
|
|
)
|
||
|
|
if with_identifier:
|
||
|
|
identifier = self._to_string(family, "dynamic", do_not_raise=True)
|
||
|
|
if identifier is None:
|
||
|
|
identifier = family.identifiers(only_self=True)
|
||
|
|
if not isinstance(identifier, list):
|
||
|
|
identifier = [identifier]
|
||
|
|
informations["identifier"] = identifier
|
||
|
|
elif family_type == "namespace":
|
||
|
|
informations.setdefault("help", []).append(_("This family is a namespace"))
|
||
|
|
return informations
|
||
|
|
|
||
|
|
def collect_variable(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
informations: dict,
|
||
|
|
*,
|
||
|
|
only_one=False,
|
||
|
|
) -> Optional[dict]:
|
||
|
|
name = child.name(uncalculated=True)
|
||
|
|
sub_informations = {}
|
||
|
|
if not self._collect_variable(
|
||
|
|
child,
|
||
|
|
sub_informations,
|
||
|
|
):
|
||
|
|
return None
|
||
|
|
informations[name] = sub_informations
|
||
|
|
if child.isleader():
|
||
|
|
# if not self.default_values:
|
||
|
|
# child.value.set(sub_informations["gen_examples"][0])
|
||
|
|
return sub_informations
|
||
|
|
return None
|
||
|
|
|
||
|
|
def _collect_variable(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
informations: dict,
|
||
|
|
):
|
||
|
|
if not self._collect(child, informations):
|
||
|
|
return False
|
||
|
|
informations["type"] = "variable"
|
||
|
|
default = self._set_default(
|
||
|
|
child,
|
||
|
|
informations,
|
||
|
|
)
|
||
|
|
self._parse_type(
|
||
|
|
child,
|
||
|
|
informations,
|
||
|
|
)
|
||
|
|
if child.ismulti():
|
||
|
|
multi = not child.isfollower() or child.issubmulti()
|
||
|
|
else:
|
||
|
|
multi = False
|
||
|
|
if multi:
|
||
|
|
informations["multiple"] = True
|
||
|
|
examples = child.information.get("examples", None)
|
||
|
|
if examples is None:
|
||
|
|
examples = child.information.get("test", None)
|
||
|
|
if examples is not None:
|
||
|
|
if len(examples) == 1:
|
||
|
|
name = _("Example")
|
||
|
|
values = examples[0]
|
||
|
|
else:
|
||
|
|
name = _("Examples")
|
||
|
|
values = list(examples)
|
||
|
|
informations["examples"] = {"name": name, "values": values}
|
||
|
|
tags = child.information.get("tags", None)
|
||
|
|
if tags:
|
||
|
|
if len(tags) == 1:
|
||
|
|
name = _("Tag")
|
||
|
|
values = tags[0]
|
||
|
|
else:
|
||
|
|
name = _("Tags")
|
||
|
|
values = list(tags)
|
||
|
|
informations["tags"] = {
|
||
|
|
"name": name,
|
||
|
|
"values": values,
|
||
|
|
}
|
||
|
|
alternative_name = child.information.get("alternative_name", None)
|
||
|
|
if alternative_name:
|
||
|
|
informations["alternative_name"] = alternative_name
|
||
|
|
return True
|
||
|
|
|
||
|
|
def _collect(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
informations: dict,
|
||
|
|
*,
|
||
|
|
family_type: str = None,
|
||
|
|
):
|
||
|
|
display, properties = self._get_properties(child, informations)
|
||
|
|
if not display:
|
||
|
|
return False
|
||
|
|
informations["path"] = self.doc_path(child.path(uncalculated=True))
|
||
|
|
informations["name"] = child.name(uncalculated=True)
|
||
|
|
description = self._get_description(child, family_type)
|
||
|
|
if description is not None:
|
||
|
|
informations["description"] = description
|
||
|
|
help_ = child.information.get("help", None)
|
||
|
|
if help_:
|
||
|
|
informations["help"] = [to_phrase(help_)]
|
||
|
|
if "properties" in informations:
|
||
|
|
informations["properties"].extend(properties)
|
||
|
|
else:
|
||
|
|
informations["properties"] = properties
|
||
|
|
properties = child.property.get(uncalculated=True)
|
||
|
|
for mode in self.modes_level:
|
||
|
|
if mode not in properties:
|
||
|
|
continue
|
||
|
|
informations["mode"] = mode
|
||
|
|
break
|
||
|
|
if child.isdynamic():
|
||
|
|
informations["identifiers"] = []
|
||
|
|
path = child.path(uncalculated=True)
|
||
|
|
if child.has_identifiers():
|
||
|
|
identifiers = child.identifiers()
|
||
|
|
else:
|
||
|
|
identifiers = [child.identifiers()]
|
||
|
|
for identifier in identifiers:
|
||
|
|
cpath = calc_path(path, identifiers=identifier)
|
||
|
|
child_identifier = self.true_config.option(cpath)
|
||
|
|
if not self.is_inaccessible_user_data(child_identifier):
|
||
|
|
informations["identifiers"].append(identifier)
|
||
|
|
if not informations["identifiers"]:
|
||
|
|
informations["identifiers"] = [["example"]]
|
||
|
|
return True
|
||
|
|
|
||
|
|
def _parse_type(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
informations,
|
||
|
|
):
|
||
|
|
variable_type = child.information.get("type")
|
||
|
|
doc_type = self.convert_option.get(variable_type, {"params": {}})
|
||
|
|
informations["variable_type"] = doc_type.get("msg", variable_type)
|
||
|
|
# extra parameters for types
|
||
|
|
option = child.get()
|
||
|
|
validators = []
|
||
|
|
if "params" in doc_type:
|
||
|
|
for param, msg in doc_type["params"].items():
|
||
|
|
value = option.impl_get_extra(f"_{param}")
|
||
|
|
if value is None:
|
||
|
|
value = option.impl_get_extra(param)
|
||
|
|
if value is not None and value is not False:
|
||
|
|
if isinstance(value, set):
|
||
|
|
value = list(value)
|
||
|
|
if isinstance(value, list):
|
||
|
|
value = display_list(value, add_quote=True)
|
||
|
|
if "doc" in msg:
|
||
|
|
validators.append(msg["doc"].format(value))
|
||
|
|
else:
|
||
|
|
validators.append(msg["description"].format(value))
|
||
|
|
|
||
|
|
# get validation information from annotator
|
||
|
|
for name in child.information.list():
|
||
|
|
if not name.startswith("validators_calculation"):
|
||
|
|
continue
|
||
|
|
validators.extend(
|
||
|
|
self._to_string(
|
||
|
|
child,
|
||
|
|
"validators",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
break
|
||
|
|
if variable_type == "regexp":
|
||
|
|
validators.append(
|
||
|
|
_('text based with regular expressions "{0}"').format(child.pattern())
|
||
|
|
)
|
||
|
|
if validators:
|
||
|
|
if len(validators) == 1:
|
||
|
|
key = _("Validator")
|
||
|
|
validators = validators[0]
|
||
|
|
else:
|
||
|
|
key = _("Validators")
|
||
|
|
informations["validators"] = {"name": key, "values": validators}
|
||
|
|
if variable_type == "choice":
|
||
|
|
choices = self._to_string(child, "choice", do_not_raise=True)
|
||
|
|
if choices is None:
|
||
|
|
choices = child.value.list(uncalculated=True)
|
||
|
|
for idx, val in enumerate(choices):
|
||
|
|
if isinstance(val, Calculation):
|
||
|
|
choices[idx] = self._to_string(child, "choice", f"_{idx}")
|
||
|
|
informations["choices"] = {"name": _("Choices"), "values": choices}
|
||
|
|
|
||
|
|
def _get_properties(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
child_informations,
|
||
|
|
):
|
||
|
|
informations = []
|
||
|
|
properties = child.property.get(uncalculated=True)
|
||
|
|
if "not_for_commandline" in properties:
|
||
|
|
child_informations["not_for_commandline"] = True
|
||
|
|
for prop, translated_prop in self.property_to_string:
|
||
|
|
annotation = False
|
||
|
|
if child.information.get(f"{prop}_calculation", False):
|
||
|
|
annotation = self._to_string(child, prop)
|
||
|
|
if annotation is None and prop in HIDDEN_PROPERTIES:
|
||
|
|
return False, []
|
||
|
|
if annotation is True and prop in DISABLED_PROPERTIES:
|
||
|
|
return False, []
|
||
|
|
if not annotation:
|
||
|
|
continue
|
||
|
|
elif prop not in properties:
|
||
|
|
# this property is not in the variable so, do not comment it
|
||
|
|
continue
|
||
|
|
elif prop in HIDDEN_PROPERTIES:
|
||
|
|
return False, []
|
||
|
|
prop_obj = {
|
||
|
|
"type": "property",
|
||
|
|
"name": translated_prop,
|
||
|
|
"ori_name": prop,
|
||
|
|
"access_control": prop in HIDDEN_PROPERTIES,
|
||
|
|
}
|
||
|
|
if annotation:
|
||
|
|
prop_obj["annotation"] = annotation
|
||
|
|
informations.append(prop_obj)
|
||
|
|
return True, informations
|
||
|
|
|
||
|
|
def _set_default(
|
||
|
|
self,
|
||
|
|
child,
|
||
|
|
informations,
|
||
|
|
):
|
||
|
|
default = self._to_string(child, "default", do_not_raise=True)
|
||
|
|
if default is None and child.information.get("default_value_makes_sense", True):
|
||
|
|
default_ = child.value.default(uncalculated=True)
|
||
|
|
if not isinstance(default_, Calculation):
|
||
|
|
default = default_
|
||
|
|
if default == []:
|
||
|
|
default = None
|
||
|
|
if default is not None:
|
||
|
|
informations["default"] = {"name": _("Default"), "values": default}
|
||
|
|
|
||
|
|
def _get_description(self, child, family_type):
|
||
|
|
if child.information.get("forced_description", False):
|
||
|
|
if (
|
||
|
|
child.isoptiondescription()
|
||
|
|
or not child.isfollower()
|
||
|
|
or not child.index()
|
||
|
|
):
|
||
|
|
# all variables must have description but display error only for first slave
|
||
|
|
msg = _('No attribute "description" for "{0}" in {1}').format(
|
||
|
|
child.path(uncalculated=True),
|
||
|
|
display_xmlfiles(child.information.get("ymlfiles")),
|
||
|
|
)
|
||
|
|
warn(
|
||
|
|
msg,
|
||
|
|
RougailWarning,
|
||
|
|
)
|
||
|
|
if family_type is not None:
|
||
|
|
# it's a vaariable
|
||
|
|
return self._convert_description(
|
||
|
|
child.description(uncalculated=True), "family", its_a_path=True
|
||
|
|
)
|
||
|
|
return None
|
||
|
|
var_type = "variable" if family_type is None else "family"
|
||
|
|
return self._convert_description(
|
||
|
|
child.description(uncalculated=True), var_type, its_a_path=False
|
||
|
|
)
|
||
|
|
|
||
|
|
def _convert_description(self, description, type_, its_a_path=False):
|
||
|
|
if not its_a_path:
|
||
|
|
description = to_phrase(description, type_)
|
||
|
|
return description
|
||
|
|
|
||
|
|
def is_inaccessible_user_data(self, child, *, only_disabled=False):
|
||
|
|
"""If family is not accessible in read_write mode (to load user_data),
|
||
|
|
do not comment this family
|
||
|
|
"""
|
||
|
|
properties = child.property.get(uncalculated=True)
|
||
|
|
if only_disabled:
|
||
|
|
hidden_properties = DISABLED_PROPERTIES
|
||
|
|
else:
|
||
|
|
hidden_properties = HIDDEN_PROPERTIES
|
||
|
|
for hidden_property in hidden_properties:
|
||
|
|
if hidden_property in properties:
|
||
|
|
return True
|
||
|
|
|
||
|
|
calculation = child.information.get(f"{hidden_property}_calculation", None)
|
||
|
|
if calculation:
|
||
|
|
calculation_type = calculation.get("type")
|
||
|
|
if (
|
||
|
|
calculation_type == "variable"
|
||
|
|
and calculation["value"]["type"] == "condition"
|
||
|
|
):
|
||
|
|
condition = calculation["value"]
|
||
|
|
variable = self.true_config.forcepermissive.option(
|
||
|
|
condition["path"]
|
||
|
|
)
|
||
|
|
try:
|
||
|
|
variable.value.get()
|
||
|
|
except AttributeError:
|
||
|
|
variable = None
|
||
|
|
if variable and self.is_inaccessible_user_data(
|
||
|
|
variable, only_disabled=only_disabled
|
||
|
|
):
|
||
|
|
try:
|
||
|
|
variable_value = self._get_unmodified_default_value(
|
||
|
|
variable
|
||
|
|
)
|
||
|
|
except VariableCalculationDependencyError:
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
condition_type = condition["condition"]
|
||
|
|
value = condition["value"]
|
||
|
|
if self.calc_condition_when(
|
||
|
|
condition_type, value, variable_value
|
||
|
|
):
|
||
|
|
return True
|
||
|
|
elif calculation_type == "identifier":
|
||
|
|
if self.calc_condition_when(
|
||
|
|
calculation["condition"],
|
||
|
|
calculation["value"],
|
||
|
|
child.identifiers()[-1],
|
||
|
|
):
|
||
|
|
return True
|
||
|
|
if not child.isoptiondescription():
|
||
|
|
for hidden_property in self.disabled_modes:
|
||
|
|
if hidden_property in properties:
|
||
|
|
return True
|
||
|
|
return False
|
||
|
|
|
||
|
|
def calc_condition_when(self, condition, value, current_value):
|
||
|
|
return (condition == "when" and value == current_value) or (
|
||
|
|
condition == "when_not" and value != current_value
|
||
|
|
)
|
||
|
|
|
||
|
|
def _get_unmodified_default_value(self, child):
|
||
|
|
calculation = child.information.get(f"default_calculation", None)
|
||
|
|
if not calculation:
|
||
|
|
return child.value.get()
|
||
|
|
if calculation["type"] == "variable":
|
||
|
|
variable = self.true_config.forcepermissive.option(
|
||
|
|
calculation["value"]["path"]
|
||
|
|
)
|
||
|
|
if variable and self.is_inaccessible_user_data(variable):
|
||
|
|
return self._get_unmodified_default_value(variable)
|
||
|
|
raise VariableCalculationDependencyError()
|