862 lines
34 KiB
Python
862 lines
34 KiB
Python
"""
|
|
Silique (https://www.silique.fr)
|
|
Copyright (C) 2024-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 warnings import warn
|
|
from typing import Optional
|
|
from itertools import chain
|
|
from re import compile
|
|
|
|
from tiramisu import Calculation, groups
|
|
from tiramisu.error import ConfigError, display_list, PropertiesOptionError
|
|
from rougail.tiramisu import display_xmlfiles, normalize_family
|
|
from rougail.utils import undefined, get_properties_to_string, PROPERTY_ATTRIBUTE
|
|
from rougail.error import VariableCalculationDependencyError, RougailWarning
|
|
|
|
from .config import OutPuts
|
|
from .i18n import _
|
|
from .utils import DocTypes, dump, to_phrase, calc_path
|
|
from .example import Examples
|
|
from .changelog import Changelog
|
|
|
|
|
|
HIDDEN_PROPERTIES = [
|
|
"hidden",
|
|
"disabled",
|
|
]
|
|
|
|
|
|
class RougailOutputDoc(Examples, Changelog):
|
|
"""Rougail Output Doc:
|
|
Generate documentation from rougail description files
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: "Config",
|
|
*,
|
|
rougailconfig: "RougailConfig" = None,
|
|
**kwarg,
|
|
):
|
|
# Import here to avoid circular import
|
|
from rougail.tiramisu import CONVERT_OPTION
|
|
|
|
self.convert_option = CONVERT_OPTION
|
|
if rougailconfig is None:
|
|
from rougail import RougailConfig
|
|
|
|
rougailconfig = RougailConfig
|
|
if rougailconfig["step.output"] != "doc":
|
|
rougailconfig["step.output"] = "doc"
|
|
if rougailconfig["step.output"] != "doc":
|
|
raise Exception("doc is not set as step.output")
|
|
outputs = OutPuts().get()
|
|
output_format = rougailconfig["doc.output_format"]
|
|
if output_format not in outputs:
|
|
raise Exception(
|
|
f'cannot find output "{output_format}", available outputs: {list(outputs)}'
|
|
)
|
|
self.conf = config
|
|
self.modes_level = rougailconfig["modes_level"]
|
|
if self.modes_level:
|
|
self.disabled_modes = rougailconfig["doc.disabled_modes"]
|
|
if self.disabled_modes:
|
|
self.conf.property.setdefault(
|
|
frozenset(self.disabled_modes), "read_write", "append"
|
|
)
|
|
else:
|
|
self.disabled_modes = []
|
|
self.conf.property.read_write()
|
|
# self.conf.property.remove("cache")
|
|
self.output_format = output_format
|
|
self.level = rougailconfig["doc.title_level"]
|
|
self.contents = rougailconfig["doc.contents"]
|
|
self.example = "example" in self.contents
|
|
if "variables" in self.contents:
|
|
self.with_family = not rougailconfig["doc.without_family"]
|
|
else:
|
|
self.with_family = True
|
|
if "changelog" in self.contents:
|
|
self.previous_json_file = rougailconfig["doc.previous_json_file"]
|
|
if output_format == 'console':
|
|
force_true_color_terminal = rougailconfig["doc.force_true_color_terminal"]
|
|
else:
|
|
force_true_color_terminal = None
|
|
self.formater = outputs[output_format](self.with_family, force_true_color_terminal=force_true_color_terminal)
|
|
self.informations = None
|
|
try:
|
|
groups.namespace
|
|
self.support_namespace = True
|
|
except AttributeError:
|
|
self.support_namespace = False
|
|
self.property_to_string = get_properties_to_string()
|
|
super().__init__()
|
|
|
|
def run(self) -> str:
|
|
"""Print documentation in stdout"""
|
|
self.load()
|
|
return_string = ""
|
|
if "variables" in self.contents:
|
|
return_string += self.formater.run(self.informations, self.level)
|
|
if "example" in self.contents:
|
|
return_string += self.gen_doc_examples()
|
|
if "changelog" in self.contents:
|
|
return_string += self.gen_doc_changelog()
|
|
return True, return_string
|
|
|
|
def print(self) -> None:
|
|
ret, data = self.run()
|
|
print(data)
|
|
return ret
|
|
|
|
def load(self):
|
|
self.dynamic_paths = {}
|
|
config = self.conf.unrestraint
|
|
self.populate_dynamics(config=config)
|
|
informations = self.parse_families(config)
|
|
if informations is None:
|
|
informations = {}
|
|
self.informations = informations
|
|
|
|
def populate_dynamics(self, *, config=None, reload=False):
|
|
if config is None:
|
|
config = self.conf.unrestraint
|
|
self._populate_dynamics(config, reload)
|
|
|
|
def _populate_dynamics(self, family, reload, uncalculated=False) -> None:
|
|
def populate(child, uncalculated):
|
|
if child.isoptiondescription():
|
|
type_ = "family"
|
|
else:
|
|
type_ = "variable"
|
|
if child.isdynamic():
|
|
self.populate_dynamic(child, type_, reload, uncalculated)
|
|
if child.isoptiondescription():
|
|
self._populate_dynamics(child, reload, uncalculated)
|
|
for child in family.list(uncalculated=uncalculated):
|
|
populate(child, uncalculated)
|
|
if not uncalculated:
|
|
for child in family.list(uncalculated=True):
|
|
if child.isdynamic() and child.path(uncalculated=True) not in self.dynamic_paths:
|
|
populate(family, uncalculated=True)
|
|
|
|
def populate_dynamic(self, obj, type_, reload, uncalculated) -> None:
|
|
path = obj.path(uncalculated=True)
|
|
if path not in self.dynamic_paths:
|
|
new_name = True
|
|
description = obj.description(uncalculated=True)
|
|
name = obj.name(uncalculated=True)
|
|
self.dynamic_paths[path] = {
|
|
"names": [],
|
|
"identifiers": [],
|
|
"path": path,
|
|
}
|
|
if not obj.information.get("forced_description", False):
|
|
self.dynamic_paths[path]["description"] = self._convert_description(
|
|
description, obj, type_, its_a_path=False
|
|
)
|
|
elif obj.isoptiondescription():
|
|
self.dynamic_paths[path]["description"] = self._convert_description(
|
|
description, obj, type_, its_a_path=True
|
|
)
|
|
if uncalculated:
|
|
return
|
|
dynamic_obj = self.dynamic_paths[path]
|
|
if reload and obj.identifiers() in dynamic_obj["identifiers"]:
|
|
return
|
|
dynamic_obj["names"].append(obj.name())
|
|
dynamic_obj["identifiers"].append(obj.identifiers())
|
|
|
|
def parse_families(self, family) -> dict:
|
|
informations = {}
|
|
leader = None
|
|
for child in family.list():
|
|
if self.is_inaccessible_user_data(child):
|
|
continue
|
|
if child.type(only_self=True) == "symlink":
|
|
continue
|
|
if not child.isoptiondescription():
|
|
leader = self.parse_variable(child, leader, informations)
|
|
else:
|
|
self.parse_family(child, informations)
|
|
return informations
|
|
|
|
def is_inaccessible_user_data(self, child):
|
|
"""If family is not accessible in read_write mode (to load user_data),
|
|
do not comment this family
|
|
"""
|
|
properties = child.property.get(uncalculated=True)
|
|
for hidden_property in HIDDEN_PROPERTIES:
|
|
if hidden_property in properties:
|
|
return True
|
|
|
|
calculation = child.information.get(f"{hidden_property}_calculation", None)
|
|
if calculation and calculation.get("type") == "variable":
|
|
variable_path, value, condition = calculation["value"]
|
|
variable = self.conf.forcepermissive.option(variable_path)
|
|
try:
|
|
variable.value.get()
|
|
except AttributeError:
|
|
variable = None
|
|
if variable and self.is_inaccessible_user_data(variable):
|
|
try:
|
|
variable_value = self._get_unmodified_default_value(variable)
|
|
except VariableCalculationDependencyError:
|
|
pass
|
|
else:
|
|
if (condition == "when" and value == variable_value) or (condition == "when_not" and value != variable_value):
|
|
return True
|
|
if not child.isoptiondescription():
|
|
for hidden_property in self.disabled_modes:
|
|
if hidden_property in properties:
|
|
return True
|
|
return False
|
|
|
|
def parse_family(self, family, informations: dict, *, force_injection=False) -> None:
|
|
path = family.path(uncalculated=True)
|
|
name = family.name(uncalculated=True)
|
|
sub_informations = self.parse_families(family)
|
|
if not force_injection and not sub_informations:
|
|
return
|
|
# if self.with_family:
|
|
family_informations = self._populate_family(
|
|
family,
|
|
path,
|
|
)
|
|
if family_informations is not False:
|
|
informations[name] = {
|
|
"type": self._get_family_type(family),
|
|
"informations": family_informations,
|
|
"children": sub_informations,
|
|
}
|
|
|
|
def parse_variable(
|
|
self,
|
|
variable,
|
|
leader: dict,
|
|
informations: dict,
|
|
*,
|
|
only_one=False,
|
|
) -> Optional[dict]:
|
|
path = variable.path(uncalculated=True)
|
|
name = variable.name(uncalculated=True)
|
|
potential_leader = None
|
|
if variable.isdynamic():
|
|
# information is already set
|
|
potential_leader = self._parse_variable_dynamic(
|
|
variable, leader, name, path, informations, only_one
|
|
)
|
|
else:
|
|
if variable.isfollower() and variable.index():
|
|
self._parse_variable_follower_with_index(
|
|
variable, leader, name, informations
|
|
)
|
|
else:
|
|
potential_leader = self.parse_variable_normal(
|
|
variable, leader, name, path, informations
|
|
)
|
|
if potential_leader:
|
|
leader = potential_leader
|
|
return leader
|
|
|
|
def parse_variable_normal(
|
|
self, variable, leader, name: str, path: str, informations: dict
|
|
) -> Optional[dict]:
|
|
if variable.isdynamic():
|
|
sub_informations = self.dynamic_paths[path]
|
|
elif variable.isfollower() and path in informations: # variable.index():
|
|
sub_informations = informations[name]
|
|
else:
|
|
sub_informations = {}
|
|
if not self._populate_variable(
|
|
variable,
|
|
sub_informations,
|
|
):
|
|
return None
|
|
if self.example:
|
|
self._add_examples(variable, sub_informations, leader)
|
|
informations[name] = sub_informations
|
|
if variable.isleader():
|
|
return sub_informations
|
|
return None
|
|
|
|
def _parse_variable_follower_with_index(
|
|
self, variable, leader: dict, name: str, informations: dict
|
|
) -> None:
|
|
if not self.example or (variable.index() + 1) > len(leader["example"][-1]):
|
|
return
|
|
informations[name]["example"][-1][variable.index()] = self._get_example(
|
|
variable, informations[name], None
|
|
)
|
|
|
|
def _parse_variable_dynamic(
|
|
self, variable, leader, name, path, informations, only_one
|
|
) -> None:
|
|
# if path not in self.dynamic_paths:
|
|
# self.populate_dynamic(variable, path)
|
|
dynamic_variable = self.dynamic_paths[path]
|
|
if (not only_one or path in informations) and "type" in dynamic_variable:
|
|
if self.example:
|
|
dynamic_variable["example"].append(
|
|
self._get_example(variable, dynamic_variable, leader)
|
|
)
|
|
if variable.isleader():
|
|
return dynamic_variable
|
|
if not only_one:
|
|
return None
|
|
return self.parse_variable_normal(variable, leader, name, path, 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 _populate_family(
|
|
self,
|
|
family,
|
|
path: str,
|
|
) -> dict:
|
|
if family.isdynamic():
|
|
informations = self.dynamic_paths[path]
|
|
else:
|
|
informations = {}
|
|
if not self._populate(family, informations, "family"):
|
|
return False
|
|
if family.isleadership():
|
|
informations.setdefault("help", []).append(
|
|
_("This family contains lists of variable blocks.")
|
|
)
|
|
if family.isdynamic(only_self=True):
|
|
identifiers = self._to_string(family, "dynamic", do_not_raise=True)
|
|
if identifiers is None:
|
|
identifiers = family.identifiers(only_self=True)
|
|
if not isinstance(identifiers, list):
|
|
identifiers = [identifiers]
|
|
informations["identifier"] = identifiers
|
|
informations.setdefault("help", []).append(
|
|
_("This family builds families dynamically.")
|
|
)
|
|
return informations
|
|
|
|
def _populate_variable(
|
|
self,
|
|
variable,
|
|
informations: dict,
|
|
):
|
|
informations["type"] = "variable"
|
|
default = self._get_default(
|
|
variable,
|
|
)
|
|
if default is not None:
|
|
informations["default"] = {"name": _("Default"), "values": default}
|
|
self._parse_type(
|
|
variable,
|
|
informations,
|
|
)
|
|
if not self._populate(variable, informations, "variable"):
|
|
return False
|
|
if variable.ismulti():
|
|
multi = not variable.isfollower() or variable.issubmulti()
|
|
else:
|
|
multi = False
|
|
if multi:
|
|
informations["properties"].append(
|
|
{
|
|
"type": "multiple",
|
|
"name": _("multiple"),
|
|
}
|
|
)
|
|
examples = variable.information.get("examples", None)
|
|
if examples is None:
|
|
examples = variable.information.get("test", None)
|
|
if examples is not None:
|
|
if len(examples) == 1:
|
|
informations["examples"] = {"name": _("Example"), "values": examples[0]}
|
|
else:
|
|
informations["examples"] = {
|
|
"name": _("Examples"),
|
|
"values": list(examples),
|
|
}
|
|
return True
|
|
|
|
def _populate(
|
|
self,
|
|
child,
|
|
informations: dict,
|
|
type_: str,
|
|
):
|
|
need_disabled, properties = self._parse_properties(child)
|
|
if not need_disabled:
|
|
return False
|
|
name = child.name(uncalculated=True)
|
|
if child.information.get("forced_description", False):
|
|
if (
|
|
not child.isoptiondescription()
|
|
or not self.support_namespace
|
|
or child.group_type() is not groups.namespace
|
|
):
|
|
if (
|
|
child.isoptiondescription()
|
|
or not child.isfollower()
|
|
or not child.index()
|
|
):
|
|
warning = _('No attribute "description" for "{0}" in {1}').format(
|
|
child.path(uncalculated=True),
|
|
display_xmlfiles(child.information.get("ymlfiles")),
|
|
)
|
|
warn(
|
|
warning,
|
|
RougailWarning,
|
|
)
|
|
if child.isoptiondescription():
|
|
description = self._convert_description(
|
|
child.description(uncalculated=True), child, type_, its_a_path=True
|
|
)
|
|
else:
|
|
description = None
|
|
else:
|
|
description = self._convert_description(
|
|
child.description(uncalculated=True), child, type_, its_a_path=False
|
|
)
|
|
if not child.isdynamic():
|
|
informations["path"] = child.path(uncalculated=True)
|
|
informations["names"] = [child.name()]
|
|
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
|
|
return True
|
|
|
|
def _convert_description(self, description, obj, type_, its_a_path=False):
|
|
if not its_a_path:
|
|
description = to_phrase(description, type_)
|
|
# if "{{ identifier }}" in description:
|
|
# description = {"description": description,
|
|
# "identifier": obj.identifiers()[-1],
|
|
# }
|
|
return description
|
|
|
|
def _add_examples(self, variable, informations: dict, leader) -> None:
|
|
if not variable.index():
|
|
example = self._get_example(variable, informations, leader)
|
|
informations["example"] = [example]
|
|
informations["mandatory_without_value"] = "mandatory" in variable.property.get(
|
|
uncalculated=True
|
|
) and (
|
|
not variable.information.get("default_value_makes_sense", True)
|
|
or variable.value.get(uncalculated=True) in [None, []]
|
|
)
|
|
|
|
def _get_example(self, variable, informations: dict, leader):
|
|
example = informations.get("examples", {}).get("values")
|
|
if example is not None:
|
|
if isinstance(example, tuple):
|
|
example = list(example)
|
|
for prop in informations["properties"]:
|
|
if prop["type"] == "multiple":
|
|
if not isinstance(example, list):
|
|
example = [example]
|
|
break
|
|
else:
|
|
if isinstance(example, list):
|
|
index = variable.index()
|
|
if index is None or len(example) - 1 >= index:
|
|
index = 0
|
|
example = example[index]
|
|
else:
|
|
if variable.information.get("fake_default", False):
|
|
default = None
|
|
else:
|
|
try:
|
|
default = variable.value.get()
|
|
except ConfigError:
|
|
default = None
|
|
if default not in [None, []]:
|
|
example = default
|
|
else:
|
|
example = self.get_type_default_value(
|
|
variable, informations["properties"]
|
|
)
|
|
if leader is not None and variable.isfollower():
|
|
example = [example] + [undefined] * (len(leader["example"][-1]) - 1)
|
|
return example
|
|
|
|
def get_type_default_value(self, variable, properties):
|
|
example = self.convert_option.get(variable.information.get("type"), {}).get(
|
|
"example", None
|
|
)
|
|
if example is None:
|
|
example = "xxx"
|
|
for prop in properties:
|
|
if prop["type"] == "multiple":
|
|
multi = True
|
|
break
|
|
else:
|
|
multi = False
|
|
if multi:
|
|
example = [example]
|
|
return example
|
|
|
|
def _parse_type(
|
|
self,
|
|
child,
|
|
informations,
|
|
):
|
|
variable_type = child.information.get("type")
|
|
doc_type = DocTypes.get(variable_type, {"params": {}})
|
|
informations["properties"] = [
|
|
{
|
|
"type": "type",
|
|
"name": doc_type.get("msg", variable_type),
|
|
}
|
|
]
|
|
# extra parameters for types
|
|
option = child.get()
|
|
validators = []
|
|
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)
|
|
validators.append(msg.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 child.information.get("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 child.information.get("type") == "choice":
|
|
choices = self._to_string(child, "choice", do_not_raise=True)
|
|
if choices is None:
|
|
choices = child.value.list()
|
|
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 _parse_properties(
|
|
self,
|
|
child,
|
|
):
|
|
informations = []
|
|
properties = child.property.get(uncalculated=True)
|
|
for mode in self.modes_level:
|
|
if mode not in properties:
|
|
continue
|
|
informations.append(
|
|
{
|
|
"type": "mode",
|
|
"name": mode,
|
|
}
|
|
)
|
|
break
|
|
for prop, translated_prop in self.property_to_string:
|
|
if prop in properties:
|
|
prop_obj = {
|
|
"type": "property",
|
|
"name": translated_prop,
|
|
}
|
|
elif child.information.get(f"{prop}_calculation", False):
|
|
annotation = self._to_string(child, prop)
|
|
if annotation is None or isinstance(annotation, bool):
|
|
if annotation is None and prop in HIDDEN_PROPERTIES:
|
|
return False, {}
|
|
if not annotation:
|
|
continue
|
|
prop_obj = {
|
|
"type": "property",
|
|
"name": translated_prop,
|
|
}
|
|
else:
|
|
prop_obj = {
|
|
"type": "property",
|
|
"name": translated_prop,
|
|
"annotation": annotation,
|
|
}
|
|
else:
|
|
# this property is not in the variable so, do not comment it
|
|
continue
|
|
informations.append(prop_obj)
|
|
return True, informations
|
|
|
|
def _get_default(
|
|
self,
|
|
variable,
|
|
):
|
|
default = self._to_string(variable, "default", do_not_raise=True)
|
|
if default is not None:
|
|
if default == []:
|
|
default = None
|
|
return default
|
|
if variable.information.get("default_value_makes_sense", True):
|
|
default_ = variable.value.get(uncalculated=True)
|
|
if not isinstance(default_, Calculation):
|
|
default = default_
|
|
if default == []:
|
|
default = None
|
|
return default
|
|
|
|
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 Exception(
|
|
f'cannot find "{prop}_calculation" information, '
|
|
"do you have declare doc has a plugins?"
|
|
)
|
|
if isinstance(calculation, list):
|
|
values = []
|
|
for cal in calculation:
|
|
value = self._calculation_to_string(child, cal, prop, inside_list=True)
|
|
if value is not None:
|
|
values.append(value)
|
|
return values
|
|
return self._calculation_to_string(child, calculation, prop)
|
|
|
|
def _calculation_to_string(self, child, calculation, prop, inside_list=False):
|
|
if "description" in calculation:
|
|
values = calculation["description"]
|
|
if not values.endswith("."):
|
|
values += "."
|
|
return values
|
|
if "type" not in calculation:
|
|
return calculation["value"]
|
|
if calculation["type"] == "jinja":
|
|
values = self._calculation_jinja_to_string(child, calculation, prop)
|
|
elif calculation["type"] == "variable":
|
|
values = self._calculation_variable_to_string(child, calculation, prop)
|
|
elif calculation["type"] == "identifier":
|
|
if prop in PROPERTY_ATTRIBUTE:
|
|
values = calculation["value"]
|
|
else:
|
|
values = _("the value of the identifier")
|
|
elif calculation["type"] == "information":
|
|
values = calculation["value"]
|
|
else:
|
|
values = _("the value of the {0}").format(calculation["type"])
|
|
if not inside_list and isinstance(values, str) and not values.endswith("."):
|
|
values += "."
|
|
return values
|
|
|
|
def _calculation_jinja_to_string(self, child, calculation, prop):
|
|
if calculation["value"] is not True:
|
|
values = calculation["value"]
|
|
else:
|
|
values = _("depends on a calculation")
|
|
if (
|
|
child.isoptiondescription()
|
|
or not child.isfollower()
|
|
or not child.index()
|
|
):
|
|
warning = _(
|
|
'"{0}" is a calculation for {1} but has no description in {2}'
|
|
).format(
|
|
prop,
|
|
child.path(),
|
|
display_xmlfiles(child.information.get("ymlfiles")),
|
|
)
|
|
warn(
|
|
warning,
|
|
RougailWarning,
|
|
)
|
|
return values
|
|
|
|
def _calculation_variable_to_string(self, child, calculation, prop):
|
|
if prop in PROPERTY_ATTRIBUTE:
|
|
variable_path, value, condition = calculation["value"]
|
|
variable = self.conf.forcepermissive.option(variable_path)
|
|
try:
|
|
variable.value.get()
|
|
except AttributeError as err:
|
|
if prop in HIDDEN_PROPERTIES:
|
|
return False
|
|
variable = None
|
|
if variable and self.is_inaccessible_user_data(variable):
|
|
try:
|
|
variable_value = self._get_unmodified_default_value(variable)
|
|
except VariableCalculationDependencyError:
|
|
msg = _("depends on an undocumented variable")
|
|
else:
|
|
if (
|
|
condition == "when"
|
|
and value == variable_value
|
|
or condition == "when_not"
|
|
and value != variable_value
|
|
):
|
|
if prop in HIDDEN_PROPERTIES:
|
|
return
|
|
# always "{prop}" (but depends on an undocumented variable)
|
|
return True
|
|
# depends on an undocumented variable but is never "{prop}"
|
|
return False
|
|
elif condition == "when_not":
|
|
if not calculation["optional"]:
|
|
msg = _('when the variable "{0}" hasn\'t the value "{1}"')
|
|
else:
|
|
msg = _(
|
|
'when the variable "{0}" is defined and hasn\'t the value "{1}"'
|
|
)
|
|
else:
|
|
if not calculation["optional"]:
|
|
msg = _('when the variable "{0}" has the value "{1}"')
|
|
else:
|
|
msg = _(
|
|
'when the variable "{0}" is defined and has the value "{1}"'
|
|
)
|
|
if not isinstance(value, str):
|
|
value = dump(value)
|
|
values = msg.format(variable_path, value)
|
|
else:
|
|
if calculation["optional"]:
|
|
path = calculation["value"]
|
|
if "{{ identifier }}" in path:
|
|
if path not in self.dynamic_paths:
|
|
return None
|
|
else:
|
|
try:
|
|
self.conf.forcepermissive.option(path).get()
|
|
except AttributeError:
|
|
return None
|
|
if not calculation["optional"]:
|
|
true_msg = _('the value of the variable "{0}"')
|
|
else:
|
|
true_msg = _('the value of the variable "{0}" if it is defined')
|
|
hidden_msg = _("the value of an undocumented variable")
|
|
if "{{ identifier }}" in calculation["ori_path"]:
|
|
if calculation["value"] == calculation["ori_path"]:
|
|
regexp = None
|
|
else:
|
|
regexp = compile(
|
|
"^"
|
|
+ calculation["ori_path"].replace("{{ identifier }}", "(.*)")
|
|
+ "$"
|
|
)
|
|
informations = [self.dynamic_paths[calculation["value"]]]
|
|
values = []
|
|
all_is_undocumented = None
|
|
for information in informations:
|
|
# if calculation["ori_path"] == information['path']:
|
|
path = information["path"]
|
|
for identifiers in information["identifiers"]:
|
|
cpath = calc_path(path, identifiers=identifiers)
|
|
if regexp and not regexp.search(cpath):
|
|
continue
|
|
if self.is_inaccessible_user_data(self.conf.option(cpath)):
|
|
if all_is_undocumented is None:
|
|
all_is_undocumented = True
|
|
msg = hidden_msg
|
|
else:
|
|
if "{{ identifier }}" in path:
|
|
msg = {
|
|
"message": true_msg,
|
|
"path": {
|
|
"path": path,
|
|
"identifiers": identifiers.copy(),
|
|
},
|
|
}
|
|
else:
|
|
msg = true_msg.format(path)
|
|
all_is_undocumented = False
|
|
values.append(msg)
|
|
if all_is_undocumented and len(values) > 1:
|
|
values = _("the values of undocumented variables")
|
|
else:
|
|
variable_path = calculation["ori_path"]
|
|
variable = self.conf.forcepermissive.option(variable_path)
|
|
try:
|
|
isfollower = variable.isfollower()
|
|
except AttributeError as err:
|
|
pass
|
|
else:
|
|
if not isfollower and self.is_inaccessible_user_data(variable):
|
|
try:
|
|
uncalculated = variable.value.get(uncalculated=True)
|
|
except PropertiesOptionError:
|
|
true_msg = None
|
|
else:
|
|
if uncalculated and not isinstance(
|
|
uncalculated, Calculation
|
|
):
|
|
if isinstance(uncalculated, list):
|
|
true_msg = {
|
|
"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 = _("depends on an undocumented variable")
|
|
if true_msg:
|
|
if isinstance(true_msg, dict):
|
|
values = true_msg
|
|
else:
|
|
values = true_msg.format(calculation["ori_path"])
|
|
else:
|
|
values = None
|
|
return values
|
|
|
|
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.conf.forcepermissive.option(calculation["value"])
|
|
if variable and self.is_inaccessible_user_data(variable):
|
|
return self._get_unmodified_default_value(variable)
|
|
raise VariableCalculationDependencyError()
|