""" Silique (https://www.silique.fr) Copyright (C) 2024-2025 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 rougail.utils import undefined, PROPERTY_ATTRIBUTE from rougail.annotator.variable import Walk from rougail.output_doc.i18n import _ from rougail.convert.object_model import ( Calculation, JinjaCalculation, VariableCalculation, VariablePropertyCalculation, IdentifierCalculation, IdentifierPropertyCalculation, InformationCalculation, IndexCalculation, NamespaceCalculation, CONVERT_OPTION, ) 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.change_default_value = self.objectspace.rougailconfig[ "doc.change_default_value" ] self.populate_family() self.populate_variable() def populate_family(self) -> None: """Set doc, path, ... to family""" for family in self.get_families(): self.add_informations_from_properties(family) if family.type == "dynamic": self.force_default_value_in_suffix_variable(family) self.calculation_to_information( family.path, "dynamic", family.dynamic, family.version, ) path = family.path if path in self.objectspace.forced_descriptions: self.objectspace.informations.add( path, "forced_description", True, ) def force_default_value_in_suffix_variable(self, family) -> None: if not self.change_default_value: return if not isinstance(family.dynamic, list): self._force_default_value_in_suffix_variable(family, family.dynamic) else: for value in family.dynamic: self._force_default_value_in_suffix_variable( family, value, return_a_list=False ) def _force_default_value_in_suffix_variable( self, family, value, *, return_a_list=True, ) -> None: """For dynamic we must have values to document it""" if not isinstance(value, Calculation): return value_added = False 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 and not variable.default: values = self.add_examples_values(variable) value_added = True if not value_added: default_values = ["example"] if not return_a_list: default_values = default_values[0] value.default_values = default_values def add_examples_values(self, variable) -> list: """Check examples or test information to define examples values in a variable""" 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) if values: variable.default = list(values) else: variable.default = [CONVERT_OPTION[variable.type]["example"]] self.objectspace.informations.add( variable.path, "default_value_makes_sense", False ) return values def populate_variable(self) -> None: """convert variables""" for variable in self.get_variables(): if variable.type == "symlink": continue path = variable.path if variable.type == "choice": self.calculation_to_information( path, "choice", variable.choices, variable.version, ) default = variable.default if default is None and path in self.objectspace.default_multi: default = self.objectspace.default_multi[path] self.calculation_to_information( path, "default", default, variable.version, ) self.calculation_to_information( path, "validators", variable.validators, variable.version, ) if ( self.change_default_value and path in self.objectspace.leaders and not default ): self.add_examples_values(variable) self.add_informations_from_properties(variable) if path in self.objectspace.forced_descriptions: self.objectspace.informations.add( path, "forced_description", True, ) def add_informations_from_properties( 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, ) if isinstance(prop_value, Calculation): prop_value.default_values = False def calculation_to_information( self, path: str, prop: str, values, version: str, ): """tranform calculation to an information""" one_is_calculation = False if not isinstance(values, list): if not isinstance(values, Calculation): return one_is_calculation = True datas = self.calculation_to_string(path, prop, values, version) else: datas = [] for idx, val in enumerate(values): data = self.calculation_to_string(path, prop, val, version) if data is None: continue if "type" in data or "description" in data: one_is_calculation = True datas.append(data) if one_is_calculation: 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 values.description: return {"description": values.description} if isinstance(values, JinjaCalculation): if values.description: value = values.description else: value = True return { "type": "jinja", "value": value, } if isinstance(values, (VariableCalculation, VariablePropertyCalculation)): variable_path = self.get_path_from_variable(values, version, path) value = self.calculation_to_information_variable( variable_path, values, prop, version, path ) if value is None: return return { "type": "variable", "value": value, "ori_path": variable_path, "optional": values.optional, } 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, } if isinstance(values, NamespaceCalculation): return { "type": "namespace", "value": True, } raise Exception(f'unknown calculation {type(values)} "{values}"') def calculation_to_information_variable( self, variable_path: str, values, prop: str, version: str, path: str ) -> str: # is optional variable = self.objectspace.paths.get_with_dynamic( variable_path, path, values.version, values.namespace, values.xmlfiles, )[0] if isinstance(values, VariableCalculation) and values.optional and not variable: return None if variable: variable_path = variable.path if prop in PROPERTY_ATTRIBUTE: # get comparative value if values.when_not is not undefined: value = values.when_not condition = "when_not" elif values.when is not undefined: value = values.when condition = "when" else: value = True condition = "when" # set message values_calculation = (variable_path, value, condition) 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, ) elif version == "1.0" and "{{ suffix }}" in variable_path: variable_path = variable_path.replace("{{ suffix }}", "{{ identifier }}") return variable_path