1 # SPDX-License-Identifier: GPL-2.0 2 # Copyright 2019 Jonathan Corbet <corbet@lwn.ne 3 # 4 # Apply kernel-specific tweaks after the initi 5 # has been done. 6 # 7 from docutils import nodes 8 import sphinx 9 from sphinx import addnodes 10 from sphinx.errors import NoUri 11 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 22 # 23 # Regex nastiness. Of course. 24 # Try to identify "function()" that's not alre 25 # other way. Sphinx doesn't like a lot of stu 26 # :c:func: block (i.e. ":c:func:`mmap()`s" fla 27 # bit tries to restrict matches to things that 28 # 29 RE_function = re.compile(r'\b(([a-zA-Z_]\w+)\( 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 60 # 61 # Many places in the docs refer to common syst 62 # pointless to try to cross-reference them and 63 # to happen, somebody defining a function by t 64 # to the creation of incorrect and confusing c 65 # just don't even try with these names. 66 # 67 Skipfuncs = [ 'open', 'close', 'read', 'write' 68 'select', 'poll', 'fork', 'execv 69 'socket' ] 70 71 c_namespace = '' 72 73 # 74 # Detect references to commits. 75 # 76 RE_git = re.compile(r'commit\s+(?P<rev>[0-9a-f 77 flags=re.IGNORECASE | re.DOTALL) 78 79 def markup_refs(docname, app, node): 80 t = node.astext() 81 done = 0 82 repl = [ ] 83 # 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 # 110 # Include any text prior to match as a 111 # 112 if m.start() > done: 113 repl.append(nodes.Text(t[done:m.st 114 115 # 116 # Call the function associated with th 117 # append its return to the text 118 # 119 repl.append(markup_func[m.re](docname, 120 121 done = m.end() 122 if done < len(t): 123 repl.append(nodes.Text(t[done:])) 124 return repl 125 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): 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 # 313 # This loop could eventually be improved o 314 # want a proper tree traversal with a lot 315 # kinds of nodes to prune. But this works 316 # 317 for para in doctree.traverse(nodes.paragra 318 for node in para.traverse(condition=te 319 node.parent.replace(node, markup_r 320 321 def setup(app): 322 app.connect('doctree-resolved', auto_marku 323 return { 324 'parallel_read_safe': True, 325 'parallel_write_safe': True, 326 }
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.