Skip to content

Commit 05c1e56

Browse files
gmarullkartben
authored andcommitted
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 <[email protected]>
1 parent ae20a6a commit 05c1e56

File tree

2 files changed

+112
-125
lines changed

2 files changed

+112
-125
lines changed

doc/_extensions/zephyr/api_overview.py

Lines changed: 111 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
# Copyright (c) 2023 Intel Corporation
22
# SPDX-License-Identifier: Apache-2.0
33

4+
import os
45
from pathlib import Path
56
from typing import Any
67

78
import doxmlparser
89
from docutils import nodes
910
from doxmlparser.compound import DoxCompoundKind
10-
from sphinx.application import Sphinx
1111
from sphinx.util.docutils import SphinxDirective
1212

1313

14+
def get_group(innergroup, all_groups):
15+
try:
16+
return [
17+
g
18+
for g in all_groups
19+
if g.get_compounddef()[0].get_id() == innergroup.get_refid()
20+
][0]
21+
except IndexError as e:
22+
raise Exception(f"Unexpected group {innergroup.get_refid()}") from e
23+
24+
25+
def parse_xml_dir(dir_name):
26+
groups = []
27+
root = doxmlparser.index.parse(Path(dir_name) / "index.xml", True)
28+
for compound in root.get_compound():
29+
if compound.get_kind() == DoxCompoundKind.GROUP:
30+
file_name = Path(dir_name) / f"{compound.get_refid()}.xml"
31+
groups.append(doxmlparser.compound.parse(file_name, True))
32+
33+
return groups
34+
35+
1436
class ApiOverview(SphinxDirective):
1537
"""
1638
This is a Zephyr directive to generate a table containing an overview
@@ -21,156 +43,121 @@ class ApiOverview(SphinxDirective):
2143
2244
Configuration options:
2345
24-
api_overview_doxygen_xml_dir: Doxygen xml output directory
25-
api_overview_doxygen_base_url: Doxygen base html directory
46+
api_overview_doxygen_out_dir: Doxygen output directory
2647
"""
2748

2849
def run(self):
29-
return [self.env.api_overview_table]
30-
50+
groups = parse_xml_dir(self.config.api_overview_doxygen_out_dir + "/xml")
3151

32-
def get_group(innergroup, all_groups):
33-
try:
34-
return [
52+
toplevel = [
3553
g
36-
for g in all_groups
37-
if g.get_compounddef()[0].get_id() == innergroup.get_refid()
38-
][0]
39-
except IndexError as e:
40-
raise Exception(f"Unexpected group {innergroup.get_refid()}") from e
54+
for g in groups
55+
if g.get_compounddef()[0].get_id()
56+
not in [
57+
i.get_refid()
58+
for h in [j.get_compounddef()[0].get_innergroup() for j in groups]
59+
for i in h
60+
]
61+
]
4162

63+
return [self.generate_table(toplevel, groups)]
4264

43-
def visit_group(app, group, all_groups, rows, indent=0):
44-
version = since = ""
45-
github_uri = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/"
46-
cdef = group.get_compounddef()[0]
47-
48-
ssects = [
49-
s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect()
50-
]
51-
for sect in ssects:
52-
if sect.get_kind() == "since":
53-
since = sect.get_para()[0].get_valueOf_()
54-
elif sect.get_kind() == "version":
55-
version = sect.get_para()[0].get_valueOf_()
56-
57-
if since:
58-
since_url = nodes.inline()
59-
reference = nodes.reference(
60-
text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0"
61-
)
62-
reference.attributes["internal"] = True
63-
since_url += reference
64-
else:
65-
since_url = nodes.Text("")
65+
def generate_table(self, toplevel, groups):
66+
table = nodes.table()
67+
tgroup = nodes.tgroup()
6668

67-
url_base = Path(app.config.api_overview_doxygen_base_url)
68-
url = url_base / f"{cdef.get_id()}.html"
69+
thead = nodes.thead()
70+
thead_row = nodes.row()
71+
for header_name in ["API", "Version", "Available in Zephyr Since"]:
72+
colspec = nodes.colspec()
73+
tgroup += colspec
6974

70-
title = cdef.get_title()
75+
entry = nodes.entry()
76+
entry += nodes.Text(header_name)
77+
thead_row += entry
78+
thead += thead_row
79+
tgroup += thead
7180

72-
row_node = nodes.row()
81+
rows = []
82+
tbody = nodes.tbody()
83+
for t in toplevel:
84+
self.visit_group(t, groups, rows)
85+
tbody.extend(rows)
86+
tgroup += tbody
7387

74-
# Next entry will contain the spacer and the link with API name
75-
entry = nodes.entry()
76-
span = nodes.Text("".join(["\U000000A0"] * indent))
77-
entry += span
88+
table += tgroup
7889

79-
# API name with link
80-
inline = nodes.inline()
81-
reference = nodes.reference(text=title, refuri=str(url))
82-
reference.attributes["internal"] = True
83-
inline += reference
84-
entry += inline
85-
row_node += entry
90+
return table
8691

87-
version_node = nodes.Text(version)
88-
# Finally, add version and since
89-
for cell in [version_node, since_url]:
90-
entry = nodes.entry()
91-
entry += cell
92-
row_node += entry
93-
rows.append(row_node)
92+
def visit_group(self, group, all_groups, rows, indent=0):
93+
version = since = ""
94+
github_uri = "https://github.com/zephyrproject-rtos/zephyr/releases/tag/"
95+
cdef = group.get_compounddef()[0]
9496

