From 05c1e56f73a60f8dce67526699daa11536ff96ed Mon Sep 17 00:00:00 2001 From: Gerard Marull-Paretas Date: Mon, 2 Dec 2024 14:57:22 +0100 Subject: [PATCH] doc: extensions: api_overview: refactor extension The extension had a some major design flaws, mainly: - Ignored the `api_overview_doxygen_xml_dir` setting, instead it "sniffed" doxyrunner properties, so violating environment boundaries - Computation of Doxygen HTML path worked because of the hardcoded URL in relative form found in doc/conf.py This patch moves most code to the actual directive, so that context can be obtained from the document being parsed. Also, the only config required now is the Doxygen output dir, obtained from doxyrunner at conf.py level. Signed-off-by: Gerard Marull-Paretas --- doc/_extensions/zephyr/api_overview.py | 239 ++++++++++++------------- doc/conf.py | 2 +- 2 files changed, 114 insertions(+), 127 deletions(-) diff --git a/doc/_extensions/zephyr/api_overview.py b/doc/_extensions/zephyr/api_overview.py index 3d56e5eaee4..384836259bf 100644 --- a/doc/_extensions/zephyr/api_overview.py +++ b/doc/_extensions/zephyr/api_overview.py @@ -1,34 +1,16 @@ # Copyright (c) 2023 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +import os from pathlib import Path from typing import Any import doxmlparser from docutils import nodes from doxmlparser.compound import DoxCompoundKind -from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective -class ApiOverview(SphinxDirective): - """ - This is a Zephyr directive to generate a table containing an overview - of all APIs. This table will show the API name, version and since which - version it is present - all information extracted from Doxygen XML output. - - It is exclusively used by the doc/develop/api/overview.rst page. - - Configuration options: - - api_overview_doxygen_xml_dir: Doxygen xml output directory - api_overview_doxygen_base_url: Doxygen base html directory - """ - - def run(self): - return [self.env.api_overview_table] - - def get_group(innergroup, all_groups): try: return [ @@ -40,64 +22,6 @@ def get_group(innergroup, all_groups): raise Exception(f"Unexpected group {innergroup.get_refid()}") from e -def visit_group(app, group, all_groups, rows, indent=0): - version = since = "" - github_uri = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/" - cdef = group.get_compounddef()[0] - - ssects = [ - s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect() - ] - for sect in ssects: - if sect.get_kind() == "since": - since = sect.get_para()[0].get_valueOf_() - elif sect.get_kind() == "version": - version = sect.get_para()[0].get_valueOf_() - - if since: - since_url = nodes.inline() - reference = nodes.reference( - text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0" - ) - reference.attributes["internal"] = True - since_url += reference - else: - since_url = nodes.Text("") - - url_base = Path(app.config.api_overview_doxygen_base_url) - url = url_base / f"{cdef.get_id()}.html" - - title = cdef.get_title() - - row_node = nodes.row() - - # Next entry will contain the spacer and the link with API name - entry = nodes.entry() - span = nodes.Text("".join(["\U000000A0"] * indent)) - entry += span - - # API name with link - inline = nodes.inline() - reference = nodes.reference(text=title, refuri=str(url)) - reference.attributes["internal"] = True - inline += reference - entry += inline - row_node += entry - - version_node = nodes.Text(version) - # Finally, add version and since - for cell in [version_node, since_url]: - entry = nodes.entry() - entry += cell - row_node += entry - rows.append(row_node) - - for innergroup in cdef.get_innergroup(): - visit_group( - app, get_group(innergroup, all_groups), all_groups, rows, indent + 6 - ) - - def parse_xml_dir(dir_name): groups = [] root = doxmlparser.index.parse(Path(dir_name) / "index.xml", True) @@ -109,68 +33,131 @@ def parse_xml_dir(dir_name): return groups -def generate_table(app, toplevel, groups): - table = nodes.table() - tgroup = nodes.tgroup() +class ApiOverview(SphinxDirective): + """ + This is a Zephyr directive to generate a table containing an overview + of all APIs. This table will show the API name, version and since which + version it is present - all information extracted from Doxygen XML output. - thead = nodes.thead() - thead_row = nodes.row() - for header_name in ["API", "Version", "Available in Zephyr Since"]: - colspec = nodes.colspec() - tgroup += colspec + It is exclusively used by the doc/develop/api/overview.rst page. - entry = nodes.entry() - entry += nodes.Text(header_name) - thead_row += entry - thead += thead_row - tgroup += thead + Configuration options: - rows = [] - tbody = nodes.tbody() - for t in toplevel: - visit_group(app, t, groups, rows) - tbody.extend(rows) - tgroup += tbody + api_overview_doxygen_out_dir: Doxygen output directory + """ - table += tgroup + def run(self): + groups = parse_xml_dir(self.config.api_overview_doxygen_out_dir + "/xml") - return table - - -def sync_contents(app: Sphinx) -> None: - if app.config.doxyrunner_outdir: - doxygen_out_dir = Path(app.config.doxyrunner_outdir) - else: - doxygen_out_dir = Path(app.outdir) / "_doxygen" - - if not app.env.doxygen_input_changed: - return - - doxygen_xml_dir = doxygen_out_dir / "xml" - groups = parse_xml_dir(doxygen_xml_dir) - - toplevel = [ - g - for g in groups - if g.get_compounddef()[0].get_id() - not in [ - i.get_refid() - for h in [j.get_compounddef()[0].get_innergroup() for j in groups] - for i in h + toplevel = [ + g + for g in groups + if g.get_compounddef()[0].get_id() + not in [ + i.get_refid() + for h in [j.get_compounddef()[0].get_innergroup() for j in groups] + for i in h + ] ] - ] - app.builder.env.api_overview_table = generate_table(app, toplevel, groups) + return [self.generate_table(toplevel, groups)] + + def generate_table(self, toplevel, groups): + table = nodes.table() + tgroup = nodes.tgroup() + + thead = nodes.thead() + thead_row = nodes.row() + for header_name in ["API", "Version", "Available in Zephyr Since"]: + colspec = nodes.colspec() + tgroup += colspec + + entry = nodes.entry() + entry += nodes.Text(header_name) + thead_row += entry + thead += thead_row + tgroup += thead + + rows = [] + tbody = nodes.tbody() + for t in toplevel: + self.visit_group(t, groups, rows) + tbody.extend(rows) + tgroup += tbody + + table += tgroup + + return table + + def visit_group(self, group, all_groups, rows, indent=0): + version = since = "" + github_uri = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/" + cdef = group.get_compounddef()[0] + + ssects = [ + s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect() + ] + for sect in ssects: + if sect.get_kind() == "since": + since = sect.get_para()[0].get_valueOf_() + elif sect.get_kind() == "version": + version = sect.get_para()[0].get_valueOf_() + + if since: + since_url = nodes.inline() + reference = nodes.reference( + text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0" + ) + reference.attributes["internal"] = True + since_url += reference + else: + since_url = nodes.Text("") + + url_base = Path(self.config.api_overview_doxygen_out_dir + "/html") + abs_url = url_base / f"{cdef.get_id()}.html" + doc_dir = os.path.dirname(self.get_source_info()[0]) + doc_dest = os.path.join( + self.env.app.outdir, + os.path.relpath(doc_dir, self.env.app.srcdir), + ) + url = os.path.relpath(abs_url, doc_dest) + + title = cdef.get_title() + + row_node = nodes.row() + + # Next entry will contain the spacer and the link with API name + entry = nodes.entry() + span = nodes.Text("".join(["\U000000A0"] * indent)) + entry += span + + # API name with link + inline = nodes.inline() + reference = nodes.reference(text=title, refuri=str(url)) + reference.attributes["internal"] = True + inline += reference + entry += inline + row_node += entry + + version_node = nodes.Text(version) + # Finally, add version and since + for cell in [version_node, since_url]: + entry = nodes.entry() + entry += cell + row_node += entry + rows.append(row_node) + + for innergroup in cdef.get_innergroup(): + self.visit_group( + get_group(innergroup, all_groups), all_groups, rows, indent + 6 + ) def setup(app) -> dict[str, Any]: - app.add_config_value("api_overview_doxygen_xml_dir", "html/doxygen/xml", "env") - app.add_config_value("api_overview_doxygen_base_url", "../../doxygen/html", "env") + app.add_config_value("api_overview_doxygen_out_dir", "", "env") app.add_directive("api-overview-table", ApiOverview) - app.connect("builder-inited", sync_contents) - return { "version": "0.1", "parallel_read_safe": True, diff --git a/doc/conf.py b/doc/conf.py index fd1ab005388..c399ea6d4e5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -356,7 +356,7 @@ linkcheck_anchors = False # -- Options for zephyr.api_overview -------------------------------------- -api_overview_doxygen_base_url = "../../doxygen/html" +api_overview_doxygen_out_dir = str(doxyrunner_outdir) def setup(app): # theme customizations