(root)/
Python-3.11.7/
Doc/
tools/
extensions/
c_annotations.py
       1  # -*- coding: utf-8 -*-
       2  """
       3      c_annotations.py
       4      ~~~~~~~~~~~~~~~~
       5  
       6      Supports annotations for C API elements:
       7  
       8      * reference count annotations for C API functions.  Based on
       9        refcount.py and anno-api.py in the old Python documentation tools.
      10  
      11      * stable API annotations
      12  
      13      Usage:
      14      * Set the `refcount_file` config value to the path to the reference
      15      count data file.
      16      * Set the `stable_abi_file` config value to the path to stable ABI list.
      17  
      18      :copyright: Copyright 2007-2014 by Georg Brandl.
      19      :license: Python license.
      20  """
      21  
      22  from os import path
      23  import docutils
      24  from docutils import nodes
      25  from docutils.parsers.rst import directives
      26  from docutils.parsers.rst import Directive
      27  from docutils.statemachine import StringList
      28  from sphinx.locale import _ as sphinx_gettext
      29  import csv
      30  
      31  from sphinx import addnodes
      32  from sphinx.domains.c import CObject
      33  
      34  
      35  REST_ROLE_MAP = {
      36      'function': 'func',
      37      'var': 'data',
      38      'type': 'type',
      39      'macro': 'macro',
      40      'type': 'type',
      41      'member': 'member',
      42  }
      43  
      44  
      45  # Monkeypatch nodes.Node.findall for forwards compatability
      46  # This patch can be dropped when the minimum Sphinx version is 4.4.0
      47  # or the minimum Docutils version is 0.18.1.
      48  if docutils.__version_info__ < (0, 18, 1):
      49      def findall(self, *args, **kwargs):
      50          return iter(self.traverse(*args, **kwargs))
      51  
      52      nodes.Node.findall = findall
      53  
      54  
      55  class ESC[4;38;5;81mRCEntry:
      56      def __init__(self, name):
      57          self.name = name
      58          self.args = []
      59          self.result_type = ''
      60          self.result_refs = None
      61  
      62  
      63  class ESC[4;38;5;81mAnnotations:
      64      def __init__(self, refcount_filename, stable_abi_file):
      65          self.refcount_data = {}
      66          with open(refcount_filename, 'r') as fp:
      67              for line in fp:
      68                  line = line.strip()
      69                  if line[:1] in ("", "#"):
      70                      # blank lines and comments
      71                      continue
      72                  parts = line.split(":", 4)
      73                  if len(parts) != 5:
      74                      raise ValueError("Wrong field count in %r" % line)
      75                  function, type, arg, refcount, comment = parts
      76                  # Get the entry, creating it if needed:
      77                  try:
      78                      entry = self.refcount_data[function]
      79                  except KeyError:
      80                      entry = self.refcount_data[function] = RCEntry(function)
      81                  if not refcount or refcount == "null":
      82                      refcount = None
      83                  else:
      84                      refcount = int(refcount)
      85                  # Update the entry with the new parameter or the result
      86                  # information.
      87                  if arg:
      88                      entry.args.append((arg, type, refcount))
      89                  else:
      90                      entry.result_type = type
      91                      entry.result_refs = refcount
      92  
      93          self.stable_abi_data = {}
      94          with open(stable_abi_file, 'r') as fp:
      95              for record in csv.DictReader(fp):
      96                  role = record['role']
      97                  name = record['name']
      98                  self.stable_abi_data[name] = record
      99  
     100      def add_annotations(self, app, doctree):
     101          for node in doctree.findall(addnodes.desc_content):
     102              par = node.parent
     103              if par['domain'] != 'c':
     104                  continue
     105              if not par[0].has_key('ids') or not par[0]['ids']:
     106                  continue
     107              name = par[0]['ids'][0]
     108              if name.startswith("c."):
     109                  name = name[2:]
     110  
     111              objtype = par['objtype']
     112  
     113              # Stable ABI annotation. These have two forms:
     114              #   Part of the [Stable ABI](link).
     115              #   Part of the [Stable ABI](link) since version X.Y.
     116              # For structs, there's some more info in the message:
     117              #   Part of the [Limited API](link) (as an opaque struct).
     118              #   Part of the [Stable ABI](link) (including all members).
     119              #   Part of the [Limited API](link) (Only some members are part
     120              #       of the stable ABI.).
     121              # ... all of which can have "since version X.Y" appended.
     122              record = self.stable_abi_data.get(name)
     123              if record:
     124                  if record['role'] != objtype:
     125                      raise ValueError(
     126                          f"Object type mismatch in limited API annotation "
     127                          f"for {name}: {record['role']!r} != {objtype!r}")
     128                  stable_added = record['added']
     129                  message = ' Part of the '
     130                  emph_node = nodes.emphasis(message, message,
     131                                             classes=['stableabi'])
     132                  ref_node = addnodes.pending_xref(
     133                      'Stable ABI', refdomain="std", reftarget='stable',
     134                      reftype='ref', refexplicit="False")
     135                  struct_abi_kind = record['struct_abi_kind']
     136                  if struct_abi_kind in {'opaque', 'members'}:
     137                      ref_node += nodes.Text('Limited API')
     138                  else:
     139                      ref_node += nodes.Text('Stable ABI')
     140                  emph_node += ref_node
     141                  if struct_abi_kind == 'opaque':
     142                      emph_node += nodes.Text(' (as an opaque struct)')
     143                  elif struct_abi_kind == 'full-abi':
     144                      emph_node += nodes.Text(' (including all members)')
     145                  if record['ifdef_note']:
     146                      emph_node += nodes.Text(' ' + record['ifdef_note'])
     147                  if stable_added == '3.2':
     148                      # Stable ABI was introduced in 3.2.
     149                      pass
     150                  else:
     151                      emph_node += nodes.Text(f' since version {stable_added}')
     152                  emph_node += nodes.Text('.')
     153                  if struct_abi_kind == 'members':
     154                      emph_node += nodes.Text(
     155                          ' (Only some members are part of the stable ABI.)')
     156                  node.insert(0, emph_node)
     157  
     158              # Return value annotation
     159              if objtype != 'function':
     160                  continue
     161              entry = self.refcount_data.get(name)
     162              if not entry:
     163                  continue
     164              elif not entry.result_type.endswith("Object*"):
     165                  continue
     166              if entry.result_refs is None:
     167                  rc = sphinx_gettext('Return value: Always NULL.')
     168              elif entry.result_refs:
     169                  rc = sphinx_gettext('Return value: New reference.')
     170              else:
     171                  rc = sphinx_gettext('Return value: Borrowed reference.')
     172              node.insert(0, nodes.emphasis(rc, rc, classes=['refcount']))
     173  
     174  
     175  def init_annotations(app):
     176      annotations = Annotations(
     177          path.join(app.srcdir, app.config.refcount_file),
     178          path.join(app.srcdir, app.config.stable_abi_file),
     179      )
     180      app.connect('doctree-read', annotations.add_annotations)
     181  
     182      class ESC[4;38;5;81mLimitedAPIList(ESC[4;38;5;149mDirective):
     183  
     184          has_content = False
     185          required_arguments = 0
     186          optional_arguments = 0
     187          final_argument_whitespace = True
     188  
     189          def run(self):
     190              content = []
     191              for record in annotations.stable_abi_data.values():
     192                  role = REST_ROLE_MAP[record['role']]
     193                  name = record['name']
     194                  content.append(f'* :c:{role}:`{name}`')
     195  
     196              pnode = nodes.paragraph()
     197              self.state.nested_parse(StringList(content), 0, pnode)
     198              return [pnode]
     199  
     200      app.add_directive('limited-api-list', LimitedAPIList)
     201  
     202  
     203  def setup(app):
     204      app.add_config_value('refcount_file', '', True)
     205      app.add_config_value('stable_abi_file', '', True)
     206      app.connect('builder-inited', init_annotations)
     207  
     208      # monkey-patch C object...
     209      CObject.option_spec = {
     210          'noindex': directives.flag,
     211          'stableabi': directives.flag,
     212      }
     213      old_handle_signature = CObject.handle_signature
     214      def new_handle_signature(self, sig, signode):
     215          signode.parent['stableabi'] = 'stableabi' in self.options
     216          return old_handle_signature(self, sig, signode)
     217      CObject.handle_signature = new_handle_signature
     218      return {'version': '1.0', 'parallel_read_safe': True}