"""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