Sphinx 8.1.3__py3-none-any.whl → 8.2.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Sphinx might be problematic. Click here for more details.

Files changed (193) hide show
  1. sphinx/__init__.py +8 -4
  2. sphinx/__main__.py +2 -0
  3. sphinx/_cli/__init__.py +2 -5
  4. sphinx/_cli/util/colour.py +34 -11
  5. sphinx/_cli/util/errors.py +128 -61
  6. sphinx/addnodes.py +51 -35
  7. sphinx/application.py +362 -230
  8. sphinx/builders/__init__.py +87 -64
  9. sphinx/builders/_epub_base.py +65 -56
  10. sphinx/builders/changes.py +17 -23
  11. sphinx/builders/dirhtml.py +8 -13
  12. sphinx/builders/epub3.py +70 -38
  13. sphinx/builders/gettext.py +93 -73
  14. sphinx/builders/html/__init__.py +240 -186
  15. sphinx/builders/html/_assets.py +9 -2
  16. sphinx/builders/html/_build_info.py +3 -0
  17. sphinx/builders/latex/__init__.py +64 -54
  18. sphinx/builders/latex/constants.py +14 -11
  19. sphinx/builders/latex/nodes.py +2 -0
  20. sphinx/builders/latex/theming.py +8 -9
  21. sphinx/builders/latex/transforms.py +7 -5
  22. sphinx/builders/linkcheck.py +193 -149
  23. sphinx/builders/manpage.py +17 -17
  24. sphinx/builders/singlehtml.py +28 -16
  25. sphinx/builders/texinfo.py +28 -21
  26. sphinx/builders/text.py +10 -15
  27. sphinx/builders/xml.py +10 -19
  28. sphinx/cmd/build.py +49 -119
  29. sphinx/cmd/make_mode.py +35 -31
  30. sphinx/cmd/quickstart.py +78 -62
  31. sphinx/config.py +265 -163
  32. sphinx/directives/__init__.py +51 -54
  33. sphinx/directives/admonitions.py +107 -0
  34. sphinx/directives/code.py +24 -19
  35. sphinx/directives/other.py +21 -42
  36. sphinx/directives/patches.py +28 -16
  37. sphinx/domains/__init__.py +54 -31
  38. sphinx/domains/_domains_container.py +22 -17
  39. sphinx/domains/_index.py +5 -8
  40. sphinx/domains/c/__init__.py +366 -245
  41. sphinx/domains/c/_ast.py +378 -256
  42. sphinx/domains/c/_ids.py +89 -31
  43. sphinx/domains/c/_parser.py +283 -214
  44. sphinx/domains/c/_symbol.py +269 -198
  45. sphinx/domains/changeset.py +39 -24
  46. sphinx/domains/citation.py +54 -24
  47. sphinx/domains/cpp/__init__.py +517 -362
  48. sphinx/domains/cpp/_ast.py +999 -682
  49. sphinx/domains/cpp/_ids.py +133 -65
  50. sphinx/domains/cpp/_parser.py +746 -588
  51. sphinx/domains/cpp/_symbol.py +692 -489
  52. sphinx/domains/index.py +10 -8
  53. sphinx/domains/javascript.py +152 -74
  54. sphinx/domains/math.py +48 -40
  55. sphinx/domains/python/__init__.py +402 -211
  56. sphinx/domains/python/_annotations.py +114 -57
  57. sphinx/domains/python/_object.py +151 -67
  58. sphinx/domains/rst.py +94 -49
  59. sphinx/domains/std/__init__.py +510 -249
  60. sphinx/environment/__init__.py +345 -61
  61. sphinx/environment/adapters/asset.py +7 -1
  62. sphinx/environment/adapters/indexentries.py +15 -20
  63. sphinx/environment/adapters/toctree.py +19 -9
  64. sphinx/environment/collectors/__init__.py +3 -1
  65. sphinx/environment/collectors/asset.py +18 -15
  66. sphinx/environment/collectors/dependencies.py +8 -10
  67. sphinx/environment/collectors/metadata.py +6 -4
  68. sphinx/environment/collectors/title.py +3 -1
  69. sphinx/environment/collectors/toctree.py +4 -4
  70. sphinx/errors.py +1 -3
  71. sphinx/events.py +4 -4
  72. sphinx/ext/apidoc/__init__.py +21 -0
  73. sphinx/ext/apidoc/__main__.py +9 -0
  74. sphinx/ext/apidoc/_cli.py +356 -0
  75. sphinx/ext/apidoc/_generate.py +356 -0
  76. sphinx/ext/apidoc/_shared.py +66 -0
  77. sphinx/ext/autodoc/__init__.py +829 -480
  78. sphinx/ext/autodoc/directive.py +57 -21
  79. sphinx/ext/autodoc/importer.py +184 -67
  80. sphinx/ext/autodoc/mock.py +25 -10
  81. sphinx/ext/autodoc/preserve_defaults.py +17 -9
  82. sphinx/ext/autodoc/type_comment.py +56 -29
  83. sphinx/ext/autodoc/typehints.py +49 -26
  84. sphinx/ext/autosectionlabel.py +28 -11
  85. sphinx/ext/autosummary/__init__.py +271 -143
  86. sphinx/ext/autosummary/generate.py +121 -51
  87. sphinx/ext/coverage.py +152 -91
  88. sphinx/ext/doctest.py +169 -101
  89. sphinx/ext/duration.py +12 -6
  90. sphinx/ext/extlinks.py +33 -21
  91. sphinx/ext/githubpages.py +8 -8
  92. sphinx/ext/graphviz.py +175 -109
  93. sphinx/ext/ifconfig.py +11 -6
  94. sphinx/ext/imgconverter.py +48 -25
  95. sphinx/ext/imgmath.py +127 -97
  96. sphinx/ext/inheritance_diagram.py +177 -103
  97. sphinx/ext/intersphinx/__init__.py +22 -13
  98. sphinx/ext/intersphinx/__main__.py +3 -1
  99. sphinx/ext/intersphinx/_cli.py +18 -14
  100. sphinx/ext/intersphinx/_load.py +91 -82
  101. sphinx/ext/intersphinx/_resolve.py +108 -74
  102. sphinx/ext/intersphinx/_shared.py +2 -2
  103. sphinx/ext/linkcode.py +28 -12
  104. sphinx/ext/mathjax.py +60 -29
  105. sphinx/ext/napoleon/__init__.py +19 -7
  106. sphinx/ext/napoleon/docstring.py +229 -231
  107. sphinx/ext/todo.py +44 -49
  108. sphinx/ext/viewcode.py +105 -57
  109. sphinx/extension.py +3 -1
  110. sphinx/highlighting.py +13 -7
  111. sphinx/io.py +9 -13
  112. sphinx/jinja2glue.py +29 -26
  113. sphinx/locale/__init__.py +8 -9
  114. sphinx/parsers.py +8 -7
  115. sphinx/project.py +2 -2
  116. sphinx/pycode/__init__.py +31 -21
  117. sphinx/pycode/ast.py +6 -3
  118. sphinx/pycode/parser.py +14 -8
  119. sphinx/pygments_styles.py +4 -5
  120. sphinx/registry.py +192 -92
  121. sphinx/roles.py +58 -7
  122. sphinx/search/__init__.py +75 -54
  123. sphinx/search/en.py +11 -13
  124. sphinx/search/fi.py +1 -1
  125. sphinx/search/ja.py +8 -6
  126. sphinx/search/nl.py +1 -1
  127. sphinx/search/zh.py +19 -21
  128. sphinx/testing/fixtures.py +26 -29
  129. sphinx/testing/path.py +26 -62
  130. sphinx/testing/restructuredtext.py +14 -8
  131. sphinx/testing/util.py +21 -19
  132. sphinx/texinputs/make.bat.jinja +50 -50
  133. sphinx/texinputs/sphinx.sty +4 -3
  134. sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
  135. sphinx/texinputs/sphinxlatexobjects.sty +29 -10
  136. sphinx/themes/basic/static/searchtools.js +8 -5
  137. sphinx/theming.py +49 -61
  138. sphinx/transforms/__init__.py +17 -38
  139. sphinx/transforms/compact_bullet_list.py +5 -3
  140. sphinx/transforms/i18n.py +8 -21
  141. sphinx/transforms/post_transforms/__init__.py +142 -93
  142. sphinx/transforms/post_transforms/code.py +5 -5
  143. sphinx/transforms/post_transforms/images.py +28 -24
  144. sphinx/transforms/references.py +3 -1
  145. sphinx/util/__init__.py +109 -60
  146. sphinx/util/_files.py +39 -23
  147. sphinx/util/_importer.py +4 -1
  148. sphinx/util/_inventory_file_reader.py +76 -0
  149. sphinx/util/_io.py +2 -2
  150. sphinx/util/_lines.py +6 -3
  151. sphinx/util/_pathlib.py +40 -2
  152. sphinx/util/build_phase.py +2 -0
  153. sphinx/util/cfamily.py +19 -14
  154. sphinx/util/console.py +44 -179
  155. sphinx/util/display.py +9 -10
  156. sphinx/util/docfields.py +140 -122
  157. sphinx/util/docstrings.py +1 -1
  158. sphinx/util/docutils.py +118 -77
  159. sphinx/util/fileutil.py +25 -26
  160. sphinx/util/http_date.py +2 -0
  161. sphinx/util/i18n.py +77 -64
  162. sphinx/util/images.py +8 -6
  163. sphinx/util/inspect.py +147 -38
  164. sphinx/util/inventory.py +215 -116
  165. sphinx/util/logging.py +33 -33
  166. sphinx/util/matching.py +12 -4
  167. sphinx/util/nodes.py +18 -13
  168. sphinx/util/osutil.py +38 -39
  169. sphinx/util/parallel.py +22 -13
  170. sphinx/util/parsing.py +2 -1
  171. sphinx/util/png.py +6 -2
  172. sphinx/util/requests.py +33 -2
  173. sphinx/util/rst.py +3 -2
  174. sphinx/util/tags.py +1 -1
  175. sphinx/util/template.py +18 -10
  176. sphinx/util/texescape.py +8 -6
  177. sphinx/util/typing.py +148 -122
  178. sphinx/versioning.py +3 -3
  179. sphinx/writers/html.py +3 -1
  180. sphinx/writers/html5.py +61 -50
  181. sphinx/writers/latex.py +80 -65
  182. sphinx/writers/manpage.py +19 -38
  183. sphinx/writers/texinfo.py +44 -45
  184. sphinx/writers/text.py +48 -30
  185. sphinx/writers/xml.py +11 -8
  186. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/LICENSE.rst +1 -1
  187. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/METADATA +23 -15
  188. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/RECORD +190 -186
  189. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/WHEEL +1 -1
  190. sphinx/builders/html/transforms.py +0 -90
  191. sphinx/ext/apidoc.py +0 -721
  192. sphinx/util/exceptions.py +0 -74
  193. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/entry_points.txt +0 -0
