Sphinx 7.4.6__py3-none-any.whl → 8.0.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 (242) hide show
  1. sphinx/__init__.py +2 -2
  2. sphinx/_cli/__init__.py +4 -4
  3. sphinx/application.py +2 -2
  4. sphinx/builders/__init__.py +2 -3
  5. sphinx/builders/_epub_base.py +33 -12
  6. sphinx/builders/changes.py +13 -5
  7. sphinx/builders/epub3.py +6 -2
  8. sphinx/builders/html/__init__.py +90 -59
  9. sphinx/builders/latex/__init__.py +38 -12
  10. sphinx/builders/latex/transforms.py +1 -1
  11. sphinx/builders/linkcheck.py +8 -49
  12. sphinx/builders/texinfo.py +12 -6
  13. sphinx/builders/text.py +7 -3
  14. sphinx/builders/xml.py +7 -3
  15. sphinx/cmd/quickstart.py +10 -20
  16. sphinx/config.py +13 -13
  17. sphinx/deprecation.py +8 -8
  18. sphinx/directives/__init__.py +14 -9
  19. sphinx/directives/other.py +2 -3
  20. sphinx/directives/patches.py +2 -2
  21. sphinx/domains/__init__.py +4 -2
  22. sphinx/domains/c/__init__.py +2 -2
  23. sphinx/domains/c/_ast.py +3 -2
  24. sphinx/domains/c/_parser.py +4 -3
  25. sphinx/domains/cpp/__init__.py +2 -2
  26. sphinx/domains/cpp/_ast.py +1 -2
  27. sphinx/domains/cpp/_parser.py +2 -2
  28. sphinx/domains/cpp/_symbol.py +2 -2
  29. sphinx/domains/javascript.py +1 -1
  30. sphinx/domains/math.py +1 -1
  31. sphinx/domains/python/__init__.py +1 -1
  32. sphinx/domains/python/_annotations.py +23 -1
  33. sphinx/domains/python/_object.py +0 -1
  34. sphinx/domains/std/__init__.py +7 -8
  35. sphinx/environment/__init__.py +15 -32
  36. sphinx/environment/adapters/indexentries.py +4 -6
  37. sphinx/environment/adapters/toctree.py +4 -4
  38. sphinx/environment/collectors/title.py +1 -1
  39. sphinx/environment/collectors/toctree.py +1 -1
  40. sphinx/events.py +3 -1
  41. sphinx/ext/autodoc/__init__.py +25 -67
  42. sphinx/ext/autodoc/directive.py +7 -5
  43. sphinx/ext/autodoc/importer.py +2 -1
  44. sphinx/ext/autodoc/preserve_defaults.py +2 -2
  45. sphinx/ext/autosummary/__init__.py +15 -7
  46. sphinx/ext/autosummary/generate.py +5 -4
  47. sphinx/ext/doctest.py +5 -5
  48. sphinx/ext/graphviz.py +1 -1
  49. sphinx/ext/imgmath.py +1 -1
  50. sphinx/ext/inheritance_diagram.py +1 -1
  51. sphinx/ext/intersphinx/__init__.py +25 -5
  52. sphinx/ext/intersphinx/_cli.py +7 -6
  53. sphinx/ext/intersphinx/_load.py +240 -115
  54. sphinx/ext/intersphinx/_resolve.py +12 -11
  55. sphinx/ext/intersphinx/_shared.py +102 -9
  56. sphinx/ext/mathjax.py +1 -1
  57. sphinx/ext/napoleon/docstring.py +2 -2
  58. sphinx/ext/todo.py +2 -2
  59. sphinx/ext/viewcode.py +2 -1
  60. sphinx/highlighting.py +3 -3
  61. sphinx/io.py +2 -2
  62. sphinx/jinja2glue.py +13 -6
  63. sphinx/locale/__init__.py +4 -3
  64. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  65. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2383 -2186
  66. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  67. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2249 -2052
  68. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  69. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2412 -2215
  70. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  71. sphinx/locale/ca/LC_MESSAGES/sphinx.po +3029 -2832
  72. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  73. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2308 -2111
  74. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  75. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2469 -2272
  76. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  77. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2393 -2196
  78. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  79. sphinx/locale/da/LC_MESSAGES/sphinx.po +2532 -2335
  80. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  81. sphinx/locale/de/LC_MESSAGES/sphinx.po +2492 -2295
  82. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  83. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2250 -2053
  84. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  85. sphinx/locale/el/LC_MESSAGES/sphinx.po +2879 -2682
  86. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  87. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2250 -2053
  88. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2250 -2053
  90. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2989 -2792
  92. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2250 -2053
  94. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2297 -2100
  96. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/es/LC_MESSAGES/sphinx.po +3017 -2820
  98. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2250 -2053
  100. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/et/LC_MESSAGES/sphinx.po +2748 -2551
  102. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2459 -2262
  104. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/fa/LC_MESSAGES/sphinx.po +2957 -2760
  106. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/fi/LC_MESSAGES/sphinx.po +2321 -2124
  108. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/fr/LC_MESSAGES/sphinx.po +2977 -2780
  110. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +2250 -2053
  112. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2992 -2795
  114. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/he/LC_MESSAGES/sphinx.po +2375 -2178
  116. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/hi/LC_MESSAGES/sphinx.po +2937 -2740
  118. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +2250 -2053
  120. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/hr/LC_MESSAGES/sphinx.po +2532 -2335
  122. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/hu/LC_MESSAGES/sphinx.po +2505 -2308
  124. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/id/LC_MESSAGES/sphinx.po +2925 -2728
  126. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/is/LC_MESSAGES/sphinx.po +2307 -2110
  128. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/it/LC_MESSAGES/sphinx.po +2514 -2317
  130. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2970 -2773
  132. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2868 -2671
  134. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/ko/LC_MESSAGES/sphinx.po +3016 -2819
  136. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  137. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2476 -2279
  138. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  139. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2477 -2280
  140. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  141. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2292 -2095
  142. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  143. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2479 -2282
  144. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  145. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2481 -2284
  146. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  147. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2557 -2360
  148. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  149. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2696 -2499
  150. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  151. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2250 -2053
  152. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  153. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2979 -2782
  154. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  155. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2469 -2272
  156. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  157. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2473 -2276
  158. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  159. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2746 -2549
  160. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  161. sphinx/locale/si/LC_MESSAGES/sphinx.po +2331 -2134
  162. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  163. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2966 -2769
  164. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  165. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2404 -2207
  166. sphinx/locale/sphinx.pot +2262 -2065
  167. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2972 -2775
  169. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2440 -2243
  171. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2483 -2286
  173. sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
  174. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  175. sphinx/locale/ta/LC_MESSAGES/sphinx.po +1578 -1843
  176. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  177. sphinx/locale/te/LC_MESSAGES/sphinx.po +2250 -2053
  178. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  179. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2892 -2695
  180. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  181. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2400 -2203
  182. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  183. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2250 -2053
  184. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  185. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2422 -2225
  186. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  187. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2250 -2053
  188. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +496 -704
  189. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2250 -2053
  191. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  192. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +3028 -2831
  193. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  194. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2250 -2053
  195. sphinx/project.py +25 -20
  196. sphinx/pycode/ast.py +2 -2
  197. sphinx/pycode/parser.py +2 -2
  198. sphinx/pygments_styles.py +3 -3
  199. sphinx/registry.py +3 -8
  200. sphinx/search/__init__.py +1 -1
  201. sphinx/testing/path.py +2 -1
  202. sphinx/testing/util.py +1 -1
  203. sphinx/texinputs/Makefile.jinja +2 -1
  204. sphinx/texinputs_win/Makefile.jinja +2 -1
  205. sphinx/theming.py +3 -12
  206. sphinx/transforms/__init__.py +5 -5
  207. sphinx/transforms/references.py +1 -1
  208. sphinx/util/__init__.py +11 -35
  209. sphinx/util/_pathlib.py +31 -19
  210. sphinx/util/_timestamps.py +12 -0
  211. sphinx/util/cfamily.py +5 -5
  212. sphinx/util/console.py +4 -3
  213. sphinx/util/display.py +3 -3
  214. sphinx/util/docfields.py +1 -1
  215. sphinx/util/docutils.py +44 -10
  216. sphinx/util/fileutil.py +41 -9
  217. sphinx/util/i18n.py +9 -4
  218. sphinx/util/images.py +3 -2
  219. sphinx/util/inspect.py +29 -44
  220. sphinx/util/inventory.py +2 -2
  221. sphinx/util/matching.py +2 -2
  222. sphinx/util/math.py +1 -1
  223. sphinx/util/nodes.py +8 -8
  224. sphinx/util/osutil.py +52 -26
  225. sphinx/util/parallel.py +2 -2
  226. sphinx/util/requests.py +1 -1
  227. sphinx/util/template.py +3 -3
  228. sphinx/util/typing.py +67 -70
  229. sphinx/writers/html.py +1 -1
  230. sphinx/writers/html5.py +1 -1
  231. sphinx/writers/latex.py +4 -4
  232. sphinx/writers/manpage.py +2 -2
  233. sphinx/writers/texinfo.py +5 -5
  234. sphinx/writers/text.py +4 -4
  235. sphinx/writers/xml.py +2 -2
  236. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/METADATA +11 -10
  237. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/RECORD +240 -241
  238. sphinx/templates/quickstart/Makefile.jinja +0 -98
  239. sphinx/templates/quickstart/make.bat.jinja +0 -110
  240. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/LICENSE.rst +0 -0
  241. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/WHEEL +0 -0
  242. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/entry_points.txt +0 -0
