266 lines
10 KiB
Python
266 lines
10 KiB
Python
"""
|
|
Silique (https://www.silique.fr)
|
|
Copyright (C) 2024-2026
|
|
|
|
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
|
|
|
|
from tiramisu import Calculation, owners
|
|
|
|
from .utils import _, dump, to_phrase
|
|
|
|
|
|
# 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")
|
|
|
|
|
|
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)
|
|
# XXX # pylint: disable=W0511
|
|
|
|
|
|
class Examples: # pylint: disable=no-member,too-few-public-methods
|
|
"""Build examples"""
|
|
|
|
def __init__(self):
|
|
self.comment_examples = None
|
|
self.level = None
|
|
self.comment_examples_column = None
|
|
|
|
def gen_doc_examples(self):
|
|
"""Return examples"""
|
|
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):
|
|
self.comment_examples = self.rougailconfig["doc.examples.comment"]
|
|
self.level = self.rougailconfig["doc.title_level"]
|
|
if self.comment_examples:
|
|
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)
|
|
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:
|
|
true_results = results
|
|
return true_results
|
|
|
|
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]
|
|
else:
|
|
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.unrestraint.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)
|
|
else:
|
|
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, dump_type):
|
|
for option, values in config.items():
|
|
if option.isoptiondescription():
|
|
if option.isleadership():
|
|
subresults = self._example_parse_leadership(values, dump_type)
|
|
if subresults:
|
|
name = option.name()
|
|
results[name] = subresults
|
|
self._set_description(results, name, option)
|
|
else:
|
|
subresults = CommentedMap()
|
|
self._example_parse_family(values, subresults, dump_type)
|
|
if subresults:
|
|
name = option.name()
|
|
results[name] = subresults
|
|
self._set_description(results, name, option)
|
|
else:
|
|
self._set_example_value(results, option, values, dump_type)
|
|
|
|
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)
|
|
|
|
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()
|
|
return (
|
|
(dump_type == 'modified' and not is_default and option.owner.get() != owners.forced)
|
|
or (dump_type == 'default' and is_default)
|
|
)
|
|
|
|
def _example_parse_leadership(self, values, dump_type):
|
|
leadership_iter = iter(values.items())
|
|
leader, leader_values = next(leadership_iter)
|
|
if not self._is_valid_owner(leader, dump_type):
|
|
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, dump_type)
|
|
for option, value in leadership_iter:
|
|
idx = option.index()
|
|
self._set_example_value(leadership[idx], option, value, dump_type)
|
|
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
|
|
)
|