1 # SPDX-License-Identifier: GPL-2.0 1 # SPDX-License-Identifier: GPL-2.0 2 # Copyright 2019 Jonathan Corbet <corbet@lwn.ne 2 # Copyright 2019 Jonathan Corbet <corbet@lwn.net> 3 # 3 # 4 # Apply kernel-specific tweaks after the initi 4 # Apply kernel-specific tweaks after the initial document processing 5 # has been done. 5 # has been done. 6 # 6 # 7 from docutils import nodes 7 from docutils import nodes 8 import sphinx << 9 from sphinx import addnodes 8 from sphinx import addnodes 10 from sphinx.errors import NoUri !! 9 from sphinx.environment import NoUri 11 import re 10 import re 12 from itertools import chain << 13 << 14 # << 15 # Python 2 lacks re.ASCII... << 16 # << 17 try: << 18 ascii_p3 = re.ASCII << 19 except AttributeError: << 20 ascii_p3 = 0 << 21 11 22 # 12 # 23 # Regex nastiness. Of course. 13 # Regex nastiness. Of course. 24 # Try to identify "function()" that's not alre 14 # Try to identify "function()" that's not already marked up some 25 # other way. Sphinx doesn't like a lot of stu 15 # other way. Sphinx doesn't like a lot of stuff right after a 26 # :c:func: block (i.e. ":c:func:`mmap()`s" fla 16 # :c:func: block (i.e. ":c:func:`mmap()`s" flakes out), so the last 27 # bit tries to restrict matches to things that 17 # bit tries to restrict matches to things that won't create trouble. 28 # 18 # 29 RE_function = re.compile(r'\b(([a-zA-Z_]\w+)\( !! 19 RE_function = re.compile(r'([\w_][\w\d_]+\(\))') 30 << 31 # << 32 # Sphinx 2 uses the same :c:type role for stru << 33 # << 34 RE_generic_type = re.compile(r'\b(struct|union << 35 flags=ascii_p3) << 36 << 37 # << 38 # Sphinx 3 uses a different C role for each on << 39 # typedef << 40 # << 41 RE_struct = re.compile(r'\b(struct)\s+([a-zA-Z << 42 RE_union = re.compile(r'\b(union)\s+([a-zA-Z_] << 43 RE_enum = re.compile(r'\b(enum)\s+([a-zA-Z_]\w << 44 RE_typedef = re.compile(r'\b(typedef)\s+([a-zA << 45 << 46 # << 47 # Detects a reference to a documentation page << 48 # an optional extension << 49 # << 50 RE_doc = re.compile(r'(\bDocumentation/)?((\.\ << 51 << 52 RE_namespace = re.compile(r'^\s*..\s*c:namespa << 53 << 54 # << 55 # Reserved C words that we should skip when cr << 56 # << 57 Skipnames = [ 'for', 'if', 'register', 'sizeof << 58 << 59 20 60 # 21 # 61 # Many places in the docs refer to common syst 22 # Many places in the docs refer to common system calls. It is 62 # pointless to try to cross-reference them and 23 # pointless to try to cross-reference them and, as has been known 63 # to happen, somebody defining a function by t 24 # to happen, somebody defining a function by these names can lead 64 # to the creation of incorrect and confusing c 25 # to the creation of incorrect and confusing cross references. So 65 # just don't even try with these names. 26 # just don't even try with these names. 66 # 27 # 67 Skipfuncs = [ 'open', 'close', 'read', 'write' 28 Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap', 68 'select', 'poll', 'fork', 'execv !! 29 'select', 'poll', 'fork', 'execve', 'clone', 'ioctl'] 69 'socket' ] << 70 << 71 c_namespace = '' << 72 30 73 # 31 # 74 # Detect references to commits. !! 32 # Find all occurrences of function() and try to replace them with >> 33 # appropriate cross references. 75 # 34 # 76 RE_git = re.compile(r'commit\s+(?P<rev>[0-9a-f !! 35 def markup_funcs(docname, app, node): 77 flags=re.IGNORECASE | re.DOTALL) !! 36 cdom = app.env.domains['c'] 78 << 79 def markup_refs(docname, app, node): << 80 t = node.astext() 37 t = node.astext() 81 done = 0 38 done = 0 82 repl = [ ] 39 repl = [ ] 83 # !! 40 for m in RE_function.finditer(t): 84 # Associate each regex with the function t << 85 # << 86 markup_func_sphinx2 = {RE_doc: markup_doc_ << 87 RE_function: markup << 88 RE_generic_type: ma << 89 << 90 markup_func_sphinx3 = {RE_doc: markup_doc_ << 91 RE_function: markup << 92 RE_struct: markup_c << 93 RE_union: markup_c_ << 94 RE_enum: markup_c_r << 95 RE_typedef: markup_ << 96 RE_git: markup_git} << 97 << 98 if sphinx.version_info[0] >= 3: << 99 markup_func = markup_func_sphinx3 << 100 else: << 101 markup_func = markup_func_sphinx2 << 102 << 103 match_iterators = [regex.finditer(t) for r << 104 # << 105 # Sort all references by the starting posi << 106 # << 107 sorted_matches = sorted(chain(*match_itera << 108 for m in sorted_matches: << 109 # 41 # 110 # Include any text prior to match as a !! 42 # Include any text prior to function() as a normal text node. 111 # 43 # 112 if m.start() > done: 44 if m.start() > done: 113 repl.append(nodes.Text(t[done:m.st 45 repl.append(nodes.Text(t[done:m.start()])) 114 << 115 # 46 # 116 # Call the function associated with th !! 47 # Go through the dance of getting an xref out of the C domain 117 # append its return to the text << 118 # 48 # 119 repl.append(markup_func[m.re](docname, !! 49 target = m.group(1)[:-2] 120 !! 50 target_text = nodes.Text(target + '()') >> 51 xref = None >> 52 if target not in Skipfuncs: >> 53 lit_text = nodes.literal(classes=['xref', 'c', 'c-func']) >> 54 lit_text += target_text >> 55 pxref = addnodes.pending_xref('', refdomain = 'c', >> 56 reftype = 'function', >> 57 reftarget = target, modname = None, >> 58 classname = None) >> 59 # >> 60 # XXX The Latex builder will throw NoUri exceptions here, >> 61 # work around that by ignoring them. >> 62 # >> 63 try: >> 64 xref = cdom.resolve_xref(app.env, docname, app.builder, >> 65 'function', target, pxref, lit_text) >> 66 except NoUri: >> 67 xref = None >> 68 # >> 69 # Toss the xref into the list if we got it; otherwise just put >> 70 # the function text. >> 71 # >> 72 if xref: >> 73 repl.append(xref) >> 74 else: >> 75 repl.append(target_text) 121 done = m.end() 76 done = m.end() 122 if done < len(t): 77 if done < len(t): 123 repl.append(nodes.Text(t[done:])) 78 repl.append(nodes.Text(t[done:])) 124 return repl 79 return repl 125 80 126 # << 127 # Keep track of cross-reference lookups that f << 128 # do them again. << 129 # << 130 failed_lookups = { } << 131 def failure_seen(target): << 132 return (target) in failed_lookups << 133 def note_failure(target): << 134 failed_lookups[target] = True << 135 << 136 # << 137 # In sphinx3 we can cross-reference to C macro << 138 # own C role, but both match the same regex, s << 139 # << 140 def markup_func_ref_sphinx3(docname, app, matc << 141 cdom = app.env.domains['c'] << 142 # << 143 # Go through the dance of getting an xref << 144 # << 145 base_target = match.group(2) << 146 target_text = nodes.Text(match.group(0)) << 147 xref = None << 148 possible_targets = [base_target] << 149 # Check if this document has a namespace, << 150 # cross-referencing inside it first. << 151 if c_namespace: << 152 possible_targets.insert(0, c_namespace << 153 << 154 if base_target not in Skipnames: << 155 for target in possible_targets: << 156 if (target not in Skipfuncs) and n << 157 lit_text = nodes.literal(class << 158 lit_text += target_text << 159 pxref = addnodes.pending_xref( << 160 << 161 << 162 << 163 << 164 # << 165 # XXX The Latex builder will t << 166 # work around that by ignoring << 167 # << 168 try: << 169 xref = cdom.resolve_xref(a << 170 ' << 171 l << 172 except NoUri: << 173 xref = None << 174 << 175 if xref: << 176 return xref << 177 note_failure(target) << 178 << 179 return target_text << 180 << 181 def markup_c_ref(docname, app, match): << 182 class_str = {# Sphinx 2 only << 183 RE_function: 'c-func', << 184 RE_generic_type: 'c-type', << 185 # Sphinx 3+ only << 186 RE_struct: 'c-struct', << 187 RE_union: 'c-union', << 188 RE_enum: 'c-enum', << 189 RE_typedef: 'c-type', << 190 } << 191 reftype_str = {# Sphinx 2 only << 192 RE_function: 'function', << 193 RE_generic_type: 'type', << 194 # Sphinx 3+ only << 195 RE_struct: 'struct', << 196 RE_union: 'union', << 197 RE_enum: 'enum', << 198 RE_typedef: 'type', << 199 } << 200 << 201 cdom = app.env.domains['c'] << 202 # << 203 # Go through the dance of getting an xref << 204 # << 205 base_target = match.group(2) << 206 target_text = nodes.Text(match.group(0)) << 207 xref = None << 208 possible_targets = [base_target] << 209 # Check if this document has a namespace, << 210 # cross-referencing inside it first. << 211 if c_namespace: << 212 possible_targets.insert(0, c_namespace << 213 << 214 if base_target not in Skipnames: << 215 for target in possible_targets: << 216 if not (match.re == RE_function an << 217 lit_text = nodes.literal(class << 218 lit_text += target_text << 219 pxref = addnodes.pending_xref( << 220 << 221 << 222 << 223 # << 224 # XXX The Latex builder will t << 225 # work around that by ignoring << 226 # << 227 try: << 228 xref = cdom.resolve_xref(a << 229 r << 230 l << 231 except NoUri: << 232 xref = None << 233 << 234 if xref: << 235 return xref << 236 << 237 return target_text << 238 << 239 # << 240 # Try to replace a documentation reference of << 241 # cross reference to that page << 242 # << 243 def markup_doc_ref(docname, app, match): << 244 stddom = app.env.domains['std'] << 245 # << 246 # Go through the dance of getting an xref << 247 # << 248 absolute = match.group(1) << 249 target = match.group(2) << 250 if absolute: << 251 target = "/" + target << 252 xref = None << 253 pxref = addnodes.pending_xref('', refdomai << 254 reftarget = << 255 classname = << 256 # << 257 # XXX The Latex builder will throw NoUri e << 258 # work around that by ignoring them. << 259 # << 260 try: << 261 xref = stddom.resolve_xref(app.env, do << 262 target, pxr << 263 except NoUri: << 264 xref = None << 265 # << 266 # Return the xref if we got it; otherwise << 267 # << 268 if xref: << 269 return xref << 270 else: << 271 return nodes.Text(match.group(0)) << 272 << 273 def get_c_namespace(app, docname): << 274 source = app.env.doc2path(docname) << 275 with open(source) as f: << 276 for l in f: << 277 match = RE_namespace.search(l) << 278 if match: << 279 return match.group(1) << 280 return '' << 281 << 282 def markup_git(docname, app, match): << 283 # While we could probably assume that we a << 284 # repository, we can't know for sure, so l << 285 # turn them into git.kernel.org links with << 286 # validity. (Maybe we can do something in << 287 # these references if this is explicitly r << 288 text = match.group(0) << 289 rev = match.group('rev') << 290 return nodes.reference('', nodes.Text(text << 291 refuri=f'https://git.kernel.org/torval << 292 << 293 def auto_markup(app, doctree, name): 81 def auto_markup(app, doctree, name): 294 global c_namespace << 295 c_namespace = get_c_namespace(app, name) << 296 def text_but_not_a_reference(node): << 297 # The nodes.literal test catches ``lit << 298 # avoid adding cross-references to fun << 299 # marked with cc:func:. << 300 if not isinstance(node, nodes.Text) or << 301 return False << 302 << 303 child_of_reference = False << 304 parent = node.parent << 305 while parent: << 306 if isinstance(parent, nodes.Refere << 307 child_of_reference = True << 308 break << 309 parent = parent.parent << 310 return not child_of_reference << 311 << 312 # 82 # 313 # This loop could eventually be improved o 83 # This loop could eventually be improved on. Someday maybe we 314 # want a proper tree traversal with a lot 84 # want a proper tree traversal with a lot of awareness of which 315 # kinds of nodes to prune. But this works 85 # kinds of nodes to prune. But this works well for now. 316 # 86 # >> 87 # The nodes.literal test catches ``literal text``, its purpose is to >> 88 # avoid adding cross-references to functions that have been explicitly >> 89 # marked with cc:func:. >> 90 # 317 for para in doctree.traverse(nodes.paragra 91 for para in doctree.traverse(nodes.paragraph): 318 for node in para.traverse(condition=te !! 92 for node in para.traverse(nodes.Text): 319 node.parent.replace(node, markup_r !! 93 if not isinstance(node.parent, nodes.literal): >> 94 node.parent.replace(node, markup_funcs(name, app, node)) 320 95 321 def setup(app): 96 def setup(app): 322 app.connect('doctree-resolved', auto_marku 97 app.connect('doctree-resolved', auto_markup) 323 return { 98 return { 324 'parallel_read_safe': True, 99 'parallel_read_safe': True, 325 'parallel_write_safe': True, 100 'parallel_write_safe': True, 326 } 101 }
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.