1# Copyright (c) 2023 Intel Corporation 2# SPDX-License-Identifier: Apache-2.0 3 4import doxmlparser 5 6from docutils import nodes 7from doxmlparser.compound import DoxCompoundKind 8from pathlib import Path 9from sphinx.application import Sphinx 10from sphinx.util.docutils import SphinxDirective 11from typing import Any, Dict 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(text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0") 60 reference.attributes["internal"] = True 61 since_url += reference 62 else: 63 since_url = nodes.Text("") 64 65 url_base = Path(app.config.api_overview_doxygen_base_url) 66 url = url_base / f"{cdef.get_id()}.html" 67 68 title = cdef.get_title() 69 70 row_node = nodes.row() 71 72 # Next entry will contain the spacer and the link with API name 73 entry = nodes.entry() 74 span = nodes.Text("".join(["\U000000A0"] * indent)) 75 entry += span 76 77 # API name with link 78 inline = nodes.inline() 79 reference = nodes.reference(text=title, refuri=str(url)) 80 reference.attributes["internal"] = True 81 inline += reference 82 entry += inline 83 row_node += entry 84 85 version_node = nodes.Text(version) 86 # Finally, add version and since 87 for cell in [version_node, since_url]: 88 entry = nodes.entry() 89 entry += cell 90 row_node += entry 91 rows.append(row_node) 92 93 for innergroup in cdef.get_innergroup(): 94 visit_group( 95 app, get_group(innergroup, all_groups), all_groups, rows, indent + 6 96 ) 97 98 99def parse_xml_dir(dir_name): 100 groups = [] 101 root = doxmlparser.index.parse(Path(dir_name) / "index.xml", True) 102 for compound in root.get_compound(): 103 if compound.get_kind() == DoxCompoundKind.GROUP: 104 file_name = Path(dir_name) / f"{compound.get_refid()}.xml" 105 groups.append(doxmlparser.compound.parse(file_name, True)) 106 107 return groups 108 109 110def generate_table(app, toplevel, groups): 111 table = nodes.table() 112 tgroup = nodes.tgroup() 113 114 thead = nodes.thead() 115 thead_row = nodes.row() 116 for header_name in ["API", "Version", "Available in Zephyr Since"]: 117 colspec = nodes.colspec() 118 tgroup += colspec 119 120 entry = nodes.entry() 121 entry += nodes.Text(header_name) 122 thead_row += entry 123 thead += thead_row 124 tgroup += thead 125 126 rows = [] 127 tbody = nodes.tbody() 128 for t in toplevel: 129 visit_group(app, t, groups, rows) 130 tbody.extend(rows) 131 tgroup += tbody 132 133 table += tgroup 134 135 return table 136 137 138def sync_contents(app: Sphinx) -> None: 139 if app.config.doxyrunner_outdir: 140 doxygen_out_dir = Path(app.config.doxyrunner_outdir) 141 else: 142 doxygen_out_dir = Path(app.outdir) / "_doxygen" 143 144 if not app.env.doxygen_input_changed: 145 return 146 147 doxygen_xml_dir = doxygen_out_dir / "xml" 148 groups = parse_xml_dir(doxygen_xml_dir) 149 150 toplevel = [ 151 g 152 for g in groups 153 if g.get_compounddef()[0].get_id() 154 not in [ 155 i.get_refid() 156 for h in [j.get_compounddef()[0].get_innergroup() for j in groups] 157 for i in h 158 ] 159 ] 160 161 app.builder.env.api_overview_table = generate_table(app, toplevel, groups) 162 163 164def setup(app) -> Dict[str, Any]: 165 app.add_config_value("api_overview_doxygen_xml_dir", "html/doxygen/xml", "env") 166 app.add_config_value("api_overview_doxygen_base_url", "../../doxygen/html", "env") 167 168 app.add_directive("api-overview-table", ApiOverview) 169 170 app.connect("builder-inited", sync_contents) 171 172 return { 173 "version": "0.1", 174 "parallel_read_safe": True, 175 "parallel_write_safe": True, 176 } 177