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