mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 02:15:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			234 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- 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
 | |
| import docutils
 | |
| 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',
 | |
| }
 | |
| 
 | |
| 
 | |
| # Monkeypatch nodes.Node.findall for forwards compatability
 | |
| # This patch can be dropped when the minimum Sphinx version is 4.4.0
 | |
| # or the minimum Docutils version is 0.18.1.
 | |
| if docutils.__version_info__ < (0, 18, 1):
 | |
|     def findall(self, *args, **kwargs):
 | |
|         return iter(self.traverse(*args, **kwargs))
 | |
| 
 | |
|     nodes.Node.findall = findall
 | |
| 
 | |
| 
 | |
| 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.findall(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)
 | |
| 
 | |
|             # Unstable API annotation.
 | |
|             if name.startswith('PyUnstable'):
 | |
|                 warn_node = nodes.admonition(
 | |
|                     classes=['unstable-c-api', 'warning'])
 | |
|                 message = 'This is '
 | |
|                 emph_node = nodes.emphasis(message, message)
 | |
|                 ref_node = addnodes.pending_xref(
 | |
|                     'Unstable API', refdomain="std",
 | |
|                     reftarget='unstable-c-api',
 | |
|                     reftype='ref', refexplicit="False")
 | |
|                 ref_node += nodes.Text('Unstable API')
 | |
|                 emph_node += ref_node
 | |
|                 emph_node += nodes.Text('. It may change without warning in minor releases.')
 | |
|                 warn_node += emph_node
 | |
|                 node.insert(0, warn_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}
 | 
