feat: add 'document_a_type' option

This commit is contained in:
egarette@silique.fr 2026-01-14 13:56:51 +01:00
parent 8d305c96fb
commit 8e2e70486c
5 changed files with 105 additions and 63 deletions

View file

@ -49,13 +49,17 @@ class Annotator(Walk):
self, self,
objectspace, objectspace,
*args, # pylint: disable=unused-argument *args, # pylint: disable=unused-argument
**kwargs,
) -> None: ) -> None:
if not objectspace.paths: if not objectspace.paths:
return return
self.objectspace = objectspace self.objectspace = objectspace
self.default_values = self.objectspace.rougailconfig[ if "force_default_value" in kwargs:
"doc.default_values" self.default_values = kwargs["force_default_value"]
] else:
self.default_values = self.objectspace.rougailconfig[
"doc.default_values"
]
self.regexp_description_get_paths = None self.regexp_description_get_paths = None
self.populate_family() self.populate_family()
self.populate_variable() self.populate_variable()
@ -283,12 +287,16 @@ class Annotator(Walk):
"propertyerror": values.propertyerror, "propertyerror": values.propertyerror,
} }
if isinstance(values, InformationCalculation): if isinstance(values, InformationCalculation):
return { value, variable_path = self.calculation_to_information_information(
"type": "information",
"value": self.calculation_to_information_information(
values, version, path values, version, path
), )
ret = {
"type": "information",
"value": value,
} }
if variable_path:
ret["path"] = variable_path
return ret
if isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)): if isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)):
return { return {
"type": "identifier", "type": "identifier",
@ -347,10 +355,10 @@ class Annotator(Walk):
) -> Union[str, bool]: ) -> Union[str, bool]:
if values.variable: if values.variable:
variable_path = self.get_path_from_variable(values, version, path) variable_path = self.get_path_from_variable(values, version, path)
return _('the value of the information "{0}" of the variable "{1}"').format( return _('the value of the information "{0}" of the variable "{{0}}"').format(
values.information, variable_path values.information
) ), variable_path
return _('the value of the global information "{0}"').format(values.information) return _('the value of the global information "{0}"').format(values.information), None
def calculation_to_information_identifier( def calculation_to_information_identifier(
self, values, prop: str, version: str self, values, prop: str, version: str

View file

@ -202,6 +202,10 @@ doc:
types: types:
- file - file
document_a_type:
description: {_("Documentation a structural type")}
default: false
tabulars: tabulars:
description: {_('Variables and changelog documentation')} description: {_('Variables and changelog documentation')}
disabled: disabled:

View file

@ -44,6 +44,7 @@ class RougailOutputDoc(Examples, Changelog):
"""Rougail Output Doc: """Rougail Output Doc:
Generate documentation from rougail description files Generate documentation from rougail description files
""" """
output_name = "doc"
def __init__( def __init__(
self, self,
@ -61,34 +62,17 @@ class RougailOutputDoc(Examples, Changelog):
from rougail import RougailConfig from rougail import RougailConfig
rougailconfig = RougailConfig rougailconfig = RougailConfig
if rougailconfig["step.output"] != "doc": if rougailconfig["step.output"] != self.output_name:
rougailconfig["step.output"] = "doc" rougailconfig["step.output"] = self.output_name
if rougailconfig["step.output"] != "doc": if rougailconfig["step.output"] != self.output_name:
raise Exception("doc is not set as step.output") raise Exception(_("{0} is not set as step.output").format(self.output_name))
self.outputs = OutPuts().get() self.config = config
self.conf = config
if true_config is None: if true_config is None:
self.true_config = config self.true_config = config
else: else:
self.true_config = true_config self.true_config = true_config
self.modes_level = rougailconfig["modes_level"]
self.disabled_modes = []
if self.modes_level:
for mode in self.modes_level:
for prop in self.conf.property.get():
if mode != prop:
continue
self.disabled_modes.append(prop)
# self.conf.property.read_write()
# # self.conf.property.remove("cache")
self.rougailconfig = rougailconfig self.rougailconfig = rougailconfig
self.informations = None self.informations = None
try:
groups.namespace
self.support_namespace = True
except AttributeError:
self.support_namespace = False
self.property_to_string = get_properties_to_string()
self.formatter = None self.formatter = None
super().__init__() super().__init__()
@ -116,17 +100,33 @@ class RougailOutputDoc(Examples, Changelog):
return ret return ret
def load(self): def load(self):
self.document_a_type = self.rougailconfig["doc.document_a_type"]
self.modes_level = self.rougailconfig["modes_level"]
self.disabled_modes = []
if self.modes_level:
for mode in self.modes_level:
for prop in self.true_config.property.get():
if mode != prop:
continue
self.disabled_modes.append(prop)
try:
groups.namespace
self.support_namespace = True
except AttributeError:
self.support_namespace = False
self.property_to_string = get_properties_to_string()
self.outputs = OutPuts().get()
self.dynamic_paths = {} self.dynamic_paths = {}
config = self.conf.unrestraint config = self.config.unrestraint
self.populate_dynamics(config=config) self.populate_dynamics(config=config)
informations = self.parse_families(config) informations = self.parse_families(config)
if informations is None: if informations is None:
informations = {} informations = {}
elif self.conf.type() not in ['config', 'metaconfig', 'groupconfig', 'mixconfig']: elif self.config.type() not in ['config', 'metaconfig', 'groupconfig', 'mixconfig']:
root_informations = {} root_informations = {}
infos = root_informations infos = root_informations
family = self.true_config family = self.true_config
for path in self.conf.path().split('.'): for path in self.config.path().split('.'):
family = family.option(path) family = family.option(path)
name = family.name(uncalculated=True) name = family.name(uncalculated=True)
infos[name] = self.get_root_family(family) infos[name] = self.get_root_family(family)
@ -148,7 +148,7 @@ class RougailOutputDoc(Examples, Changelog):
def populate_dynamics(self, *, config=None, reload=False): def populate_dynamics(self, *, config=None, reload=False):
if config is None: if config is None:
config = self.conf.unrestraint config = self.config.unrestraint
self._populate_dynamics(config, reload) self._populate_dynamics(config, reload)
def _populate_dynamics(self, family, reload, uncalculated=False) -> None: def _populate_dynamics(self, family, reload, uncalculated=False) -> None:
@ -177,7 +177,7 @@ class RougailOutputDoc(Examples, Changelog):
self.dynamic_paths[path] = { self.dynamic_paths[path] = {
"names": [], "names": [],
"identifiers": [], "identifiers": [],
"path": path, "path": self.doc_path(path),
} }
if not obj.information.get("forced_description", False): if not obj.information.get("forced_description", False):
self.dynamic_paths[path]["description"] = self._convert_description( self.dynamic_paths[path]["description"] = self._convert_description(
@ -459,7 +459,7 @@ class RougailOutputDoc(Examples, Changelog):
child.description(uncalculated=True), type_, its_a_path=False child.description(uncalculated=True), type_, its_a_path=False
) )
if not child.isdynamic(): if not child.isdynamic():
informations["path"] = child.path(uncalculated=True) informations["path"] = self.doc_path(child.path(uncalculated=True))
informations["names"] = [child.name()] informations["names"] = [child.name()]
if description is not None: if description is not None:
informations["description"] = description informations["description"] = description
@ -672,21 +672,22 @@ class RougailOutputDoc(Examples, Changelog):
if isinstance(calculation, list): if isinstance(calculation, list):
values = [] values = []
for cal in calculation: for cal in calculation:
value = self._calculation_to_string(child, cal, prop, inside_list=True) value = self._calculation_to_string(child, cal, prop)
if value is not None: if value is not None:
values.append(value) values.append(value)
return values return values
return self._calculation_to_string(child, calculation, prop) return self._calculation_to_string(child, calculation, prop)
def _calculation_to_string(self, child, calculation, prop, inside_list=False): def _calculation_to_string(self, child, calculation, prop):
if "description" in calculation: if "description" in calculation:
values = calculation["description"] values = calculation
# if not values.endswith("."): if self.document_a_type and "variables" in values:
# values += "." for variable in list(values["variables"]):
return calculation variable["path"] = self.doc_path(variable["path"])
if "type" not in calculation:
return calculation["value"] elif "type" not in calculation:
if calculation["type"] == "jinja": values = calculation["value"]
elif calculation["type"] == "jinja":
values = self._calculation_jinja_to_string(child, calculation, prop) values = self._calculation_jinja_to_string(child, calculation, prop)
elif calculation["type"] == "variable": elif calculation["type"] == "variable":
values = self._calculation_variable_to_string(child, calculation, prop) values = self._calculation_variable_to_string(child, calculation, prop)
@ -697,10 +698,11 @@ class RougailOutputDoc(Examples, Changelog):
values = _("the value of the identifier") values = _("the value of the identifier")
elif calculation["type"] == "information": elif calculation["type"] == "information":
values = calculation["value"] values = calculation["value"]
if "path" in calculation:
variable_path = self.doc_path(calculation["path"])
values = values.format(variable_path)
else: else:
values = _("the value of the {0}").format(calculation["type"]) values = _("the value of the {0}").format(calculation["type"])
# if not inside_list and isinstance(values, str) and not values.endswith("."):
# values += "."
return values return values
def _calculation_jinja_to_string(self, child, calculation, prop): def _calculation_jinja_to_string(self, child, calculation, prop):
@ -717,7 +719,7 @@ class RougailOutputDoc(Examples, Changelog):
'"{0}" is a calculation for {1} but has no description in {2}' '"{0}" is a calculation for {1} but has no description in {2}'
).format( ).format(
prop, prop,
child.path(), self.doc_path(child.path()),
display_xmlfiles(child.information.get("ymlfiles")), display_xmlfiles(child.information.get("ymlfiles")),
) )
warn( warn(
@ -751,7 +753,7 @@ class RougailOutputDoc(Examples, Changelog):
if cpath: if cpath:
all_is_undocumented = False all_is_undocumented = False
path_obj = { path_obj = {
"path": cpath, "path": self.doc_path(cpath),
} }
if identifiers: if identifiers:
path_obj["identifiers"] = identifiers path_obj["identifiers"] = identifiers
@ -813,7 +815,7 @@ class RougailOutputDoc(Examples, Changelog):
values = { values = {
"message": true_msg, "message": true_msg,
"path": { "path": {
"path": calculation["ori_path"], "path": self.doc_path(calculation["ori_path"]),
}, },
"description": description, "description": description,
} }
@ -901,7 +903,7 @@ class RougailOutputDoc(Examples, Changelog):
else: else:
msg = _('when the variable "{{0}}" has the value "{0}"') msg = _('when the variable "{{0}}" has the value "{0}"')
path_obj = { path_obj = {
"path": variable_path, "path": self.doc_path(variable_path),
} }
if identifiers: if identifiers:
path_obj["identifiers"] = identifiers path_obj["identifiers"] = identifiers
@ -925,7 +927,7 @@ class RougailOutputDoc(Examples, Changelog):
+ "$" + "$"
) )
information = self.dynamic_paths[current_path] information = self.dynamic_paths[current_path]
path = information["path"] path = current_path
for identifiers in information["identifiers"]: for identifiers in information["identifiers"]:
cpath = calc_path(path, identifiers=identifiers) cpath = calc_path(path, identifiers=identifiers)
if regexp and not regexp.search(cpath): if regexp and not regexp.search(cpath):
@ -948,3 +950,10 @@ class RougailOutputDoc(Examples, Changelog):
if variable and self.is_inaccessible_user_data(variable): if variable and self.is_inaccessible_user_data(variable):
return self._get_unmodified_default_value(variable) return self._get_unmodified_default_value(variable)
raise VariableCalculationDependencyError() raise VariableCalculationDependencyError()
def doc_path(self, path):
if self.document_a_type:
if not '.' in path:
return None
return path.split('.', 1)[-1]
return path

View file

@ -57,18 +57,34 @@ class Examples: # pylint: disable=no-member,too-few-public-methods
return_string = "" return_string = ""
datas = [] datas = []
if self.examples_mandatories: if self.examples_mandatories:
if self.document_a_type:
col = list(self.examples_mandatories)
if len(col) == 1:
examples_mandatories = self.examples_mandatories[col[0]]
else:
examples_mandatories = self.examples_mandatories
else:
examples_mandatories = self.examples_mandatories
datas.extend([ datas.extend([
self.formatter.title( self.formatter.title(
_("Example with mandatory variables not filled in"), self.level _("Example with mandatory variables not filled in"), self.level
), ),
self.formatter.yaml(self.examples_mandatories), self.formatter.yaml(examples_mandatories),
self.formatter.end_family(self.level), self.formatter.end_family(self.level),
]) ])
if self.examples: if self.examples:
if self.document_a_type:
col = list(self.examples)
if len(col) == 1:
examples = self.examples[col[0]]
else:
examples = self.examples
else:
examples = self.examples
datas.extend([self.formatter.title( datas.extend([self.formatter.title(
_("Example with all variables modifiable"), self.level _("Example with all variables modifiable"), self.level
), ),
self.formatter.yaml(self.examples), self.formatter.yaml(examples),
self.formatter.end_family(self.level), self.formatter.end_family(self.level),
]) ])
return self.formatter.compute(datas) return self.formatter.compute(datas)

View file

@ -1,7 +1,7 @@
""" """
Silique (https://www.silique.fr) Silique (https://www.silique.fr)
Copyright (C) 2024-2026 Copyright (C) 2024-2026
This program is free software: you can redistribute it and/or modify it 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 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 Free Software Foundation, either version 3 of the License, or (at your
@ -332,6 +332,8 @@ class CommonFormatter:
return path return path
ret_paths = [] ret_paths = []
path = informations["path"] path = informations["path"]
if not path:
return None
if is_bold: if is_bold:
bold = self.bold bold = self.bold
else: else:
@ -426,15 +428,14 @@ class CommonFormatter:
ret = [self.namespace_to_title(informations, level)] ret = [self.namespace_to_title(informations, level)]
else: else:
ret = [self.title(self.get_description("family", informations, {}, None), level)] ret = [self.title(self.get_description("family", informations, {}, None), level)]
fam_info = self.family_informations()
if fam_info:
ret.append(fam_info)
msg = [] msg = []
helps = informations.get("help") helps = informations.get("help")
if helps: if helps:
for help_ in helps: for help_ in helps:
msg.extend([to_phrase(h) for h in help_.strip().split('\n')]) msg.extend([to_phrase(h) for h in help_.strip().split('\n')])
msg.append(self.section(_("Path"), self.display_paths(informations, {}, None, with_anchor=False, is_bold=False), type_="family")) path = self.display_paths(informations, {}, None, with_anchor=False, is_bold=False)
if path:
msg.append(self.section(_("Path"), path, type_="family"))
calculated_properties = [] calculated_properties = []
property_str = self.property_to_string(informations, calculated_properties, {}) property_str = self.property_to_string(informations, calculated_properties, {})
if property_str: if property_str:
@ -447,7 +448,11 @@ class CommonFormatter:
self.section(_("Identifiers"), informations["identifier"], type_="family") self.section(_("Identifiers"), informations["identifier"], type_="family")
) )
starts_line = self.family_informations_starts_line() starts_line = self.family_informations_starts_line()
ret.append(self.family_informations_ends_line().join([starts_line + m for m in msg]) + self.end_family_informations()) if msg:
fam_info = self.family_informations()
if fam_info:
ret.append(fam_info)
ret.append(self.family_informations_ends_line().join([starts_line + m for m in msg]) + self.end_family_informations())
return ret return ret
def family_informations_starts_line(self) -> str: def family_informations_starts_line(self) -> str: