zephyr/doc/_extensions/zephyr/doxybridge.py
Benjamin Cabé cf005feb9d doc: _extensions: apply ruff lint rules
This makes all Python scripts in doc/_extensions compliant w.r.t current
Ruff rules

Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
2024-11-26 15:43:52 -05:00

233 lines
6.8 KiB
Python

"""
Copyright (c) 2021 Nordic Semiconductor ASA
Copyright (c) 2024 The Linux Foundation
SPDX-License-Identifier: Apache-2.0
"""
import concurrent.futures
import os
from typing import Any
import doxmlparser
from docutils import nodes
from doxmlparser.compound import DoxCompoundKind, DoxMemberKind
from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.domains.c import CXRefRole
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
logger = logging.getLogger(__name__)
KIND_D2S = {
DoxMemberKind.DEFINE: "macro",
DoxMemberKind.VARIABLE: "var",
DoxMemberKind.TYPEDEF: "type",
DoxMemberKind.ENUM: "enum",
DoxMemberKind.FUNCTION: "func",
}
class DoxygenGroupDirective(SphinxDirective):
has_content = False
required_arguments = 1
optional_arguments = 0
def run(self):
desc_node = addnodes.desc()
desc_node["domain"] = "c"
desc_node["objtype"] = "group"
title_signode = addnodes.desc_signature()
group_xref = addnodes.pending_xref(
"",
refdomain="c",
reftype="group",
reftarget=self.arguments[0],
refwarn=True,
)
group_xref += nodes.Text(self.arguments[0])
title_signode += group_xref
desc_node.append(title_signode)
return [desc_node]
class DoxygenReferencer(SphinxPostTransform):
"""Mapping between Doxygen memberdef kind and Sphinx kinds"""
default_priority = 5
def run(self, **kwargs: Any) -> None:
for node in self.document.traverse(addnodes.pending_xref):
if node.get("refdomain") != "c":
continue
reftype = node.get("reftype")
# "member", "data" and "var" are equivalent as per Sphinx documentation for C domain
if reftype in ("member", "data"):
reftype = "var"
entry = self.app.env.doxybridge_cache.get(reftype)
if not entry:
continue
reftarget = node.get("reftarget").replace(".", "::").rstrip("()")
id = entry.get(reftarget)
if not id:
if reftype == "func":
# macros are sometimes referenced as functions, so try that
id = self.app.env.doxybridge_cache.get("macro").get(reftarget)
if not id:
continue
else:
continue
if reftype in ("struct", "union", "group"):
doxygen_target = f"{id}.html"
else:
split = id.split("_")
doxygen_target = f"{'_'.join(split[:-1])}.html#{split[-1][1:]}"
doxygen_target = str(self.app.config.doxybridge_dir) + "/html/" + doxygen_target
doc_dir = os.path.dirname(self.document.get("source"))
doc_dest = os.path.join(
self.app.outdir,
os.path.relpath(doc_dir, self.app.srcdir),
)
rel_uri = os.path.relpath(doxygen_target, doc_dest)
refnode = nodes.reference("", "", internal=True, refuri=rel_uri, reftitle="")
refnode.append(node[0].deepcopy())
if reftype == "group":
refnode["classes"].append("doxygroup")
title = self.app.env.doxybridge_group_titles.get(reftarget, "group")
refnode[0] = nodes.Text(title)
node.replace_self([refnode])
def parse_members(sectiondef):
cache = {}
for memberdef in sectiondef.get_memberdef():
kind = KIND_D2S.get(memberdef.get_kind())
if not kind:
continue
id = memberdef.get_id()
if memberdef.get_kind() == DoxMemberKind.VARIABLE:
name = memberdef.get_qualifiedname() or memberdef.get_name()
else:
name = memberdef.get_name()
cache.setdefault(kind, {})[name] = id
if memberdef.get_kind() == DoxMemberKind.ENUM:
for enumvalue in memberdef.get_enumvalue():
enumname = enumvalue.get_name()
enumid = enumvalue.get_id()
cache.setdefault("enumerator", {})[enumname] = enumid
return cache
def parse_sections(compounddef):
cache = {}
for sectiondef in compounddef.get_sectiondef():
members = parse_members(sectiondef)
for kind, data in members.items():
cache.setdefault(kind, {}).update(data)
return cache
def parse_compound(inDirName, baseName) -> dict:
rootObj = doxmlparser.compound.parse(inDirName + "/" + baseName + ".xml", True)
cache = {}
group_titles = {}
for compounddef in rootObj.get_compounddef():
name = compounddef.get_compoundname()
id = compounddef.get_id()
kind = None
if compounddef.get_kind() == DoxCompoundKind.STRUCT:
kind = "struct"
elif compounddef.get_kind() == DoxCompoundKind.UNION:
kind = "union"
elif compounddef.get_kind() == DoxCompoundKind.GROUP:
kind = "group"
group_titles[name] = compounddef.get_title()
if kind:
cache.setdefault(kind, {})[name] = id
sections = parse_sections(compounddef)
for kind, data in sections.items():
cache.setdefault(kind, {}).update(data)
return cache, group_titles
def parse_index(app: Sphinx, inDirName):
rootObj = doxmlparser.index.parse(inDirName + "/index.xml", True)
compounds = rootObj.get_compound()
with concurrent.futures.ProcessPoolExecutor() as executor:
futures = [
executor.submit(parse_compound, inDirName, compound.get_refid())
for compound in compounds
]
for future in concurrent.futures.as_completed(futures):
cache, group_titles = future.result()
for kind, data in cache.items():
app.env.doxybridge_cache.setdefault(kind, {}).update(data)
app.env.doxybridge_group_titles.update(group_titles)
def doxygen_parse(app: Sphinx) -> None:
if not app.env.doxygen_input_changed:
return
app.env.doxybridge_cache = {
"macro": {},
"var": {},
"type": {},
"enum": {},
"enumerator": {},
"func": {},
"union": {},
"struct": {},
"group": {},
}
app.env.doxybridge_group_titles = {}
parse_index(app, str(app.config.doxybridge_dir / "xml"))
def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("doxybridge_dir", None, "env")
app.add_directive("doxygengroup", DoxygenGroupDirective)
app.add_role_to_domain("c", "group", CXRefRole())
app.add_post_transform(DoxygenReferencer)
app.connect("builder-inited", doxygen_parse)
return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}