Viewing file: c_annotations.py (7.52 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- coding: utf-8 -*- """ c_annotations.py ~~~~~~~~~~~~~~~~
Supports annotations for C API elements:
* reference count annotations for C API functions. Based on refcount.py and anno-api.py in the old Python documentation tools.
* stable API annotations
Usage: * Set the `refcount_file` config value to the path to the reference count data file. * Set the `stable_abi_file` config value to the path to stable ABI list.
:copyright: Copyright 2007-2014 by Georg Brandl. :license: Python license. """
from os import path from docutils import nodes from docutils.parsers.rst import directives from docutils.parsers.rst import Directive from docutils.statemachine import StringList from sphinx.locale import _ as sphinx_gettext import csv
from sphinx import addnodes from sphinx.domains.c import CObject
REST_ROLE_MAP = { 'function': 'func', 'var': 'data', 'type': 'type', 'macro': 'macro', 'type': 'type', 'member': 'member', }
class RCEntry: def __init__(self, name): self.name = name self.args = [] self.result_type = '' self.result_refs = None
class Annotations: def __init__(self, refcount_filename, stable_abi_file): self.refcount_data = {} with open(refcount_filename, 'r') as fp: for line in fp: line = line.strip() if line[:1] in ("", "#"): # blank lines and comments continue parts = line.split(":", 4) if len(parts) != 5: raise ValueError("Wrong field count in %r" % line) function, type, arg, refcount, comment = parts # Get the entry, creating it if needed: try: entry = self.refcount_data[function] except KeyError: entry = self.refcount_data[function] = RCEntry(function) if not refcount or refcount == "null": refcount = None else: refcount = int(refcount) # Update the entry with the new parameter or the result # information. if arg: entry.args.append((arg, type, refcount)) else: entry.result_type = type entry.result_refs = refcount
self.stable_abi_data = {} with open(stable_abi_file, 'r') as fp: for record in csv.DictReader(fp): role = record['role'] name = record['name'] self.stable_abi_data[name] = record
def add_annotations(self, app, doctree): for node in doctree.traverse(addnodes.desc_content): par = node.parent if par['domain'] != 'c': continue if not par[0].has_key('ids') or not par[0]['ids']: continue name = par[0]['ids'][0] if name.startswith("c."): name = name[2:]
objtype = par['objtype']
# Stable ABI annotation. These have two forms: # Part of the [Stable ABI](link). # Part of the [Stable ABI](link) since version X.Y. # For structs, there's some more info in the message: # Part of the [Limited API](link) (as an opaque struct). # Part of the [Stable ABI](link) (including all members). # Part of the [Limited API](link) (Only some members are part # of the stable ABI.). # ... all of which can have "since version X.Y" appended. record = self.stable_abi_data.get(name) if record: if record['role'] != objtype: raise ValueError( f"Object type mismatch in limited API annotation " f"for {name}: {record['role']!r} != {objtype!r}") stable_added = record['added'] message = ' Part of the ' emph_node = nodes.emphasis(message, message, classes=['stableabi']) ref_node = addnodes.pending_xref( 'Stable ABI', refdomain="std", reftarget='stable', reftype='ref', refexplicit="False") struct_abi_kind = record['struct_abi_kind'] if struct_abi_kind in {'opaque', 'members'}: ref_node += nodes.Text('Limited API') else: ref_node += nodes.Text('Stable ABI') emph_node += ref_node if struct_abi_kind == 'opaque': emph_node += nodes.Text(' (as an opaque struct)') elif struct_abi_kind == 'full-abi': emph_node += nodes.Text(' (including all members)') if record['ifdef_note']: emph_node += nodes.Text(' ' + record['ifdef_note']) if stable_added == '3.2': # Stable ABI was introduced in 3.2. pass else: emph_node += nodes.Text(f' since version {stable_added}') emph_node += nodes.Text('.') if struct_abi_kind == 'members': emph_node += nodes.Text( ' (Only some members are part of the stable ABI.)') node.insert(0, emph_node)
# Return value annotation if objtype != 'function': continue entry = self.refcount_data.get(name) if not entry: continue elif not entry.result_type.endswith("Object*"): continue if entry.result_refs is None: rc = sphinx_gettext('Return value: Always NULL.') elif entry.result_refs: rc = sphinx_gettext('Return value: New reference.') else: rc = sphinx_gettext('Return value: Borrowed reference.') node.insert(0, nodes.emphasis(rc, rc, classes=['refcount']))
def init_annotations(app): annotations = Annotations( path.join(app.srcdir, app.config.refcount_file), path.join(app.srcdir, app.config.stable_abi_file), ) app.connect('doctree-read', annotations.add_annotations)
class LimitedAPIList(Directive):
has_content = False required_arguments = 0 optional_arguments = 0 final_argument_whitespace = True
def run(self): content = [] for record in annotations.stable_abi_data.values(): role = REST_ROLE_MAP[record['role']] name = record['name'] content.append(f'* :c:{role}:`{name}`')
pnode = nodes.paragraph() self.state.nested_parse(StringList(content), 0, pnode) return [pnode]
app.add_directive('limited-api-list', LimitedAPIList)
def setup(app): app.add_config_value('refcount_file', '', True) app.add_config_value('stable_abi_file', '', True) app.connect('builder-inited', init_annotations)
# monkey-patch C object... CObject.option_spec = { 'noindex': directives.flag, 'stableabi': directives.flag, } old_handle_signature = CObject.handle_signature def new_handle_signature(self, sig, signode): signode.parent['stableabi'] = 'stableabi' in self.options return old_handle_signature(self, sig, signode) CObject.handle_signature = new_handle_signature return {'version': '1.0', 'parallel_read_safe': True}
|