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

334 lines
11 KiB
Python

"""Annotate for documentation
Silique (https://www.silique.fr)
Copyright (C) 2024
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
"""
from typing import Union
from tiramisu import undefined
from rougail.annotator.variable import Walk
from rougail.output_doc.i18n import _
from rougail.object_model import (
Calculation,
JinjaCalculation,
VariableCalculation,
VariablePropertyCalculation,
IdentifierCalculation,
IdentifierPropertyCalculation,
InformationCalculation,
IndexCalculation,
CONVERT_OPTION,
PROPERTY_ATTRIBUTE,
)
from rougail.output_doc.utils import dump
class Annotator(Walk):
"""Annotate for documentation"""
level = 95
def __init__(
self,
objectspace,
*args, # pylint: disable=unused-argument
) -> 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):
self.dynamic_values(family, family.dynamic)
else:
for value in family.dynamic:
self.dynamic_values(family, value, return_a_list=False)
self.calculation_to_information(
family.path,
"dynamic",
family.dynamic,
family.version,
)
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,
value.path_prefix,
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
def populate_variable(self) -> None:
"""convert variables"""
for variable in self.get_variables():
if variable.type == "symlink":
continue
if variable.type == "choice":
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)
if values:
variable.default = list(values)
else:
variable.default = [CONVERT_OPTION[variable.type]["example"]]
self.objectspace.informations.add(variable.path, "fake_default", True)
self.objectspace.informations.add(
variable.path, "dictionaries", variable.xmlfiles
)
self.convert_variable_property(variable)
def convert_variable_property(
self,
variable: dict,
) -> None:
"""convert properties"""
for prop in PROPERTY_ATTRIBUTE:
prop_value = getattr(variable, prop, None)
if not prop_value:
continue
self.calculation_to_information(
variable.path,
prop,
prop_value,
variable.version,
)
def calculation_to_information(
self,
path: str,
prop: str,
values,
version: str,
):
"""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
for idx, val in enumerate(values):
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:
self._calculation_to_information(
path,
prop,
datas,
)
def _calculation_to_information(
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(
self,
path: str,
prop: str,
values,
version: str,
):
if not isinstance(values, Calculation):
return {"value": values}
if isinstance(values, JinjaCalculation):
return {
"type": "jinja",
"value": self._calculation_to_information_jinja(values),
}
if isinstance(values, (VariableCalculation, VariablePropertyCalculation)):
value = self._calculation_to_information_variable(
values, prop, version, path
)
if value is None:
return
return {
"type": "variable",
"value": value,
}
if isinstance(values, InformationCalculation):
return {
"type": "information",
"value": self._calculation_to_information_information(
values, version, path
),
}
if isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)):
return {
"type": "identifier",
"value": self._calculation_to_information_identifier(
values, prop, version
),
}
if isinstance(values, IndexCalculation):
return {
"type": "index",
"value": True,
}
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,
values.path_prefix,
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:
if values.when is not undefined:
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
)
return _('the value of the global information "{0}"').format(values.information)
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,
)
return variable_path