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 = [s for p in cdef.get_detaileddescription().get_para() for s in p.get_simplesect()] 93 for sect in ssects: 94 if sect.get_kind() == "since": 95 since = sect.get_para()[0].get_valueOf_() 96 elif sect.get_kind() == "version": 97 version = sect.get_para()[0].get_valueOf_() 98 99 if since: 100 since_url = nodes.inline() 101 reference = nodes.reference( 102 text=f"v{since.strip()}.0", refuri=f"{github_uri}/v{since.strip()}.0" 103 ) 104 reference.attributes["internal"] = True 105 since_url += reference 106 else: 107 since_url = nodes.Text("") 108 109 url_base = Path(self.config.api_overview_doxygen_out_dir + "/html") 110 abs_url = url_base / f"{cdef.get_id()}.html" 111 doc_dir = os.path.dirname(self.get_source_info()[0]) 112 doc_dest = os.path.join( 113 self.env.app.outdir, 114 os.path.relpath(doc_dir, self.env.app.srcdir), 115 ) 116 url = os.path.relpath(abs_url, doc_dest) 117 118 title = cdef.get_title() 119 120 row_node = nodes.row() 121 122 # Next entry will contain the spacer and the link with API name 123 entry = nodes.entry() 124 span = nodes.Text("".join(["\U000000a0"] * indent)) 125 entry += span 126 127 # API name with link 128 inline = nodes.inline() 129 reference = nodes.reference(text=title, refuri=str(url)) 130 reference.attributes["internal"] = True 131 inline += reference 132 entry += inline 133 row_node += entry 134 135 version_node = nodes.Text(version) 136 # Finally, add version and since 137 for cell in [version_node, since_url]: 138 entry = nodes.entry() 139 entry += cell 140 row_node += entry 141 rows.append(row_node) 142 143 for innergroup in cdef.get_innergroup(): 144 self.visit_group(get_group(innergroup, all_groups), all_groups, rows, indent + 6) 145 146 147def setup(app) -> dict[str, Any]: 148 app.add_config_value("api_overview_doxygen_out_dir", "", "env") 149 app.add_config_value("api_overview_base_url", "", "env") 150 151 app.add_directive("api-overview-table", ApiOverview) 152 153 return { 154 "version": "0.1", 155 "parallel_read_safe": True, 156 "parallel_write_safe": True, 157 } 158