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

267 lines
10 KiB
Python
Raw Normal View History

2024-11-15 08:13:45 +01:00
"""
Silique (https://www.silique.fr)
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/>.
"""
from ruamel.yaml import CommentedMap
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
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
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
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"""
2026-04-30 06:58:12 +02:00
datas = []
config = self._build_example_config()
for only_modified in [True, False]:
results = self._gen_doc_examples(config, only_modified)
if results:
if only_modified:
title = _("Example with mandatory variables not filled in")
else:
title = _("Example with all variables modifiable")
datas.append(self.formatter.title(title, self.level))
datas.append(self.formatter.yaml(dump(results)))
end = self.formatter.end_family(self.level)
if end:
datas.append(end)
return self.formatter.compute(datas)
def _build_example_config(self):
2025-11-29 22:21:06 +01:00
self.comment_examples = self.rougailconfig["doc.examples.comment"]
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)
2026-04-30 06:58:12 +02:00
return config
def _gen_doc_examples(self, config, only_modified: bool):
if not only_modified:
self._set_examples(config)
results = CommentedMap()
if self.true_config == self.config:
root_config = config
true_results = None
n_results = None
else:
root_config = config.option(self.config.path())
current_option = self.true_config
subpaths = self.config.path().split(".")
n_results = CommentedMap()
true_results = n_results
if not self.config.isoptiondescription():
subpaths = subpaths[:-1]
for subpath in subpaths:
current_option = current_option.option(subpath)
name = current_option.name()
new_results = CommentedMap()
self._set_description(new_results, name, current_option)
n_results[name] = new_results
n_results = n_results[name]
if only_modified:
dump_type = 'modified'
else:
dump_type = 'all'
if root_config.isoptiondescription():
self._example_parse_family(
root_config.value.get(), results, dump_type
)
else:
self._set_example_value(
results, root_config, root_config.value.get(), dump_type
)
if true_results and results:
n_results.update(results)
else:
2026-03-29 11:01:15 +02:00
true_results = results
2026-04-30 06:58:12 +02:00
return true_results
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)
2026-04-30 06:58:12 +02:00
option.unrestraint.value.set(value)
2026-03-29 11:01:15 +02:00
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()
2026-04-30 06:58:12 +02:00
def _example_parse_family(self, config, results, dump_type):
2026-03-29 11:01:15 +02:00
for option, values in config.items():
if option.isoptiondescription():
if option.isleadership():
2026-04-30 06:58:12 +02:00
subresults = self._example_parse_leadership(values, dump_type)
2026-03-29 11:01:15 +02:00
if subresults:
name = option.name()
results[name] = subresults
self._set_description(results, name, option)
else:
2026-03-29 11:01:15 +02:00
subresults = CommentedMap()
2026-04-30 06:58:12 +02:00
self._example_parse_family(values, subresults, dump_type)
2026-03-29 11:01:15 +02:00
if subresults:
name = option.name()
results[name] = subresults
self._set_description(results, name, option)
else:
2026-04-30 06:58:12 +02:00
self._set_example_value(results, option, values, dump_type)
2026-03-29 11:01:15 +02:00
2026-04-30 06:58:12 +02:00
def _set_example_value(self, results, option, values, dump_type):
if not self._is_valid_owner(option, dump_type):
return
if dump_type == "hidden" and values is None:
values = self._get_an_example(option)
name = option.name()
results[name] = values
self._set_description(results, name, option)
2026-03-29 11:01:15 +02:00
2026-04-30 06:58:12 +02:00
def _is_valid_owner(self, option, dump_type):
if option.type(only_self=True, translation=False) == "symlink":
return False
if dump_type == "all":
return True
if dump_type == "hidden":
return "hidden" in option.property.get()
is_default = option.owner.isdefault()
2026-03-29 11:01:15 +02:00
return (
2026-04-30 06:58:12 +02:00
(dump_type == 'modified' and not is_default and option.owner.get() != owners.forced)
or (dump_type == 'default' and is_default)
2026-03-29 11:01:15 +02:00
)
2026-04-30 06:58:12 +02:00
def _example_parse_leadership(self, values, dump_type):
2026-03-29 11:01:15 +02:00
leadership_iter = iter(values.items())
leader, leader_values = next(leadership_iter)
2026-04-30 06:58:12 +02:00
if not self._is_valid_owner(leader, dump_type):
2026-03-29 11:01:15 +02:00
return None
leadership = [CommentedMap() for idx in range(len(leader_values))]
for idx, value in enumerate(leader_values):
2026-04-30 06:58:12 +02:00
self._set_example_value(leadership[idx], leader, value, dump_type)
2026-03-29 11:01:15 +02:00
for option, value in leadership_iter:
idx = option.index()
2026-04-30 06:58:12 +02:00
self._set_example_value(leadership[idx], option, value, dump_type)
2026-03-29 11:01:15 +02:00
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
)