rougail-output-doc/src/rougail/output_doc/annotator.py

334 lines
11 KiB
Python
Raw Permalink Normal View History

"""
2024-07-10 21:27:48 +02:00
Silique (https://www.silique.fr)
2025-02-10 09:52:12 +01:00
Copyright (C) 2024-2025
2024-07-10 21:27:48 +02:00
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
2024-11-01 11:17:14 +01:00
2024-11-15 08:13:45 +01:00
from typing import Union
2024-07-10 21:27:48 +02:00
from tiramisu import undefined
from rougail.annotator.variable import Walk
from rougail.output_doc.i18n import _
2024-11-01 11:17:14 +01:00
from rougail.object_model import (
Calculation,
JinjaCalculation,
VariableCalculation,
VariablePropertyCalculation,
IdentifierCalculation,
IdentifierPropertyCalculation,
InformationCalculation,
IndexCalculation,
CONVERT_OPTION,
PROPERTY_ATTRIBUTE,
)
2024-11-15 08:13:45 +01:00
from rougail.output_doc.utils import dump
2024-07-10 21:27:48 +02:00
class Annotator(Walk):
"""Annotate for documentation"""
level = 95
def __init__(
self,
objectspace,
2024-11-15 08:13:45 +01:00
*args, # pylint: disable=unused-argument
2024-07-10 21:27:48 +02:00
) -> None:
if not objectspace.paths:
return
self.objectspace = objectspace
self.populate_family()
self.populate_variable()
def populate_family(self) -> None:
"""Set doc, path, ... to family"""
for family in self.get_families():
self.objectspace.informations.add(
family.path, "dictionaries", family.xmlfiles
)
self.convert_variable_property(family)
if family.type != "dynamic":
continue
if not isinstance(family.dynamic, list):
2024-11-15 08:13:45 +01:00
self.dynamic_values(family, family.dynamic)
2024-07-10 21:27:48 +02:00
else:
for value in family.dynamic:
2024-11-15 08:13:45 +01:00
self.dynamic_values(family, value, return_a_list=False)
2024-11-01 11:17:14 +01:00
self.calculation_to_information(
family.path,
"dynamic",
family.dynamic,
family.version,
)
2024-07-10 21:27:48 +02:00
2024-11-15 08:13:45 +01:00
def dynamic_values(
self,
family,
value,
*,
return_a_list=True,
) -> None:
"""For dynamic we must have values to document it"""
if not isinstance(value, Calculation):
return
default_values = ["example"]
if isinstance(value, (VariableCalculation, VariablePropertyCalculation)):
variable = self.objectspace.paths.get_with_dynamic(
value.variable,
family.path,
value.version,
value.namespace,
value.xmlfiles,
)[0]
if variable:
values = self.get_examples_values(variable)
if values:
default_values = values
if not return_a_list:
default_values = default_values[0]
value.default_values = default_values
def get_examples_values(self, variable) -> list:
"""Check examples or test information to define examples values"""
values = self.objectspace.informations.get(variable.path).get("examples", None)
if not values:
values = self.objectspace.informations.get(variable.path).get("test", None)
if isinstance(values, tuple):
values = list(values)
return values
2024-07-10 21:27:48 +02:00
def populate_variable(self) -> None:
"""convert variables"""
for variable in self.get_variables():
if variable.type == "symlink":
continue
if variable.type == "choice":
2024-11-01 11:17:14 +01:00
self.calculation_to_information(
variable.path,
"choice",
variable.choices,
variable.version,
)
self.calculation_to_information(
variable.path,
"default",
variable.default,
variable.version,
)
self.calculation_to_information(
variable.path,
"validators",
variable.validators,
variable.version,
)
if variable.path in self.objectspace.leaders and not variable.default:
values = self.get_examples_values(variable)
2024-07-10 21:27:48 +02:00
if values:
variable.default = list(values)
else:
2024-11-01 11:17:14 +01:00
variable.default = [CONVERT_OPTION[variable.type]["example"]]
self.objectspace.informations.add(variable.path, "default_value_makes_sense", False)
2024-07-10 21:27:48 +02:00
self.objectspace.informations.add(
variable.path, "dictionaries", variable.xmlfiles
)
self.convert_variable_property(variable)
def convert_variable_property(
self,
variable: dict,
) -> None:
"""convert properties"""
2024-11-15 08:13:45 +01:00
for prop in PROPERTY_ATTRIBUTE:
2024-07-10 21:27:48 +02:00
prop_value = getattr(variable, prop, None)
if not prop_value:
continue
2024-11-01 11:17:14 +01:00
self.calculation_to_information(
variable.path,
prop,
prop_value,
variable.version,
)
def calculation_to_information(
self,
path: str,
prop: str,
values,
version: str,
):
2024-11-15 08:13:45 +01:00
"""tranform calculation to an information"""
if not isinstance(values, list):
if not isinstance(values, Calculation):
return
self._calculation_to_information(
path,
prop,
self._calculation_to_string(path, prop, values, version),
)
else:
datas = []
one_is_calculation = False
2024-07-10 21:27:48 +02:00
for idx, val in enumerate(values):
2024-11-15 08:13:45 +01:00
data = self._calculation_to_string(path, prop, val, version)
if data is None:
continue
if "type" in data:
one_is_calculation = True
datas.append(data)
if one_is_calculation:
2024-11-01 11:17:14 +01:00
self._calculation_to_information(
path,
prop,
2024-11-15 08:13:45 +01:00
datas,
2024-11-01 11:17:14 +01:00
)
def _calculation_to_information(
2024-11-15 08:13:45 +01:00
self,
path: str,
prop: str,
datas: Union[dict, list[dict]],
) -> None:
if datas is None:
return
self.objectspace.informations.add(
path,
f"{prop}_calculation",
datas,
)
def _calculation_to_string(
2024-11-01 11:17:14 +01:00
self,
path: str,
prop: str,
values,
version: str,
):
if not isinstance(values, Calculation):
2024-11-20 21:12:56 +01:00
return {"value": values}
2024-07-10 21:27:48 +02:00
if isinstance(values, JinjaCalculation):
2024-11-20 21:12:56 +01:00
return {
"type": "jinja",
"value": self._calculation_to_information_jinja(values),
}
2024-11-15 08:13:45 +01:00
if isinstance(values, (VariableCalculation, VariablePropertyCalculation)):
value = self._calculation_to_information_variable(
values, prop, version, path
)
if value is None:
return
2024-11-20 21:12:56 +01:00
return {
"type": "variable",
"value": value,
}
2024-11-15 08:13:45 +01:00
if isinstance(values, InformationCalculation):
2024-11-20 21:12:56 +01:00
return {
"type": "information",
"value": self._calculation_to_information_information(
values, version, path
),
}
2024-11-15 08:13:45 +01:00
if isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)):
2024-11-20 21:12:56 +01:00
return {
"type": "identifier",
"value": self._calculation_to_information_identifier(
values, prop, version
),
}
2024-11-15 08:13:45 +01:00
if isinstance(values, IndexCalculation):
2024-11-20 21:12:56 +01:00
return {
"type": "index",
"value": True,
}
2024-11-15 08:13:45 +01:00
raise Exception('unknown calculation "{values}"')
def _calculation_to_information_jinja(self, values):
if values.description:
return values.description
return True
def _calculation_to_information_variable(
self, values, prop: str, version: str, path: str
) -> str:
# is optional
if isinstance(values, VariableCalculation) and values.optional:
variable = self.objectspace.paths.get_with_dynamic(
values.variable,
path,
values.version,
values.namespace,
values.xmlfiles,
)[0]
if not variable:
return None
variable_path = variable.path
else:
variable_path = self._get_path_from_variable(values, version, path)
if prop in PROPERTY_ATTRIBUTE:
# get comparative value
if values.when_not is not undefined:
value = values.when_not
msg = _('when the variable "{0}" hasn\'t the value "{1}"')
else:
2024-11-01 11:17:14 +01:00
if values.when is not undefined:
2024-11-15 08:13:45 +01:00
value = values.when
else:
value = True
msg = _('when the variable "{0}" has the value "{1}"')
if not isinstance(value, str):
value = dump(value)
# set message
values_calculation = msg.format(variable_path, value)
else:
values_calculation = variable_path
return values_calculation
def _calculation_to_information_information(
self, values, version: str, path: str
) -> Union[str, bool]:
if values.variable:
variable_path = self._get_path_from_variable(values, version, path)
return _('the value of the information "{0}" of the variable "{1}"').format(
values.information, variable_path
)
2024-11-20 21:12:56 +01:00
return _('the value of the global information "{0}"').format(values.information)
2024-11-15 08:13:45 +01:00
def _calculation_to_information_identifier(
self, values, prop: str, version: str
) -> Union[str, bool]:
if version != "1.0" and prop in PROPERTY_ATTRIBUTE:
if values.when is not undefined:
return _('when the identifier is "{0}"').format(values.when)
if values.when_not is not undefined:
return _('when the identifier is not "{0}"').format(values.when_not)
return True
def _get_path_from_variable(self, values, version: str, path: str) -> str:
variable_path = values.variable
paths = self.objectspace.paths
if version != "1.0" and paths.regexp_relative.search(variable_path):
variable_path = paths.get_full_path(
variable_path,
path,
)
elif version == '1.0' and '{{ suffix }}' in variable_path:
variable_path = variable_path.replace("{{ suffix }}", "{{ identifier }}")
2024-11-15 08:13:45 +01:00
return variable_path