1# Copyright (c) 2023 Intel Corporation 2# SPDX-License-Identifier: Apache-2.0 3 4import os 5from pathlib import Path 6from typing import Any 7 8import doxmlparser 9from docutils import nodes 10from doxmlparser.compound import DoxCompoundKind 11from sphinx.util.docutils import SphinxDirective 12 13 14def get_group(innergroup, all_groups): 15 try: 16 return [ 17 group 18 for group in all_groups 19 if group.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 25def 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 36class ApiOverview(SphinxDirective): 37 """ 38 This is a Zephyr directive to generate a table containing an overview 39 of all APIs. This table will show the API name, version and since which 40 version it is present - all information extracted from Doxygen XML output. 41 42 It is exclusively used by the doc/develop/api/overview.rst page. 43 44 Configuration options: 45 46 api_overview_doxygen_out_dir: Doxygen output directory 47 """ 48 49 def run(self): 50 groups = parse_xml_dir(self.config.api_overview_doxygen_out_dir + "/xml") 51 52 inners = [group.get_compounddef()[0].get_innergroup() for group in groups] 53 inner_ids = [i.get_refid() for inner in inners for i in inner] 54 toplevel = [ 55 group for group in groups if group.get_compounddef()[0].get_id() not in inner_ids 56 ] 57 58 return [self.generate_table(toplevel, groups)] 59 60 def generate_table(self, toplevel, groups): 61 table = nodes.table() 62 tgroup = nodes.tgroup() 63 64 thead = nodes.thead() 65 thead_row = nodes.row() 66 for header_name in ["API", "Version", "Available in Zephyr Since"]: 67 colspec = nodes.colspec() 68 tgroup += colspec 69 70 entry = nodes.entry() 71 entry += nodes.Text(header_name) 72 thead_row += entry 73 thead += thead_row 74 tgroup += thead 75 76 rows = [] 77 tbody = nodes.tbody() 78 for t in toplevel: 79 self.visit_group(t, groups, rows) 80 tbody.extend(rows) 81 tgroup += tbody 82 83 table += tgroup 84 85 return table 86 87 def visit_group(self, group, all_groups, rows, indent=0): 88 version = since = "" 89 github_uri = self.config.api_overview_base_url + "/releases/tag/" 90 cdef = group.get_compounddef()[0] 91 92 ssects = [ 93 s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect() 94 ] 95 for sect in ssects: 96 if sect.get_kind() == "since": 97 since = sect.get_para()[0].get_valueOf_() 98 elif sect.get_kind() == "version": 99 version = sect.get_para()[0].get_valueOf_() 100 101 if since: 102 since_url = nodes.inline() 103 reference = nodes.reference( 104 text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0" 105 ) 106 reference.attributes["internal"] = True 107 since_url += reference 108 else: 109 since_url = nodes.Text("") 110 111 url_base = Path(self.config.api_overview_doxygen_out_dir + "/html") 112 abs_url = url_base / f"{cdef.get_id()}.html" 113 doc_dir = os.path.dirname(self.get_source_info()[0]) 114 doc_dest = os.path.join( 115 self.env.app.outdir, 116 os.path.relpath(doc_dir, self.env.app.srcdir), 117 ) 118 url = os.path.relpath(abs_url, doc_dest) 119 120 title = cdef.get_title() 121 122 row_node = nodes.row() 123 124 # Next entry will contain the spacer and the link with API name 125 entry = nodes.entry() 126 span = nodes.Text("".join(["\U000000A0"] * indent)) 127 entry += span 128 129 # API name with link 130 inline = nodes.inline() 131 reference = nodes.reference(text=title, refuri=str(url)) 132 reference.attributes["internal"] = True 133 inline += reference 134 entry += inline 135 row_node += entry 136 137 version_node = nodes.Text(version) 138 # Finally, add version and since 139 for cell in [version_node, since_url]: 140 entry = nodes.entry() 141 entry += cell 142 row_node += entry 143 rows.append(row_node) 144 145 for innergroup in cdef.get_innergroup(): 146 self.visit_group( 147 get_group(innergroup, all_groups), all_groups, rows, indent + 6 148 ) 149 150 151def setup(app) -> dict[str, Any]: 152 app.add_config_value("api_overview_doxygen_out_dir", "", "env") 153 app.add_config_value("api_overview_base_url", "", "env") 154 155 app.add_directive("api-overview-table", ApiOverview) 156 157 return { 158 "version": "0.1", 159 "parallel_read_safe": True, 160 "parallel_write_safe": True, 161 } 162