sphinx/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """The Sphinx documentation toolchain."""
2
2
 
3
- __version__ = '7.4.6'
3
+ __version__ = '8.0.0'
4
4
  __display_version__ = __version__ # used for command line version
5
5
 
6
6
  # Keep this file executable as-is in Python 3!
@@ -27,7 +27,7 @@ warnings.filterwarnings(
27
27
  #:
28
28
  #: .. versionadded:: 1.2
29
29
  #: Before version 1.2, check the string ``sphinx.__version__``.
30
- version_info = (7, 4, 6, 'final', 0)
30
+ version_info = (8, 0, 0, 'final', 0)
31
31
 
32
32
  package_dir = os.path.abspath(os.path.dirname(__file__))
33
33
 
sphinx/_cli/__init__.py CHANGED
@@ -36,10 +36,10 @@ from sphinx.locale import __, init_console
36
36
 
37
37
  if TYPE_CHECKING:
38
38
  from collections.abc import Callable, Iterable, Iterator, Sequence
39
- from typing import NoReturn
39
+ from typing import NoReturn, TypeAlias
40
40
 
41
- _PARSER_SETUP = Callable[[argparse.ArgumentParser], argparse.ArgumentParser]
42
- _RUNNER = Callable[[argparse.Namespace], int]
41
+ _PARSER_SETUP: TypeAlias = Callable[[argparse.ArgumentParser], argparse.ArgumentParser]
42
+ _RUNNER: TypeAlias = Callable[[argparse.Namespace], int]
43
43
 
44
44
  from typing import Protocol
45
45
 
@@ -79,7 +79,7 @@ class _RootArgumentParser(argparse.ArgumentParser):
79
79
  ]
