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