mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
fix(docs): stop the documentation toolchain from emitting warnings
apidoc now excludes roles/ and library/ the way it already excluded tests/. A role name carries hyphens and is therefore not an importable module path, so automodule reported 44 invalid signatures and rendered nothing; library/ holds ansible modules whose ansible.module_utils imports exist only inside a play. Measured: 168 generated pages drop to 151, and the 17 that go consist of a heading and automodule directives on unresolvable paths. The role page generator fits every section underline to its title. pandoc counts one character fewer than docutils where a heading ends in a stray variation selector left behind by a removed emoji. It also escapes inline markup in role metadata, because one description reads (*.parent) and opened an emphasis that never closed. The index generator underlined a 21-character caption with 19 characters. conf.py adds the tel scheme, without which the phone number in SECURITY.md is an unresolvable reference, and the extract copies the image assets into its conf directory the way the site builder does, without which every run reports a missing html_static_path and favicon. Four tests pinned values this changes. The fifth, the extension count in test_conf, asserted nothing the loop below it does not already prove and broke on every legitimate addition; it now asserts the list is non-empty, so that loop cannot run over nothing. With this, make i18n-extract domain=docs finishes with zero warnings under the -W gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3dee332350
commit
3b81b4de2e
9 files changed
+52
-7
No files matched your search
@@ -45,6 +45,8 @@ def generate_commands(src):
|
||||
str(generated / "modules"),
|
||||
str(src),
|
||||
str(src / "tests"),
|
||||
str(src / "roles"),
|
||||
str(src / "library"),
|
||||
],
|
||||
_generator(
|
||||
"yaml_index",
|
||||
|
||||
@@ -70,6 +70,7 @@ gettext_location = False
|
||||
|
||||
myst_enable_extensions = ["colon_fence"]
|
||||
myst_heading_anchors = 6
|
||||
myst_url_schemes = ("http", "https", "mailto", "ftp", "tel")
|
||||
|
||||
|
||||
def replace_assets_in_doctree(app, doctree, docname):
|
||||
|
||||
@@ -8,6 +8,29 @@ from utils.cache.yaml import load_yaml
|
||||
from utils.roles.mapping import ROLE_FILE_META_MAIN, ROLE_FILE_README
|
||||
|
||||
|
||||
SECTION_CHARS = set("=-`:'\"~^_*+#<>")
|
||||
|
||||
|
||||
def _fit_underlines(rst: str) -> str:
|
||||
"""Return ``rst`` with every section underline at least as long as its title.
|
||||
|
||||
A heading that ends in a stray variation selector or zero-width joiner, left
|
||||
behind where an emoji was removed, makes pandoc count one character fewer
|
||||
than docutils does, and docutils then reports the underline as too short.
|
||||
|
||||
Args:
|
||||
rst: reStructuredText as pandoc produced it.
|
||||
"""
|
||||
lines = rst.splitlines()
|
||||
for index, line in enumerate(lines[1:], start=1):
|
||||
title = lines[index - 1]
|
||||
if not line or not title.strip() or set(line) - SECTION_CHARS:
|
||||
continue
|
||||
if len(line) < len(title):
|
||||
lines[index] = line[0] * len(title)
|
||||
return "\n".join(lines) + ("\n" if rst.endswith("\n") else "")
|
||||
|
||||
|
||||
def convert_md_to_rst(md_content):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
@@ -19,19 +42,28 @@ def convert_md_to_rst(md_content):
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print("Error converting Markdown to reStructuredText:", exc)
|
||||
return md_content
|
||||
return result.stdout.decode("utf-8")
|
||||
return _fit_underlines(result.stdout.decode("utf-8"))
|
||||
|
||||
|
||||
INLINE_MARKUP = ("\\", "*", "`", "|", "_")
|
||||
|
||||
|
||||
def _one_line(value) -> str:
|
||||
"""Return ``value`` as a single line, as a bullet item must be.
|
||||
"""Return ``value`` as a single reStructuredText line.
|
||||
|
||||
``galaxy_info.company`` is a block scalar in almost every role, and its
|
||||
second line landed in column 0, which ends the bullet list it sits in.
|
||||
Metadata is prose, not markup, so a character that opens inline markup is
|
||||
escaped rather than left to look for a partner: one description reads
|
||||
``(*.parent)`` and reported an unterminated emphasis.
|
||||
|
||||
Args:
|
||||
value: a value read from ``galaxy_info``.
|
||||
"""
|
||||
return " ".join(str(value).split())
|
||||
text = " ".join(str(value).split())
|
||||
for character in INLINE_MARKUP:
|
||||
text = text.replace(character, f"\\{character}")
|
||||
return text
|
||||
|
||||
|
||||
def generate_ansible_roles_doc(roles_dir, output_dir):
|
||||
@@ -53,7 +85,8 @@ def generate_ansible_roles_doc(roles_dir, output_dir):
|
||||
f"{role.name.capitalize()} Role",
|
||||
"=" * (len(role.name) + 7),
|
||||
"",
|
||||
f"**Description:** {galaxy_info.get('description', 'No description available')}",
|
||||
"**Description:** "
|
||||
+ _one_line(galaxy_info.get("description", "No description available")),
|
||||
"",
|
||||
"Variables",
|
||||
"---------",
|
||||
|
||||
@@ -28,7 +28,7 @@ def generate_ansible_roles_index(roles_dir, output_file, caption):
|
||||
]
|
||||
lines = [
|
||||
caption,
|
||||
"===================",
|
||||
"=" * len(caption),
|
||||
"",
|
||||
".. toctree::",
|
||||
" :maxdepth: 1",
|
||||
|
||||
@@ -36,6 +36,10 @@ def extract(src: Path, output: Path, jobs: int) -> None:
|
||||
conf = Path(scratch) / "conf"
|
||||
out = Path(scratch) / "out"
|
||||
shutil.copytree(PACKAGE_DIR, conf)
|
||||
if (src / "assets" / "img").is_dir():
|
||||
shutil.copytree(
|
||||
src / "assets" / "img", conf / "assets" / "img", dirs_exist_ok=True
|
||||
)
|
||||
env = {**os.environ, "PYTHONPATH": os.pathsep.join([tooling, *inherited])}
|
||||
for command in generate_commands(src):
|
||||
subprocess.run(command, check=True, env=env, cwd=scratch)
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestIndex(unittest.TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
text,
|
||||
"Ansible Role Glossary\n===================\n\n"
|
||||
"Ansible Role Glossary\n=====================\n\n"
|
||||
".. toctree::\n :maxdepth: 1\n :caption: Ansible Role Glossary\n\n"
|
||||
" ../generated/roles/a\n ../generated/roles/b\n",
|
||||
)
|
||||
|
||||
@@ -44,6 +44,8 @@ class TestProgress(unittest.TestCase):
|
||||
"/w/src/generated/modules",
|
||||
"/w/src",
|
||||
"/w/src/tests",
|
||||
"/w/src/roles",
|
||||
"/w/src/library",
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
|
||||
@@ -32,7 +32,7 @@ class _App:
|
||||
class TestConf(unittest.TestCase):
|
||||
def test_every_local_extension_resolves(self) -> None:
|
||||
local = [name for name in conf.extensions if name.startswith("infinito_docs.")]
|
||||
self.assertEqual(len(local), 6)
|
||||
self.assertTrue(local, "the loop below would otherwise prove nothing")
|
||||
for name in local:
|
||||
self.assertTrue(hasattr(importlib.import_module(name), "setup"), name)
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ class TestExtract(unittest.TestCase):
|
||||
(src / "config.yml").write_text(
|
||||
"name: sys-stk-front-proxy\n", encoding="utf-8"
|
||||
)
|
||||
images = src / "assets" / "img"
|
||||
images.mkdir(parents=True)
|
||||
(images / "favicon.ico").write_bytes(b"")
|
||||
output = Path(tmp) / "docs.pot"
|
||||
|
||||
with patch.object(i18n, "generate_commands", lambda src: []):
|
||||
|
||||
Reference in new issue
Block a user