Commit | Line | Data |
---|---|---|
e8f5c617 | 1 | # -*- coding: utf-8; mode: python -*- |
56cd8692 | 2 | # pylint: disable=W0141,C0113,C0103,C0325 |
e8f5c617 MH |
3 | u""" |
4 | cdomain | |
5 | ~~~~~~~ | |
6 | ||
7 | Replacement for the sphinx c-domain. | |
8 | ||
9 | :copyright: Copyright (C) 2016 Markus Heiser | |
10 | :license: GPL Version 2, June 1991 see Linux/COPYING for details. | |
2c645cd7 MH |
11 | |
12 | List of customizations: | |
13 | ||
556aa6d5 MH |
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``. | |
17 | ||
2c645cd7 MH |
18 | * Add option 'name' to the "c:function:" directive. With option 'name' the |
19 | ref-name of a function can be modified. E.g.:: | |
20 | ||
21 | .. c:function:: int ioctl( int fd, int request ) | |
22 | :name: VIDIOC_LOG_STATUS | |
23 | ||
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:: | |
26 | ||
27 | * :c:func:`VIDIOC_LOG_STATUS` or | |
28 | * :any:`VIDIOC_LOG_STATUS` (``:any:`` needs sphinx 1.3) | |
56cd8692 MH |
29 | |
30 | * Handle signatures of function-like macros well. Don't try to deduce | |
31 | arguments types of function-like macros. | |
32 | ||
e8f5c617 MH |
33 | """ |
34 | ||
56cd8692 | 35 | from docutils import nodes |
2c645cd7 MH |
36 | from docutils.parsers.rst import directives |
37 | ||
b495360e | 38 | import sphinx |
56cd8692 MH |
39 | from sphinx import addnodes |
40 | from sphinx.domains.c import c_funcptr_sig_re, c_sig_re | |
e8f5c617 MH |
41 | from sphinx.domains.c import CObject as Base_CObject |
42 | from sphinx.domains.c import CDomain as Base_CDomain | |
71e552ae MCC |
43 | from itertools import chain |
44 | import re | |
e8f5c617 | 45 | |
71e552ae | 46 | __version__ = '1.1' |
e8f5c617 | 47 | |
b495360e | 48 | # Get Sphinx version |
c46988ae | 49 | major, minor, patch = sphinx.version_info[:3] |
b495360e | 50 | |
71e552ae MCC |
51 | # Namespace to be prepended to the full name |
52 | namespace = None | |
53 | ||
54 | # | |
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 | |
95f49490 | 57 | # |
71e552ae MCC |
58 | RE_namespace = re.compile(r'^\s*..\s*c:namespace::\s*(\S+)\s*$') |
59 | ||
60 | def markup_namespace(match): | |
61 | global namespace | |
62 | ||
63 | namespace = match.group(1) | |
64 | ||
65 | return "" | |
66 | ||
95f49490 MCC |
67 | # |
68 | # Handle c:macro for function-style declaration | |
69 | # | |
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) | |
73 | ||
74 | # | |
75 | # Handle newer c domain tags that are evaluated as .. c:type: for | |
76 | # backward-compatibility with Sphinx < 3.0 | |
77 | # | |
78 | RE_ctype = re.compile(r'^\s*..\s*c:(struct|union|enum|enumerator|alias)::\s*(.*)$') | |
79 | ||
80 | def markup_ctype(match): | |
81 | return ".. c:type:: " + match.group(2) | |
82 | ||
83 | # | |
84 | # Handle newer c domain tags that are evaluated as :c:type: for | |
85 | # backward-compatibility with Sphinx < 3.0 | |
86 | # | |
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) + '`' | |
90 | ||
91 | # | |
92 | # Simply convert :c:expr: and :c:texpr: into a literal block. | |
93 | # | |
94 | RE_expr = re.compile(r':c:(expr|texpr):`([^\`]+)`') | |
95 | def markup_c_expr(match): | |
96 | return '\ ``' + match.group(2) + '``\ ' | |
97 | ||
98 | # | |
99 | # Parse Sphinx 3.x C markups, replacing them by backward-compatible ones | |
100 | # | |
71e552ae MCC |
101 | def c_markups(app, docname, source): |
102 | result = "" | |
103 | markup_func = { | |
104 | RE_namespace: markup_namespace, | |
95f49490 MCC |
105 | RE_expr: markup_c_expr, |
106 | RE_macro: markup_macro, | |
107 | RE_ctype: markup_ctype, | |
108 | RE_ctype_refs: markup_ctype_refs, | |
71e552ae MCC |
109 | } |
110 | ||
111 | lines = iter(source[0].splitlines(True)) | |
112 | for n in lines: | |
113 | match_iterators = [regex.finditer(n) for regex in markup_func] | |
114 | matches = sorted(chain(*match_iterators), key=lambda m: m.start()) | |
115 | for m in matches: | |
116 | n = n[:m.start()] + markup_func[m.re](m) + n[m.end():] | |
117 | ||
118 | result = result + n | |
119 | ||
120 | source[0] = result | |
121 | ||
122 | # | |
123 | # Now implements support for the cdomain namespacing logic | |
124 | # | |
125 | ||
e8f5c617 MH |
126 | def setup(app): |
127 | ||
71e552ae MCC |
128 | # Handle easy Sphinx 3.1+ simple new tags: :c:expr and .. c:namespace:: |
129 | app.connect('source-read', c_markups) | |
130 | ||
42f6ebd8 MCC |
131 | if (major == 1 and minor < 8): |
132 | app.override_domain(CDomain) | |
133 | else: | |
134 | app.add_domain(CDomain, override=True) | |
e8f5c617 MH |
135 | |
136 | return dict( | |
137 | version = __version__, | |
138 | parallel_read_safe = True, | |
139 | parallel_write_safe = True | |
140 | ) | |
141 | ||
142 | class CObject(Base_CObject): | |
143 | ||
144 | """ | |
145 | Description of a C language object. | |
146 | """ | |
2c645cd7 MH |
147 | option_spec = { |
148 | "name" : directives.unchanged | |
149 | } | |
150 | ||
56cd8692 MH |
151 | def handle_func_like_macro(self, sig, signode): |
152 | u"""Handles signatures of function-like macros. | |
153 | ||
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. """ | |
157 | ||
71e552ae MCC |
158 | global namespace |
159 | ||
56cd8692 MH |
160 | if not self.objtype == 'function': |
161 | return False | |
162 | ||
163 | m = c_funcptr_sig_re.match(sig) | |
164 | if m is None: | |
165 | m = c_sig_re.match(sig) | |
166 | if m is None: | |
167 | raise ValueError('no match') | |
168 | ||
169 | rettype, fullname, arglist, _const = m.groups() | |
170 | arglist = arglist.strip() | |
171 | if rettype or not arglist: | |
172 | return False | |
173 | ||
174 | arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup | |
175 | arglist = [a.strip() for a in arglist.split(",")] | |
176 | ||
177 | # has the first argument a type? | |
178 | if len(arglist[0].split(" ")) > 1: | |
179 | return False | |
180 | ||
d56b699d | 181 | # This is a function-like macro, its arguments are typeless! |
56cd8692 MH |
182 | signode += addnodes.desc_name(fullname, fullname) |
183 | paramlist = addnodes.desc_parameterlist() | |
184 | signode += paramlist | |
185 | ||
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) | |
190 | paramlist += param | |
191 | ||
71e552ae MCC |
192 | if namespace: |
193 | fullname = namespace + "." + fullname | |
194 | ||
56cd8692 MH |
195 | return fullname |
196 | ||
2c645cd7 MH |
197 | def handle_signature(self, sig, signode): |
198 | """Transform a C signature into RST nodes.""" | |
56cd8692 | 199 | |
71e552ae MCC |
200 | global namespace |
201 | ||
56cd8692 MH |
202 | fullname = self.handle_func_like_macro(sig, signode) |
203 | if not fullname: | |
204 | fullname = super(CObject, self).handle_signature(sig, signode) | |
205 | ||
2c645cd7 MH |
206 | if "name" in self.options: |
207 | if self.objtype == 'function': | |
208 | fullname = self.options["name"] | |
209 | else: | |
210 | # FIXME: handle :name: value of other declaration types? | |
211 | pass | |
71e552ae MCC |
212 | else: |
213 | if namespace: | |
214 | fullname = namespace + "." + fullname | |
215 | ||
2c645cd7 MH |
216 | return fullname |
217 | ||
556aa6d5 MH |
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]), | |
234 | line=self.lineno) | |
235 | inv[name] = (self.env.docname, self.objtype) | |
236 | ||
237 | indextext = self.get_index_text(name) | |
238 | if indextext: | |
f546ff0c | 239 | self.indexnode['entries'].append( |
b495360e | 240 | ('single', indextext, targetname, '', None)) |
e8f5c617 MH |
241 | |
242 | class CDomain(Base_CDomain): | |
243 | ||
244 | """C language domain.""" | |
245 | name = 'c' | |
246 | label = 'C' | |
247 | directives = { | |
248 | 'function': CObject, | |
249 | 'member': CObject, | |
250 | 'macro': CObject, | |
251 | 'type': CObject, | |
252 | 'var': CObject, | |
253 | } |