xref: /linux/Documentation/sphinx/cdomain.py (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
1# -*- coding: utf-8; mode: python -*-
2# SPDX-License-Identifier: GPL-2.0
3# pylint: disable=W0141,C0113,C0103,C0325
4"""
5    cdomain
6    ~~~~~~~
7
8    Replacement for the sphinx c-domain.
9
10    :copyright:  Copyright (C) 2016  Markus Heiser
11    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
12
13    List of customizations:
14
15    * Moved the *duplicate C object description* warnings for function
16      declarations in the nitpicky mode. See Sphinx documentation for
17      the config values for ``nitpick`` and ``nitpick_ignore``.
18
19    * Add option 'name' to the "c:function:" directive.  With option 'name' the
20      ref-name of a function can be modified. E.g.::
21
22          .. c:function:: int ioctl( int fd, int request )
23             :name: VIDIOC_LOG_STATUS
24
25      The func-name (e.g. ioctl) remains in the output but the ref-name changed
26      from 'ioctl' to 'VIDIOC_LOG_STATUS'. The function is referenced by::
27
28          * :c:func:`VIDIOC_LOG_STATUS` or
29          * :any:`VIDIOC_LOG_STATUS` (``:any:`` needs sphinx 1.3)
30
31     * Handle signatures of function-like macros well. Don't try to deduce
32       arguments types of function-like macros.
33
34"""
35
36from docutils import nodes
37from docutils.parsers.rst import directives
38
39import sphinx
40from sphinx import addnodes
41from sphinx.domains.c import c_funcptr_sig_re, c_sig_re
42from sphinx.domains.c import CObject as Base_CObject
43from sphinx.domains.c import CDomain as Base_CDomain
44from itertools import chain
45import re
46
47__version__  = '1.1'
48
49# Namespace to be prepended to the full name
50namespace = None
51
52#
53# Handle trivial newer c domain tags that are part of Sphinx 3.1 c domain tags
54# - Store the namespace if ".. c:namespace::" tag is found
55#
56RE_namespace = re.compile(r'^\s*..\s*c:namespace::\s*(\S+)\s*$')
57
58def markup_namespace(match):
59    global namespace
60
61    namespace = match.group(1)
62
63    return ""
64
65#
66# Handle c:macro for function-style declaration
67#
68RE_macro = re.compile(r'^\s*..\s*c:macro::\s*(\S+)\s+(\S.*)\s*$')
69def markup_macro(match):
70    return ".. c:function:: " + match.group(1) + ' ' + match.group(2)
71
72#
73# Handle newer c domain tags that are evaluated as .. c:type: for
74# backward-compatibility with Sphinx < 3.0
75#
76RE_ctype = re.compile(r'^\s*..\s*c:(struct|union|enum|enumerator|alias)::\s*(.*)$')
77
78def markup_ctype(match):
79    return ".. c:type:: " + match.group(2)
80
81#
82# Handle newer c domain tags that are evaluated as :c:type: for
83# backward-compatibility with Sphinx < 3.0
84#
85RE_ctype_refs = re.compile(r':c:(var|struct|union|enum|enumerator)::`([^\`]+)`')
86def markup_ctype_refs(match):
87    return ":c:type:`" + match.group(2) + '`'
88
89#
90# Simply convert :c:expr: and :c:texpr: into a literal block.
91#
92RE_expr = re.compile(r':c:(expr|texpr):`([^\`]+)`')
93def markup_c_expr(match):
94    return '\\ ``' + match.group(2) + '``\\ '
95
96#
97# Parse Sphinx 3.x C markups, replacing them by backward-compatible ones
98#
99def c_markups(app, docname, source):
100    result = ""
101    markup_func = {
102        RE_namespace: markup_namespace,
103        RE_expr: markup_c_expr,
104        RE_macro: markup_macro,
105        RE_ctype: markup_ctype,
106        RE_ctype_refs: markup_ctype_refs,
107    }
108
109    lines = iter(source[0].splitlines(True))
110    for n in lines:
111        match_iterators = [regex.finditer(n) for regex in markup_func]
112        matches = sorted(chain(*match_iterators), key=lambda m: m.start())
113        for m in matches:
114            n = n[:m.start()] + markup_func[m.re](m) + n[m.end():]
115
116        result = result + n
117
118    source[0] = result
119
120#
121# Now implements support for the cdomain namespacing logic
122#
123
124def setup(app):
125
126    # Handle easy Sphinx 3.1+ simple new tags: :c:expr and .. c:namespace::
127    app.connect('source-read', c_markups)
128    app.add_domain(CDomain, override=True)
129
130    return dict(
131        version = __version__,
132        parallel_read_safe = True,
133        parallel_write_safe = True
134    )
135
136class CObject(Base_CObject):
137
138    """
139    Description of a C language object.
140    """
141    option_spec = {
142        "name" : directives.unchanged
143    }
144
145    def handle_func_like_macro(self, sig, signode):
146        """Handles signatures of function-like macros.
147
148        If the objtype is 'function' and the signature ``sig`` is a
149        function-like macro, the name of the macro is returned. Otherwise
150        ``False`` is returned.  """
151
152        global namespace
153
154        if not self.objtype == 'function':
155            return False
156
157        m = c_funcptr_sig_re.match(sig)
158        if m is None:
159            m = c_sig_re.match(sig)
160            if m is None:
161                raise ValueError('no match')
162
163        rettype, fullname, arglist, _const = m.groups()
164        arglist = arglist.strip()
165        if rettype or not arglist:
166            return False
167
168        arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup
169        arglist = [a.strip() for a in arglist.split(",")]
170
171        # has the first argument a type?
172        if len(arglist[0].split(" ")) > 1:
173            return False
174
175        # This is a function-like macro, its arguments are typeless!
176        signode  += addnodes.desc_name(fullname, fullname)
177        paramlist = addnodes.desc_parameterlist()
178        signode  += paramlist
179
180        for argname in arglist:
181            param = addnodes.desc_parameter('', '', noemph=True)
182            # separate by non-breaking space in the output
183            param += nodes.emphasis(argname, argname)
184            paramlist += param
185
186        if namespace:
187            fullname = namespace + "." + fullname
188
189        return fullname
190
191    def handle_signature(self, sig, signode):
192        """Transform a C signature into RST nodes."""
193
194        global namespace
195
196        fullname = self.handle_func_like_macro(sig, signode)
197        if not fullname:
198            fullname = super(CObject, self).handle_signature(sig, signode)
199
200        if "name" in self.options:
201            if self.objtype == 'function':
202                fullname = self.options["name"]
203            else:
204                # FIXME: handle :name: value of other declaration types?
205                pass
206        else:
207            if namespace:
208                fullname = namespace + "." + fullname
209
210        return fullname
211
212    def add_target_and_index(self, name, sig, signode):
213        # for C API items we add a prefix since names are usually not qualified
214        # by a module name and so easily clash with e.g. section titles
215        targetname = 'c.' + name
216        if targetname not in self.state.document.ids:
217            signode['names'].append(targetname)
218            signode['ids'].append(targetname)
219            signode['first'] = (not self.names)
220            self.state.document.note_explicit_target(signode)
221            inv = self.env.domaindata['c']['objects']
222            if (name in inv and self.env.config.nitpicky):
223                if self.objtype == 'function':
224                    if ('c:func', name) not in self.env.config.nitpick_ignore:
225                        self.state_machine.reporter.warning(
226                            'duplicate C object description of %s, ' % name +
227                            'other instance in ' + self.env.doc2path(inv[name][0]),
228                            line=self.lineno)
229            inv[name] = (self.env.docname, self.objtype)
230
231        indextext = self.get_index_text(name)
232        if indextext:
233            self.indexnode['entries'].append(
234                    ('single', indextext, targetname, '', None))
235
236class CDomain(Base_CDomain):
237
238    """C language domain."""
239    name = 'c'
240    label = 'C'
241    directives = {
242        'function': CObject,
243        'member':   CObject,
244        'macro':    CObject,
245        'type':     CObject,
246        'var':      CObject,
247    }
248