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: Set the `refcount_file` config value to the path to the reference
14    count data file.
15
16    :copyright: Copyright 2007-2014 by Georg Brandl.
17    :license: Python license.
18"""
19
20from os import path
21from docutils import nodes
22from docutils.parsers.rst import directives
23
24from sphinx import addnodes
25from sphinx.domains.c import CObject
26
27
28class RCEntry:
29    def __init__(self, name):
30        self.name = name
31        self.args = []
32        self.result_type = ''
33        self.result_refs = None
34
35
36class Annotations(dict):
37    @classmethod
38    def fromfile(cls, filename):
39        d = cls()
40        fp = open(filename, 'r')
41        try:
42            for line in fp:
43                line = line.strip()
44                if line[:1] in ("", "#"):
45                    # blank lines and comments
46                    continue
47                parts = line.split(":", 4)
48                if len(parts) != 5:
49                    raise ValueError("Wrong field count in %r" % line)
50                function, type, arg, refcount, comment = parts
51                # Get the entry, creating it if needed:
52                try:
53                    entry = d[function]
54                except KeyError:
55                    entry = d[function] = RCEntry(function)
56                if not refcount or refcount == "null":
57                    refcount = None
58                else:
59                    refcount = int(refcount)
60                # Update the entry with the new parameter or the result
61                # information.
62                if arg:
63                    entry.args.append((arg, type, refcount))
64                else:
65                    entry.result_type = type
66                    entry.result_refs = refcount
67        finally:
68            fp.close()
69        return d
70
71    def add_annotations(self, app, doctree):
72        for node in doctree.traverse(addnodes.desc_content):
73            par = node.parent
74            if par['domain'] != 'c':
75                continue
76            if par['stableabi']:
77                node.insert(0, nodes.emphasis(' Part of the stable ABI.',
78                                              ' Part of the stable ABI.',
79                                              classes=['stableabi']))
80            if par['objtype'] != 'function':
81                continue
82            if not par[0].has_key('names') or not par[0]['names']:
83                continue
84            name = par[0]['names'][0]
85            if name.startswith("c."):
86                name = name[2:]
87            entry = self.get(name)
88            if not entry:
89                continue
90            elif entry.result_type not in ("PyObject*", "PyVarObject*"):
91                continue
92            if entry.result_refs is None:
93                rc = 'Return value: Always NULL.'
94            elif entry.result_refs:
95                rc = 'Return value: New reference.'
96            else:
97                rc = 'Return value: Borrowed reference.'
98            node.insert(0, nodes.emphasis(rc, rc, classes=['refcount']))
99
100
101def init_annotations(app):
102    refcounts = Annotations.fromfile(
103        path.join(app.srcdir, app.config.refcount_file))
104    app.connect('doctree-read', refcounts.add_annotations)
105
106
107def setup(app):
108    app.add_config_value('refcount_file', '', True)
109    app.connect('builder-inited', init_annotations)
110
111    # monkey-patch C object...
112    CObject.option_spec = {
113        'noindex': directives.flag,
114        'stableabi': directives.flag,
115    }
116    old_handle_signature = CObject.handle_signature
117    def new_handle_signature(self, sig, signode):
118        signode.parent['stableabi'] = 'stableabi' in self.options
119        return old_handle_signature(self, sig, signode)
120    CObject.handle_signature = new_handle_signature
121    return {'version': '1.0', 'parallel_read_safe': True}
122