blob: 272eb2bdfab1b399194ceb59ce7d99fbc410a23a [file] [log] [blame]
Jonathan Corbetd74b0d32019-04-25 07:55:07 -06001# 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
Jonathan Corbetbcac3862020-01-22 16:06:28 -07008import sphinx
Jonathan Corbetd74b0d32019-04-25 07:55:07 -06009from sphinx import addnodes
Jonathan Corbetbcac3862020-01-22 16:06:28 -070010if 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
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060015import re
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000016from itertools import chain
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060017
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#
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000025RE_function = re.compile(r'(([\w_][\w\d_]+)\(\))')
26RE_type = re.compile(r'(struct|union|enum|typedef)\s+([\w_][\w\d_]+)')
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060027
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#
Jonathan Neuschäfer11fec0092019-08-12 18:07:04 +020035Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap',
Jonathan Neuschäfer82bf8292019-08-12 18:07:05 +020036 'select', 'poll', 'fork', 'execve', 'clone', 'ioctl',
37 'socket' ]
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060038
39#
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000040# Find all occurrences of C references (function() and struct/union/enum/typedef
41# type_name) and try to replace them with appropriate cross references.
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060042#
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000043def 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
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060047 cdom = app.env.domains['c']
48 t = node.astext()
49 done = 0
50 repl = [ ]
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000051 #
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:
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060057 #
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000058 # Include any text prior to match as a normal text node.
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060059 #
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 #
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000065 target = m.group(2)
66 target_text = nodes.Text(m.group(0))
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060067 xref = None
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000068 if not (m.re == RE_function and target in Skipfuncs):
69 lit_text = nodes.literal(classes=['xref', 'c', class_str[m.re]])
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060070 lit_text += target_text
71 pxref = addnodes.pending_xref('', refdomain = 'c',
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000072 reftype = reftype_str[m.re],
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060073 reftarget = target, modname = None,
74 classname = None)
Mauro Carvalho Chehab454f96f2019-07-06 13:28:42 -030075 #
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,
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +000081 reftype_str[m.re], target, pxref,
82 lit_text)
Mauro Carvalho Chehab454f96f2019-07-06 13:28:42 -030083 except NoUri:
84 xref = None
Jonathan Corbetd74b0d32019-04-25 07:55:07 -060085 #
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):
Nícolas F. R. A. Pradod82b1e82020-09-03 00:58:19 +0000111 node.parent.replace(node, markup_c_refs(name, app, node))
Jonathan Corbetd74b0d32019-04-25 07:55:07 -0600112
113def setup(app):
114 app.connect('doctree-resolved', auto_markup)
115 return {
116 'parallel_read_safe': True,
117 'parallel_write_safe': True,
118 }