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