PageRenderTime 55ms CodeModel.GetById 16ms RepoModel.GetById 2ms app.codeStats 0ms

/Documentation/sphinx/automarkup.py

https://github.com/kvaneesh/linux
Python | 293 lines | 246 code | 10 blank | 37 comment | 6 complexity | e571077c199042d311180d0235450c63 MD5 | raw file
  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. # Python 2 lacks re.ASCII...
  19. #
  20. try:
  21. ascii_p3 = re.ASCII
  22. except AttributeError:
  23. ascii_p3 = 0
  24. #
  25. # Regex nastiness. Of course.
  26. # Try to identify "function()" that's not already marked up some
  27. # other way. Sphinx doesn't like a lot of stuff right after a
  28. # :c:func: block (i.e. ":c:func:`mmap()`s" flakes out), so the last
  29. # bit tries to restrict matches to things that won't create trouble.
  30. #
  31. RE_function = re.compile(r'\b(([a-zA-Z_]\w+)\(\))', flags=ascii_p3)
  32. #
  33. # Sphinx 2 uses the same :c:type role for struct, union, enum and typedef
  34. #
  35. RE_generic_type = re.compile(r'\b(struct|union|enum|typedef)\s+([a-zA-Z_]\w+)',
  36. flags=ascii_p3)
  37. #
  38. # Sphinx 3 uses a different C role for each one of struct, union, enum and
  39. # typedef
  40. #
  41. RE_struct = re.compile(r'\b(struct)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
  42. RE_union = re.compile(r'\b(union)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
  43. RE_enum = re.compile(r'\b(enum)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
  44. RE_typedef = re.compile(r'\b(typedef)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
  45. #
  46. # Detects a reference to a documentation page of the form Documentation/... with
  47. # an optional extension
  48. #
  49. RE_doc = re.compile(r'(\bDocumentation/)?((\.\./)*[\w\-/]+)\.(rst|txt)')
  50. RE_namespace = re.compile(r'^\s*..\s*c:namespace::\s*(\S+)\s*$')
  51. #
  52. # Reserved C words that we should skip when cross-referencing
  53. #
  54. Skipnames = [ 'for', 'if', 'register', 'sizeof', 'struct', 'unsigned' ]
  55. #
  56. # Many places in the docs refer to common system calls. It is
  57. # pointless to try to cross-reference them and, as has been known
  58. # to happen, somebody defining a function by these names can lead
  59. # to the creation of incorrect and confusing cross references. So
  60. # just don't even try with these names.
  61. #
  62. Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap',
  63. 'select', 'poll', 'fork', 'execve', 'clone', 'ioctl',
  64. 'socket' ]
  65. c_namespace = ''
  66. def markup_refs(docname, app, node):
  67. t = node.astext()
  68. done = 0
  69. repl = [ ]
  70. #
  71. # Associate each regex with the function that will markup its matches
  72. #
  73. markup_func_sphinx2 = {RE_doc: markup_doc_ref,
  74. RE_function: markup_c_ref,
  75. RE_generic_type: markup_c_ref}
  76. markup_func_sphinx3 = {RE_doc: markup_doc_ref,
  77. RE_function: markup_func_ref_sphinx3,
  78. RE_struct: markup_c_ref,
  79. RE_union: markup_c_ref,
  80. RE_enum: markup_c_ref,
  81. RE_typedef: markup_c_ref}
  82. if sphinx.version_info[0] >= 3:
  83. markup_func = markup_func_sphinx3
  84. else:
  85. markup_func = markup_func_sphinx2
  86. match_iterators = [regex.finditer(t) for regex in markup_func]
  87. #
  88. # Sort all references by the starting position in text
  89. #
  90. sorted_matches = sorted(chain(*match_iterators), key=lambda m: m.start())
  91. for m in sorted_matches:
  92. #
  93. # Include any text prior to match as a normal text node.
  94. #
  95. if m.start() > done:
  96. repl.append(nodes.Text(t[done:m.start()]))
  97. #
  98. # Call the function associated with the regex that matched this text and
  99. # append its return to the text
  100. #
  101. repl.append(markup_func[m.re](docname, app, m))
  102. done = m.end()
  103. if done < len(t):
  104. repl.append(nodes.Text(t[done:]))
  105. return repl
  106. #
  107. # In sphinx3 we can cross-reference to C macro and function, each one with its
  108. # own C role, but both match the same regex, so we try both.
  109. #
  110. def markup_func_ref_sphinx3(docname, app, match):
  111. class_str = ['c-func', 'c-macro']
  112. reftype_str = ['function', 'macro']
  113. cdom = app.env.domains['c']
  114. #
  115. # Go through the dance of getting an xref out of the C domain
  116. #
  117. base_target = match.group(2)
  118. target_text = nodes.Text(match.group(0))
  119. xref = None
  120. possible_targets = [base_target]
  121. # Check if this document has a namespace, and if so, try
  122. # cross-referencing inside it first.
  123. if c_namespace:
  124. possible_targets.insert(0, c_namespace + "." + base_target)
  125. if base_target not in Skipnames:
  126. for target in possible_targets:
  127. if target not in Skipfuncs:
  128. for class_s, reftype_s in zip(class_str, reftype_str):
  129. lit_text = nodes.literal(classes=['xref', 'c', class_s])
  130. lit_text += target_text
  131. pxref = addnodes.pending_xref('', refdomain = 'c',
  132. reftype = reftype_s,
  133. reftarget = target, modname = None,
  134. classname = None)
  135. #
  136. # XXX The Latex builder will throw NoUri exceptions here,
  137. # work around that by ignoring them.
  138. #
  139. try:
  140. xref = cdom.resolve_xref(app.env, docname, app.builder,
  141. reftype_s, target, pxref,
  142. lit_text)
  143. except NoUri:
  144. xref = None
  145. if xref:
  146. return xref
  147. return target_text
  148. def markup_c_ref(docname, app, match):
  149. class_str = {# Sphinx 2 only
  150. RE_function: 'c-func',
  151. RE_generic_type: 'c-type',
  152. # Sphinx 3+ only
  153. RE_struct: 'c-struct',
  154. RE_union: 'c-union',
  155. RE_enum: 'c-enum',
  156. RE_typedef: 'c-type',
  157. }
  158. reftype_str = {# Sphinx 2 only
  159. RE_function: 'function',
  160. RE_generic_type: 'type',
  161. # Sphinx 3+ only
  162. RE_struct: 'struct',
  163. RE_union: 'union',
  164. RE_enum: 'enum',
  165. RE_typedef: 'type',
  166. }
  167. cdom = app.env.domains['c']
  168. #
  169. # Go through the dance of getting an xref out of the C domain
  170. #
  171. base_target = match.group(2)
  172. target_text = nodes.Text(match.group(0))
  173. xref = None
  174. possible_targets = [base_target]
  175. # Check if this document has a namespace, and if so, try
  176. # cross-referencing inside it first.
  177. if c_namespace:
  178. possible_targets.insert(0, c_namespace + "." + base_target)
  179. if base_target not in Skipnames:
  180. for target in possible_targets:
  181. if not (match.re == RE_function and target in Skipfuncs):
  182. lit_text = nodes.literal(classes=['xref', 'c', class_str[match.re]])
  183. lit_text += target_text
  184. pxref = addnodes.pending_xref('', refdomain = 'c',
  185. reftype = reftype_str[match.re],
  186. reftarget = target, modname = None,
  187. classname = None)
  188. #
  189. # XXX The Latex builder will throw NoUri exceptions here,
  190. # work around that by ignoring them.
  191. #
  192. try:
  193. xref = cdom.resolve_xref(app.env, docname, app.builder,
  194. reftype_str[match.re], target, pxref,
  195. lit_text)
  196. except NoUri:
  197. xref = None
  198. if xref:
  199. return xref
  200. return target_text
  201. #
  202. # Try to replace a documentation reference of the form Documentation/... with a
  203. # cross reference to that page
  204. #
  205. def markup_doc_ref(docname, app, match):
  206. stddom = app.env.domains['std']
  207. #
  208. # Go through the dance of getting an xref out of the std domain
  209. #
  210. absolute = match.group(1)
  211. target = match.group(2)
  212. if absolute:
  213. target = "/" + target
  214. xref = None
  215. pxref = addnodes.pending_xref('', refdomain = 'std', reftype = 'doc',
  216. reftarget = target, modname = None,
  217. classname = None, refexplicit = False)
  218. #
  219. # XXX The Latex builder will throw NoUri exceptions here,
  220. # work around that by ignoring them.
  221. #
  222. try:
  223. xref = stddom.resolve_xref(app.env, docname, app.builder, 'doc',
  224. target, pxref, None)
  225. except NoUri:
  226. xref = None
  227. #
  228. # Return the xref if we got it; otherwise just return the plain text.
  229. #
  230. if xref:
  231. return xref
  232. else:
  233. return nodes.Text(match.group(0))
  234. def get_c_namespace(app, docname):
  235. source = app.env.doc2path(docname)
  236. with open(source) as f:
  237. for l in f:
  238. match = RE_namespace.search(l)
  239. if match:
  240. return match.group(1)
  241. return ''
  242. def auto_markup(app, doctree, name):
  243. global c_namespace
  244. c_namespace = get_c_namespace(app, name)
  245. #
  246. # This loop could eventually be improved on. Someday maybe we
  247. # want a proper tree traversal with a lot of awareness of which
  248. # kinds of nodes to prune. But this works well for now.
  249. #
  250. # The nodes.literal test catches ``literal text``, its purpose is to
  251. # avoid adding cross-references to functions that have been explicitly
  252. # marked with cc:func:.
  253. #
  254. for para in doctree.traverse(nodes.paragraph):
  255. for node in para.traverse(nodes.Text):
  256. if not isinstance(node.parent, nodes.literal):
  257. node.parent.replace(node, markup_refs(name, app, node))
  258. def setup(app):
  259. app.connect('doctree-resolved', auto_markup)
  260. return {
  261. 'parallel_read_safe': True,
  262. 'parallel_write_safe': True,
  263. }