80
80
 
81
81
  if commands := list(_load_subcommand_descriptions()):
82
- command_max_length = min(max(map(len, next(zip(*commands), ()))), 22)
82
+ command_max_length = min(max(map(len, next(zip(*commands, strict=True), ()))), 22)
83
83
  help_fragments += [
84
84
  '\n',
85
85
  bold(underline(__('Commands:'))),
sphinx/application.py CHANGED
@@ -10,10 +10,10 @@ import os
10
10
  import pickle
11
11
  import sys
12
12
  from collections import deque
13
- from collections.abc import Collection, Sequence # NoQA: TCH003
13
+ from collections.abc import Callable, Collection, Sequence # NoQA: TCH003
14
14
  from io import StringIO
15
15
  from os import path
16
- from typing import IO, TYPE_CHECKING, Any, Callable, Literal
16
+ from typing import IO, TYPE_CHECKING, Any, Literal
17
17
 
18
18
  from docutils.nodes import TextElement # NoQA: TCH002
19
19
  from docutils.parsers.rst import Directive, roles
@@ -38,7 +38,6 @@ if TYPE_CHECKING:
38
38
  from sphinx.config import Config
39
39
  from sphinx.events import EventManager
40
40
  from sphinx.util.tags import Tags
41
- from sphinx.util.typing import NoneType
42
41
 
43
42
 
44
43
  logger = logging.getLogger(__name__)
@@ -520,7 +519,7 @@ class Builder:
520
519
  if path.isfile(docutilsconf):
521
520
  self.env.note_dependency(docutilsconf)
522
521
 
523
- filename = self.env.doc2path(docname)
522
+ filename = str(self.env.doc2path(docname))
524
523
  filetype = get_filetype(self.app.config.source_suffix, filename)
525
524
  publisher = self.app.registry.get_publisher(self.app, filetype)
526
525
  self.env.temp_data['_parser'] = publisher.parser
@@ -645,7 +644,7 @@ class Builder:
645
644
  progress = status_iterator(chunks, __('writing output... '), "darkgreen",
646
645
  len(chunks), self.app.verbosity)
647
646
 
648
- def on_chunk_done(args: list[tuple[str, NoneType]], result: NoneType) -> None:
647
+ def on_chunk_done(args: list[tuple[str, nodes.document]], result: None) -> None:
649
648
  next(progress)
650
649
 
651
650
  self.app.phase = BuildPhase.RESOLVING
@@ -27,8 +27,9 @@ if TYPE_CHECKING:
27
27
 
28
28
  try:
29
29
  from PIL import Image
30
+ PILLOW_AVAILABLE = True
30
31
  except ImportError:
31
- Image = None
32
+ PILLOW_AVAILABLE = False
32
33
 
33
34
 
34
35
  logger = logging.getLogger(__name__)
@@ -107,8 +108,8 @@ class NavPoint(NamedTuple):
107
108
 
108
109
  def sphinx_smarty_pants(t: str, language: str = 'en') -> str:
109
110
  t = t.replace('"', '"')
110
- t = smartquotes.educateDashesOldSchool(t)
111
- t = smartquotes.educateQuotes(t, language)
111
+ t = smartquotes.educateDashesOldSchool(t) # type: ignore[no-untyped-call]
112
+ t = smartquotes.educateQuotes(t, language) # type: ignore[no-untyped-call]
112
113
  t = t.replace('"', '"')
113
114
  return t
114
115
 
@@ -411,8 +412,11 @@ class EpubBuilder(StandaloneHTMLBuilder):
411
412
  logger.warning(__('cannot read image file %r: copying it instead'),
412
413
  path.join(self.srcdir, src))
413
414
  try:
414
- copyfile(path.join(self.srcdir, src),
415
- path.join(self.outdir, self.imagedir, dest))
415
+ copyfile(
416
+ self.srcdir / src,
417
+ self.outdir / self.imagedir / dest,
418
+ force=True,
419
+ )
416
420
  except OSError as err:
417
421
  logger.warning(__('cannot copy image file %r: %s'),
418
422
  path.join(self.srcdir, src), err)
@@ -440,7 +444,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
440
444
  """
441
445
  if self.images:
442
446
  if self.config.epub_fix_images or self.config.epub_max_image_width:
443
- if not Image:
447
+ if not PILLOW_AVAILABLE:
444
448
  logger.warning(__('Pillow not found - copying image files'))
445
449
  super().copy_image_files()
446
450
  else:
@@ -474,14 +478,22 @@ class EpubBuilder(StandaloneHTMLBuilder):
474
478
  def build_mimetype(self) -> None:
475
479
  """Write the metainfo file mimetype."""
476
480
  logger.info(__('writing mimetype file...'))
477
- copy_asset_file(path.join(self.template_dir, 'mimetype'), self.outdir)
481
+ copyfile(
482
+ path.join(self.template_dir, 'mimetype'),
483
+ self.outdir / 'mimetype',
484
+ force=True,
485
+ )
478
486
 
479
487
  def build_container(self, outname: str = 'META-INF/container.xml') -> None:
480
488
  """Write the metainfo file META-INF/container.xml."""
481
489
  logger.info(__('writing META-INF/container.xml file...'))
482
- outdir = path.join(self.outdir, 'META-INF')
490
+ outdir = self.outdir / 'META-INF'
483
491
  ensuredir(outdir)
484
- copy_asset_file(path.join(self.template_dir, 'container.xml'), outdir)
492
+ copyfile(
493
+ path.join(self.template_dir, 'container.xml'),
494
+ outdir / 'container.xml',
495
+ force=True,
496
+ )
485
497
 
486
498
  def content_metadata(self) -> dict[str, Any]:
487
499
  """Create a dictionary with all metadata for the content.opf
@@ -621,7 +633,12 @@ class EpubBuilder(StandaloneHTMLBuilder):
621
633
  html.escape(self.refnodes[0]['refuri'])))
