Path: blob/main/Doc/tools/extensions/c_annotations.py
12 views
# -*- coding: utf-8 -*-1"""2c_annotations.py3~~~~~~~~~~~~~~~~45Supports annotations for C API elements:67* reference count annotations for C API functions. Based on8refcount.py and anno-api.py in the old Python documentation tools.910* stable API annotations1112Usage:13* Set the `refcount_file` config value to the path to the reference14count data file.15* Set the `stable_abi_file` config value to the path to stable ABI list.1617:copyright: Copyright 2007-2014 by Georg Brandl.18:license: Python license.19"""2021from os import path22import docutils23from docutils import nodes24from docutils.parsers.rst import directives25from docutils.parsers.rst import Directive26from docutils.statemachine import StringList27from sphinx.locale import _ as sphinx_gettext28import csv2930from sphinx import addnodes31from sphinx.domains.c import CObject323334REST_ROLE_MAP = {35'function': 'func',36'var': 'data',37'type': 'type',38'macro': 'macro',39'type': 'type',40'member': 'member',41}424344# Monkeypatch nodes.Node.findall for forwards compatability45# This patch can be dropped when the minimum Sphinx version is 4.4.046# or the minimum Docutils version is 0.18.1.47if docutils.__version_info__ < (0, 18, 1):48def findall(self, *args, **kwargs):49return iter(self.traverse(*args, **kwargs))5051nodes.Node.findall = findall525354class RCEntry:55def __init__(self, name):56self.name = name57self.args = []58self.result_type = ''59self.result_refs = None606162class Annotations:63def __init__(self, refcount_filename, stable_abi_file):64self.refcount_data = {}65with open(refcount_filename, 'r') as fp:66for line in fp:67line = line.strip()68if line[:1] in ("", "#"):69# blank lines and comments70continue71parts = line.split(":", 4)72if len(parts) != 5:73raise ValueError("Wrong field count in %r" % line)74function, type, arg, refcount, comment = parts75# Get the entry, creating it if needed:76try:77entry = self.refcount_data[function]78except KeyError:79entry = self.refcount_data[function] = RCEntry(function)80if not refcount or refcount == "null":81refcount = None82else:83refcount = int(refcount)84# Update the entry with the new parameter or the result85# information.86if arg:87entry.args.append((arg, type, refcount))88else:89entry.result_type = type90entry.result_refs = refcount9192self.stable_abi_data = {}93with open(stable_abi_file, 'r') as fp:94for record in csv.DictReader(fp):95role = record['role']96name = record['name']97self.stable_abi_data[name] = record9899def add_annotations(self, app, doctree):100for node in doctree.findall(addnodes.desc_content):101par = node.parent102if par['domain'] != 'c':103continue104if not par[0].has_key('ids') or not par[0]['ids']:105continue106name = par[0]['ids'][0]107if name.startswith("c."):108name = name[2:]109110objtype = par['objtype']111112# Stable ABI annotation. These have two forms:113# Part of the [Stable ABI](link).114# Part of the [Stable ABI](link) since version X.Y.115# For structs, there's some more info in the message:116# Part of the [Limited API](link) (as an opaque struct).117# Part of the [Stable ABI](link) (including all members).118# Part of the [Limited API](link) (Only some members are part119# of the stable ABI.).120# ... all of which can have "since version X.Y" appended.121record = self.stable_abi_data.get(name)122if record:123if record['role'] != objtype:124raise ValueError(125f"Object type mismatch in limited API annotation "126f"for {name}: {record['role']!r} != {objtype!r}")127stable_added = record['added']128message = ' Part of the '129emph_node = nodes.emphasis(message, message,130classes=['stableabi'])131ref_node = addnodes.pending_xref(132'Stable ABI', refdomain="std", reftarget='stable',133reftype='ref', refexplicit="False")134struct_abi_kind = record['struct_abi_kind']135if struct_abi_kind in {'opaque', 'members'}:136ref_node += nodes.Text('Limited API')137else:138ref_node += nodes.Text('Stable ABI')139emph_node += ref_node140if struct_abi_kind == 'opaque':141emph_node += nodes.Text(' (as an opaque struct)')142elif struct_abi_kind == 'full-abi':143emph_node += nodes.Text(' (including all members)')144if record['ifdef_note']:145emph_node += nodes.Text(' ' + record['ifdef_note'])146if stable_added == '3.2':147# Stable ABI was introduced in 3.2.148pass149else:150emph_node += nodes.Text(f' since version {stable_added}')151emph_node += nodes.Text('.')152if struct_abi_kind == 'members':153emph_node += nodes.Text(154' (Only some members are part of the stable ABI.)')155node.insert(0, emph_node)156157# Unstable API annotation.158if name.startswith('PyUnstable'):159warn_node = nodes.admonition(160classes=['unstable-c-api', 'warning'])161message = 'This is '162emph_node = nodes.emphasis(message, message)163ref_node = addnodes.pending_xref(164'Unstable API', refdomain="std",165reftarget='unstable-c-api',166reftype='ref', refexplicit="False")167ref_node += nodes.Text('Unstable API')168emph_node += ref_node169emph_node += nodes.Text('. It may change without warning in minor releases.')170warn_node += emph_node171node.insert(0, warn_node)172173# Return value annotation174if objtype != 'function':175continue176entry = self.refcount_data.get(name)177if not entry:178continue179elif not entry.result_type.endswith("Object*"):180continue181if entry.result_refs is None:182rc = sphinx_gettext('Return value: Always NULL.')183elif entry.result_refs:184rc = sphinx_gettext('Return value: New reference.')185else:186rc = sphinx_gettext('Return value: Borrowed reference.')187node.insert(0, nodes.emphasis(rc, rc, classes=['refcount']))188189190def init_annotations(app):191annotations = Annotations(192path.join(app.srcdir, app.config.refcount_file),193path.join(app.srcdir, app.config.stable_abi_file),194)195app.connect('doctree-read', annotations.add_annotations)196197class LimitedAPIList(Directive):198199has_content = False200required_arguments = 0201optional_arguments = 0202final_argument_whitespace = True203204def run(self):205content = []206for record in annotations.stable_abi_data.values():207role = REST_ROLE_MAP[record['role']]208name = record['name']209content.append(f'* :c:{role}:`{name}`')210211pnode = nodes.paragraph()212self.state.nested_parse(StringList(content), 0, pnode)213return [pnode]214215app.add_directive('limited-api-list', LimitedAPIList)216217218def setup(app):219app.add_config_value('refcount_file', '', True)220app.add_config_value('stable_abi_file', '', True)221app.connect('builder-inited', init_annotations)222223# monkey-patch C object...224CObject.option_spec = {225'noindex': directives.flag,226'stableabi': directives.flag,227}228old_handle_signature = CObject.handle_signature229def new_handle_signature(self, sig, signode):230signode.parent['stableabi'] = 'stableabi' in self.options231return old_handle_signature(self, sig, signode)232CObject.handle_signature = new_handle_signature233return {'version': '1.0', 'parallel_read_safe': True}234235236