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# Detects a reference to a documentation page of the form Documentation/... with 29# an optional extension 30# 31RE_doc = re.compile(r'Documentation(/[\w\-_/]+)(\.\w+)*') 32 33# 34# Many places in the docs refer to common system calls. It is 35# pointless to try to cross-reference them and, as has been known 36# to happen, somebody defining a function by these names can lead 37# to the creation of incorrect and confusing cross references. So 38# just don't even try with these names. 39# 40Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap', 41 'select', 'poll', 'fork', 'execve', 'clone', 'ioctl', 42 'socket' ] 43 44def markup_refs(docname, app, node): 45 t = node.astext() 46 done = 0 47 repl = [ ] 48 # 49 # Associate each regex with the function that will markup its matches 50 # 51 markup_func = {RE_type: markup_c_ref, 52 RE_function: markup_c_ref, 53 RE_doc: markup_doc_ref} 54 match_iterators = [regex.finditer(t) for regex in markup_func] 55 # 56 # Sort all references by the starting position in text 57 # 58 sorted_matches = sorted(chain(*match_iterators), key=lambda m: m.start()) 59 for m in sorted_matches: 60 # 61 # Include any text prior to match as a normal text node. 62 # 63 if m.start() > done: 64 repl.append(nodes.Text(t[done:m.start()])) 65 66 # 67 # Call the function associated with the regex that matched this text and 68 # append its return to the text 69 # 70 repl.append(markup_func[m.re](docname, app, m)) 71 72 done = m.end() 73 if done < len(t): 74 repl.append(nodes.Text(t[done:])) 75 return repl 76 77# 78# Try to replace a C reference (function() or struct/union/enum/typedef 79# type_name) with an appropriate cross reference. 80# 81def markup_c_ref(docname, app, match): 82 class_str = {RE_function: 'c-func', RE_type: 'c-type'} 83 reftype_str = {RE_function: 'function', RE_type: 'type'} 84 85 cdom = app.env.domains['c'] 86 # 87 # Go through the dance of getting an xref out of the C domain 88 # 89 target = match.group(2) 90 target_text = nodes.Text(match.group(0)) 91 xref = None 92 if not (match.re == RE_function and target in Skipfuncs): 93 lit_text = nodes.literal(classes=['xref', 'c', class_str[match.re]]) 94 lit_text += target_text 95 pxref = addnodes.pending_xref('', refdomain = 'c', 96 reftype = reftype_str[match.re], 97 reftarget = target, modname = None, 98 classname = None) 99 # 100 # XXX The Latex builder will throw NoUri exceptions here, 101 # work around that by ignoring them. 102 # 103 try: 104 xref = cdom.resolve_xref(app.env, docname, app.builder, 105 reftype_str[match.re], target, pxref, 106 lit_text) 107 except NoUri: 108 xref = None 109 # 110 # Return the xref if we got it; otherwise just return the plain text. 111 # 112 if xref: 113 return xref 114 else: 115 return target_text 116 117# 118# Try to replace a documentation reference of the form Documentation/... with a 119# cross reference to that page 120# 121def markup_doc_ref(docname, app, match): 122 stddom = app.env.domains['std'] 123 # 124 # Go through the dance of getting an xref out of the std domain 125 # 126 target = match.group(1) 127 xref = None 128 pxref = addnodes.pending_xref('', refdomain = 'std', reftype = 'doc', 129 reftarget = target, modname = None, 130 classname = None, refexplicit = False) 131 # 132 # XXX The Latex builder will throw NoUri exceptions here, 133 # work around that by ignoring them. 134 # 135 try: 136 xref = stddom.resolve_xref(app.env, docname, app.builder, 'doc', 137 target, pxref, None) 138 except NoUri: 139 xref = None 140 # 141 # Return the xref if we got it; otherwise just return the plain text. 142 # 143 if xref: 144 return xref 145 else: 146 return nodes.Text(match.group(0)) 147 148def auto_markup(app, doctree, name): 149 # 150 # This loop could eventually be improved on. Someday maybe we 151 # want a proper tree traversal with a lot of awareness of which 152 # kinds of nodes to prune. But this works well for now. 153 # 154 # The nodes.literal test catches ``literal text``, its purpose is to 155 # avoid adding cross-references to functions that have been explicitly 156 # marked with cc:func:. 157 # 158 for para in doctree.traverse(nodes.paragraph): 159 for node in para.traverse(nodes.Text): 160 if not isinstance(node.parent, nodes.literal): 161 node.parent.replace(node, markup_refs(name, app, node)) 162 163def setup(app): 164 app.connect('doctree-resolved', auto_markup) 165 return { 166 'parallel_read_safe': True, 167 'parallel_write_safe': True, 168 } 169