Sphinx 8.1.2__py3-none-any.whl → 8.2.0__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 (328) 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 +50 -40
  55. sphinx/domains/python/__init__.py +402 -211
  56. sphinx/domains/python/_annotations.py +134 -61
  57. sphinx/domains/python/_object.py +155 -68
  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 +66 -0
  73. sphinx/ext/apidoc/__main__.py +9 -0
  74. sphinx/ext/apidoc/_cli.py +356 -0
  75. sphinx/ext/apidoc/_extension.py +262 -0
  76. sphinx/ext/apidoc/_generate.py +356 -0
  77. sphinx/ext/apidoc/_shared.py +99 -0
  78. sphinx/ext/autodoc/__init__.py +837 -483
  79. sphinx/ext/autodoc/directive.py +57 -21
  80. sphinx/ext/autodoc/importer.py +184 -67
  81. sphinx/ext/autodoc/mock.py +25 -10
  82. sphinx/ext/autodoc/preserve_defaults.py +17 -9
  83. sphinx/ext/autodoc/type_comment.py +56 -29
  84. sphinx/ext/autodoc/typehints.py +49 -26
  85. sphinx/ext/autosectionlabel.py +28 -11
  86. sphinx/ext/autosummary/__init__.py +281 -142
  87. sphinx/ext/autosummary/generate.py +121 -51
  88. sphinx/ext/coverage.py +152 -91
  89. sphinx/ext/doctest.py +169 -101
  90. sphinx/ext/duration.py +12 -6
  91. sphinx/ext/extlinks.py +33 -21
  92. sphinx/ext/githubpages.py +8 -8
  93. sphinx/ext/graphviz.py +175 -109
  94. sphinx/ext/ifconfig.py +11 -6
  95. sphinx/ext/imgconverter.py +48 -25
  96. sphinx/ext/imgmath.py +127 -97
  97. sphinx/ext/inheritance_diagram.py +177 -103
  98. sphinx/ext/intersphinx/__init__.py +22 -13
  99. sphinx/ext/intersphinx/__main__.py +3 -1
  100. sphinx/ext/intersphinx/_cli.py +18 -14
  101. sphinx/ext/intersphinx/_load.py +91 -82
  102. sphinx/ext/intersphinx/_resolve.py +108 -74
  103. sphinx/ext/intersphinx/_shared.py +2 -2
  104. sphinx/ext/linkcode.py +28 -12
  105. sphinx/ext/mathjax.py +60 -29
  106. sphinx/ext/napoleon/__init__.py +19 -7
  107. sphinx/ext/napoleon/docstring.py +229 -231
  108. sphinx/ext/todo.py +44 -49
  109. sphinx/ext/viewcode.py +105 -57
  110. sphinx/extension.py +3 -1
  111. sphinx/highlighting.py +13 -7
  112. sphinx/io.py +9 -13
  113. sphinx/jinja2glue.py +29 -26
  114. sphinx/locale/__init__.py +8 -9
  115. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  116. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2155 -2050
  117. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  118. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2045 -1940
  119. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  120. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2175 -2070
  121. sphinx/locale/ca/LC_MESSAGES/sphinx.js +3 -3
  122. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/ca/LC_MESSAGES/sphinx.po +2690 -2585
  124. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.js +63 -0
  125. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.mo +0 -0
  126. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.po +4216 -0
  127. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  128. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2096 -1991
  129. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  130. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2248 -2143
  131. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  132. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2201 -2096
  133. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  134. sphinx/locale/da/LC_MESSAGES/sphinx.po +2282 -2177
  135. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  136. sphinx/locale/de/LC_MESSAGES/sphinx.po +2261 -2156
  137. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2045 -1940
  139. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/el/LC_MESSAGES/sphinx.po +2604 -2499
  141. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2045 -1940
  143. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2045 -1940
  145. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2631 -2526
  147. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2045 -1940
  149. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2078 -1973
  151. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/es/LC_MESSAGES/sphinx.po +2633 -2528
  153. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2045 -1940
  155. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/et/LC_MESSAGES/sphinx.po +2449 -2344
  157. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2241 -2136
  159. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/fa/LC_MESSAGES/sphinx.po +504 -500
  161. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/fi/LC_MESSAGES/sphinx.po +499 -495
  163. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/fr/LC_MESSAGES/sphinx.po +513 -509
  165. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +499 -495
  167. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2644 -2539
  169. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/he/LC_MESSAGES/sphinx.po +499 -495
  171. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/hi/LC_MESSAGES/sphinx.po +504 -500
  173. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +499 -495
  175. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/hr/LC_MESSAGES/sphinx.po +501 -497
  177. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/hu/LC_MESSAGES/sphinx.po +499 -495
  179. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/id/LC_MESSAGES/sphinx.po +2609 -2504
  181. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/is/LC_MESSAGES/sphinx.po +499 -495
  183. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/it/LC_MESSAGES/sphinx.po +2265 -2160
  185. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2621 -2516
  187. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2567 -2462
  189. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/ko/LC_MESSAGES/sphinx.po +2631 -2526
  191. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  192. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2214 -2109
  193. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  194. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2218 -2113
  195. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  196. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2088 -1983
  197. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  198. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2247 -2142
  199. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  200. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2227 -2122
  201. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  202. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2316 -2211
  203. sphinx/locale/pl/LC_MESSAGES/sphinx.js +2 -2
  204. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2442 -2336
  206. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2045 -1940
  208. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2657 -2552
  210. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2243 -2138
  212. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2244 -2139
  214. sphinx/locale/ru/LC_MESSAGES/sphinx.js +1 -1
  215. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  216. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2660 -2555
  217. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  218. sphinx/locale/si/LC_MESSAGES/sphinx.po +2134 -2029
  219. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  220. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2614 -2509
  221. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  222. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2167 -2062
  223. sphinx/locale/sphinx.pot +2069 -1964
  224. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  225. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2661 -2556
  226. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  227. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2213 -2108
  228. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  229. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2229 -2124
  230. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  231. sphinx/locale/te/LC_MESSAGES/sphinx.po +2045 -1940
  232. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  233. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2608 -2503
  234. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  235. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2167 -2062
  236. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  237. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2045 -1940
  238. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  239. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2204 -2099
  240. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  241. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2045 -1940
  242. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  243. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2045 -1940
  244. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  245. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +2659 -2554
  246. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  247. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2045 -1940
  248. sphinx/parsers.py +8 -7
  249. sphinx/project.py +2 -2
  250. sphinx/pycode/__init__.py +31 -21
  251. sphinx/pycode/ast.py +6 -3
  252. sphinx/pycode/parser.py +14 -8
  253. sphinx/pygments_styles.py +4 -5
  254. sphinx/registry.py +192 -92
  255. sphinx/roles.py +58 -7
  256. sphinx/search/__init__.py +75 -54
  257. sphinx/search/en.py +11 -13
  258. sphinx/search/fi.py +1 -1
  259. sphinx/search/ja.py +8 -6
  260. sphinx/search/nl.py +1 -1
  261. sphinx/search/zh.py +19 -21
  262. sphinx/testing/fixtures.py +26 -29
  263. sphinx/testing/path.py +26 -62
  264. sphinx/testing/restructuredtext.py +14 -8
  265. sphinx/testing/util.py +21 -19
  266. sphinx/texinputs/make.bat.jinja +50 -50
  267. sphinx/texinputs/sphinx.sty +4 -3
  268. sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
  269. sphinx/texinputs/sphinxlatexobjects.sty +29 -10
  270. sphinx/themes/basic/static/searchtools.js +8 -5
  271. sphinx/theming.py +49 -61
  272. sphinx/transforms/__init__.py +17 -38
  273. sphinx/transforms/compact_bullet_list.py +5 -3
  274. sphinx/transforms/i18n.py +8 -21
  275. sphinx/transforms/post_transforms/__init__.py +142 -93
  276. sphinx/transforms/post_transforms/code.py +5 -5
  277. sphinx/transforms/post_transforms/images.py +28 -24
  278. sphinx/transforms/references.py +3 -1
  279. sphinx/util/__init__.py +109 -60
  280. sphinx/util/_files.py +39 -23
  281. sphinx/util/_importer.py +4 -1
  282. sphinx/util/_inventory_file_reader.py +76 -0
  283. sphinx/util/_io.py +2 -2
  284. sphinx/util/_lines.py +6 -3
  285. sphinx/util/_pathlib.py +40 -2
  286. sphinx/util/build_phase.py +2 -0
  287. sphinx/util/cfamily.py +19 -14
  288. sphinx/util/console.py +44 -179
  289. sphinx/util/display.py +9 -10
  290. sphinx/util/docfields.py +140 -122
  291. sphinx/util/docstrings.py +1 -1
  292. sphinx/util/docutils.py +118 -77
  293. sphinx/util/fileutil.py +25 -26
  294. sphinx/util/http_date.py +2 -0
  295. sphinx/util/i18n.py +77 -64
  296. sphinx/util/images.py +8 -6
  297. sphinx/util/inspect.py +147 -38
  298. sphinx/util/inventory.py +215 -116
  299. sphinx/util/logging.py +33 -33
  300. sphinx/util/matching.py +12 -4
  301. sphinx/util/nodes.py +18 -13
  302. sphinx/util/osutil.py +38 -39
  303. sphinx/util/parallel.py +22 -13
  304. sphinx/util/parsing.py +2 -1
  305. sphinx/util/png.py +6 -2
  306. sphinx/util/requests.py +33 -2
  307. sphinx/util/rst.py +3 -2
  308. sphinx/util/tags.py +1 -1
  309. sphinx/util/template.py +18 -10
  310. sphinx/util/texescape.py +8 -6
  311. sphinx/util/typing.py +148 -122
  312. sphinx/versioning.py +3 -3
  313. sphinx/writers/html.py +3 -1
  314. sphinx/writers/html5.py +63 -52
  315. sphinx/writers/latex.py +83 -67
  316. sphinx/writers/manpage.py +19 -38
  317. sphinx/writers/texinfo.py +47 -47
  318. sphinx/writers/text.py +50 -32
  319. sphinx/writers/xml.py +11 -8
  320. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/LICENSE.rst +1 -1
  321. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/METADATA +25 -15
  322. sphinx-8.2.0.dist-info/RECORD +606 -0
  323. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/WHEEL +1 -1
  324. sphinx/builders/html/transforms.py +0 -90
  325. sphinx/ext/apidoc.py +0 -721
  326. sphinx/util/exceptions.py +0 -74
  327. sphinx-8.1.2.dist-info/RECORD +0 -598
  328. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/entry_points.txt +0 -0