95-
for innergroup in cdef.get_innergroup():
96-
visit_group(
97-
app, get_group(innergroup, all_groups), all_groups, rows, indent + 6
97+
ssects = [
98+
s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect()
99+
]
100+
for sect in ssects:
101+
if sect.get_kind() == "since":
102+
since = sect.get_para()[0].get_valueOf_()
103+
elif sect.get_kind() == "version":
104+
version = sect.get_para()[0].get_valueOf_()
105+
106+
if since:
107+
since_url = nodes.inline()
108+
reference = nodes.reference(
109+
text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0"
110+
)
111+
reference.attributes["internal"] = True
112+
since_url += reference
113+
else:
114+
since_url = nodes.Text("")
115+
116+
url_base = Path(self.config.api_overview_doxygen_out_dir + "/html")
117+
abs_url = url_base / f"{cdef.get_id()}.html"
118+
doc_dir = os.path.dirname(self.get_source_info()[0])
119+
doc_dest = os.path.join(
120+
self.env.app.outdir,
121+
os.path.relpath(doc_dir, self.env.app.srcdir),
98122
)
123+
url = os.path.relpath(abs_url, doc_dest)
99124

125+
title = cdef.get_title()
100126

101-
def parse_xml_dir(dir_name):
102-
groups = []
103-
root = doxmlparser.index.parse(Path(dir_name) / "index.xml", True)
104-
for compound in root.get_compound():
105-
if compound.get_kind() == DoxCompoundKind.GROUP:
106-
file_name = Path(dir_name) / f"{compound.get_refid()}.xml"
107-
groups.append(doxmlparser.compound.parse(file_name, True))
108-
109-
return groups
110-
127+
row_node = nodes.row()
111128

112-
def generate_table(app, toplevel, groups):
113-
table = nodes.table()
114-
tgroup = nodes.tgroup()
129+
# Next entry will contain the spacer and the link with API name
130+
entry = nodes.entry()
131+
span = nodes.Text("".join(["\U000000A0"] * indent))
132+
entry += span
115133

116-
thead = nodes.thead()
117-
thead_row = nodes.row()
118-
for header_name in ["API", "Version", "Available in Zephyr Since"]:
119-
colspec = nodes.colspec()
120-
tgroup += colspec
134+
# API name with link
135+
inline = nodes.inline()
136+
reference = nodes.reference(text=title, refuri=str(url))
137+
reference.attributes["internal"] = True
138+
inline += reference
139+
entry += inline
140+
row_node += entry
121141

122-
entry = nodes.entry()
123-
entry += nodes.Text(header_name)
124-
thead_row += entry
125-
thead += thead_row
126-
tgroup += thead
127-
128-
rows = []
129-
tbody = nodes.tbody()
130-
for t in toplevel:
131-
visit_group(app, t, groups, rows)
132-
tbody.extend(rows)
133-
tgroup += tbody
134-
135-
table += tgroup
136-
137-
return table
138-
139-
140-
def sync_contents(app: Sphinx) -> None:
141-
if app.config.doxyrunner_outdir:
142-
doxygen_out_dir = Path(app.config.doxyrunner_outdir)
143-
else:
144-
doxygen_out_dir = Path(app.outdir) / "_doxygen"
145-
146-
if not app.env.doxygen_input_changed:
147-
return
148-
149-
doxygen_xml_dir = doxygen_out_dir / "xml"
150-
groups = parse_xml_dir(doxygen_xml_dir)
151-
152-
toplevel = [
153-
g
154-
for g in groups
155-
if g.get_compounddef()[0].get_id()
156-
not in [
157-
i.get_refid()
158-
for h in [j.get_compounddef()[0].get_innergroup() for j in groups]
159-
for i in h
160-
]
161-
]
142+
version_node = nodes.Text(version)
143+
# Finally, add version and since
144+
for cell in [version_node, since_url]:
145+
entry = nodes.entry()
146+
entry += cell
147+
row_node += entry
148+
rows.append(row_node)
162149

163-
app.builder.env.api_overview_table = generate_table(app, toplevel, groups)
150+
for innergroup in cdef.get_innergroup():
151+
self.visit_group(
152+
get_group(innergroup, all_groups), all_groups, rows, indent + 6
153+
)
164154

165155

166156
def setup(app) -> dict[str, Any]:
167-
app.add_config_value("api_overview_doxygen_xml_dir", "html/doxygen/xml", "env")
168-
app.add_config_value("api_overview_doxygen_base_url", "../../doxygen/html", "env")
157+
app.add_config_value("api_overview_doxygen_out_dir", "", "env")
169158

170159
app.add_directive("api-overview-table", ApiOverview)
171160

172-
app.connect("builder-inited", sync_contents)
173-
174161
return {
175162
"version": "0.1",
176163
"parallel_read_safe": True,

doc/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@
356356

357357
# -- Options for zephyr.api_overview --------------------------------------
358358

359-
api_overview_doxygen_base_url = "../../doxygen/html"
359+
api_overview_doxygen_out_dir = str(doxyrunner_outdir)
360360

361361
def setup(app):
362362
# theme customizations

0 commit comments

Comments
 (0)