sphinx/__init__.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """The Sphinx documentation toolchain."""
2
2
 
3
- __version__ = '8.1.3'
3
+ from __future__ import annotations
4
+
5
+ __version__ = '8.2.0rc1'
4
6
  __display_version__ = __version__ # used for command line version
5
7
 
6
8
  # Keep this file executable as-is in Python 3!
@@ -9,6 +11,8 @@ __display_version__ = __version__ # used for command line version
9
11
  import os
10
12
  import warnings
11
13
 
14
+ from sphinx.util._pathlib import _StrPath
15
+
12
16
  # by default, all DeprecationWarning under sphinx package will be emit.
13
17
  # Users can avoid this by using environment variable: PYTHONWARNINGS=
14
18
  if 'PYTHONWARNINGS' not in os.environ:
@@ -30,9 +34,9 @@ warnings.filterwarnings(
30
34
  #:
31
35
  #: .. versionadded:: 1.2
32
36
  #: Before version 1.2, check the string ``sphinx.__version__``.
33
- version_info = (8, 1, 3, 'final', 0)
37
+ version_info = (8, 2, 0, 'candidate', 1)
34
38
 
35
- package_dir = os.path.abspath(os.path.dirname(__file__))
39
+ package_dir = _StrPath(__file__).resolve().parent
36
40
 
37
41
  _in_development = False
38
42
  if _in_development:
@@ -41,7 +45,7 @@ if _in_development:
41
45
 
42
46
  try:
43
47
  if ret := subprocess.run(
44
- ['git', 'rev-parse', '--short', 'HEAD'],
48
+ ['git', 'rev-parse', '--short', 'HEAD'], # NoQA: S607
45
49
  cwd=package_dir,
46
50
  capture_output=True,
47
51
  check=False,
sphinx/__main__.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """The Sphinx documentation toolchain."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import sys
4
6
 
5
7
  from sphinx.cmd.build import main
sphinx/_cli/__init__.py CHANGED
@@ -168,11 +168,8 @@ class _RootArgumentParser(argparse.ArgumentParser):
168
168
  raise ValueError(msg)
169
169
 
170
170
  def error(self, message: str) -> NoReturn:
171
- sys.stderr.write(
172
- __(
173
- '{0}: error: {1}\n' "Run '{0} --help' for information" # NoQA: COM812
174
- ).format(self.prog, message)
175
- )
171
+ msg = __("{0}: error: {1}\nRun '{0} --help' for information")
172
+ sys.stderr.write(msg.format(self.prog, message))
176
173
  raise SystemExit(2)
177
174
 
178
175
 
@@ -2,24 +2,32 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  import sys
7
- from collections.abc import Callable # NoQA: TCH003
6
+ from os import environ as _environ
7
+
8
+ TYPE_CHECKING = False
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
8
11
 
9
12
  if sys.platform == 'win32':
10
13
  import colorama
11
14
 
15
+ colorama.just_fix_windows_console()
16
+ del colorama
17
+
12
18
 
13
- _COLOURING_DISABLED = True
19
+ _COLOURING_DISABLED = False
14
20
 
15
21
 
16
22
  def terminal_supports_colour() -> bool:
17
23
  """Return True if coloured terminal output is supported."""
18
- if 'NO_COLOUR' in os.environ or 'NO_COLOR' in os.environ:
24
+ if 'NO_COLOUR' in _environ or 'NO_COLOR' in _environ:
19
25
  return False
20
26
  if sys.platform == 'win32':
21
- colorama.just_fix_windows_console()
22
- if 'FORCE_COLOUR' in os.environ or 'FORCE_COLOR' in os.environ:
27
+ return True
28
+ if 'FORCE_COLOUR' in _environ or 'FORCE_COLOR' in _environ:
29
+ return True
30
+ if _environ.get('CI', '').lower() in {'true', '1'}:
23
31
  return True
24
32
 
25
33
  try:
@@ -31,23 +39,37 @@ def terminal_supports_colour() -> bool:
31
39
  return False
32
40
 
33
41
  # Do not colour output if on a dumb terminal
34
- return os.environ.get('TERM', 'unknown').lower() not in {'dumb', 'unknown'}
42
+ return _environ.get('TERM', 'unknown').lower() not in {'dumb', 'unknown'}
35
43
 
36
44
 
37
45
  def disable_colour() -> None:
38
- global _COLOURING_DISABLED
46
+ global _COLOURING_DISABLED # NoQA: PLW0603
39
47
  _COLOURING_DISABLED = True
40
48
 
41
49
 
42
50
  def enable_colour() -> None:
43
- global _COLOURING_DISABLED
51
+ global _COLOURING_DISABLED # NoQA: PLW0603
44
52
  _COLOURING_DISABLED = False
45
53
 
46
54
 
47
55
  def colourise(colour_name: str, text: str, /) -> str:
48
56
  if _COLOURING_DISABLED:
49
57
  return text
50
- return globals()[colour_name](text)
58
+ if colour_name.startswith('_') or colour_name in {
59
+ 'annotations',
60
+ 'sys',
61
+ 'terminal_supports_colour',
62
+ 'disable_colour',
63
+ 'enable_colour',
64
+ 'colourise',
65
+ }:
66
+ msg = f'Invalid colour name: {colour_name!r}'
67
+ raise ValueError(msg)
68
+ try:
69
+ return globals()[colour_name](text)
70
+ except KeyError:
71
+ msg = f'Invalid colour name: {colour_name!r}'
72
+ raise ValueError(msg) from None
51
73
 
52
74
 
53
75
  def _create_colour_func(escape_code: str, /) -> Callable[[str], str]:
@@ -56,6 +78,7 @@ def _create_colour_func(escape_code: str, /) -> Callable[[str], str]:
56
78
  return text
57
79
  return f'\x1b[{escape_code}m{text}\x1b[39;49;00m'
58
80
 
81
+ inner.__escape_code = escape_code # type: ignore[attr-defined]
59
82
  return inner
60
83
 
61
84
 
@@ -73,7 +96,7 @@ else:
73
96
  def inner(text: str) -> str:
74
97
  if _COLOURING_DISABLED:
75
98
  return text
76
- return f'\x01\x1b[{escape_code}m\x02{text}\x01\x1b[39;49;00m\x02'
99
+ return f'\1\x1b[{escape_code}m\2{text}\1\x1b[39;49;00m\2'
77
100
 
78
101
  return inner
79
102
 
@@ -2,15 +2,35 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  import sys
5
- import tempfile
6
- from typing import TYPE_CHECKING, TextIO
5
+ from typing import TYPE_CHECKING
7
6
 
8
- from sphinx.errors import SphinxParallelError
7
+ from sphinx.errors import SphinxError, SphinxParallelError
9
8
 
10
9
  if TYPE_CHECKING:
11
- from sphinx.application import Sphinx
10
+ from collections.abc import Collection
11
+ from typing import Final, Protocol
12
12
 
13
- _ANSI_COLOUR_CODES: re.Pattern[str] = re.compile('\x1b.*?m')
13
+ from sphinx.extension import Extension
14
+
15
+ class SupportsWrite(Protocol):
16
+ def write(self, text: str, /) -> int | None: ...
17
+
18
+
19
+ _CSI: Final[str] = re.escape('\x1b[') # 'ESC [': Control Sequence Introducer
20
+
21
+ # Pattern matching ANSI CSI colors (SGR) and erase line (EL) sequences.
22
+ #
23
+ # See ``_strip_escape_sequences()`` for details.
24
+ _ANSI_CODES: Final[re.Pattern[str]] = re.compile(
25
+ '\x1b'
26
+ r"""\[
27
+ (?:
28
+ (?:\d+;){0,2}\d*m # ANSI color code ('m' is equivalent to '0m')
29
+ |
30
+ [012]?K # ANSI Erase in Line ('K' is equivalent to '0K')
31
+ )""",
32
+ re.VERBOSE | re.ASCII,
33
+ )
14
34
 
15
35
 
16
36
  def terminal_safe(s: str, /) -> str:
@@ -18,11 +38,63 @@ def terminal_safe(s: str, /) -> str:
18
38
  return s.encode('ascii', 'backslashreplace').decode('ascii')
19
39
 
20
40
 
21
- def strip_colors(s: str, /) -> str:
22
- return _ANSI_COLOUR_CODES.sub('', s).strip()
41
+ def strip_escape_sequences(text: str, /) -> str:
42
+ r"""Remove the ANSI CSI colors and "erase in line" sequences.
43
+
44
+ Other `escape sequences <https://en.wikipedia.org/wiki/ANSI_escape_code>`_
45
+ (e.g., VT100-specific functions) are not supported. Only control sequences
46
+ *natively* known to Sphinx (i.e., colour sequences used in Sphinx
47
+ and "erase entire line" (``'\x1b[2K'``)) are stripped by this function.
48
+
49
+ .. warning:: This function only for use within Sphinx..
50
+
51
+ __ https://en.wikipedia.org/wiki/ANSI_escape_code
52
+ """
53
+ return _ANSI_CODES.sub('', text)
54
+
55
+
56
+ def full_exception_context(
57
+ exception: BaseException,
58
+ *,
59
+ message_log: Collection[str] = (),
60
+ extensions: Collection[Extension] = (),
61
+ full_traceback: bool = True,
62
+ ) -> str:
63
+ """Return a formatted message containing useful debugging context."""
64
+ messages = [f' {strip_escape_sequences(msg)}'.rstrip() for msg in message_log]
65
+ while messages and not messages[-1]:
66
+ messages.pop()
67
+ last_msgs = '\n'.join(messages)
68
+ exts_list = '\n'.join(
69
+ f'* {ext.name} ({ext.version})'
70
+ for ext in extensions
71
+ if ext.version != 'builtin'
72
+ )
73
+ exc_format = format_traceback(exception, short_traceback=not full_traceback)
74
+ return error_info(last_msgs or 'None.', exts_list or 'None.', exc_format)
75
+
76
+
77
+ def format_traceback(
78
+ exception: BaseException, /, *, short_traceback: bool = False
79
+ ) -> str:
80
+ """Format the given exception's traceback."""
81
+ if short_traceback:
82
+ from traceback import TracebackException
83
+
84
+ # format an exception with traceback, but only the last frame.
85
+ te = TracebackException.from_exception(exception, limit=-1)
86
+ exc_format = te.stack.format()[-1] + ''.join(te.format_exception_only())
87
+ elif isinstance(exception, SphinxParallelError):
88
+ exc_format = f'(Error in parallel process)\n{exception.traceback}'
89
+ else:
90
+ from traceback import format_exception
91
+
92
+ exc_format = ''.join(format_exception(exception))
93
+ return '\n'.join(f' {line}' for line in exc_format.rstrip().splitlines())
23
94
 
24
95
 
25
96
  def error_info(messages: str, extensions: str, traceback: str) -> str:
97
+ """Format the traceback and extensions list with environment information."""
26
98
  import platform
27
99
 
28
100
  import docutils
@@ -59,29 +131,30 @@ Traceback
59
131
  """
60
132
 
61
133
 
62
- def save_traceback(app: Sphinx | None, exc: BaseException) -> str:
134
+ def save_traceback(
135
+ exception: BaseException,
136
+ *,
137
+ message_log: Collection[str] = (),
138
+ extensions: Collection[Extension] = (),
139
+ ) -> str:
63
140
  """Save the given exception's traceback in a temporary file."""
64
- if isinstance(exc, SphinxParallelError):
65
- exc_format = '(Error in parallel process)\n' + exc.traceback
66
- else:
67
- import traceback
68
-
69
- exc_format = traceback.format_exc()
70
-
71
- last_msgs = exts_list = ''
72
- if app is not None:
73
- extensions = app.extensions.values()
74
- last_msgs = '\n'.join(f'* {strip_colors(s)}' for s in app.messagelog)
75
- exts_list = '\n'.join(
76
- f'* {ext.name} ({ext.version})'
77
- for ext in extensions
78
- if ext.version != 'builtin'
79
- )
141
+ output = full_exception_context(
142
+ exception=exception,
143
+ message_log=message_log,
144
+ extensions=extensions,
145
+ )
146
+ filename = write_temporary_file(output)
147
+ return filename
148
+
149
+
150
+ def write_temporary_file(content: str) -> str:
151
+ """Write content to a temporary file and return the filename."""
152
+ import tempfile
80
153
 
81
154
  with tempfile.NamedTemporaryFile(
82
- suffix='.log', prefix='sphinx-err-', delete=False
155
+ 'w', encoding='utf-8', suffix='.log', prefix='sphinx-err-', delete=False
83
156
  ) as f:
84
- f.write(error_info(last_msgs, exts_list, exc_format).encode('utf-8'))
157
+ f.write(content)
85
158
 
86
159
  return f.name
87
160
 
@@ -90,18 +163,17 @@ def handle_exception(
90
163
  exception: BaseException,
91
164
  /,
92
165
  *,
93
- stderr: TextIO = sys.stderr,
166
+ stderr: SupportsWrite = sys.stderr,
94
167
  use_pdb: bool = False,
95
168
  print_traceback: bool = False,
96
- app: Sphinx | None = None,
169
+ message_log: Collection[str] = (),
170
+ extensions: Collection[Extension] = (),
97
171
  ) -> None:
98
172
  from bdb import BdbQuit
99
- from traceback import TracebackException, print_exc
100
173
 
101
174
  from docutils.utils import SystemMessage
102
175
 
103
176
  from sphinx._cli.util.colour import red
104
- from sphinx.errors import SphinxError
105
177
  from sphinx.locale import __
106
178
 
107
179
  if isinstance(exception, BdbQuit):
@@ -114,57 +186,52 @@ def handle_exception(
114
186
  print_err(*map(red, values))
115
187
 
116
188
  print_err()
117
- if print_traceback or use_pdb:
118
- print_exc(file=stderr)
119
- print_err()
120
-
121
- if use_pdb:
122
- from pdb import post_mortem
123
-
124
- print_red(__('Exception occurred, starting debugger:'))
125
- post_mortem()
126
- return
127
-
128
- if isinstance(exception, KeyboardInterrupt):
189
+ if not use_pdb and isinstance(exception, KeyboardInterrupt):
129
190
  print_err(__('Interrupted!'))
130
191
  return
131
192
 
132
193
  if isinstance(exception, SystemMessage):
133
- print_red(__('reStructuredText markup error:'))
134
- print_err(str(exception))
135
- return
194
+ print_red(__('reStructuredText markup error!'))
136
195
 
137
196
  if isinstance(exception, SphinxError):
138
- print_red(f'{exception.category}:')
139
- print_err(str(exception))
140
- return
197
+ print_red(f'{exception.category}!')
141
198
 
142
199
  if isinstance(exception, UnicodeError):
143
- print_red(__('Encoding error:'))
144
- print_err(str(exception))
145
- return
200
+ print_red(__('Encoding error!'))
146
201
 
147
202
  if isinstance(exception, RecursionError):
148
- print_red(__('Recursion error:'))
149
- print_err(str(exception))
203
+ print_red(__('Recursion error!'))
150
204
  print_err()
151
205
  print_err(
152
206
  __(
153
207
  'This can happen with very large or deeply nested source '
154
208
  'files. You can carefully increase the default Python '
155
- 'recursion limit of 1000 in conf.py with e.g.:'
209
+ 'recursion limit of 1,000 in conf.py with e.g.:'
156
210
  )
157
211
  )
158
212
  print_err('\n import sys\n sys.setrecursionlimit(1_500)\n')
159
- return
160
213
 
161
- # format an exception with traceback, but only the last frame.
162
- te = TracebackException.from_exception(exception, limit=-1)
163
- formatted_tb = te.stack.format()[-1] + ''.join(te.format_exception_only()).rstrip()
214
+ print_err()
215
+ error_context = full_exception_context(
216
+ exception,
217
+ message_log=message_log,
218
+ extensions=extensions,
219
+ full_traceback=print_traceback or use_pdb,
220
+ )
221
+ print_err(error_context)
222
+ print_err()
223
+
224
+ if use_pdb:
225
+ from pdb import post_mortem
164
226
 
165
- print_red(__('Exception occurred:'))
166
- print_err(formatted_tb)
167
- traceback_info_path = save_traceback(app, exception)
227
+ print_red(__('Starting debugger:'))
228
+ post_mortem(exception.__traceback__)
229
+ return
230
+
231
+ # Save full traceback to log file
232
+ traceback_info_path = save_traceback(
233
+ exception, message_log=message_log, extensions=extensions
234
+ )
168
235
  print_err(__('The full traceback has been saved in:'))
169
236
  print_err(traceback_info_path)
170
237
  print_err()
sphinx/addnodes.py CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING
6
6
 
7
7
  from docutils import nodes
8
+ from docutils.nodes import document # NoQA: F401
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from collections.abc import Sequence
12
+ from typing import Any
11
13
 
12
14
  from docutils.nodes import Element
13
15
 
@@ -15,21 +17,6 @@ if TYPE_CHECKING:
15
17
  from sphinx.util.typing import ExtensionMetadata
16
18
 
17
19
 
18
- class document(nodes.document):
19
- """The document root element patched by Sphinx.
20
-
21
- This fixes that document.set_id() does not support a node having multiple node Ids.
22
- see https://sourceforge.net/p/docutils/patches/167/
23
-
24
- .. important:: This is only for Sphinx internal use. Please don't use this
25
- in your extensions. It will be removed without deprecation period.
26
- """
27
-
28
- def set_id(self, node: Element, msgnode: Element | None = None,
29
- suggested_prefix: str = '') -> str:
30
- return super().set_id(node, msgnode, suggested_prefix)
31
-
32
-
33
20
  class translatable(nodes.Node):
34
21
  """Node which supports translation.
35
22
 
@@ -48,7 +35,9 @@ class translatable(nodes.Node):
48
35
  """Preserve original translatable messages."""
49
36
  raise NotImplementedError
50
37
 
51
- def apply_translated_message(self, original_message: str, translated_message: str) -> None:
38
+ def apply_translated_message(
39
+ self, original_message: str, translated_message: str
40
+ ) -> None:
52
41
  """Apply translated message."""
53
42
  raise NotImplementedError
54
43
 
@@ -80,7 +69,9 @@ class toctree(nodes.General, nodes.Element, translatable):
80
69
  if self.get('caption'):
81
70
  self['rawcaption'] = self['caption']
82
71
 
83
- def apply_translated_message(self, original_message: str, translated_message: str) -> None:
72
+ def apply_translated_message(
73
+ self, original_message: str, translated_message: str
74
+ ) -> None:
84
75
  # toctree entries
85
76
  for i, (title, docname) in enumerate(self['entries']):
86
77
  if title == original_message:
@@ -106,6 +97,7 @@ class toctree(nodes.General, nodes.Element, translatable):
106
97
  # Domain-specific object descriptions (class, function etc.)
107
98
  #############################################################
108
99
 
100
+
109
101
  class _desc_classes_injector(nodes.Element, not_smartquotable):
110
102
  """Helper base class for injecting a fixed list of classes.
111
103
 
@@ -122,6 +114,7 @@ class _desc_classes_injector(nodes.Element, not_smartquotable):
122
114
  # Top-level nodes
123
115
  #################
124
116
 
117
+
125
118
  class desc(nodes.Admonition, nodes.Element):
126
119
  """Node for a list of object signatures and a common description of them.
127
120
 
@@ -138,7 +131,9 @@ class desc(nodes.Admonition, nodes.Element):
138
131
  # that forces the specification of the domain and objtyp?
139
132
 
140
133
 
141
- class desc_signature(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.TextElement):
134
+ class desc_signature(
135
+ _desc_classes_injector, nodes.Part, nodes.Inline, nodes.TextElement
136
+ ):
142
137
  """Node for a single object signature.
143
138
 
144
139
  As default the signature is a single-line signature.
@@ -198,7 +193,10 @@ class desc_inline(_desc_classes_injector, nodes.Inline, nodes.TextElement):
198
193
 
199
194
  # nodes to use within a desc_signature or desc_signature_line
200
195
 
201
- class desc_name(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement):
196
+
197
+ class desc_name(
198
+ _desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement
199
+ ):
202
200
  """Node for the main object name.
203
201
 
204
202
  For example, in the declaration of a Python class ``MyModule.MyClass``,
@@ -210,7 +208,9 @@ class desc_name(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTex
210
208
  classes = ['sig-name', 'descname'] # 'descname' is for backwards compatibility
211
209
 
212
210
 
213
- class desc_addname(_desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement):
211
+ class desc_addname(
212
+ _desc_classes_injector, nodes.Part, nodes.Inline, nodes.FixedTextElement
213
+ ):
214
214
  """Node for additional name parts for an object.
215
215
 
216
216
  For example, in the declaration of a Python class ``MyModule.MyClass``,
@@ -244,6 +244,8 @@ class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
244
244
  As default the parameter list is written in line with the rest of the signature.
245
245
  Set ``multi_line_parameter_list = True`` to describe a multi-line parameter list.
246
246
  In that case each parameter will then be written on its own, indented line.
247
+ A trailing comma will be added on the last line
248
+ if ``multi_line_trailing_comma`` is True.
247
249
  """
248
250
 
249
251
  child_text_separator = ', '
@@ -258,6 +260,8 @@ class desc_type_parameter_list(nodes.Part, nodes.Inline, nodes.FixedTextElement)
258
260
  As default the type parameters list is written in line with the rest of the signature.
259
261
  Set ``multi_line_parameter_list = True`` to describe a multi-line type parameters list.
260
262
  In that case each type parameter will then be written on its own, indented line.
263
+ A trailing comma will be added on the last line
264
+ if ``multi_line_trailing_comma`` is True.
261
265
  """
262
266
 
263
267
  child_text_separator = ', '
@@ -305,13 +309,15 @@ SIG_ELEMENTS: set[type[desc_sig_element]] = set()
305
309
  # When adding a new one, add it to SIG_ELEMENTS via the class
306
310
  # keyword argument `_sig_element=True` (e.g., see `desc_sig_space`).
307
311
 
312
+
308
313
  class desc_sig_element(nodes.inline, _desc_classes_injector):
309
314
  """Common parent class of nodes for inline text of a signature."""
310
315
 
311
316
  classes: list[str] = []
312
317
 
313
- def __init__(self, rawsource: str = '', text: str = '',
314
- *children: Element, **attributes: Any) -> None:
318
+ def __init__(
319
+ self, rawsource: str = '', text: str = '', *children: Element, **attributes: Any
320
+ ) -> None:
315
321
  super().__init__(rawsource, text, *children, **attributes)
316
322
  self['classes'].extend(self.classes)
317
323
 
@@ -325,67 +331,74 @@ class desc_sig_element(nodes.inline, _desc_classes_injector):
325
331
  # to not reinvent the wheel, the classes in the following desc_sig classes
326
332
  # are based on those used in Pygments
327
333
 
334
+
328
335
  class desc_sig_space(desc_sig_element, _sig_element=True):
329
336
  """Node for a space in a signature."""
330
337
 
331
- classes = ["w"]
338
+ classes = ['w']
332
339
 
333
- def __init__(self, rawsource: str = '', text: str = ' ',
334
- *children: Element, **attributes: Any) -> None:
340
+ def __init__(
341
+ self,
342
+ rawsource: str = '',
343
+ text: str = ' ',
344
+ *children: Element,
345
+ **attributes: Any,
346
+ ) -> None:
335
347
  super().__init__(rawsource, text, *children, **attributes)
336
348
 
337
349
 
338
350
  class desc_sig_name(desc_sig_element, _sig_element=True):
339
351
  """Node for an identifier in a signature."""
340
352
 
341
- classes = ["n"]
353
+ classes = ['n']
342
354
 
343
355
 
344
356
  class desc_sig_operator(desc_sig_element, _sig_element=True):
345
357
  """Node for an operator in a signature."""
346
358
 
347
- classes = ["o"]
359
+ classes = ['o']
348
360
 
349
361
 
350
362
  class desc_sig_punctuation(desc_sig_element, _sig_element=True):
351
363
  """Node for punctuation in a signature."""
352
364
 
353
- classes = ["p"]
365
+ classes = ['p']
354
366
 
355
367
 
356
368
  class desc_sig_keyword(desc_sig_element, _sig_element=True):
357
369
  """Node for a general keyword in a signature."""
358
370
 
359
- classes = ["k"]
371
+ classes = ['k']
360
372
 
361
373
 
362
374
  class desc_sig_keyword_type(desc_sig_element, _sig_element=True):
363
375
  """Node for a keyword which is a built-in type in a signature."""
364
376
 
365
- classes = ["kt"]
377
+ classes = ['kt']
366
378
 
367
379
 
368
380
  class desc_sig_literal_number(desc_sig_element, _sig_element=True):
369
381
  """Node for a numeric literal in a signature."""
370
382
 
371
- classes = ["m"]
383
+ classes = ['m']
372
384
 
373
385
 
374
386
  class desc_sig_literal_string(desc_sig_element, _sig_element=True):
375
387
  """Node for a string literal in a signature."""
376
388
 
377
- classes = ["s"]
389
+ classes = ['s']
378
390
 
379
391
 
380
392
  class desc_sig_literal_char(desc_sig_element, _sig_element=True):
381
393
  """Node for a character literal in a signature."""
382
394
 
383
- classes = ["sc"]
395
+ classes = ['sc']
384
396
 
385
397
 
386
398
  ###############################################################
387
399
  # new admonition-like constructs
388
400
 
401
+
389
402
  class versionmodified(nodes.Admonition, nodes.TextElement):
390
403
  """Node for version change entries.
391
404
 
@@ -411,6 +424,7 @@ class production(nodes.Part, nodes.Inline, nodes.FixedTextElement):
411
424
 
412
425
  # other directive-level nodes
413
426
 
427
+
414
428
  class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
415
429
  """Node for index entries.
416
430
 
@@ -422,7 +436,7 @@ class index(nodes.Invisible, nodes.Inline, nodes.TextElement):
422
436
 
423
437
  *key* is categorization characters (usually a single character) for
424
438
  general index page. For the details of this, please see also:
425
- :rst:dir:`glossary` and issue #2320.
439
+ :rst:dir:`glossary` and https://github.com/sphinx-doc/sphinx/pull/2320.
426
440
  """
427
441
 
428
442
 
@@ -458,6 +472,7 @@ class only(nodes.Element):
458
472
 
459
473
  # meta-information nodes
460
474
 
475
+
461
476
  class start_of_file(nodes.Element):
462
477
  """Node to mark start of a new file, used in the LaTeX builder only."""
463
478
 
@@ -474,6 +489,7 @@ class tabular_col_spec(nodes.Element):
474
489
 
475
490
  # inline nodes
476
491
 
492
+
477
493
  class pending_xref(nodes.Inline, nodes.Element):
478
494
  """Node for cross-references that cannot be resolved without complete
479
495
  information about all documents.