1 # -*- coding: utf-8; mode: python -*-
2 # pylint: disable=W0141,C0113,C0103,C0325
7 Replacement for the sphinx c-domain.
9 :copyright: Copyright (C) 2016 Markus Heiser
10 :license: GPL Version 2, June 1991 see Linux/COPYING for details.
12 List of customizations:
14 * Moved the *duplicate C object description* warnings for function
15 declarations in the nitpicky mode. See Sphinx documentation for
16 the config values for ``nitpick`` and ``nitpick_ignore``.
18 * Add option 'name' to the "c:function:" directive. With option 'name' the
19 ref-name of a function can be modified. E.g.::
21 .. c:function:: int ioctl( int fd, int request )
22 :name: VIDIOC_LOG_STATUS
24 The func-name (e.g. ioctl) remains in the output but the ref-name changed
25 from 'ioctl' to 'VIDIOC_LOG_STATUS'. The function is referenced by::
27 * :c:func:`VIDIOC_LOG_STATUS` or
28 * :any:`VIDIOC_LOG_STATUS` (``:any:`` needs sphinx 1.3)
30 * Handle signatures of function-like macros well. Don't try to deduce
31 arguments types of function-like macros.
35 from docutils
import nodes
36 from docutils
.parsers
.rst
import directives
39 from sphinx
import addnodes
40 from sphinx
.domains
.c
import c_funcptr_sig_re
, c_sig_re
41 from sphinx
.domains
.c
import CObject
as Base_CObject
42 from sphinx
.domains
.c
import CDomain
as Base_CDomain
43 from itertools
import chain
49 major
, minor
, patch
= sphinx
.version_info
[:3]
51 # Namespace to be prepended to the full name
55 # Handle trivial newer c domain tags that are part of Sphinx 3.1 c domain tags
56 # - Store the namespace if ".. c:namespace::" tag is found
58 RE_namespace
= re
.compile(r
'^\s*..\s*c:namespace::\s*(\S+)\s*$')
60 def markup_namespace(match
):
63 namespace
= match
.group(1)
68 # Handle c:macro for function-style declaration
70 RE_macro
= re
.compile(r
'^\s*..\s*c:macro::\s*(\S+)\s+(\S.*)\s*$')
71 def markup_macro(match
):
72 return ".. c:function:: " + match
.group(1) + ' ' + match
.group(2)
75 # Handle newer c domain tags that are evaluated as .. c:type: for
76 # backward-compatibility with Sphinx < 3.0
78 RE_ctype
= re
.compile(r
'^\s*..\s*c:(struct|union|enum|enumerator|alias)::\s*(.*)$')
80 def markup_ctype(match
):
81 return ".. c:type:: " + match
.group(2)
84 # Handle newer c domain tags that are evaluated as :c:type: for
85 # backward-compatibility with Sphinx < 3.0
87 RE_ctype_refs
= re
.compile(r
':c:(var|struct|union|enum|enumerator)::`([^\`]+)`')
88 def markup_ctype_refs(match
):
89 return ":c:type:`" + match
.group(2) + '`'
92 # Simply convert :c:expr: and :c:texpr: into a literal block.
94 RE_expr
= re
.compile(r
':c:(expr|texpr):`([^\`]+)`')
95 def markup_c_expr(match
):
96 return '\ ``' + match
.group(2) + '``\ '
99 # Parse Sphinx 3.x C markups, replacing them by backward-compatible ones
101 def c_markups(app
, docname
, source
):
104 RE_namespace
: markup_namespace
,
105 RE_expr
: markup_c_expr
,
106 RE_macro
: markup_macro
,
107 RE_ctype
: markup_ctype
,
108 RE_ctype_refs
: markup_ctype_refs
,
111 lines
= iter(source
[0].splitlines(True))
113 match_iterators
= [regex
.finditer(n
) for regex
in markup_func
]
114 matches
= sorted(chain(*match_iterators
), key
=lambda m
: m
.start())
116 n
= n
[:m
.start()] + markup_func
[m
.re
](m
) + n
[m
.end():]
123 # Now implements support for the cdomain namespacing logic
128 # Handle easy Sphinx 3.1+ simple new tags: :c:expr and .. c:namespace::
129 app
.connect('source-read', c_markups
)
131 if (major
== 1 and minor
< 8):
132 app
.override_domain(CDomain
)
134 app
.add_domain(CDomain
, override
=True)
137 version
= __version__
,
138 parallel_read_safe
= True,
139 parallel_write_safe
= True
142 class CObject(Base_CObject
):
145 Description of a C language object.
148 "name" : directives
.unchanged
151 def handle_func_like_macro(self
, sig
, signode
):
152 u
"""Handles signatures of function-like macros.
154 If the objtype is 'function' and the the signature ``sig`` is a
155 function-like macro, the name of the macro is returned. Otherwise
156 ``False`` is returned. """
160 if not self
.objtype
== 'function':
163 m
= c_funcptr_sig_re
.match(sig
)
165 m
= c_sig_re
.match(sig
)
167 raise ValueError('no match')
169 rettype
, fullname
, arglist
, _const
= m
.groups()
170 arglist
= arglist
.strip()
171 if rettype
or not arglist
:
174 arglist
= arglist
.replace('`', '').replace('\\ ', '') # remove markup
175 arglist
= [a
.strip() for a
in arglist
.split(",")]
177 # has the first argument a type?
178 if len(arglist
[0].split(" ")) > 1:
181 # This is a function-like macro, it's arguments are typeless!
182 signode
+= addnodes
.desc_name(fullname
, fullname
)
183 paramlist
= addnodes
.desc_parameterlist()
186 for argname
in arglist
:
187 param
= addnodes
.desc_parameter('', '', noemph
=True)
188 # separate by non-breaking space in the output
189 param
+= nodes
.emphasis(argname
, argname
)
193 fullname
= namespace
+ "." + fullname
197 def handle_signature(self
, sig
, signode
):
198 """Transform a C signature into RST nodes."""
202 fullname
= self
.handle_func_like_macro(sig
, signode
)
204 fullname
= super(CObject
, self
).handle_signature(sig
, signode
)
206 if "name" in self
.options
:
207 if self
.objtype
== 'function':
208 fullname
= self
.options
["name"]
210 # FIXME: handle :name: value of other declaration types?
214 fullname
= namespace
+ "." + fullname
218 def add_target_and_index(self
, name
, sig
, signode
):
219 # for C API items we add a prefix since names are usually not qualified
220 # by a module name and so easily clash with e.g. section titles
221 targetname
= 'c.' + name
222 if targetname
not in self
.state
.document
.ids
:
223 signode
['names'].append(targetname
)
224 signode
['ids'].append(targetname
)
225 signode
['first'] = (not self
.names
)
226 self
.state
.document
.note_explicit_target(signode
)
227 inv
= self
.env
.domaindata
['c']['objects']
228 if (name
in inv
and self
.env
.config
.nitpicky
):
229 if self
.objtype
== 'function':
230 if ('c:func', name
) not in self
.env
.config
.nitpick_ignore
:
231 self
.state_machine
.reporter
.warning(
232 'duplicate C object description of %s, ' % name
+
233 'other instance in ' + self
.env
.doc2path(inv
[name
][0]),
235 inv
[name
] = (self
.env
.docname
, self
.objtype
)
237 indextext
= self
.get_index_text(name
)
239 if major
== 1 and minor
< 4:
240 # indexnode's tuple changed in 1.4
241 # https://github.com/sphinx-doc/sphinx/commit/e6a5a3a92e938fcd75866b4227db9e0524d58f7c
242 self
.indexnode
['entries'].append(
243 ('single', indextext
, targetname
, ''))
245 self
.indexnode
['entries'].append(
246 ('single', indextext
, targetname
, '', None))
248 class CDomain(Base_CDomain
):
250 """C language domain."""