622
634
 
623
635
  # write the project file
624
- copy_asset_file(path.join(self.template_dir, 'content.opf.jinja'), self.outdir, metadata) # NoQA: E501
636
+ copy_asset_file(
637
+ path.join(self.template_dir, 'content.opf.jinja'),
638
+ self.outdir,
639
+ context=metadata,
640
+ force=True,
641
+ )
625
642
 
626
643
  def new_navpoint(self, node: dict[str, Any], level: int, incr: bool = True) -> NavPoint:
627
644
  """Create a new entry in the toc from the node at given level."""
@@ -704,8 +721,12 @@ class EpubBuilder(StandaloneHTMLBuilder):
704
721
  navpoints = self.build_navpoints(refnodes)
705
722
  level = max(item['level'] for item in self.refnodes)
706
723
  level = min(level, self.config.epub_tocdepth)
707
- copy_asset_file(path.join(self.template_dir, 'toc.ncx.jinja'), self.outdir,
708
- self.toc_metadata(level, navpoints))
724
+ copy_asset_file(
725
+ path.join(self.template_dir, 'toc.ncx.jinja'),
726
+ self.outdir,
727
+ context=self.toc_metadata(level, navpoints),
728
+ force=True,
729
+ )
709
730
 
710
731
  def build_epub(self) -> None:
711
732
  """Write the epub file.
@@ -134,16 +134,24 @@ class ChangesBuilder(Builder):
134
134
  with open(targetfn, 'w', encoding='utf-8') as f:
135
135
  text = ''.join(hl(i + 1, line) for (i, line) in enumerate(lines))
136
136
  ctx = {
137
- 'filename': self.env.doc2path(docname, False),
137
+ 'filename': str(self.env.doc2path(docname, False)),
138
138
  'text': text,
139
139
  }
140
140
  f.write(self.templates.render('changes/rstsource.html', ctx))
141
141
  themectx = {'theme_' + key: val for (key, val) in
142
142
  self.theme.get_options({}).items()}
143
- copy_asset_file(path.join(package_dir, 'themes', 'default', 'static', 'default.css.jinja'), # NoQA: E501
144
- self.outdir, context=themectx, renderer=self.templates)
145
- copy_asset_file(path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'),
146
- self.outdir)
143
+ copy_asset_file(
144
+ path.join(package_dir, 'themes', 'default', 'static', 'default.css.jinja'),
145
+ self.outdir,
146
+ context=themectx,
147
+ renderer=self.templates,
148
+ force=True,
149
+ )
150
+ copy_asset_file(
151
+ path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'),
152
+ self.outdir / 'basic.css',
153
+ force=True,
154
+ )
147
155
 
148
156
  def hl(self, text: str, version: str) -> str:
149
157
  text = html.escape(text)
sphinx/builders/epub3.py CHANGED
@@ -194,8 +194,12 @@ class Epub3Builder(_epub_base.EpubBuilder):
194
194
  # 'includehidden'
195
195
  refnodes = self.refnodes
196
196
  navlist = self.build_navlist(refnodes)
197
- copy_asset_file(path.join(self.template_dir, 'nav.xhtml.jinja'), self.outdir,
198
- self.navigation_doc_metadata(navlist))
197
+ copy_asset_file(
198
+ path.join(self.template_dir, 'nav.xhtml.jinja'),
199
+ self.outdir,
200
+ context=self.navigation_doc_metadata(navlist),
201
+ force=True,
202
+ )
199
203
 
200
204
  # Add nav.xhtml to epub file
201
205
  if 'nav.xhtml' not in self.files:
@@ -9,10 +9,10 @@ import os
9
9
  import posixpath
10
10
  import re
11
11
  import sys
12
- import time
13
12
  import types
14
13
  import warnings
15
14
  from os import path
15
+ from pathlib import Path
16
16
  from typing import IO, TYPE_CHECKING, Any
17
17
  from urllib.parse import quote
18
18
 
@@ -39,18 +39,27 @@ from sphinx.locale import _, __
39
39
  from sphinx.search import js_index
40
40
  from sphinx.theming import HTMLThemeFactory
41
41
  from sphinx.util import isurl, logging
42
+ from sphinx.util._timestamps import _format_rfc3339_microseconds
42
43
  from sphinx.util.display import progress_message, status_iterator
43
44
  from sphinx.util.docutils import new_document
44
45
  from sphinx.util.fileutil import copy_asset
45
46
  from sphinx.util.i18n import format_date
46
47
  from sphinx.util.inventory import InventoryFile
47
48
  from sphinx.util.matching import DOTFILES, Matcher, patmatch
48
- from sphinx.util.osutil import SEP, copyfile, ensuredir, os_path, relative_uri
49
+ from sphinx.util.osutil import (
50
+ SEP,
51
+ _last_modified_time,
52
+ copyfile,
53
+ ensuredir,
54
+ os_path,
55
+ relative_uri,
56
+ )
49
57
  from sphinx.writers.html import HTMLWriter
50
58
  from sphinx.writers.html5 import HTML5Translator
51
59
 
52
60
  if TYPE_CHECKING:
53
61
  from collections.abc import Iterable, Iterator, Set
62
+ from typing import TypeAlias
54
63
 
55
64
  from docutils.nodes import Node
56
65
  from docutils.readers import Reader
@@ -67,7 +76,7 @@ INVENTORY_FILENAME = 'objects.inv'
67
76
  logger = logging.getLogger(__name__)
68
77
  return_codes_re = re.compile('[\r\n]+')
69
78
 
70
- DOMAIN_INDEX_TYPE = tuple[
79
+ DOMAIN_INDEX_TYPE: TypeAlias = tuple[
71
80
  # Index name (e.g. py-modindex)
72
81
  str,
73
82
  # Index class
@@ -87,9 +96,9 @@ def _stable_hash(obj: Any) -> str:
87
96
  """
