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 <gerard.marull@nordicsemi.no>
This commit is contained in:
parent
ae20a6aa91
commit
05c1e56f73
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user