1# SPDX-License-Identifier: GPL-2.0 2# Copyright 2019 Jonathan Corbet <corbet@lwn.net> 3# 4# Apply kernel-specific tweaks after the initial document processing 5# has been done. 6# 7from docutils import nodes 8import sphinx 9from sphinx import addnodes 10if sphinx.version_info[0] < 2 or \ 11 sphinx.version_info[0] == 2 and sphinx.version_info[1] < 1: 12 from sphinx.environment import NoUri 13else: 14 from sphinx.errors import NoUri 15import re 16from itertools import chain 17 18# 19# Regex nastiness. Of course. 20# Try to identify "function()" that's not already marked up some 21# other way. Sphinx doesn't like a lot of stuff right after a 22# :c:func: block (i.e. ":c:func:`mmap()`s" flakes out), so the last 23# bit tries to restrict matches to things that won't create trouble. 24# 25RE_function = re.compile(r'(([\w_][\w\d_]+)\(\))') 26RE_type = re.compile(r'(struct|union|enum|typedef)\s+([\w_][\w\d_]+)') 27 28# 29# Many places in the docs refer to common system calls. It is 30# pointless to try to cross-reference them and, as has been known 31# to happen, somebody defining a function by these names can lead 32# to the creation of incorrect and confusing cross references. So 33# just don't even try with these names. 34# 35Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap', 36 'select', 'poll', 'fork', 'execve', 'clone', 'ioctl', 37 'socket' ] 38 39# 40# Find all occurrences of C references (function() and struct/union/enum/typedef 41# type_name) and try to replace them with appropriate cross references. 42# 43def markup_c_refs(docname, app, node): 44 class_str = {RE_function: 'c-func', RE_type: 'c-type'} 45 reftype_str = {RE_function: 'function', RE_type: 'type'} 46 47 cdom = app.env.domains['c'] 48 t = node.astext() 49 done = 0 50 repl = [ ] 51 # 52 # Sort all C references by the starting position in text 53 # 54 sorted_matches = sorted(chain(RE_type.finditer(t), RE_function.finditer(t)), 55 key=lambda m: m.start()) 56 for m in sorted_matches: 57 # 58 # Include any text prior to match as a normal text node. 59 # 60 if m.start() > done: 61 repl.append(nodes.Text(t[done:m.start()])) 62 # 63 # Go through the dance of getting an xref out of the C domain 64 # 65 target = m.group(2) 66 target_text = nodes.Text(m.group(0)) 67 xref = None 68 if not (m.re == RE_function and target in Skipfuncs): 69 lit_text = nodes.literal(classes=['xref', 'c', class_str[m.re]]) 70 lit_text += target_text 71 pxref = addnodes.pending_xref('', refdomain = 'c', 72 reftype = reftype_str[m.re], 73 reftarget = target, modname = None, 74 classname = None) 75 # 76 # XXX The Latex builder will throw NoUri exceptions here, 77 # work around that by ignoring them. 78 # 79 try: 80 xref = cdom.resolve_xref(app.env, docname, app.builder, 81 reftype_str[m.re], target, pxref, 82 lit_text) 83 except NoUri: 84 xref = None 85 # 86 # Toss the xref into the list if we got it; otherwise just put 87 # the function text. 88 # 89 if xref: 90 repl.append(xref) 91 else: 92 repl.append(target_text) 93 done = m.end() 94 if done < len(t): 95 repl.append(nodes.Text(t[done:])) 96 return repl 97 98def auto_markup(app, doctree, name): 99 # 100 # This loop could eventually be improved on. Someday maybe we 101 # want a proper tree traversal with a lot of awareness of which 102 # kinds of nodes to prune. But this works well for now. 103 # 104 # The nodes.literal test catches ``literal text``, its purpose is to 105 # avoid adding cross-references to functions that have been explicitly 106 # marked with cc:func:. 107 # 108 for para in doctree.traverse(nodes.paragraph): 109 for node in para.traverse(nodes.Text): 110 if not isinstance(node.parent, nodes.literal): 111 node.parent.replace(node, markup_c_refs(name, app, node)) 112 113def setup(app): 114 app.connect('doctree-resolved', auto_markup) 115 return { 116 'parallel_read_safe': True, 117 'parallel_write_safe': True, 118 } 119