sphinx/util/i18n.py CHANGED
@@ -3,11 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ import os.path
6
7
  import re
7
- import sys
8
- from datetime import datetime, timezone
9
- from os import path
10
- from typing import TYPE_CHECKING, NamedTuple
8
+ from datetime import datetime
9
+ from typing import TYPE_CHECKING
11
10
 
12
11
  import babel.dates
13
12
  from babel.messages.mofile import write_mo
@@ -16,12 +15,8 @@ from babel.messages.pofile import read_po
16
15
  from sphinx.errors import SphinxError
17
16
  from sphinx.locale import __
18
17
  from sphinx.util import logging
19
- from sphinx.util.osutil import (
20
- SEP,
21
- _last_modified_time,
22
- canon_path,
23
- relpath,
24
- )
18
+ from sphinx.util._pathlib import _StrPath
19
+ from sphinx.util.osutil import SEP, _last_modified_time
25
20
 
26
21
  if TYPE_CHECKING:
27
22
  import datetime as dt
@@ -33,7 +28,7 @@ if TYPE_CHECKING:
33
28
  from sphinx.environment import BuildEnvironment
34
29
 
35
30
  class DateFormatter(Protocol):
36
- def __call__( # NoQA: E704
31
+ def __call__(
37
32
  self,
38
33
  date: dt.date | None = ...,
39
34
  format: str = ...,
@@ -41,7 +36,7 @@ if TYPE_CHECKING:
41
36
  ) -> str: ...
42
37
 
43
38
  class TimeFormatter(Protocol):
44
- def __call__( # NoQA: E704
39
+ def __call__(
45
40
  self,
46
41
  time: dt.time | dt.datetime | float | None = ...,
47
42
  format: str = ...,
@@ -50,7 +45,7 @@ if TYPE_CHECKING:
50
45
  ) -> str: ...
51
46
 
52
47
  class DatetimeFormatter(Protocol):
53
- def __call__( # NoQA: E704
48
+ def __call__(
54
49
  self,
55
50
  datetime: dt.date | dt.time | float | None = ...,
56
51
  format: str = ...,
@@ -60,56 +55,67 @@ if TYPE_CHECKING:
60
55
 
61
56
  Formatter: TypeAlias = DateFormatter | TimeFormatter | DatetimeFormatter
62
57
 
63
- if sys.version_info[:2] >= (3, 11):
64
- from datetime import UTC
65
- else:
66
- UTC = timezone.utc
58
+ from datetime import UTC
67
59
 
68
60
  logger = logging.getLogger(__name__)
69
61
 
70
62
 
71
- class LocaleFileInfoBase(NamedTuple):
72
- base_dir: str
73
- domain: str
74
- charset: str
63
+ class CatalogInfo:
64
+ __slots__ = 'base_dir', 'domain', 'charset'
75
65
 
66
+ def __init__(
67
+ self, base_dir: str | os.PathLike[str], domain: str, charset: str
68
+ ) -> None:
69
+ self.base_dir = _StrPath(base_dir)
70
+ self.domain = domain
71
+ self.charset = charset
76
72
 
77
- class CatalogInfo(LocaleFileInfoBase):
78
73
  @property
79
74
  def po_file(self) -> str:
80
- return self.domain + '.po'
75
+ return f'{self.domain}.po'
81
76
 
82
77
  @property
83
78
  def mo_file(self) -> str:
84
- return self.domain + '.mo'
79
+ return f'{self.domain}.mo'
85
80
 
86
81
  @property
87
- def po_path(self) -> str:
88
- return path.join(self.base_dir, self.po_file)
82
+ def po_path(self) -> _StrPath:
83
+ return self.base_dir / self.po_file
89
84
 
90
85
  @property
91
- def mo_path(self) -> str:
92
- return path.join(self.base_dir, self.mo_file)
86
+ def mo_path(self) -> _StrPath:
87
+ return self.base_dir / self.mo_file
93
88
 
94
89
  def is_outdated(self) -> bool:
95
- return (
96
- not path.exists(self.mo_path)
97
- or _last_modified_time(self.mo_path) < _last_modified_time(self.po_path)
98
- ) # fmt: skip
90
+ return not self.mo_path.exists() or (
91
+ _last_modified_time(self.mo_path) < _last_modified_time(self.po_path)
92
+ )
99
93
 
100
94
  def write_mo(self, locale: str, use_fuzzy: bool = False) -> None:
101
95
  with open(self.po_path, encoding=self.charset) as file_po:
102
96
  try:
103
97
  po = read_po(file_po, locale)
104
98
  except Exception as exc:
105
- logger.warning(__('reading error: %s, %s'), self.po_path, exc)
99
+ logger.warning(
100
+ __('reading error: %s, %s'),
101
+ self.po_path,
102
+ exc,
103
+ type='i18n',
104
+ subtype='not_readable',
105
+ )
106
106
  return
107
107
 
108
108
  with open(self.mo_path, 'wb') as file_mo:
109
109
  try:
110
110
  write_mo(file_mo, po, use_fuzzy)
111
111
  except Exception as exc:
112
- logger.warning(__('writing error: %s, %s'), self.mo_path, exc)
112
+ logger.warning(
113
+ __('writing error: %s, %s'),
114
+ self.mo_path,
115
+ exc,
116
+ type='i18n',
117
+ subtype='not_writeable',
118
+ )
113
119
 
114
120
 
115
121
  class CatalogRepository:
@@ -122,42 +128,38 @@ class CatalogRepository:
122
128
  language: str,
123
129
  encoding: str,
124
130
  ) -> None:
125
- self.basedir = basedir
131
+ self.basedir = _StrPath(basedir)
126
132
  self._locale_dirs = locale_dirs
127
133
  self.language = language
128
134
  self.encoding = encoding
129
135
 
130
136
  @property
131
- def locale_dirs(self) -> Iterator[str]:
137
+ def locale_dirs(self) -> Iterator[_StrPath]:
132
138
  if not self.language:
133
139
  return
134
140
 
135
141
  for locale_dir in self._locale_dirs:
136
- locale_dir = path.join(self.basedir, locale_dir)
137
- locale_path = path.join(locale_dir, self.language, 'LC_MESSAGES')
138
- if path.exists(locale_path):
139
- yield locale_dir
142
+ locale_path = self.basedir / locale_dir / self.language / 'LC_MESSAGES'
143
+ if locale_path.exists():
144
+ yield self.basedir / locale_dir
140
145
  else:
141
146
  logger.verbose(__('locale_dir %s does not exist'), locale_path)
142
147
 
143
148
  @property
144
- def pofiles(self) -> Iterator[tuple[str, str]]:
149
+ def pofiles(self) -> Iterator[tuple[_StrPath, _StrPath]]:
145
150
  for locale_dir in self.locale_dirs:
146
- basedir = path.join(locale_dir, self.language, 'LC_MESSAGES')
147
- for root, dirnames, filenames in os.walk(basedir):
151
+ locale_path = locale_dir / self.language / 'LC_MESSAGES'
152
+ for abs_path in locale_path.rglob('*.po'):
153
+ rel_path = abs_path.relative_to(locale_path)
148
154
  # skip dot-directories
149
- for dirname in [d for d in dirnames if d.startswith('.')]:
150
- dirnames.remove(dirname)
151
-
152
- for filename in filenames:
153
- if filename.endswith('.po'):
154
- fullpath = path.join(root, filename)
155
- yield basedir, relpath(fullpath, basedir)
155
+ if any(part.startswith('.') for part in rel_path.parts[:-1]):
156
+ continue
157
+ yield locale_path, rel_path
156
158
 
157
159
  @property
158
160
  def catalogs(self) -> Iterator[CatalogInfo]:
159
161
  for basedir, filename in self.pofiles:
160
- domain = canon_path(path.splitext(filename)[0])
162
+ domain = filename.with_suffix('').as_posix()
161
163
  yield CatalogInfo(basedir, domain, self.encoding)
162
164
 
163
165
 
@@ -173,11 +175,11 @@ def docname_to_domain(docname: str, compaction: bool | str) -> str:
173
175
 
174
176
  # date_format mappings: ustrftime() to babel.dates.format_datetime()
175
177
  date_format_mappings = {
176
- '%a': 'EEE', # Weekday as locales abbreviated name.
177
- '%A': 'EEEE', # Weekday as locales full name.
178
- '%b': 'MMM', # Month as locales abbreviated name.
179
- '%B': 'MMMM', # Month as locales full name.
180
- '%c': 'medium', # Locales appropriate date and time representation.
178
+ '%a': 'EEE', # Weekday as locale's abbreviated name.
179
+ '%A': 'EEEE', # Weekday as locale's full name.
180
+ '%b': 'MMM', # Month as locale's abbreviated name.
181
+ '%B': 'MMMM', # Month as locale's full name.
182
+ '%c': 'medium', # Locale's appropriate date and time representation.
181
183
  '%-d': 'd', # Day of the month as a decimal number.
182
184
  '%d': 'dd', # Day of the month as a zero-padded decimal number.
183
185
  '%-H': 'H', # Hour (24-hour clock) as a decimal number [0,23].
@@ -190,7 +192,7 @@ date_format_mappings = {
190
192
  '%m': 'MM', # Month as a zero-padded decimal number.
191
193
  '%-M': 'm', # Minute as a decimal number [0,59].
192
194
  '%M': 'mm', # Minute as a zero-padded decimal number [00,59].
193
- '%p': 'a', # Locales equivalent of either AM or PM.
195
+ '%p': 'a', # Locale's equivalent of either AM or PM.
194
196
  '%-S': 's', # Second as a decimal number.
195
197
  '%S': 'ss', # Second as a zero-padded decimal number.
196
198
  '%U': 'WW', # Week number of the year (Sunday as the first day of the week)
@@ -202,8 +204,8 @@ date_format_mappings = {
202
204
  # Monday are considered to be in week 0.
203
205
  '%W': 'WW', # Week number of the year (Monday as the first day of the week)
204
206
  # as a zero-padded decimal number.
205
- '%x': 'medium', # Locales appropriate date representation.
206
- '%X': 'medium', # Locales appropriate time representation.
207
+ '%x': 'medium', # Locale's appropriate date representation.
208
+ '%X': 'medium', # Locale's appropriate time representation.
207
209
  '%y': 'YY', # Year without century as a zero-padded decimal number.
208
210
  '%Y': 'yyyy', # Year with century as a decimal number.
209
211
  '%Z': 'zzz', # Time zone name (no characters if no time zone exists).
@@ -230,6 +232,12 @@ def babel_format_date(
230
232
  return formatter(date, format, locale=locale)
231
233
  except (ValueError, babel.core.UnknownLocaleError):
232
234
  # fallback to English
235
+ logger.warning(
236
+ __('Invalid Babel locale: %r.'),
237
+ locale,
238
+ type='i18n',
239
+ subtype='babel',
240
+ )
233
241
  return formatter(date, format, locale='en')
234
242
  except AttributeError:
235
243
  logger.warning(
@@ -238,6 +246,8 @@ def babel_format_date(
238
246
  'if you want to output it directly: %s'
239
247
  ),
240
248
  format,
249
+ type='i18n',
250
+ subtype='babel',
241
251
  )
242
252
  return format
243
253
 
@@ -255,6 +265,9 @@ def format_date(
255
265
  source_date_epoch = os.getenv('SOURCE_DATE_EPOCH')
256
266
  if source_date_epoch is not None:
257
267
  date = datetime.fromtimestamp(float(source_date_epoch), tz=UTC)
268
+ # If SOURCE_DATE_EPOCH is set, users likely want a reproducible result,
269
+ # so enforce GMT/UTC for consistency.
270
+ local_time = False
258
271
  else:
259
272
  date = datetime.now(tz=UTC)
260
273
 
@@ -296,15 +309,15 @@ def get_image_filename_for_language(
296
309
  filename: str | os.PathLike[str],
297
310
  env: BuildEnvironment,
298
311
  ) -> str:
299
- root, ext = path.splitext(filename)
300
- dirname = path.dirname(root)
301
- docpath = path.dirname(env.docname)
312
+ root, ext = os.path.splitext(filename)
313
+ dirname = os.path.dirname(root)
314
+ docpath = os.path.dirname(env.docname)
302
315
  try:
303
316
  return env.config.figure_language_filename.format(
304
317
  root=root,
305
318
  ext=ext,
306
319
  path=dirname and dirname + SEP,
307
- basename=path.basename(root),
320
+ basename=os.path.basename(root),
308
321
  docpath=docpath and docpath + SEP,
309
322
  language=env.config.language,
310
323
  )
@@ -316,7 +329,7 @@ def get_image_filename_for_language(
316
329
  def search_image_for_language(filename: str, env: BuildEnvironment) -> str:
317
330
  translated = get_image_filename_for_language(filename, env)
318
331
  _, abspath = env.relfn2path(translated)
319
- if path.exists(abspath):
332
+ if os.path.exists(abspath):
320
333
  return translated
321
334
  else:
322
335
  return filename
sphinx/util/images.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import base64
6
- from os import path
6
+ from pathlib import Path
7
7
  from typing import TYPE_CHECKING, NamedTuple, overload
8
8
 
9
9
  import imagesize
@@ -37,7 +37,8 @@ class DataURI(NamedTuple):
37
37
  data: bytes
38
38
 
39
39
 
40
- def get_image_size(filename: str) -> tuple[int, int] | None:
40
+ def get_image_size(filename: str | PathLike[str]) -> tuple[int, int] | None:
41
+ filename = Path(filename)
41
42
  try:
42
43
  size = imagesize.get(filename)
43
44
  if size[0] == -1:
@@ -55,11 +56,11 @@ def get_image_size(filename: str) -> tuple[int, int] | None:
55
56
 
56
57
 
57
58
  @overload
58
- def guess_mimetype(filename: PathLike[str] | str, default: str) -> str: ... # NoQA: E704
59
+ def guess_mimetype(filename: PathLike[str] | str, default: str) -> str: ...
59
60
 
60
61
 
61
62
  @overload
62
- def guess_mimetype( # NoQA: E704
63
+ def guess_mimetype(
63
64
  filename: PathLike[str] | str, default: None = None
64
65
  ) -> str | None: ...
65
66
 
@@ -68,10 +69,11 @@ def guess_mimetype(
68
69
  filename: PathLike[str] | str = '',
69
70
  default: str | None = None,
70
71
  ) -> str | None:
71
- ext = path.splitext(filename)[1].lower()
72
+ filename = Path(filename)
73
+ ext = filename.suffix.lower()
72
74
  if ext in mime_suffixes:
73
75
  return mime_suffixes[ext]
74
- if path.exists(filename):
76
+ if filename.exists():
75
77
  try:
76
78
  imgtype = _image_type_from_file(filename)
77
79
  except ValueError: