Merge tag 'hyperv-next-signed' of git://git.kernel.org/pub/scm/linux/kernel/git/hyper...
[linux-2.6-microblaze.git] / Documentation / sphinx / automarkup.py
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 #
7 from docutils import nodes
8 import sphinx
9 from sphinx import addnodes
10 if sphinx.version_info[0] < 2 or \
11    sphinx.version_info[0] == 2 and sphinx.version_info[1] < 1:
12     from sphinx.environment import NoUri
13 else:
14     from sphinx.errors import NoUri
15 import re
16 from 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 #
25 RE_function = re.compile(r'(([\w_][\w\d_]+)\(\))')
26 RE_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 #
31 RE_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 #
40 Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap',
41               'select', 'poll', 'fork', 'execve', 'clone', 'ioctl',
42               'socket' ]
43
44 def 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 #
81 def 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 #
121 def 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
148 def 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
163 def setup(app):
164     app.connect('doctree-resolved', auto_markup)
165     return {
166         'parallel_read_safe': True,
167         'parallel_write_safe': True,
168         }