17db96d56Sopenharmony_ci# -*- coding: utf-8 -*- 27db96d56Sopenharmony_ci""" 37db96d56Sopenharmony_ci c_annotations.py 47db96d56Sopenharmony_ci ~~~~~~~~~~~~~~~~ 57db96d56Sopenharmony_ci 67db96d56Sopenharmony_ci Supports annotations for C API elements: 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_ci * reference count annotations for C API functions. Based on 97db96d56Sopenharmony_ci refcount.py and anno-api.py in the old Python documentation tools. 107db96d56Sopenharmony_ci 117db96d56Sopenharmony_ci * stable API annotations 127db96d56Sopenharmony_ci 137db96d56Sopenharmony_ci Usage: 147db96d56Sopenharmony_ci * Set the `refcount_file` config value to the path to the reference 157db96d56Sopenharmony_ci count data file. 167db96d56Sopenharmony_ci * Set the `stable_abi_file` config value to the path to stable ABI list. 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ci :copyright: Copyright 2007-2014 by Georg Brandl. 197db96d56Sopenharmony_ci :license: Python license. 207db96d56Sopenharmony_ci""" 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_cifrom os import path 237db96d56Sopenharmony_ciimport docutils 247db96d56Sopenharmony_cifrom docutils import nodes 257db96d56Sopenharmony_cifrom docutils.parsers.rst import directives 267db96d56Sopenharmony_cifrom docutils.parsers.rst import Directive 277db96d56Sopenharmony_cifrom docutils.statemachine import StringList 287db96d56Sopenharmony_cifrom sphinx.locale import _ as sphinx_gettext 297db96d56Sopenharmony_ciimport csv 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_cifrom sphinx import addnodes 327db96d56Sopenharmony_cifrom sphinx.domains.c import CObject 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ciREST_ROLE_MAP = { 367db96d56Sopenharmony_ci 'function': 'func', 377db96d56Sopenharmony_ci 'var': 'data', 387db96d56Sopenharmony_ci 'type': 'type', 397db96d56Sopenharmony_ci 'macro': 'macro', 407db96d56Sopenharmony_ci 'type': 'type', 417db96d56Sopenharmony_ci 'member': 'member', 427db96d56Sopenharmony_ci} 437db96d56Sopenharmony_ci 447db96d56Sopenharmony_ci 457db96d56Sopenharmony_ci# Monkeypatch nodes.Node.findall for forwards compatability 467db96d56Sopenharmony_ci# This patch can be dropped when the minimum Sphinx version is 4.4.0 477db96d56Sopenharmony_ci# or the minimum Docutils version is 0.18.1. 487db96d56Sopenharmony_ciif docutils.__version_info__ < (0, 18, 1): 497db96d56Sopenharmony_ci def findall(self, *args, **kwargs): 507db96d56Sopenharmony_ci return iter(self.traverse(*args, **kwargs)) 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci nodes.Node.findall = findall 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci 557db96d56Sopenharmony_ciclass RCEntry: 567db96d56Sopenharmony_ci def __init__(self, name): 577db96d56Sopenharmony_ci self.name = name 587db96d56Sopenharmony_ci self.args = [] 597db96d56Sopenharmony_ci self.result_type = '' 607db96d56Sopenharmony_ci self.result_refs = None 617db96d56Sopenharmony_ci 627db96d56Sopenharmony_ci 637db96d56Sopenharmony_ciclass Annotations: 647db96d56Sopenharmony_ci def __init__(self, refcount_filename, stable_abi_file): 657db96d56Sopenharmony_ci self.refcount_data = {} 667db96d56Sopenharmony_ci with open(refcount_filename, 'r') as fp: 677db96d56Sopenharmony_ci for line in fp: 687db96d56Sopenharmony_ci line = line.strip() 697db96d56Sopenharmony_ci if line[:1] in ("", "#"): 707db96d56Sopenharmony_ci # blank lines and comments 717db96d56Sopenharmony_ci continue 727db96d56Sopenharmony_ci parts = line.split(":", 4) 737db96d56Sopenharmony_ci if len(parts) != 5: 747db96d56Sopenharmony_ci raise ValueError("Wrong field count in %r" % line) 757db96d56Sopenharmony_ci function, type, arg, refcount, comment = parts 767db96d56Sopenharmony_ci # Get the entry, creating it if needed: 777db96d56Sopenharmony_ci try: 787db96d56Sopenharmony_ci entry = self.refcount_data[function] 797db96d56Sopenharmony_ci except KeyError: 807db96d56Sopenharmony_ci entry = self.refcount_data[function] = RCEntry(function) 817db96d56Sopenharmony_ci if not refcount or refcount == "null": 827db96d56Sopenharmony_ci refcount = None 837db96d56Sopenharmony_ci else: 847db96d56Sopenharmony_ci refcount = int(refcount) 857db96d56Sopenharmony_ci # Update the entry with the new parameter or the result 867db96d56Sopenharmony_ci # information. 877db96d56Sopenharmony_ci if arg: 887db96d56Sopenharmony_ci entry.args.append((arg, type, refcount)) 897db96d56Sopenharmony_ci else: 907db96d56Sopenharmony_ci entry.result_type = type 917db96d56Sopenharmony_ci entry.result_refs = refcount 927db96d56Sopenharmony_ci 937db96d56Sopenharmony_ci self.stable_abi_data = {} 947db96d56Sopenharmony_ci with open(stable_abi_file, 'r') as fp: 957db96d56Sopenharmony_ci for record in csv.DictReader(fp): 967db96d56Sopenharmony_ci role = record['role'] 977db96d56Sopenharmony_ci name = record['name'] 987db96d56Sopenharmony_ci self.stable_abi_data[name] = record 997db96d56Sopenharmony_ci 1007db96d56Sopenharmony_ci def add_annotations(self, app, doctree): 1017db96d56Sopenharmony_ci for node in doctree.findall(addnodes.desc_content): 1027db96d56Sopenharmony_ci par = node.parent 1037db96d56Sopenharmony_ci if par['domain'] != 'c': 1047db96d56Sopenharmony_ci continue 1057db96d56Sopenharmony_ci if not par[0].has_key('ids') or not par[0]['ids']: 1067db96d56Sopenharmony_ci continue 1077db96d56Sopenharmony_ci name = par[0]['ids'][0] 1087db96d56Sopenharmony_ci if name.startswith("c."): 1097db96d56Sopenharmony_ci name = name[2:] 1107db96d56Sopenharmony_ci 1117db96d56Sopenharmony_ci objtype = par['objtype'] 1127db96d56Sopenharmony_ci 1137db96d56Sopenharmony_ci # Stable ABI annotation. These have two forms: 1147db96d56Sopenharmony_ci # Part of the [Stable ABI](link). 1157db96d56Sopenharmony_ci # Part of the [Stable ABI](link) since version X.Y. 1167db96d56Sopenharmony_ci # For structs, there's some more info in the message: 1177db96d56Sopenharmony_ci # Part of the [Limited API](link) (as an opaque struct). 1187db96d56Sopenharmony_ci # Part of the [Stable ABI](link) (including all members). 1197db96d56Sopenharmony_ci # Part of the [Limited API](link) (Only some members are part 1207db96d56Sopenharmony_ci # of the stable ABI.). 1217db96d56Sopenharmony_ci # ... all of which can have "since version X.Y" appended. 1227db96d56Sopenharmony_ci record = self.stable_abi_data.get(name) 1237db96d56Sopenharmony_ci if record: 1247db96d56Sopenharmony_ci if record['role'] != objtype: 1257db96d56Sopenharmony_ci raise ValueError( 1267db96d56Sopenharmony_ci f"Object type mismatch in limited API annotation " 1277db96d56Sopenharmony_ci f"for {name}: {record['role']!r} != {objtype!r}") 1287db96d56Sopenharmony_ci stable_added = record['added'] 1297db96d56Sopenharmony_ci message = ' Part of the ' 1307db96d56Sopenharmony_ci emph_node = nodes.emphasis(message, message, 1317db96d56Sopenharmony_ci classes=['stableabi']) 1327db96d56Sopenharmony_ci ref_node = addnodes.pending_xref( 1337db96d56Sopenharmony_ci 'Stable ABI', refdomain="std", reftarget='stable', 1347db96d56Sopenharmony_ci reftype='ref', refexplicit="False") 1357db96d56Sopenharmony_ci struct_abi_kind = record['struct_abi_kind'] 1367db96d56Sopenharmony_ci if struct_abi_kind in {'opaque', 'members'}: 1377db96d56Sopenharmony_ci ref_node += nodes.Text('Limited API') 1387db96d56Sopenharmony_ci else: 1397db96d56Sopenharmony_ci ref_node += nodes.Text('Stable ABI') 1407db96d56Sopenharmony_ci emph_node += ref_node 1417db96d56Sopenharmony_ci if struct_abi_kind == 'opaque': 1427db96d56Sopenharmony_ci emph_node += nodes.Text(' (as an opaque struct)') 1437db96d56Sopenharmony_ci elif struct_abi_kind == 'full-abi': 1447db96d56Sopenharmony_ci emph_node += nodes.Text(' (including all members)') 1457db96d56Sopenharmony_ci if record['ifdef_note']: 1467db96d56Sopenharmony_ci emph_node += nodes.Text(' ' + record['ifdef_note']) 1477db96d56Sopenharmony_ci if stable_added == '3.2': 1487db96d56Sopenharmony_ci # Stable ABI was introduced in 3.2. 1497db96d56Sopenharmony_ci pass 1507db96d56Sopenharmony_ci else: 1517db96d56Sopenharmony_ci emph_node += nodes.Text(f' since version {stable_added}') 1527db96d56Sopenharmony_ci emph_node += nodes.Text('.') 1537db96d56Sopenharmony_ci if struct_abi_kind == 'members': 1547db96d56Sopenharmony_ci emph_node += nodes.Text( 1557db96d56Sopenharmony_ci ' (Only some members are part of the stable ABI.)') 1567db96d56Sopenharmony_ci node.insert(0, emph_node) 1577db96d56Sopenharmony_ci 1587db96d56Sopenharmony_ci # Return value annotation 1597db96d56Sopenharmony_ci if objtype != 'function': 1607db96d56Sopenharmony_ci continue 1617db96d56Sopenharmony_ci entry = self.refcount_data.get(name) 1627db96d56Sopenharmony_ci if not entry: 1637db96d56Sopenharmony_ci continue 1647db96d56Sopenharmony_ci elif not entry.result_type.endswith("Object*"): 1657db96d56Sopenharmony_ci continue 1667db96d56Sopenharmony_ci if entry.result_refs is None: 1677db96d56Sopenharmony_ci rc = sphinx_gettext('Return value: Always NULL.') 1687db96d56Sopenharmony_ci elif entry.result_refs: 1697db96d56Sopenharmony_ci rc = sphinx_gettext('Return value: New reference.') 1707db96d56Sopenharmony_ci else: 1717db96d56Sopenharmony_ci rc = sphinx_gettext('Return value: Borrowed reference.') 1727db96d56Sopenharmony_ci node.insert(0, nodes.emphasis(rc, rc, classes=['refcount'])) 1737db96d56Sopenharmony_ci 1747db96d56Sopenharmony_ci 1757db96d56Sopenharmony_cidef init_annotations(app): 1767db96d56Sopenharmony_ci annotations = Annotations( 1777db96d56Sopenharmony_ci path.join(app.srcdir, app.config.refcount_file), 1787db96d56Sopenharmony_ci path.join(app.srcdir, app.config.stable_abi_file), 1797db96d56Sopenharmony_ci ) 1807db96d56Sopenharmony_ci app.connect('doctree-read', annotations.add_annotations) 1817db96d56Sopenharmony_ci 1827db96d56Sopenharmony_ci class LimitedAPIList(Directive): 1837db96d56Sopenharmony_ci 1847db96d56Sopenharmony_ci has_content = False 1857db96d56Sopenharmony_ci required_arguments = 0 1867db96d56Sopenharmony_ci optional_arguments = 0 1877db96d56Sopenharmony_ci final_argument_whitespace = True 1887db96d56Sopenharmony_ci 1897db96d56Sopenharmony_ci def run(self): 1907db96d56Sopenharmony_ci content = [] 1917db96d56Sopenharmony_ci for record in annotations.stable_abi_data.values(): 1927db96d56Sopenharmony_ci role = REST_ROLE_MAP[record['role']] 1937db96d56Sopenharmony_ci name = record['name'] 1947db96d56Sopenharmony_ci content.append(f'* :c:{role}:`{name}`') 1957db96d56Sopenharmony_ci 1967db96d56Sopenharmony_ci pnode = nodes.paragraph() 1977db96d56Sopenharmony_ci self.state.nested_parse(StringList(content), 0, pnode) 1987db96d56Sopenharmony_ci return [pnode] 1997db96d56Sopenharmony_ci 2007db96d56Sopenharmony_ci app.add_directive('limited-api-list', LimitedAPIList) 2017db96d56Sopenharmony_ci 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_cidef setup(app): 2047db96d56Sopenharmony_ci app.add_config_value('refcount_file', '', True) 2057db96d56Sopenharmony_ci app.add_config_value('stable_abi_file', '', True) 2067db96d56Sopenharmony_ci app.connect('builder-inited', init_annotations) 2077db96d56Sopenharmony_ci 2087db96d56Sopenharmony_ci # monkey-patch C object... 2097db96d56Sopenharmony_ci CObject.option_spec = { 2107db96d56Sopenharmony_ci 'noindex': directives.flag, 2117db96d56Sopenharmony_ci 'stableabi': directives.flag, 2127db96d56Sopenharmony_ci } 2137db96d56Sopenharmony_ci old_handle_signature = CObject.handle_signature 2147db96d56Sopenharmony_ci def new_handle_signature(self, sig, signode): 2157db96d56Sopenharmony_ci signode.parent['stableabi'] = 'stableabi' in self.options 2167db96d56Sopenharmony_ci return old_handle_signature(self, sig, signode) 2177db96d56Sopenharmony_ci CObject.handle_signature = new_handle_signature 2187db96d56Sopenharmony_ci return {'version': '1.0', 'parallel_read_safe': True} 219