2024-11-15 08:13:45 +01:00
|
|
|
"""
|
|
|
|
|
Silique (https://www.silique.fr)
|
2026-01-04 19:16:18 +01:00
|
|
|
Copyright (C) 2024-2026
|
|
|
|
|
|
2024-11-15 08:13:45 +01:00
|
|
|
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
|
|
|
|
|
Free Software Foundation, either version 3 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 Lesser General Public License for more
|
|
|
|
|
details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
"""
|
|
|
|
|
|
2025-10-28 06:21:53 +01:00
|
|
|
from ruamel.yaml import CommentedMap
|
2026-01-04 19:16:18 +01:00
|
|
|
from ruamel.yaml.representer import RoundTripRepresenter
|
|
|
|
|
|
2026-03-29 11:01:15 +02:00
|
|
|
from tiramisu import Calculation, owners
|
2024-11-15 08:13:45 +01:00
|
|
|
|
|
|
|
|
|
2026-03-29 11:01:15 +02:00
|
|
|
from .utils import _, dump, to_phrase
|
2024-11-15 08:13:45 +01:00
|
|
|
|
2026-03-29 11:01:15 +02:00
|
|
|
|
|
|
|
|
# XXX explicit null (see rougail-output-formatter # pylint: disable=W0511
|
|
|
|
|
def represent_none(
|
|
|
|
|
self, data
|
|
|
|
|
): # pylint: disable=missing-function-docstring,unused-argument
|
2026-01-04 19:16:18 +01:00
|
|
|
return self.represent_scalar("tag:yaml.org,2002:null", "null")
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 11:01:15 +02:00
|
|
|
def represent_str(self, data): # pylint: disable=missing-function-docstring
|
2026-01-04 19:16:18 +01:00
|
|
|
if data == "":
|
|
|
|
|
return self.represent_scalar("tag:yaml.org,2002:null", "")
|
|
|
|
|
return self.represent_scalar("tag:yaml.org,2002:str", data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RoundTripRepresenter.add_representer(type(None), represent_none)
|
|
|
|
|
RoundTripRepresenter.add_representer(str, represent_str)
|
2026-03-29 11:01:15 +02:00
|
|
|
# XXX # pylint: disable=W0511
|
2026-01-04 19:16:18 +01:00
|
|
|
|
|
|
|
|
|
2024-11-15 08:13:45 +01:00
|
|
|
class Examples: # pylint: disable=no-member,too-few-public-methods
|
|
|
|
|
"""Build examples"""
|
2024-11-20 21:12:56 +01:00
|
|
|
|
2024-11-15 08:13:45 +01:00
|
|
|
def __init__(self):
|
2026-03-29 11:01:15 +02:00
|
|
|
self.comment_examples = None
|
|
|
|
|
self.level = None
|
|
|
|
|
self.comment_examples_column = None
|
2024-11-15 08:13:45 +01:00
|
|
|
|
|
|
|
|
def gen_doc_examples(self):
|
|
|
|
|
"""Return examples"""
|
2025-11-29 22:21:06 +01:00
|
|
|
self.comment_examples = self.rougailconfig["doc.examples.comment"]
|
2025-10-30 21:14:34 +01:00
|
|
|
self.level = self.rougailconfig["doc.title_level"]
|
|
|
|
|
if self.comment_examples:
|
2026-03-29 11:01:15 +02:00
|
|
|
self.comment_examples_column = self.rougailconfig[
|
|
|
|
|
"doc.examples.comment_column"
|
|
|
|
|
]
|
|
|
|
|
config = self.true_config.config.copy()
|
|
|
|
|
config.information.set("description_type", "description")
|
|
|
|
|
config.property.read_write()
|
|
|
|
|
self._set_mandatories(config)
|
2025-10-30 21:14:34 +01:00
|
|
|
datas = []
|
2026-03-29 11:01:15 +02:00
|
|
|
for only_modified in [True, False]:
|
|
|
|
|
if not only_modified:
|
|
|
|
|
self._set_examples(config)
|
|
|
|
|
results = CommentedMap()
|
|
|
|
|
true_results = results
|
|
|
|
|
if self.true_config == self.config:
|
|
|
|
|
root_config = config
|
2026-01-14 13:56:51 +01:00
|
|
|
else:
|
2026-03-29 11:01:15 +02:00
|
|
|
root_config = config.option(self.config.path())
|
|
|
|
|
current_option = self.true_config
|
|
|
|
|
subpaths = self.config.path().split(".")
|
|
|
|
|
if not self.config.isoptiondescription():
|
|
|
|
|
subpaths = subpaths[:-1]
|
|
|
|
|
for subpath in subpaths:
|
|
|
|
|
current_option = current_option.option(subpath)
|
|
|
|
|
name = current_option.name()
|
|
|
|
|
results[name] = CommentedMap()
|
|
|
|
|
self._set_description(results, name, current_option)
|
|
|
|
|
results = results[name]
|
|
|
|
|
if root_config.isoptiondescription():
|
|
|
|
|
self._example_parse_family(
|
|
|
|
|
root_config.value.get(), results, only_modified
|
|
|
|
|
)
|
2026-01-14 13:56:51 +01:00
|
|
|
else:
|
2026-03-29 11:01:15 +02:00
|
|
|
self._set_example_value(
|
|
|
|
|
results, root_config, root_config.value.get(), only_modified
|
|
|
|
|
)
|
|
|
|
|
if results:
|
|
|
|
|
if only_modified:
|
|
|
|
|
title = _("Example with mandatory variables not filled in")
|
|
|
|
|
else:
|
|
|
|
|
title = _("Example with all variables modifiable")
|
|
|
|
|
datas.extend(
|
|
|
|
|
[
|
|
|
|
|
self.formatter.title(title, self.level),
|
|
|
|
|
self.formatter.yaml(dump(true_results)),
|
|
|
|
|
self.formatter.end_family(self.level),
|
|
|
|
|
]
|
|
|
|
|
)
|
2025-10-30 21:14:34 +01:00
|
|
|
return self.formatter.compute(datas)
|
2024-11-15 08:13:45 +01:00
|
|
|
|
2026-03-29 11:01:15 +02:00
|
|
|
def _set_mandatories(self, config):
|
|
|
|
|
for calculated_too in [False, True]:
|
|
|
|
|
for option in config.value.mandatory():
|
|
|
|
|
if not calculated_too:
|
|
|
|
|
uncalculated = option.value.default(uncalculated=True)
|
|
|
|
|
if isinstance(uncalculated, Calculation):
|
|
|
|
|
continue
|
|
|
|
|
self._set_value_example(option, self._get_an_example(option))
|
|
|
|
|
|
|
|
|
|
def _get_an_example(self, option):
|
|
|
|
|
value = self._get_value_from_example(option)
|
|
|
|
|
if value is None:
|
|
|
|
|
variable_type = option.information.get("type")
|
|
|
|
|
if variable_type == "choice":
|
|
|
|
|
value = option.value.list()
|
|
|
|
|
if not self._is_multi(option) and value:
|
|
|
|
|
if value[0] is not None:
|
|
|
|
|
value = value[0]
|
|
|
|
|
elif len(value) > 1:
|
|
|
|
|
value = value[1]
|
2024-11-15 08:13:45 +01:00
|
|
|
else:
|
2026-03-29 11:01:15 +02:00
|
|
|
value = self.convert_option.get(option.information.get("type"), {}).get(
|
|
|
|
|
"example"
|
|
|
|
|
)
|
|
|
|
|
if value is None:
|
|
|
|
|
value = "example"
|
|
|
|
|
if self._is_multi(option):
|
|
|
|
|
value = [value]
|
|
|
|
|
if option.isfollower() and option.index() and variable_type == "string":
|
|
|
|
|
value += str(option.index())
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def _set_examples(self, config):
|
|
|
|
|
def _set_example(subconfig):
|
|
|
|
|
for option in subconfig:
|
|
|
|
|
if option.isoptiondescription():
|
|
|
|
|
_set_example(option)
|
|
|
|
|
else:
|
|
|
|
|
# force examples value + do not let empty value
|
|
|
|
|
examples = self._get_value_from_example(option)
|
|
|
|
|
if examples is None:
|
|
|
|
|
if self._is_multi(option):
|
|
|
|
|
if not option.value.get():
|
|
|
|
|
examples = self._get_an_example(option)
|
|
|
|
|
elif option.value.get() is None:
|
|
|
|
|
examples = self._get_an_example(option)
|
|
|
|
|
if examples is None:
|
|
|
|
|
continue
|
|
|
|
|
self._set_value_example(option, examples)
|
|
|
|
|
|
|
|
|
|
_set_example(config)
|
|
|
|
|
self._set_mandatories(config)
|
|
|
|
|
|
|
|
|
|
def _set_value_example(self, option, value):
|
|
|
|
|
if option.isleader():
|
|
|
|
|
ori_len = option.value.len()
|
|
|
|
|
current_len = len(value)
|
|
|
|
|
if ori_len > current_len:
|
|
|
|
|
for idx in reversed(range(current_len, ori_len)):
|
|
|
|
|
option.value.pop(idx)
|
|
|
|
|
option.value.set(value)
|
|
|
|
|
|
|
|
|
|
def _get_value_from_example(self, option):
|
|
|
|
|
examples = option.information.get("examples", None)
|
|
|
|
|
if examples is None:
|
|
|
|
|
return None
|
|
|
|
|
if self._is_multi(option):
|
|
|
|
|
examples = list(examples)
|
2025-10-14 12:58:39 +02:00
|
|
|
else:
|
2026-03-29 11:01:15 +02:00
|
|
|
examples = examples[0]
|
|
|
|
|
return examples
|
|
|
|
|
|
|
|
|
|
def _is_multi(self, option):
|
|
|
|
|
if option.isfollower():
|
|
|
|
|
return option.issubmulti()
|
|
|
|
|
return option.ismulti()
|
|
|
|
|
|
|
|
|
|
def _example_parse_family(self, config, results, only_modified):
|
|
|
|
|
for option, values in config.items():
|
|
|
|
|
if option.isoptiondescription():
|
|
|
|
|
if option.isleadership():
|
|
|
|
|
subresults = self._example_parse_leadership(values, only_modified)
|
|
|
|
|
if subresults:
|
|
|
|
|
name = option.name()
|
|
|
|
|
results[name] = subresults
|
|
|
|
|
self._set_description(results, name, option)
|
2025-10-28 06:21:53 +01:00
|
|
|
else:
|
2026-03-29 11:01:15 +02:00
|
|
|
subresults = CommentedMap()
|
|
|
|
|
self._example_parse_family(values, subresults, only_modified)
|
|
|
|
|
if subresults:
|
|
|
|
|
name = option.name()
|
|
|
|
|
results[name] = subresults
|
|
|
|
|
self._set_description(results, name, option)
|
|
|
|
|
else:
|
|
|
|
|
self._set_example_value(results, option, values, only_modified)
|
|
|
|
|
|
|
|
|
|
def _set_example_value(self, results, option, values, only_modified):
|
|
|
|
|
if self._is_valid_owner(option, only_modified):
|
|
|
|
|
name = option.name()
|
|
|
|
|
results[name] = values
|
|
|
|
|
self._set_description(results, name, option)
|
|
|
|
|
|
|
|
|
|
def _is_valid_owner(self, option, only_modified):
|
|
|
|
|
return (
|
|
|
|
|
option.type(only_self=True, translation=False) != "symlink"
|
|
|
|
|
and not only_modified
|
|
|
|
|
or (not option.owner.isdefault() and option.owner.get() != owners.forced)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _example_parse_leadership(self, values, only_modified):
|
|
|
|
|
leadership_iter = iter(values.items())
|
|
|
|
|
leader, leader_values = next(leadership_iter)
|
|
|
|
|
if not self._is_valid_owner(leader, only_modified):
|
|
|
|
|
return None
|
|
|
|
|
leadership = [CommentedMap() for idx in range(len(leader_values))]
|
|
|
|
|
for idx, value in enumerate(leader_values):
|
|
|
|
|
self._set_example_value(leadership[idx], leader, value, only_modified)
|
|
|
|
|
for option, value in leadership_iter:
|
|
|
|
|
idx = option.index()
|
|
|
|
|
self._set_example_value(leadership[idx], option, value, only_modified)
|
|
|
|
|
return leadership
|
|
|
|
|
|
|
|
|
|
def _set_description(self, results, name, option):
|
|
|
|
|
if not self.comment_examples or option.information.get(
|
|
|
|
|
"forced_description", False
|
|
|
|
|
):
|
|
|
|
|
return
|
|
|
|
|
description = to_phrase(option.description())
|
|
|
|
|
if description.endswith("."):
|
|
|
|
|
description = description[:-1]
|
|
|
|
|
results.yaml_add_eol_comment(
|
|
|
|
|
description, name, column=self.comment_examples_column
|
|
|
|
|
)
|