88
97
  if isinstance(obj, dict):
89
98
  obj = sorted(map(_stable_hash, obj.items()))
90
- if isinstance(obj, (list, tuple, set, frozenset)):
99
+ if isinstance(obj, list | tuple | set | frozenset):
91
100
  obj = sorted(map(_stable_hash, obj))
92
- elif isinstance(obj, (type, types.FunctionType)):
101
+ elif isinstance(obj, type | types.FunctionType):
93
102
  # The default repr() of functions includes the ID, which is not ideal.
94
103
  # We use the fully qualified name instead.
95
104
  obj = f'{obj.__module__}.{obj.__qualname__}'
@@ -395,7 +404,7 @@ class StandaloneHTMLBuilder(Builder):
395
404
  pass
396
405
 
397
406
  if self.templates:
398
- template_mtime = self.templates.newest_template_mtime()
407
+ template_mtime = int(self.templates.newest_template_mtime() * 10**6)
399
408
  else:
400
409
  template_mtime = 0
401
410
  for docname in self.env.found_docs:
@@ -405,19 +414,19 @@ class StandaloneHTMLBuilder(Builder):
405
414
  continue
406
415
  targetname = self.get_outfilename(docname)
407
416
  try:
408
- targetmtime = path.getmtime(targetname)
417
+ targetmtime = _last_modified_time(targetname)
409
418
  except Exception:
410
419
  targetmtime = 0
411
420
  try:
412
- srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime)
421
+ srcmtime = max(_last_modified_time(self.env.doc2path(docname)), template_mtime)
413
422
  if srcmtime > targetmtime:
414
423
  logger.debug(
415
424
  '[build target] targetname %r(%s), template(%s), docname %r(%s)',
416
425
  targetname,
417
- _format_modified_time(targetmtime),
418
- _format_modified_time(template_mtime),
426
+ _format_rfc3339_microseconds(targetmtime),
427
+ _format_rfc3339_microseconds(template_mtime),
419
428
  docname,
420
- _format_modified_time(path.getmtime(self.env.doc2path(docname))),
429
+ _format_rfc3339_microseconds(_last_modified_time(self.env.doc2path(docname))),
421
430
  )
422
431
  yield docname
423
432
  except OSError:
@@ -609,7 +618,7 @@ class StandaloneHTMLBuilder(Builder):
609
618
  title = self.render_partial(title_node)['title'] if title_node else ''
610
619
 
611
620
  # Suffix for the document
612
- source_suffix = self.env.doc2path(docname, False)[len(docname):]
621
+ source_suffix = str(self.env.doc2path(docname, False))[len(docname):]
613
622
 
614
623
  # the name for the copied source
615
624
  if self.config.html_copy_source:
@@ -734,7 +743,7 @@ class StandaloneHTMLBuilder(Builder):
734
743
  'genindex-split.html')
735
744
  self.handle_page('genindex-all', genindexcontext,
736
745
  'genindex.html')
