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