xref: /linux/Documentation/sphinx/cdomain.py (revision 71e552aebf260239463d348ecb75b972bee0804b)
1e8f5c617SMarkus Heiser# -*- coding: utf-8; mode: python -*-
256cd8692SMarkus Heiser# pylint: disable=W0141,C0113,C0103,C0325
3e8f5c617SMarkus Heiseru"""
4e8f5c617SMarkus Heiser    cdomain
5e8f5c617SMarkus Heiser    ~~~~~~~
6e8f5c617SMarkus Heiser
7e8f5c617SMarkus Heiser    Replacement for the sphinx c-domain.
8e8f5c617SMarkus Heiser
9e8f5c617SMarkus Heiser    :copyright:  Copyright (C) 2016  Markus Heiser
10e8f5c617SMarkus Heiser    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
112c645cd7SMarkus Heiser
122c645cd7SMarkus Heiser    List of customizations:
132c645cd7SMarkus Heiser
14556aa6d5SMarkus Heiser    * Moved the *duplicate C object description* warnings for function
15556aa6d5SMarkus Heiser      declarations in the nitpicky mode. See Sphinx documentation for
16556aa6d5SMarkus Heiser      the config values for ``nitpick`` and ``nitpick_ignore``.
17556aa6d5SMarkus Heiser
182c645cd7SMarkus Heiser    * Add option 'name' to the "c:function:" directive.  With option 'name' the
192c645cd7SMarkus Heiser      ref-name of a function can be modified. E.g.::
202c645cd7SMarkus Heiser
212c645cd7SMarkus Heiser          .. c:function:: int ioctl( int fd, int request )
222c645cd7SMarkus Heiser             :name: VIDIOC_LOG_STATUS
232c645cd7SMarkus Heiser
242c645cd7SMarkus Heiser      The func-name (e.g. ioctl) remains in the output but the ref-name changed
252c645cd7SMarkus Heiser      from 'ioctl' to 'VIDIOC_LOG_STATUS'. The function is referenced by::
262c645cd7SMarkus Heiser
272c645cd7SMarkus Heiser          * :c:func:`VIDIOC_LOG_STATUS` or
282c645cd7SMarkus Heiser          * :any:`VIDIOC_LOG_STATUS` (``:any:`` needs sphinx 1.3)
2956cd8692SMarkus Heiser
3056cd8692SMarkus Heiser     * Handle signatures of function-like macros well. Don't try to deduce
3156cd8692SMarkus Heiser       arguments types of function-like macros.
3256cd8692SMarkus Heiser
33e8f5c617SMarkus Heiser"""
34e8f5c617SMarkus Heiser
3556cd8692SMarkus Heiserfrom docutils import nodes
362c645cd7SMarkus Heiserfrom docutils.parsers.rst import directives
372c645cd7SMarkus Heiser
38b495360eSMarkus Heiserimport sphinx
3956cd8692SMarkus Heiserfrom sphinx import addnodes
4056cd8692SMarkus Heiserfrom sphinx.domains.c import c_funcptr_sig_re, c_sig_re
41e8f5c617SMarkus Heiserfrom sphinx.domains.c import CObject as Base_CObject
42e8f5c617SMarkus Heiserfrom sphinx.domains.c import CDomain as Base_CDomain
43*71e552aeSMauro Carvalho Chehabfrom itertools import chain
44*71e552aeSMauro Carvalho Chehabimport re
45e8f5c617SMarkus Heiser
46*71e552aeSMauro Carvalho Chehab__version__  = '1.1'
47e8f5c617SMarkus Heiser
48b495360eSMarkus Heiser# Get Sphinx version
49c46988aeSRémy Léonemajor, minor, patch = sphinx.version_info[:3]
50b495360eSMarkus Heiser
51*71e552aeSMauro Carvalho Chehab# Namespace to be prepended to the full name
52*71e552aeSMauro Carvalho Chehabnamespace = None
53*71e552aeSMauro Carvalho Chehab
54*71e552aeSMauro Carvalho Chehab#
55*71e552aeSMauro Carvalho Chehab# Handle trivial newer c domain tags that are part of Sphinx 3.1 c domain tags
56*71e552aeSMauro Carvalho Chehab# - Store the namespace if ".. c:namespace::" tag is found
57*71e552aeSMauro Carvalho Chehab
58*71e552aeSMauro Carvalho ChehabRE_namespace = re.compile(r'^\s*..\s*c:namespace::\s*(\S+)\s*$')
59*71e552aeSMauro Carvalho Chehab
60*71e552aeSMauro Carvalho Chehabdef markup_namespace(match):
61*71e552aeSMauro Carvalho Chehab    global namespace
62*71e552aeSMauro Carvalho Chehab
63*71e552aeSMauro Carvalho Chehab    namespace = match.group(1)
64*71e552aeSMauro Carvalho Chehab
65*71e552aeSMauro Carvalho Chehab    return ""
66*71e552aeSMauro Carvalho Chehab
67*71e552aeSMauro Carvalho Chehabdef c_markups(app, docname, source):
68*71e552aeSMauro Carvalho Chehab    result = ""
69*71e552aeSMauro Carvalho Chehab    markup_func = {
70*71e552aeSMauro Carvalho Chehab        RE_namespace: markup_namespace,
71*71e552aeSMauro Carvalho Chehab    }
72*71e552aeSMauro Carvalho Chehab
73*71e552aeSMauro Carvalho Chehab    lines = iter(source[0].splitlines(True))
74*71e552aeSMauro Carvalho Chehab    for n in lines:
75*71e552aeSMauro Carvalho Chehab        match_iterators = [regex.finditer(n) for regex in markup_func]
76*71e552aeSMauro Carvalho Chehab        matches = sorted(chain(*match_iterators), key=lambda m: m.start())
77*71e552aeSMauro Carvalho Chehab        for m in matches:
78*71e552aeSMauro Carvalho Chehab            n = n[:m.start()] + markup_func[m.re](m) + n[m.end():]
79*71e552aeSMauro Carvalho Chehab
80*71e552aeSMauro Carvalho Chehab        result = result + n
81*71e552aeSMauro Carvalho Chehab
82*71e552aeSMauro Carvalho Chehab    source[0] = result
83*71e552aeSMauro Carvalho Chehab
84*71e552aeSMauro Carvalho Chehab#
85*71e552aeSMauro Carvalho Chehab# Now implements support for the cdomain namespacing logic
86*71e552aeSMauro Carvalho Chehab#
87*71e552aeSMauro Carvalho Chehab
88e8f5c617SMarkus Heiserdef setup(app):
89e8f5c617SMarkus Heiser
90*71e552aeSMauro Carvalho Chehab    # Handle easy Sphinx 3.1+ simple new tags: :c:expr and .. c:namespace::
91*71e552aeSMauro Carvalho Chehab    app.connect('source-read', c_markups)
92*71e552aeSMauro Carvalho Chehab
9342f6ebd8SMauro Carvalho Chehab    if (major == 1 and minor < 8):
94e8f5c617SMarkus Heiser        app.override_domain(CDomain)
9542f6ebd8SMauro Carvalho Chehab    else:
9642f6ebd8SMauro Carvalho Chehab        app.add_domain(CDomain, override=True)
97e8f5c617SMarkus Heiser
98e8f5c617SMarkus Heiser    return dict(
99e8f5c617SMarkus Heiser        version = __version__,
100e8f5c617SMarkus Heiser        parallel_read_safe = True,
101e8f5c617SMarkus Heiser        parallel_write_safe = True
102e8f5c617SMarkus Heiser    )
103e8f5c617SMarkus Heiser
104e8f5c617SMarkus Heiserclass CObject(Base_CObject):
105e8f5c617SMarkus Heiser
106e8f5c617SMarkus Heiser    """
107e8f5c617SMarkus Heiser    Description of a C language object.
108e8f5c617SMarkus Heiser    """
1092c645cd7SMarkus Heiser    option_spec = {
1102c645cd7SMarkus Heiser        "name" : directives.unchanged
1112c645cd7SMarkus Heiser    }
1122c645cd7SMarkus Heiser
11356cd8692SMarkus Heiser    def handle_func_like_macro(self, sig, signode):
11456cd8692SMarkus Heiser        u"""Handles signatures of function-like macros.
11556cd8692SMarkus Heiser
11656cd8692SMarkus Heiser        If the objtype is 'function' and the the signature ``sig`` is a
11756cd8692SMarkus Heiser        function-like macro, the name of the macro is returned. Otherwise
11856cd8692SMarkus Heiser        ``False`` is returned.  """
11956cd8692SMarkus Heiser
120*71e552aeSMauro Carvalho Chehab        global namespace
121*71e552aeSMauro Carvalho Chehab
12256cd8692SMarkus Heiser        if not self.objtype == 'function':
12356cd8692SMarkus Heiser            return False
12456cd8692SMarkus Heiser
12556cd8692SMarkus Heiser        m = c_funcptr_sig_re.match(sig)
12656cd8692SMarkus Heiser        if m is None:
12756cd8692SMarkus Heiser            m = c_sig_re.match(sig)
12856cd8692SMarkus Heiser            if m is None:
12956cd8692SMarkus Heiser                raise ValueError('no match')
13056cd8692SMarkus Heiser
13156cd8692SMarkus Heiser        rettype, fullname, arglist, _const = m.groups()
13256cd8692SMarkus Heiser        arglist = arglist.strip()
13356cd8692SMarkus Heiser        if rettype or not arglist:
13456cd8692SMarkus Heiser            return False
13556cd8692SMarkus Heiser
13656cd8692SMarkus Heiser        arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup
13756cd8692SMarkus Heiser        arglist = [a.strip() for a in arglist.split(",")]
13856cd8692SMarkus Heiser
13956cd8692SMarkus Heiser        # has the first argument a type?
14056cd8692SMarkus Heiser        if len(arglist[0].split(" ")) > 1:
14156cd8692SMarkus Heiser            return False
14256cd8692SMarkus Heiser
14356cd8692SMarkus Heiser        # This is a function-like macro, it's arguments are typeless!
14456cd8692SMarkus Heiser        signode  += addnodes.desc_name(fullname, fullname)
14556cd8692SMarkus Heiser        paramlist = addnodes.desc_parameterlist()
14656cd8692SMarkus Heiser        signode  += paramlist
14756cd8692SMarkus Heiser
14856cd8692SMarkus Heiser        for argname in arglist:
14956cd8692SMarkus Heiser            param = addnodes.desc_parameter('', '', noemph=True)
15056cd8692SMarkus Heiser            # separate by non-breaking space in the output
15156cd8692SMarkus Heiser            param += nodes.emphasis(argname, argname)
15256cd8692SMarkus Heiser            paramlist += param
15356cd8692SMarkus Heiser
154*71e552aeSMauro Carvalho Chehab        if namespace:
155*71e552aeSMauro Carvalho Chehab            fullname = namespace + "." + fullname
156*71e552aeSMauro Carvalho Chehab
15756cd8692SMarkus Heiser        return fullname
15856cd8692SMarkus Heiser
1592c645cd7SMarkus Heiser    def handle_signature(self, sig, signode):
1602c645cd7SMarkus Heiser        """Transform a C signature into RST nodes."""
16156cd8692SMarkus Heiser
162*71e552aeSMauro Carvalho Chehab        global namespace
163*71e552aeSMauro Carvalho Chehab
16456cd8692SMarkus Heiser        fullname = self.handle_func_like_macro(sig, signode)
16556cd8692SMarkus Heiser        if not fullname:
1662c645cd7SMarkus Heiser            fullname = super(CObject, self).handle_signature(sig, signode)
16756cd8692SMarkus Heiser
1682c645cd7SMarkus Heiser        if "name" in self.options:
1692c645cd7SMarkus Heiser            if self.objtype == 'function':
1702c645cd7SMarkus Heiser                fullname = self.options["name"]
1712c645cd7SMarkus Heiser            else:
1722c645cd7SMarkus Heiser                # FIXME: handle :name: value of other declaration types?
1732c645cd7SMarkus Heiser                pass
174*71e552aeSMauro Carvalho Chehab        else:
175*71e552aeSMauro Carvalho Chehab            if namespace:
176*71e552aeSMauro Carvalho Chehab                fullname = namespace + "." + fullname
177*71e552aeSMauro Carvalho Chehab
1782c645cd7SMarkus Heiser        return fullname
1792c645cd7SMarkus Heiser
180556aa6d5SMarkus Heiser    def add_target_and_index(self, name, sig, signode):
181556aa6d5SMarkus Heiser        # for C API items we add a prefix since names are usually not qualified
182556aa6d5SMarkus Heiser        # by a module name and so easily clash with e.g. section titles
183556aa6d5SMarkus Heiser        targetname = 'c.' + name
184556aa6d5SMarkus Heiser        if targetname not in self.state.document.ids:
185556aa6d5SMarkus Heiser            signode['names'].append(targetname)
186556aa6d5SMarkus Heiser            signode['ids'].append(targetname)
187556aa6d5SMarkus Heiser            signode['first'] = (not self.names)
188556aa6d5SMarkus Heiser            self.state.document.note_explicit_target(signode)
189556aa6d5SMarkus Heiser            inv = self.env.domaindata['c']['objects']
190556aa6d5SMarkus Heiser            if (name in inv and self.env.config.nitpicky):
191556aa6d5SMarkus Heiser                if self.objtype == 'function':
192556aa6d5SMarkus Heiser                    if ('c:func', name) not in self.env.config.nitpick_ignore:
193556aa6d5SMarkus Heiser                        self.state_machine.reporter.warning(
194556aa6d5SMarkus Heiser                            'duplicate C object description of %s, ' % name +
195556aa6d5SMarkus Heiser                            'other instance in ' + self.env.doc2path(inv[name][0]),
196556aa6d5SMarkus Heiser                            line=self.lineno)
197556aa6d5SMarkus Heiser            inv[name] = (self.env.docname, self.objtype)
198556aa6d5SMarkus Heiser
199556aa6d5SMarkus Heiser        indextext = self.get_index_text(name)
200556aa6d5SMarkus Heiser        if indextext:
201b495360eSMarkus Heiser            if major == 1 and minor < 4:
202b495360eSMarkus Heiser                # indexnode's tuple changed in 1.4
203b495360eSMarkus Heiser                # https://github.com/sphinx-doc/sphinx/commit/e6a5a3a92e938fcd75866b4227db9e0524d58f7c
204b495360eSMarkus Heiser                self.indexnode['entries'].append(
205b495360eSMarkus Heiser                    ('single', indextext, targetname, ''))
206b495360eSMarkus Heiser            else:
207b495360eSMarkus Heiser                self.indexnode['entries'].append(
208b495360eSMarkus Heiser                    ('single', indextext, targetname, '', None))
209e8f5c617SMarkus Heiser
210e8f5c617SMarkus Heiserclass CDomain(Base_CDomain):
211e8f5c617SMarkus Heiser
212e8f5c617SMarkus Heiser    """C language domain."""
213e8f5c617SMarkus Heiser    name = 'c'
214e8f5c617SMarkus Heiser    label = 'C'
215e8f5c617SMarkus Heiser    directives = {
216e8f5c617SMarkus Heiser        'function': CObject,
217e8f5c617SMarkus Heiser        'member':   CObject,
218e8f5c617SMarkus Heiser        'macro':    CObject,
219e8f5c617SMarkus Heiser        'type':     CObject,
220e8f5c617SMarkus Heiser        'var':      CObject,
221e8f5c617SMarkus Heiser    }
222