737
- for (key, entries), count in zip(genindex, indexcounts):
746
+ for (key, entries), count in zip(genindex, indexcounts, strict=True):
738
747
  ctx = {'key': key, 'entries': entries, 'count': count,
739
748
  'genindexentries': genindex}
740
749
  self.handle_page('genindex-' + key, ctx,
@@ -755,17 +764,20 @@ class StandaloneHTMLBuilder(Builder):
755
764
  def copy_image_files(self) -> None:
756
765
  if self.images:
757
766
  stringify_func = ImageAdapter(self.app.env).get_original_image_uri
758
- ensuredir(path.join(self.outdir, self.imagedir))
767
+ ensuredir(self.outdir / self.imagedir)
759
768
  for src in status_iterator(self.images, __('copying images... '), "brown",
760
769
  len(self.images), self.app.verbosity,
761
770
  stringify_func=stringify_func):
762
771
  dest = self.images[src]
763
772
  try:
764
- copyfile(path.join(self.srcdir, src),
765
- path.join(self.outdir, self.imagedir, dest))
773
+ copyfile(
774
+ self.srcdir / src,
775
+ self.outdir / self.imagedir / dest,
776
+ force=True,
777
+ )
766
778
  except Exception as err:
767
- logger.warning(__('cannot copy image file %r: %s'),
768
- path.join(self.srcdir, src), err)
779
+ logger.warning(__("cannot copy image file '%s': %s"),
780
+ self.srcdir / src, err)
769
781
 
770
782
  def copy_download_files(self) -> None:
771
783
  def to_relpath(f: str) -> str:
@@ -773,17 +785,17 @@ class StandaloneHTMLBuilder(Builder):
773
785
 
774
786
  # copy downloadable files
775
787
  if self.env.dlfiles:
776
- ensuredir(path.join(self.outdir, '_downloads'))
788
+ ensuredir(self.outdir / '_downloads')
777
789
  for src in status_iterator(self.env.dlfiles, __('copying downloadable files... '),
778
790
  "brown", len(self.env.dlfiles), self.app.verbosity,
779
791
  stringify_func=to_relpath):
780
792
  try:
781
- dest = path.join(self.outdir, '_downloads', self.env.dlfiles[src][1])
782
- ensuredir(path.dirname(dest))
783
- copyfile(path.join(self.srcdir, src), dest)
793
+ dest = self.outdir / '_downloads' / self.env.dlfiles[src][1]
794
+ ensuredir(dest.parent)
795
+ copyfile(self.srcdir / src, dest, force=True)
784
796
  except OSError as err:
785
797
  logger.warning(__('cannot copy downloadable file %r: %s'),
786
- path.join(self.srcdir, src), err)
798
+ self.srcdir / src, err)
787
799
 
788
800
  def create_pygments_style_file(self) -> None:
789
801
  """Create a style file for pygments."""
@@ -800,30 +812,45 @@ class StandaloneHTMLBuilder(Builder):
800
812
  """Copy a JavaScript file for translations."""
801
813
  jsfile = self._get_translations_js()
802
814
  if jsfile:
803
- copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js'))
815
+ copyfile(
816
+ jsfile,
817
+ self.outdir / '_static' / 'translations.js',
818
+ force=True,
819
+ )
804
820
 
805
821
  def copy_stemmer_js(self) -> None:
806
822
  """Copy a JavaScript file for stemmer."""
807
823
  if self.indexer is not None:
808
824
  if hasattr(self.indexer, 'get_js_stemmer_rawcodes'):
809
825
  for jsfile in self.indexer.get_js_stemmer_rawcodes():
810
- copyfile(jsfile, path.join(self.outdir, '_static', path.basename(jsfile)))
826
+ js_path = Path(jsfile)
827
+ copyfile(
828
+ js_path,
829
+ self.outdir / '_static' / js_path.name,
830
+ force=True,
831
+ )
811
832
  else:
812
833
  if js_stemmer_rawcode := self.indexer.get_js_stemmer_rawcode():
813
- copyfile(js_stemmer_rawcode,
814
- path.join(self.outdir, '_static', '_stemmer.js'))
834
+ copyfile(
835
+ js_stemmer_rawcode,
836
+ self.outdir / '_static' / '_stemmer.js',
837
+ force=True,
838
+ )
815
839
 
816
840
  def copy_theme_static_files(self, context: dict[str, Any]) -> None:
817
841
  def onerror(filename: str, error: Exception) -> None:
818
- logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
819
- filename, error)
842
+ msg = __("Failed to copy a file in the theme's 'static' directory: %s: %r")
843
+ logger.warning(msg, filename, error)
820
844
 
821
845
  if self.theme:
822
846
  for entry in reversed(self.theme.get_theme_dirs()):
823
- copy_asset(path.join(entry, 'static'),
824
- path.join(self.outdir, '_static'),
825
- excluded=DOTFILES, context=context,
826
- renderer=self.templates, onerror=onerror)
847
+ copy_asset(
848
+ Path(entry) / 'static',
849
+ self.outdir / '_static',
850
+ excluded=DOTFILES, context=context,
851
+ renderer=self.templates, onerror=onerror,
852
+ force=True,
853
+ )
827
854
 
828
855
  def copy_html_static_files(self, context: dict[str, Any]) -> None:
829
856
  def onerror(filename: str, error: Exception) -> None:
