1# Copyright (c) 2023 Intel Corporation 2# SPDX-License-Identifier: Apache-2.0 3 4from pathlib import Path 5from typing import Any 6 7import doxmlparser 8from docutils import nodes 9from doxmlparser.compound import DoxCompoundKind 10from sphinx.application import Sphinx 11from sphinx.util.docutils import SphinxDirective 12 13 14class ApiOverview(SphinxDirective): 15 """ 16 This is a Zephyr directive to generate a table containing an overview 17 of all APIs. This table will show the API name, version and since which 18 version it is present - all information extracted from Doxygen XML output. 19 20 It is exclusively used by the doc/develop/api/overview.rst page. 21 22 Configuration options: 23 24 api_overview_doxygen_xml_dir: Doxygen xml output directory 25 api_overview_doxygen_base_url: Doxygen base html directory 26 """ 27 28 def run(self): 29 return [self.env.api_overview_table] 30 31 32def get_group(innergroup, all_groups): 33 try: 34 return [ 35 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 41 42 43def 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("") 66 67 url_base = Path(app.config.api_overview_doxygen_base_url) 68 url = url_base / f"{cdef.get_id()}.html" 69 70 title = cdef.get_title() 71 72 row_node = nodes.row() 73 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 78 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 86 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) 94 95 for innergroup in cdef.get_innergroup(): 96 visit_group( 97 app, get_group(innergroup, all_groups), all_groups, rows, indent + 6 98 ) 99 100 101def 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 111 112def generate_table(app, toplevel, groups): 113 table = nodes.table() 114 tgroup = nodes.tgroup() 115 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 121 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 140def 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 ] 162 163 app.builder.env.api_overview_table = generate_table(app, toplevel, groups) 164 165 166def 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") 169 170 app.add_directive("api-overview-table", ApiOverview) 171 172 app.connect("builder-inited", sync_contents) 173 174 return { 175 "version": "0.1", 176 "parallel_read_safe": True, 177 "parallel_write_safe": True, 178 } 179