From 7227b09a2d01bb4671ebc48329c97e59f8207e64 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:38:40 +0000 Subject: [PATCH 1/3] Replace ``path`` with ``os.path`` --- sphinx/builders/gettext.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index dbb593f8f03..2976483d55a 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -4,9 +4,11 @@ import codecs import operator +import os +import os.path import time from collections import defaultdict -from os import getenv, path, walk +from os import getenv, walk from pathlib import Path from typing import TYPE_CHECKING from uuid import uuid4 @@ -28,7 +30,6 @@ from sphinx.util.template import SphinxRenderer if TYPE_CHECKING: - import os from collections.abc import Iterable, Iterator, Sequence from typing import Any, Literal @@ -209,7 +210,7 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None: def should_write(filepath: str, new_content: str) -> bool: - if not path.exists(filepath): + if not os.path.exists(filepath): return True try: with codecs.open(filepath, encoding='utf-8') as oldpot: @@ -251,11 +252,11 @@ def init(self) -> None: def _collect_templates(self) -> set[str]: template_files = set() for template_path in self.config.templates_path: - tmpl_abs_path = path.join(self.app.srcdir, template_path) + tmpl_abs_path = os.path.join(self.app.srcdir, template_path) for dirpath, _dirs, files in walk(tmpl_abs_path): for fn in files: if fn.endswith('.html'): - filename = canon_path(path.join(dirpath, fn)) + filename = canon_path(os.path.join(dirpath, fn)) template_files.add(filename) return template_files @@ -311,7 +312,7 @@ def finish(self) -> None: operator.itemgetter(0), ): # noop if config.gettext_compact is set - ensuredir(path.join(self.outdir, path.dirname(textdomain))) + ensuredir(os.path.join(self.outdir, os.path.dirname(textdomain))) context['messages'] = list(catalog) template_path = [ @@ -320,7 +321,7 @@ def finish(self) -> None: renderer = GettextRenderer(template_path, outdir=self.outdir) content = renderer.render('message.pot.jinja', context) - pofn = path.join(self.outdir, textdomain + '.pot') + pofn = os.path.join(self.outdir, textdomain + '.pot') if should_write(pofn, content): with codecs.open(pofn, 'w', encoding='utf-8') as pofile: pofile.write(content) From 4b1d64d1e54aa3705b3246e9cd3cb647e3c4aaf1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:38:59 +0000 Subject: [PATCH 2/3] Use ``app.build(force_all=True)`` --- tests/test_domains/test_domain_cpp.py | 2 +- tests/test_extensions/test_ext_viewcode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_domains/test_domain_cpp.py b/tests/test_domains/test_domain_cpp.py index 98af279ebdf..19e5e07523d 100644 --- a/tests/test_domains/test_domain_cpp.py +++ b/tests/test_domains/test_domain_cpp.py @@ -1826,7 +1826,7 @@ def test_domain_cpp_build_field_role(app): @pytest.mark.sphinx('html', testroot='domain-cpp', confoverrides={'nitpicky': True}) def test_domain_cpp_build_operator_lookup(app): - app.builder.build_all() + app.build(force_all=True) ws = filter_warnings(app.warning, 'operator-lookup') assert len(ws) == 5 # TODO: the first one should not happen diff --git a/tests/test_extensions/test_ext_viewcode.py b/tests/test_extensions/test_ext_viewcode.py index cb6b29859dc..01d68cd3ccf 100644 --- a/tests/test_extensions/test_ext_viewcode.py +++ b/tests/test_extensions/test_ext_viewcode.py @@ -173,7 +173,7 @@ def find_source(app, modname): @pytest.mark.sphinx('html', testroot='ext-viewcode-find-package', freshenv=True) def test_find_local_package_import_path(app, status, warning): - app.builder.build_all() + app.build(force_all=True) result = (app.outdir / 'index.html').read_text(encoding='utf8') count_func1 = result.count( From fc62472a03679928a7f0ca9755dee68a7e88328c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sat, 18 Jan 2025 22:42:47 +0000 Subject: [PATCH 3/3] Replace ``os.path.join`` with pathlib (#13252) --- sphinx/builders/__init__.py | 6 +-- sphinx/builders/_epub_base.py | 36 ++++++------- sphinx/builders/changes.py | 15 +++--- sphinx/builders/epub3.py | 5 +- sphinx/builders/gettext.py | 14 +++--- sphinx/builders/latex/__init__.py | 23 ++++----- sphinx/builders/latex/theming.py | 15 +++--- sphinx/builders/linkcheck.py | 8 ++- sphinx/builders/manpage.py | 5 +- sphinx/builders/texinfo.py | 9 ++-- sphinx/builders/text.py | 10 ++-- sphinx/builders/xml.py | 10 ++-- sphinx/ext/apidoc/_generate.py | 12 +++-- sphinx/ext/autosummary/generate.py | 4 +- sphinx/ext/coverage.py | 10 ++-- sphinx/ext/graphviz.py | 29 ++++++----- sphinx/ext/imgconverter.py | 10 ++-- sphinx/ext/imgmath.py | 56 ++++++++++----------- sphinx/ext/viewcode.py | 3 +- sphinx/jinja2glue.py | 12 ++--- sphinx/transforms/post_transforms/images.py | 26 +++++----- sphinx/util/_files.py | 3 +- sphinx/util/png.py | 8 ++- 23 files changed, 163 insertions(+), 166 deletions(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 89d518f8539..0b162305e7e 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -414,7 +414,7 @@ def build( with ( progress_message(__('pickling environment')), - open(os.path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f, + open(self.doctreedir / ENV_PICKLE_FILENAME, 'wb') as f, ): pickle.dump(self.env, f, pickle.HIGHEST_PROTOCOL) @@ -622,7 +622,7 @@ def read_doc(self, docname: str, *, _cache: bool = True) -> None: env.prepare_settings(docname) # Add confdir/docutils.conf to dependencies list if exists - docutilsconf = os.path.join(self.confdir, 'docutils.conf') + docutilsconf = self.confdir / 'docutils.conf' if os.path.isfile(docutilsconf): env.note_dependency(docutilsconf) @@ -674,7 +674,7 @@ def write_doctree( doctree.settings.env = None doctree.settings.record_dependencies = None - doctree_filename = os.path.join(self.doctreedir, docname + '.doctree') + doctree_filename = self.doctreedir / f'{docname}.doctree' ensuredir(os.path.dirname(doctree_filename)) with open(doctree_filename, 'wb') as f: pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 441bb71481d..66e1fa7a22d 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -7,6 +7,7 @@ import os.path import re import time +from pathlib import Path from typing import TYPE_CHECKING, NamedTuple from urllib.parse import quote from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile @@ -19,12 +20,12 @@ from sphinx.builders.html._build_info import BuildInfo from sphinx.locale import __ from sphinx.util import logging +from sphinx.util._pathlib import _StrPath from sphinx.util.display import status_iterator from sphinx.util.fileutil import copy_asset_file from sphinx.util.osutil import copyfile, ensuredir, relpath if TYPE_CHECKING: - from pathlib import Path from typing import Any from docutils.nodes import Element, Node @@ -158,7 +159,7 @@ class EpubBuilder(StandaloneHTMLBuilder): guide_titles = GUIDE_TITLES media_types = MEDIA_TYPES refuri_re = REFURI_RE - template_dir = '' + template_dir: _StrPath = _StrPath() doctype = '' def init(self) -> None: @@ -417,7 +418,7 @@ def copy_image_files_pil(self) -> None: The method tries to read and write the files with Pillow, converting the format and resizing the image if necessary/possible. """ - ensuredir(os.path.join(self.outdir, self.imagedir)) + ensuredir(self.outdir / self.imagedir) for src in status_iterator( self.images, __('copying images... '), @@ -427,12 +428,12 @@ def copy_image_files_pil(self) -> None: ): dest = self.images[src] try: - img = Image.open(os.path.join(self.srcdir, src)) + img = Image.open(self.srcdir / src) except OSError: if not self.is_vector_graphics(src): logger.warning( __('cannot read image file %r: copying it instead'), - os.path.join(self.srcdir, src), + self.srcdir / src, ) try: copyfile( @@ -443,7 +444,7 @@ def copy_image_files_pil(self) -> None: except OSError as err: logger.warning( __('cannot copy image file %r: %s'), - os.path.join(self.srcdir, src), + self.srcdir / src, err, ) continue @@ -459,11 +460,11 @@ def copy_image_files_pil(self) -> None: nh = round((height * nw) / width) img = img.resize((nw, nh), Image.BICUBIC) try: - img.save(os.path.join(self.outdir, self.imagedir, dest)) + img.save(self.outdir / self.imagedir / dest) except OSError as err: logger.warning( __('cannot write image file %r: %s'), - os.path.join(self.srcdir, src), + self.srcdir / src, err, ) @@ -511,7 +512,7 @@ def build_mimetype(self) -> None: """Write the metainfo file mimetype.""" logger.info(__('writing mimetype file...')) copyfile( - os.path.join(self.template_dir, 'mimetype'), + self.template_dir / 'mimetype', self.outdir / 'mimetype', force=True, ) @@ -522,7 +523,7 @@ def build_container(self, outname: str = 'META-INF/container.xml') -> None: outdir = self.outdir / 'META-INF' ensuredir(outdir) copyfile( - os.path.join(self.template_dir, 'container.xml'), + self.template_dir / 'container.xml', outdir / 'container.xml', force=True, ) @@ -577,9 +578,10 @@ def build_content(self) -> None: if not self.use_index: self.ignored_files.append('genindex' + self.out_suffix) for root, dirs, files in os.walk(self.outdir): + root_path = Path(root) dirs.sort() for fn in sorted(files): - filename = relpath(os.path.join(root, fn), self.outdir) + filename = relpath(root_path / fn, self.outdir) if filename in self.ignored_files: continue ext = os.path.splitext(filename)[-1] @@ -684,7 +686,7 @@ def build_content(self) -> None: # write the project file copy_asset_file( - os.path.join(self.template_dir, 'content.opf.jinja'), + self.template_dir / 'content.opf.jinja', self.outdir, context=metadata, force=True, @@ -778,7 +780,7 @@ def build_toc(self) -> None: level = max(item['level'] for item in self.refnodes) level = min(level, self.config.epub_tocdepth) copy_asset_file( - os.path.join(self.template_dir, 'toc.ncx.jinja'), + self.template_dir / 'toc.ncx.jinja', self.outdir, context=self.toc_metadata(level, navpoints), force=True, @@ -792,10 +794,10 @@ def build_epub(self) -> None: """ outname = self.config.epub_basename + '.epub' logger.info(__('writing %s file...'), outname) - epub_filename = os.path.join(self.outdir, outname) + epub_filename = self.outdir / outname with ZipFile(epub_filename, 'w', ZIP_DEFLATED) as epub: - epub.write(os.path.join(self.outdir, 'mimetype'), 'mimetype', ZIP_STORED) + epub.write(self.outdir / 'mimetype', 'mimetype', ZIP_STORED) for filename in ('META-INF/container.xml', 'content.opf', 'toc.ncx'): - epub.write(os.path.join(self.outdir, filename), filename, ZIP_DEFLATED) + epub.write(self.outdir / filename, filename, ZIP_DEFLATED) for filename in self.files: - epub.write(os.path.join(self.outdir, filename), filename, ZIP_DEFLATED) + epub.write(self.outdir / filename, filename, ZIP_DEFLATED) diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index 585bdf3f748..7edf9c39c03 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -4,6 +4,7 @@ import html import os.path +from pathlib import Path from typing import TYPE_CHECKING from sphinx import package_dir @@ -13,7 +14,7 @@ from sphinx.theming import HTMLThemeFactory from sphinx.util import logging from sphinx.util.fileutil import copy_asset_file -from sphinx.util.osutil import ensuredir, os_path +from sphinx.util.osutil import ensuredir if TYPE_CHECKING: from collections.abc import Set @@ -103,9 +104,9 @@ def write_documents(self, _docnames: Set[str]) -> None: 'show_copyright': self.config.html_show_copyright, 'show_sphinx': self.config.html_show_sphinx, } - with open(os.path.join(self.outdir, 'index.html'), 'w', encoding='utf8') as f: + with open(self.outdir / 'index.html', 'w', encoding='utf8') as f: f.write(self.templates.render('changes/frameset.html', ctx)) - with open(os.path.join(self.outdir, 'changes.html'), 'w', encoding='utf8') as f: + with open(self.outdir / 'changes.html', 'w', encoding='utf8') as f: f.write(self.templates.render('changes/versionchanges.html', ctx)) hltext = [ @@ -141,7 +142,7 @@ def hl(no: int, line: str) -> str: 'text': text, } rendered = self.templates.render('changes/rstsource.html', ctx) - targetfn = os.path.join(self.outdir, 'rst', os_path(docname)) + '.html' + targetfn = self.outdir / 'rst' / f'{docname}.html' ensuredir(os.path.dirname(targetfn)) with open(targetfn, 'w', encoding='utf-8') as f: f.write(rendered) @@ -149,16 +150,14 @@ def hl(no: int, line: str) -> str: 'theme_' + key: val for (key, val) in self.theme.get_options({}).items() } copy_asset_file( - os.path.join( - package_dir, 'themes', 'default', 'static', 'default.css.jinja' - ), + Path(package_dir, 'themes', 'default', 'static', 'default.css.jinja'), self.outdir, context=themectx, renderer=self.templates, force=True, ) copy_asset_file( - os.path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'), + Path(package_dir, 'themes', 'basic', 'static', 'basic.css'), self.outdir / 'basic.css', force=True, ) diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index 23528adcf18..ac9ac85a754 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -17,6 +17,7 @@ from sphinx.config import ENUM from sphinx.locale import __ from sphinx.util import logging +from sphinx.util._pathlib import _StrPath from sphinx.util.fileutil import copy_asset_file from sphinx.util.osutil import make_filename @@ -84,7 +85,7 @@ class Epub3Builder(_epub_base.EpubBuilder): epilog = __('The ePub file is in %(outdir)s.') supported_remote_images = False - template_dir = os.path.join(package_dir, 'templates', 'epub3') + template_dir = _StrPath(package_dir, 'templates', 'epub3') doctype = DOCTYPE html_tag = HTML_TAG use_meta_charset = True @@ -199,7 +200,7 @@ def build_navigation_doc(self) -> None: refnodes = self.refnodes navlist = self.build_navlist(refnodes) copy_asset_file( - os.path.join(self.template_dir, 'nav.xhtml.jinja'), + self.template_dir / 'nav.xhtml.jinja', self.outdir, context=self.navigation_doc_metadata(navlist), force=True, diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 2976483d55a..1d581afb655 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -209,11 +209,11 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None: ctime = time.strftime('%Y-%m-%d %H:%M%z', timestamp) -def should_write(filepath: str, new_content: str) -> bool: +def should_write(filepath: str | os.PathLike[str], new_content: str) -> bool: if not os.path.exists(filepath): return True try: - with codecs.open(filepath, encoding='utf-8') as oldpot: + with codecs.open(str(filepath), encoding='utf-8') as oldpot: old_content = oldpot.read() old_header_index = old_content.index('"POT-Creation-Date:') new_header_index = new_content.index('"POT-Creation-Date:') @@ -252,11 +252,11 @@ def init(self) -> None: def _collect_templates(self) -> set[str]: template_files = set() for template_path in self.config.templates_path: - tmpl_abs_path = os.path.join(self.app.srcdir, template_path) + tmpl_abs_path = self.app.srcdir / template_path for dirpath, _dirs, files in walk(tmpl_abs_path): for fn in files: if fn.endswith('.html'): - filename = canon_path(os.path.join(dirpath, fn)) + filename = Path(dirpath, fn).as_posix() template_files.add(filename) return template_files @@ -312,7 +312,7 @@ def finish(self) -> None: operator.itemgetter(0), ): # noop if config.gettext_compact is set - ensuredir(os.path.join(self.outdir, os.path.dirname(textdomain))) + ensuredir(self.outdir / os.path.dirname(textdomain)) context['messages'] = list(catalog) template_path = [ @@ -321,9 +321,9 @@ def finish(self) -> None: renderer = GettextRenderer(template_path, outdir=self.outdir) content = renderer.render('message.pot.jinja', context) - pofn = os.path.join(self.outdir, textdomain + '.pot') + pofn = self.outdir / f'{textdomain}.pot' if should_write(pofn, content): - with codecs.open(pofn, 'w', encoding='utf-8') as pofile: + with codecs.open(str(pofn), 'w', encoding='utf-8') as pofile: pofile.write(content) diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 2bec3c010dc..27a4767a5f6 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -277,7 +277,7 @@ def init_multilingual(self) -> None: def write_stylesheet(self) -> None: highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style) - stylesheet = os.path.join(self.outdir, 'sphinxhighlight.sty') + stylesheet = self.outdir / 'sphinxhighlight.sty' with open(stylesheet, 'w', encoding='utf-8') as f: f.write('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n') f.write( @@ -318,7 +318,7 @@ def write_documents(self, _docnames: Set[str]) -> None: if len(entry) > 5: toctree_only = entry[5] destination = SphinxFileOutput( - destination_path=os.path.join(self.outdir, targetname), + destination_path=self.outdir / targetname, encoding='utf-8', overwrite_if_changed=True, ) @@ -444,11 +444,11 @@ def copy_support_files(self) -> None: 'xindy_lang_option': xindy_lang_option, 'xindy_cyrillic': xindy_cyrillic, } - staticdirname = os.path.join(package_dir, 'texinputs') - for filename in Path(staticdirname).iterdir(): + static_dir_name = Path(package_dir, 'texinputs') + for filename in Path(static_dir_name).iterdir(): if not filename.name.startswith('.'): copy_asset_file( - os.path.join(staticdirname, filename), + static_dir_name / filename, self.outdir, context=context, force=True, @@ -456,9 +456,9 @@ def copy_support_files(self) -> None: # use pre-1.6.x Makefile for make latexpdf on Windows if os.name == 'nt': - staticdirname = os.path.join(package_dir, 'texinputs_win') + static_dir_name = Path(package_dir, 'texinputs_win') copy_asset_file( - os.path.join(staticdirname, 'Makefile.jinja'), + static_dir_name / 'Makefile.jinja', self.outdir, context=context, force=True, @@ -496,11 +496,11 @@ def copy_image_files(self) -> None: except Exception as err: logger.warning( __('cannot copy image file %r: %s'), - os.path.join(self.srcdir, src), + self.srcdir / src, err, ) if self.config.latex_logo: - if not os.path.isfile(os.path.join(self.confdir, self.config.latex_logo)): + if not os.path.isfile(self.confdir / self.config.latex_logo): raise SphinxError( __('logo file %r does not exist') % self.config.latex_logo ) @@ -523,11 +523,8 @@ def write_message_catalog(self) -> None: if self.context['babel'] or self.context['polyglossia']: context['addtocaptions'] = r'\addto\captions%s' % self.babel.get_language() - filename = os.path.join( - package_dir, 'templates', 'latex', 'sphinxmessages.sty.jinja' - ) copy_asset_file( - filename, + Path(package_dir, 'templates', 'latex', 'sphinxmessages.sty.jinja'), self.outdir, context=context, renderer=LaTeXRenderer(), diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index de0540127df..f55c077c9ca 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -3,7 +3,6 @@ from __future__ import annotations import configparser -import os.path from typing import TYPE_CHECKING from sphinx.errors import ThemeError @@ -11,6 +10,8 @@ from sphinx.util import logging if TYPE_CHECKING: + from pathlib import Path + from sphinx.application import Sphinx from sphinx.config import Config @@ -74,10 +75,10 @@ class UserTheme(Theme): REQUIRED_CONFIG_KEYS = ['docclass', 'wrapperclass'] OPTIONAL_CONFIG_KEYS = ['papersize', 'pointsize', 'toplevel_sectioning'] - def __init__(self, name: str, filename: str) -> None: + def __init__(self, name: str, filename: Path) -> None: super().__init__(name) self.config = configparser.RawConfigParser() - self.config.read(os.path.join(filename), encoding='utf-8') + self.config.read(filename, encoding='utf-8') for key in self.REQUIRED_CONFIG_KEYS: try: @@ -103,9 +104,7 @@ class ThemeFactory: def __init__(self, app: Sphinx) -> None: self.themes: dict[str, Theme] = {} - self.theme_paths = [ - os.path.join(app.srcdir, p) for p in app.config.latex_theme_path - ] + self.theme_paths = [app.srcdir / p for p in app.config.latex_theme_path] self.config = app.config self.load_builtin_themes(app.config) @@ -127,8 +126,8 @@ def get(self, name: str) -> Theme: def find_user_theme(self, name: str) -> Theme | None: """Find a theme named as *name* from latex_theme_path.""" for theme_path in self.theme_paths: - config_path = os.path.join(theme_path, name, 'theme.conf') - if os.path.isfile(config_path): + config_path = theme_path / name / 'theme.conf' + if config_path.is_file(): try: return UserTheme(name, config_path) except ThemeError as exc: diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 4fa8e0933b2..31a130f3947 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -4,7 +4,6 @@ import contextlib import json -import os.path import re import socket import time @@ -88,8 +87,8 @@ def finish(self) -> None: checker = HyperlinkAvailabilityChecker(self.config) logger.info('') - output_text = os.path.join(self.outdir, 'output.txt') - output_json = os.path.join(self.outdir, 'output.json') + output_text = self.outdir / 'output.txt' + output_json = self.outdir / 'output.json' with ( open(output_text, 'w', encoding='utf-8') as self.txt_outfile, open(output_json, 'w', encoding='utf-8') as self.json_outfile, @@ -460,8 +459,7 @@ def _check(self, docname: str, uri: str, hyperlink: Hyperlink) -> _URIProperties # Non-supported URI schemes (ex. ftp) return _Status.UNCHECKED, '', 0 - src_dir = os.path.dirname(hyperlink.docpath) - if os.path.exists(os.path.join(src_dir, uri)): + if (hyperlink.docpath.parent / uri).exists(): return _Status.WORKING, '', 0 return _Status.BROKEN, '', 0 diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index a60a8bb9c28..5136124ca44 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os.path import warnings from typing import TYPE_CHECKING @@ -86,14 +85,14 @@ def write_documents(self, _docnames: Set[str]) -> None: if self.config.man_make_section_directory: dirname = 'man%s' % section - ensuredir(os.path.join(self.outdir, dirname)) + ensuredir(self.outdir / dirname) targetname = f'{dirname}/{name}.{section}' else: targetname = f'{name}.{section}' logger.info('%s { ', darkgreen(targetname)) destination = FileOutput( - destination_path=os.path.join(self.outdir, targetname), + destination_path=self.outdir / targetname, encoding='utf-8', ) diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 761831e0d1d..f2d8c060745 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -5,6 +5,7 @@ import os import os.path import warnings +from pathlib import Path from typing import TYPE_CHECKING from docutils import nodes @@ -35,7 +36,7 @@ from sphinx.util.typing import ExtensionMetadata logger = logging.getLogger(__name__) -template_dir = os.path.join(package_dir, 'templates', 'texinfo') +template_dir = Path(package_dir, 'templates', 'texinfo') class TexinfoBuilder(Builder): @@ -108,7 +109,7 @@ def write_documents(self, _docnames: Set[str]) -> None: if len(entry) > 7: toctree_only = entry[7] destination = FileOutput( - destination_path=os.path.join(self.outdir, targetname), + destination_path=self.outdir / targetname, encoding='utf-8', ) with progress_message(__('processing %s') % targetname, nonl=False): @@ -216,7 +217,7 @@ def copy_image_files(self, targetname: str) -> None: except Exception as err: logger.warning( __('cannot copy image file %r: %s'), - os.path.join(self.srcdir, src), + self.srcdir / src, err, ) @@ -225,7 +226,7 @@ def copy_support_files(self) -> None: with progress_message(__('copying Texinfo support files')): logger.info('Makefile ', nonl=True) copyfile( - os.path.join(template_dir, 'Makefile'), + template_dir / 'Makefile', self.outdir / 'Makefile', force=True, ) diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py index 03e22d2c73e..75be377cabd 100644 --- a/sphinx/builders/text.py +++ b/sphinx/builders/text.py @@ -10,11 +10,7 @@ from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.osutil import ( - _last_modified_time, - ensuredir, - os_path, -) +from sphinx.util.osutil import _last_modified_time, ensuredir from sphinx.writers.text import TextTranslator, TextWriter if TYPE_CHECKING: @@ -48,7 +44,7 @@ def get_outdated_docs(self) -> Iterator[str]: if docname not in self.env.all_docs: yield docname continue - targetname = os.path.join(self.outdir, docname + self.out_suffix) + targetname = self.outdir / (docname + self.out_suffix) try: targetmtime = _last_modified_time(targetname) except Exception: @@ -72,7 +68,7 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None: self.secnumbers = self.env.toc_secnumbers.get(docname, {}) destination = StringOutput(encoding='utf-8') self.writer.write(doctree, destination) - outfilename = os.path.join(self.outdir, os_path(docname) + self.out_suffix) + outfilename = self.outdir / (docname + self.out_suffix) ensuredir(os.path.dirname(outfilename)) try: with open(outfilename, 'w', encoding='utf-8') as f: diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py index e50d6f2b02d..b1204b8495f 100644 --- a/sphinx/builders/xml.py +++ b/sphinx/builders/xml.py @@ -12,11 +12,7 @@ from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.osutil import ( - _last_modified_time, - ensuredir, - os_path, -) +from sphinx.util.osutil import _last_modified_time, ensuredir from sphinx.writers.xml import PseudoXMLWriter, XMLWriter if TYPE_CHECKING: @@ -50,7 +46,7 @@ def get_outdated_docs(self) -> Iterator[str]: if docname not in self.env.all_docs: yield docname continue - targetname = os.path.join(self.outdir, docname + self.out_suffix) + targetname = self.outdir / (docname + self.out_suffix) try: targetmtime = _last_modified_time(targetname) except Exception: @@ -86,7 +82,7 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None: value[i] = list(val) destination = StringOutput(encoding='utf-8') self.writer.write(doctree, destination) - outfilename = os.path.join(self.outdir, os_path(docname) + self.out_suffix) + outfilename = self.outdir / (docname + self.out_suffix) ensuredir(os.path.dirname(outfilename)) try: with open(outfilename, 'w', encoding='utf-8') as f: diff --git a/sphinx/ext/apidoc/_generate.py b/sphinx/ext/apidoc/_generate.py index 8c6da962af3..8099eb8ada4 100644 --- a/sphinx/ext/apidoc/_generate.py +++ b/sphinx/ext/apidoc/_generate.py @@ -33,7 +33,7 @@ PY_SUFFIXES = ('.py', '.pyx', *EXTENSION_SUFFIXES) -template_dir = os.path.join(package_dir, 'templates', 'apidoc') +template_dir = Path(package_dir, 'templates', 'apidoc') def is_initpy(filename: str | Path) -> bool: @@ -84,7 +84,7 @@ def create_module_file( package: str | None, basename: str, opts: ApidocOptions, - user_template_dir: str | None = None, + user_template_dir: str | os.PathLike[str] | None = None, ) -> Path: """Build the text of the file and write the file.""" options = set(OPTIONS if not opts.automodule_options else opts.automodule_options) @@ -98,6 +98,7 @@ def create_module_file( 'qualname': qualname, 'automodule_options': sorted(options), } + template_path: Sequence[str | os.PathLike[str]] if user_template_dir is not None: template_path = [user_template_dir, template_dir] else: @@ -115,7 +116,7 @@ def create_package_file( subs: list[str], is_namespace: bool, excludes: Sequence[re.Pattern[str]] = (), - user_template_dir: str | None = None, + user_template_dir: str | os.PathLike[str] | None = None, ) -> list[Path]: """Build the text of the file and write the file. @@ -178,7 +179,7 @@ def create_modules_toc_file( modules: list[str], opts: ApidocOptions, name: str = 'modules', - user_template_dir: str | None = None, + user_template_dir: str | os.PathLike[str] | None = None, ) -> Path: """Create the module's index.""" modules.sort() @@ -195,6 +196,7 @@ def create_modules_toc_file( 'maxdepth': opts.maxdepth, 'docnames': modules, } + template_path: Sequence[str | os.PathLike[str]] if user_template_dir is not None: template_path = [user_template_dir, template_dir] else: @@ -274,7 +276,7 @@ def recurse_tree( rootpath: str | os.PathLike[str], excludes: Sequence[re.Pattern[str]], opts: ApidocOptions, - user_template_dir: str | None = None, + user_template_dir: str | os.PathLike[str] | None = None, ) -> tuple[list[Path], list[str]]: """Look for every file in the directory tree and create the corresponding ReST files. diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 617fc495be8..24f014d8cf6 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -133,9 +133,7 @@ def __init__(self, app: Sphinx) -> None: msg = 'Expected a Sphinx application object!' raise TypeError(msg) - system_templates_path = [ - os.path.join(package_dir, 'ext', 'autosummary', 'templates') - ] + system_templates_path = [Path(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader( app.srcdir, app.config.templates_path, system_templates_path ) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 4911458e319..ebb1c1f5bb0 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -176,8 +176,8 @@ class CoverageBuilder(Builder): def init(self) -> None: self.c_sourcefiles: list[str] = [] for pattern in self.config.coverage_c_path: - pattern = os.path.join(self.srcdir, pattern) - self.c_sourcefiles.extend(glob.glob(pattern)) # NoQA: PTH207 + pattern = self.srcdir / pattern + self.c_sourcefiles.extend(glob.glob(str(pattern))) # NoQA: PTH207 self.c_regexes: list[tuple[str, re.Pattern[str]]] = [] for name, exp in self.config.coverage_c_regexes.items(): @@ -244,7 +244,7 @@ def build_c_coverage(self) -> None: self.c_undoc[filename] = undoc def write_c_coverage(self) -> None: - output_file = os.path.join(self.outdir, 'c.txt') + output_file = self.outdir / 'c.txt' with open(output_file, 'w', encoding='utf-8') as op: if self.config.coverage_write_headline: write_header(op, 'Undocumented C API elements', '=') @@ -419,7 +419,7 @@ def _write_py_statistics(self, op: TextIO) -> None: op.write(f'{line}\n') def write_py_coverage(self) -> None: - output_file = os.path.join(self.outdir, 'python.txt') + output_file = self.outdir / 'python.txt' failed = [] with open(output_file, 'w', encoding='utf-8') as op: if self.config.coverage_write_headline: @@ -513,7 +513,7 @@ def write_py_coverage(self) -> None: def finish(self) -> None: # dump the coverage data to a pickle file too - picklepath = os.path.join(self.outdir, 'undoc.pickle') + picklepath = self.outdir / 'undoc.pickle' with open(picklepath, 'wb') as dumpfile: pickle.dump( (self.py_undoc, self.c_undoc, self.py_undocumented, self.py_documented), diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 107872e476d..9bbd8ed0a62 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -3,12 +3,12 @@ from __future__ import annotations import os.path -import posixpath import re import subprocess import xml.etree.ElementTree as ET from hashlib import sha1 from itertools import chain +from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING from urllib.parse import urlsplit, urlunsplit @@ -20,6 +20,7 @@ from sphinx.errors import SphinxError from sphinx.locale import _, __ from sphinx.util import logging +from sphinx.util._pathlib import _StrPath from sphinx.util.docutils import SphinxDirective from sphinx.util.i18n import search_image_for_language from sphinx.util.nodes import set_source_info @@ -235,7 +236,8 @@ def run(self) -> list[Node]: def fix_svg_relative_paths( - self: HTML5Translator | LaTeXTranslator | TexinfoTranslator, filepath: str + self: HTML5Translator | LaTeXTranslator | TexinfoTranslator, + filepath: str | os.PathLike[str], ) -> None: """Change relative links in generated svg files to be relative to imgpath.""" env = self.builder.env @@ -279,7 +281,7 @@ def render_dot( format: str, prefix: str = 'graphviz', filename: str | None = None, -) -> tuple[str | None, str | None]: +) -> tuple[_StrPath | None, _StrPath | None]: """Render graphviz code into a PNG or PDF output file.""" graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot) if not graphviz_dot: @@ -294,8 +296,8 @@ def render_dot( )).encode() fname = f'{prefix}-{sha1(hashkey, usedforsecurity=False).hexdigest()}.{format}' - relfn = posixpath.join(self.builder.imgpath, fname) - outfn = os.path.join(self.builder.outdir, self.builder.imagedir, fname) + relfn = _StrPath(self.builder.imgpath, fname) + outfn = self.builder.outdir / self.builder.imagedir / fname if os.path.isfile(outfn): return relfn, outfn @@ -307,13 +309,13 @@ def render_dot( dot_args = [graphviz_dot] dot_args.extend(self.builder.config.graphviz_dot_args) - dot_args.extend(['-T' + format, '-o' + outfn]) + dot_args.extend([f'-T{format}', f'-o{outfn}']) docname = options.get('docname', 'index') if filename: - cwd = os.path.dirname(os.path.join(self.builder.srcdir, filename)) + cwd = os.path.dirname(self.builder.srcdir / filename) else: - cwd = os.path.dirname(os.path.join(self.builder.srcdir, docname)) + cwd = os.path.dirname(self.builder.srcdir / docname) if format == 'png': dot_args.extend(['-Tcmapx', f'-o{outfn}.map']) @@ -379,6 +381,7 @@ def render_dot_html( if fname is None: self.body.append(self.encode(code)) else: + src = fname.as_posix() if alt is None: alt = node.get('alt', self.encode(code).strip()) if 'align' in node: @@ -387,7 +390,7 @@ def render_dot_html( if format == 'svg': self.body.append('
') self.body.append( - f'\n' + f'\n' ) self.body.append(f'