@@ -832,24 +859,36 @@ class StandaloneHTMLBuilder(Builder):
832
859
 
833
860
  excluded = Matcher([*self.config.exclude_patterns, '**/.*'])
834
861
  for entry in self.config.html_static_path:
835
- copy_asset(path.join(self.confdir, entry),
836
- path.join(self.outdir, '_static'),
837
- excluded, context=context, renderer=self.templates, onerror=onerror)
862
+ copy_asset(
863
+ self.confdir / entry,
864
+ self.outdir / '_static',
865
+ excluded=excluded, context=context,
866
+ renderer=self.templates, onerror=onerror,
867
+ force=True,
868
+ )
838
869
 
839
870
  def copy_html_logo(self) -> None:
840
871
  if self.config.html_logo and not isurl(self.config.html_logo):
841
- copy_asset(path.join(self.confdir, self.config.html_logo),
842
- path.join(self.outdir, '_static'))
872
+ source_path = self.confdir / self.config.html_logo
873
+ copyfile(
874
+ source_path,
875
+ self.outdir / '_static' / source_path.name,
876
+ force=True,
877
+ )
843
878
 
844
879
  def copy_html_favicon(self) -> None:
845
880
  if self.config.html_favicon and not isurl(self.config.html_favicon):
846
- copy_asset(path.join(self.confdir, self.config.html_favicon),
847
- path.join(self.outdir, '_static'))
881
+ source_path = self.confdir / self.config.html_favicon
882
+ copyfile(
883
+ source_path,
884
+ self.outdir / '_static' / source_path.name,
885
+ force=True,
886
+ )
848
887
 
849
888
  def copy_static_files(self) -> None:
850
889
  try:
851
890
  with progress_message(__('copying static files')):
852
- ensuredir(path.join(self.outdir, '_static'))
891
+ ensuredir(self.outdir / '_static')
853
892
 
854
893
  # prepare context for templates
855
894
  context = self.globalcontext.copy()
@@ -872,8 +911,12 @@ class StandaloneHTMLBuilder(Builder):
872
911
  with progress_message(__('copying extra files')):
873
912
  excluded = Matcher(self.config.exclude_patterns)
874
913
  for extra_path in self.config.html_extra_path:
875
- entry = path.join(self.confdir, extra_path)
876
- copy_asset(entry, self.outdir, excluded)
914
+ copy_asset(
915
+ self.confdir / extra_path,
916
+ self.outdir,
917
+ excluded=excluded,
918
+ force=True,
919
+ )
877
920
  except OSError as err:
878
921
  logger.warning(__('cannot copy extra file %r'), err)
879
922
 
@@ -940,7 +983,7 @@ class StandaloneHTMLBuilder(Builder):
940
983
  def index_page(self, pagename: str, doctree: nodes.document, title: str) -> None:
941
984
  # only index pages with title
942
985
  if self.indexer is not None and title:
943
- filename = self.env.doc2path(pagename, base=False)
986
+ filename = str(self.env.doc2path(pagename, base=False))
944
987
  metadata = self.env.metadata.get(pagename, {})
945
988
  if 'no-search' in metadata or 'nosearch' in metadata:
946
989
  self.indexer.feed(pagename, filename, '', new_document(''))
@@ -982,10 +1025,7 @@ class StandaloneHTMLBuilder(Builder):
982
1025
  matched = pattern
983
1026
  sidebars = pat_sidebars
984
1027
 
985
- # See error_on_html_sidebars_string_values.
986
- # Replace with simple list coercion in Sphinx 8.0
987
- # xref: RemovedInSphinx80Warning
988
- ctx['sidebars'] = sidebars
1028
+ ctx['sidebars'] = list(sidebars)
989
1029
 
990
1030
  # --------- these are overwritten by the serialization builder
991
1031
 
@@ -1142,7 +1182,7 @@ class StandaloneHTMLBuilder(Builder):
1142
1182
  source_name = path.join(self.outdir, '_sources',
1143
1183
  os_path(ctx['sourcename']))
1144
1184
  ensuredir(path.dirname(source_name))
1145
- copyfile(self.env.doc2path(pagename), source_name)
1185
+ copyfile(self.env.doc2path(pagename), source_name, force=True)
1146
1186
 
1147
1187
  def update_page_context(self, pagename: str, templatename: str,
1148
1188
  ctx: dict[str, Any], event_arg: Any) -> None:
@@ -1191,12 +1231,6 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None:
1191
1231
  config.html_css_files = html_css_files
1192
1232
 
1193
1233
 
1194
- def _format_modified_time(timestamp: float) -> str:
1195
- """Return an RFC 3339 formatted string representing the given timestamp."""
1196
- seconds, fraction = divmod(timestamp, 1)
1197
- return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(seconds)) + f'.{fraction:.3f}'
1198
-
1199
-
1200
1234
  def convert_html_js_files(app: Sphinx, config: Config) -> None:
1201
1235
  """Convert string styled html_js_files to tuple styled one."""
1202
1236
  html_js_files: list[tuple[str, dict[str, str]]] = []
@@ -1299,10 +1333,7 @@ def error_on_html_sidebars_string_values(app: Sphinx, config: Config) -> None:
1299
1333
  "Change to `html_sidebars = %r`.")