{alt}

') self.body.append('
\n') @@ -400,14 +403,14 @@ def render_dot_html( # has a map self.body.append('
') self.body.append( - f'{alt}' + f'{alt}' ) self.body.append('
\n') self.body.append(imgmap.generate_clickable_map()) else: # nothing in image map self.body.append('
') - self.body.append(f'{alt}') + self.body.append(f'{alt}') self.body.append('
\n') if 'align' in node: self.body.append('\n') @@ -504,8 +507,8 @@ def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None: def on_config_inited(_app: Sphinx, config: Config) -> None: - css_path = os.path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css') - config.html_static_path.append(css_path) + css_path = Path(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css') + config.html_static_path.append(str(css_path)) def setup(app: Sphinx) -> ExtensionMetadata: diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index 16b5967b970..9e7218b2157 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -14,6 +14,8 @@ from sphinx.util import logging if TYPE_CHECKING: + import os + from sphinx.application import Sphinx from sphinx.util.typing import ExtensionMetadata @@ -57,17 +59,19 @@ def is_available(self) -> bool: ) return False - def convert(self, _from: str, _to: str) -> bool: + def convert( + self, _from: str | os.PathLike[str], _to: str | os.PathLike[str] + ) -> bool: """Converts the image to expected one.""" try: # append an index 0 to source filename to pick up the first frame # (or first page) of image (ex. Animation GIF, PDF) - _from += '[0]' + from_ = f'{_from}[0]' args = [ self.config.image_converter, *self.config.image_converter_args, - _from, + from_, _to, ] logger.debug('Invoking %r ...', args) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index 168417ae394..3b379bc52cf 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -13,6 +13,7 @@ import subprocess import tempfile from hashlib import sha1 +from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING @@ -34,12 +35,13 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config + from sphinx.util._pathlib import _StrPath from sphinx.util.typing import ExtensionMetadata from sphinx.writers.html5 import HTML5Translator logger = logging.getLogger(__name__) -templates_path = os.path.join(package_dir, 'templates', 'imgmath') +templates_path = Path(package_dir, 'templates', 'imgmath') class MathExtError(SphinxError): @@ -66,7 +68,7 @@ class InvokeError(SphinxError): depthsvgcomment_re = re.compile(r'') -def read_svg_depth(filename: str) -> int | None: +def read_svg_depth(filename: str | os.PathLike[str]) -> int | None: """Read the depth from comment at last line of SVG file""" with open(filename, encoding='utf-8') as f: for line in f: # NoQA: B007 @@ -78,7 +80,7 @@ def read_svg_depth(filename: str) -> int | None: return None -def write_svg_depth(filename: str, depth: int) -> None: +def write_svg_depth(filename: Path, depth: int) -> None: """Write the depth to SVG file as a comment at end of file""" with open(filename, 'a', encoding='utf-8') as f: f.write('\n' % depth) @@ -88,7 +90,7 @@ def generate_latex_macro( image_format: str, math: str, config: Config, - confdir: str | os.PathLike[str] = '', + confdir: _StrPath, ) -> str: """Generate LaTeX macro.""" variables = { @@ -108,16 +110,14 @@ def generate_latex_macro( for template_dir in config.templates_path: for template_suffix in ('.jinja', '_t'): - template = os.path.join( - confdir, template_dir, template_name + template_suffix - ) - if os.path.exists(template): + template = confdir / template_dir / (template_name + template_suffix) + if template.exists(): return LaTeXRenderer().render(template, variables) - return LaTeXRenderer(templates_path).render(template_name + '.jinja', variables) + return LaTeXRenderer([templates_path]).render(template_name + '.jinja', variables) -def ensure_tempdir(builder: Builder) -> str: +def ensure_tempdir(builder: Builder) -> Path: """Create temporary directory. use only one tempdir per build -- the use of a directory is cleaner @@ -125,15 +125,15 @@ def ensure_tempdir(builder: Builder) -> str: just removing the whole directory (see cleanup_tempdir) """ if not hasattr(builder, '_imgmath_tempdir'): - builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore[attr-defined] + builder._imgmath_tempdir = Path(tempfile.mkdtemp()) # type: ignore[attr-defined] return builder._imgmath_tempdir # type: ignore[attr-defined] -def compile_math(latex: str, builder: Builder) -> str: +def compile_math(latex: str, builder: Builder) -> Path: """Compile LaTeX macros for math to DVI.""" tempdir = ensure_tempdir(builder) - filename = os.path.join(tempdir, 'math.tex') + filename = tempdir / 'math.tex' with open(filename, 'w', encoding='utf-8') as f: f.write(latex) @@ -154,9 +154,9 @@ def compile_math(latex: str, builder: Builder) -> str: command, capture_output=True, cwd=tempdir, check=True, encoding='ascii' ) if imgmath_latex_name in {'xelatex', 'tectonic'}: - return os.path.join(tempdir, 'math.xdv') + return tempdir / 'math.xdv' else: - return os.path.join(tempdir, 'math.dvi') + return tempdir / 'math.dvi' except OSError as exc: logger.warning( __( @@ -192,7 +192,7 @@ def convert_dvi_to_image(command: list[str], name: str) -> tuple[str, str]: raise MathExtError(msg, exc.stderr, exc.stdout) from exc -def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | None: +def convert_dvi_to_png(dvipath: Path, builder: Builder, out_path: Path) -> int | None: """Convert DVI file to PNG image.""" name = 'dvipng' command = [builder.config.imgmath_dvipng, '-o', out_path, '-T', 'tight', '-z9'] @@ -215,7 +215,7 @@ def convert_dvi_to_png(dvipath: str, builder: Builder, out_path: str) -> int | N return depth -def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> int | None: +def convert_dvi_to_svg(dvipath: Path, builder: Builder, out_path: Path) -> int | None: """Convert DVI file to SVG image.""" name = 'dvisvgm' command = [builder.config.imgmath_dvisvgm, '-o', out_path] @@ -239,7 +239,7 @@ def convert_dvi_to_svg(dvipath: str, builder: Builder, out_path: str) -> int | N def render_math( self: HTML5Translator, math: str, -) -> tuple[str | None, int | None]: +) -> tuple[_StrPath | None, int | None]: """Render the LaTeX math expression *math* using latex and dvipng or dvisvgm. @@ -265,11 +265,9 @@ def render_math( filename = ( f'{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}' ) - generated_path = os.path.join( - self.builder.outdir, self.builder.imagedir, 'math', filename - ) + generated_path = self.builder.outdir / self.builder.imagedir / 'math' / filename ensuredir(os.path.dirname(generated_path)) - if os.path.isfile(generated_path): + if generated_path.is_file(): if image_format == 'png': depth = read_png_depth(generated_path) elif image_format == 'svg': @@ -302,7 +300,7 @@ def render_math( return generated_path, depth -def render_maths_to_base64(image_format: str, generated_path: str) -> str: +def render_maths_to_base64(image_format: str, generated_path: Path) -> str: with open(generated_path, 'rb') as f: content = f.read() encoded = base64.b64encode(content).decode(encoding='utf-8') @@ -326,9 +324,7 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None: # in embed mode, the images are still generated in the math output dir # to be shared across workers, but are not useful to the final document with contextlib.suppress(Exception): - shutil.rmtree( - os.path.join(app.builder.outdir, app.builder.imagedir, 'math') - ) + shutil.rmtree(app.builder.outdir / app.builder.imagedir / 'math') def get_tooltip(self: HTML5Translator, node: Element) -> str: @@ -360,8 +356,8 @@ def html_visit_math(self: HTML5Translator, node: nodes.math) -> None: img_src = render_maths_to_base64(image_format, rendered_path) else: bname = os.path.basename(rendered_path) - relative_path = os.path.join(self.builder.imgpath, 'math', bname) - img_src = relative_path.replace(os.path.sep, '/') + relative_path = Path(self.builder.imgpath, 'math', bname) + img_src = relative_path.as_posix() align = f' style="vertical-align: {-depth:d}px"' if depth is not None else '' self.body.append( f'' @@ -403,8 +399,8 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non img_src = render_maths_to_base64(image_format, rendered_path) else: bname = os.path.basename(rendered_path) - relative_path = os.path.join(self.builder.imgpath, 'math', bname) - img_src = relative_path.replace(os.path.sep, '/') + relative_path = Path(self.builder.imgpath, 'math', bname) + img_src = relative_path.as_posix() self.body.append(f'

\n') raise nodes.SkipNode diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index b621c34e71c..79143ffaa67 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -4,7 +4,6 @@ import importlib.util import operator -import os.path import posixpath import traceback from typing import TYPE_CHECKING, cast @@ -259,7 +258,7 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: builder = cast('StandaloneHTMLBuilder', app.builder) basename = modname.replace('.', '/') + builder.out_suffix - page_filename = os.path.join(app.outdir, '_modules/', basename) + page_filename = app.outdir / '_modules' / basename try: if _last_modified_time(module_filename) <= _last_modified_time(page_filename): diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index e67c1b95da9..80476c8bfee 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -126,14 +126,14 @@ def get_source( else: legacy_template = None - for searchpath in self.searchpath: - filename = os.path.join(searchpath, template) - f = open_if_exists(filename) + for search_path in map(Path, self.searchpath): + filename = search_path / template + f = open_if_exists(str(filename)) if f is not None: break if legacy_template is not None: - filename = os.path.join(searchpath, legacy_template) - f = open_if_exists(filename) + filename = search_path / legacy_template + f = open_if_exists(str(filename)) if f is not None: break else: @@ -150,7 +150,7 @@ def uptodate() -> bool: except OSError: return False - return contents, filename, uptodate + return contents, str(filename), uptodate class BuiltinTemplateLoader(TemplateBridge, BaseLoader): diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 2001740f232..d4c6262e529 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -44,8 +44,8 @@ def handle(self, node: nodes.image) -> None: pass @property - def imagedir(self) -> str: - return os.path.join(self.app.doctreedir, 'images') + def imagedir(self) -> _StrPath: + return self.app.doctreedir / 'images' class ImageDownloader(BaseImageConverter): @@ -144,9 +144,9 @@ def handle(self, node: nodes.image) -> None: ) return - ensuredir(os.path.join(self.imagedir, 'embeded')) + ensuredir(self.imagedir / 'embeded') digest = sha1(image.data, usedforsecurity=False).hexdigest() - path = _StrPath(self.imagedir, 'embeded', digest + ext) + path = self.imagedir / 'embeded' / (digest + ext) self.env.original_image_uri[path] = node['uri'] with open(path, 'wb') as f: @@ -250,7 +250,7 @@ def guess_mimetypes(self, node: nodes.image) -> list[str]: if '?' in node['candidates']: return [] elif '*' in node['candidates']: - path = os.path.join(self.app.srcdir, node['uri']) + path = self.app.srcdir / node['uri'] guessed = guess_mimetype(path) return [guessed] if guessed is not None else [] else: @@ -267,20 +267,22 @@ def handle(self, node: nodes.image) -> None: filename = self.env.images[srcpath][1] filename = get_filename_for(filename, _to) ensuredir(self.imagedir) - destpath = os.path.join(self.imagedir, filename) + destpath = self.imagedir / filename - abs_srcpath = os.path.join(self.app.srcdir, srcpath) + abs_srcpath = self.app.srcdir / srcpath if self.convert(abs_srcpath, destpath): if '*' in node['candidates']: - node['candidates']['*'] = destpath + node['candidates']['*'] = str(destpath) else: - node['candidates'][_to] = destpath - node['uri'] = destpath + node['candidates'][_to] = str(destpath) + node['uri'] = str(destpath) - self.env.original_image_uri[_StrPath(destpath)] = srcpath + self.env.original_image_uri[destpath] = srcpath self.env.images.add_file(self.env.docname, destpath) - def convert(self, _from: str, _to: str) -> bool: + def convert( + self, _from: str | os.PathLike[str], _to: str | os.PathLike[str] + ) -> bool: """Convert an image file to the expected format. *_from* is a path of the source image file, and *_to* is a path diff --git a/sphinx/util/_files.py b/sphinx/util/_files.py index 0d351d8af6a..65313801be0 100644 --- a/sphinx/util/_files.py +++ b/sphinx/util/_files.py @@ -18,7 +18,8 @@ class FilenameUniqDict(dict[str, tuple[set[str], str]]): def __init__(self) -> None: self._existing: set[str] = set() - def add_file(self, docname: str, newfile: str) -> str: + def add_file(self, docname: str, newfile: str | os.PathLike[str]) -> str: + newfile = str(newfile) if newfile in self: self[newfile][0].add(docname) return self[newfile][1] diff --git a/sphinx/util/png.py b/sphinx/util/png.py index c90e27ec14e..c4d78162422 100644 --- a/sphinx/util/png.py +++ b/sphinx/util/png.py @@ -4,6 +4,10 @@ import binascii import struct +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import os LEN_IEND = 12 LEN_DEPTH = 22 @@ -13,7 +17,7 @@ IEND_CHUNK = b'\x00\x00\x00\x00IEND\xae\x42\x60\x82' -def read_png_depth(filename: str) -> int | None: +def read_png_depth(filename: str | os.PathLike[str]) -> int | None: """Read the special tEXt chunk indicating the depth from a PNG file.""" with open(filename, 'rb') as f: f.seek(-(LEN_IEND + LEN_DEPTH), 2) @@ -25,7 +29,7 @@ def read_png_depth(filename: str) -> int | None: return struct.unpack('!i', depthchunk[14:18])[0] -def write_png_depth(filename: str, depth: int) -> None: +def write_png_depth(filename: str | os.PathLike[str], depth: int) -> None: """Write the special tEXt chunk indicating the depth to a PNG file. The chunk is placed immediately before the special IEND chunk.