1300
1334
  bad_patterns = ', '.join(map(repr, errors))
1301
1335
  fixed = config.html_sidebars | errors
1302
- logger.error(msg, bad_patterns, fixed)
1303
- # Enable hard error in next major version.
1304
- # xref: RemovedInSphinx80Warning
1305
- # raise ConfigError(msg % (bad_patterns, fixed))
1336
+ raise ConfigError(msg % (bad_patterns, fixed))
1306
1337
 
1307
1338
 
1308
1339
  def error_on_html_4(_app: Sphinx, config: Config) -> None:
@@ -20,13 +20,13 @@ from sphinx.environment.adapters.asset import ImageAdapter
20
20
  from sphinx.errors import NoUri, SphinxError
21
21
  from sphinx.locale import _, __
22
22
  from sphinx.util import logging, texescape
23
- from sphinx.util.console import bold, darkgreen
23
+ from sphinx.util.console import darkgreen
24
24
  from sphinx.util.display import progress_message, status_iterator
25
25
  from sphinx.util.docutils import SphinxFileOutput, new_document
26
26
  from sphinx.util.fileutil import copy_asset_file
27
27
  from sphinx.util.i18n import format_date
28
28
  from sphinx.util.nodes import inline_all_toctrees
29
- from sphinx.util.osutil import SEP, make_filename_from_project
29
+ from sphinx.util.osutil import SEP, copyfile, make_filename_from_project
30
30
  from sphinx.util.template import LaTeXRenderer
31
31
  from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter
32
32
 
@@ -407,24 +407,36 @@ class LaTeXBuilder(Builder):
407
407
  'xindy_lang_option': xindy_lang_option,
408
408
  'xindy_cyrillic': xindy_cyrillic,
409
409
  }
410
- logger.info(bold(__('copying TeX support files...')))
411
410
  staticdirname = path.join(package_dir, 'texinputs')
412
411
  for filename in os.listdir(staticdirname):
413
412
  if not filename.startswith('.'):
414
- copy_asset_file(path.join(staticdirname, filename),
415
- self.outdir, context=context)
413
+ copy_asset_file(
414
+ path.join(staticdirname, filename),
415
+ self.outdir,
416
+ context=context,
417
+ force=True,
418
+ )
416
419
 
417
420
  # use pre-1.6.x Makefile for make latexpdf on Windows
418
421
  if os.name == 'nt':
419
422
  staticdirname = path.join(package_dir, 'texinputs_win')
420
- copy_asset_file(path.join(staticdirname, 'Makefile.jinja'),
421
- self.outdir, context=context)
423
+ copy_asset_file(
424
+ path.join(staticdirname, 'Makefile.jinja'),
425
+ self.outdir,
426
+ context=context,
427
+ force=True,
428
+ )
422
429
 
423
430
  @progress_message(__('copying additional files'))
424
431
  def copy_latex_additional_files(self) -> None:
425
432
  for filename in self.config.latex_additional_files:
426
433
  logger.info(' ' + filename, nonl=True)
427
- copy_asset_file(path.join(self.confdir, filename), self.outdir)
434
+ source = self.confdir / filename
435
+ copyfile(
436
+ source,
437
+ self.outdir / source.name,
438
+ force=True,
439
+ )
428
440
 
429
441
  def copy_image_files(self) -> None:
430
442
  if self.images:
@@ -434,15 +446,23 @@ class LaTeXBuilder(Builder):
434
446
  stringify_func=stringify_func):
435
447
  dest = self.images[src]
436
448
  try:
437
- copy_asset_file(path.join(self.srcdir, src),
438
- path.join(self.outdir, dest))
449
+ copyfile(
450
+ self.srcdir / src,
451
+ self.outdir / dest,
452
+ force=True,
453
+ )
439
454
  except Exception as err:
440
455
  logger.warning(__('cannot copy image file %r: %s'),
441
456
  path.join(self.srcdir, src), err)
442
457
  if self.config.latex_logo:
443
458
  if not path.isfile(path.join(self.confdir, self.config.latex_logo)):
444
459
  raise SphinxError(__('logo file %r does not exist') % self.config.latex_logo)
445
- copy_asset_file(path.join(self.confdir, self.config.latex_logo), self.outdir)
460
+ source = self.confdir / self.config.latex_logo
461
+ copyfile(
462
+ source,
463
+ self.outdir / source.name,
464
+ force=True,
465
+ )
446
466
 
447
467
  def write_message_catalog(self) -> None:
448
468
  formats = self.config.numfig_format
@@ -457,7 +477,13 @@ class LaTeXBuilder(Builder):
457
477
  context['addtocaptions'] = r'\addto\captions%s' % self.babel.get_language()
458
478
 
459
479
  filename = path.join(package_dir, 'templates', 'latex', 'sphinxmessages.sty.jinja')
460
- copy_asset_file(filename, self.outdir, context=context, renderer=LaTeXRenderer())
480
+ copy_asset_file(
481
+ filename,
482
+ self.outdir,
483
+ context=context,
484
+ renderer=LaTeXRenderer(),
485
+ force=True,
486
+ )
461
487
 
462
488
 
463
489
  def validate_config_values(app: Sphinx, config: Config) -> None: