mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Use pathlib in `sphinx.project
`
This commit is contained in:
parent
932e5c56db
commit
de15d61a46
@ -184,6 +184,7 @@ nitpick_ignore = {
|
||||
('py:class', 'IndexEntry'), # sphinx.domains.IndexEntry
|
||||
('py:class', 'Node'), # sphinx.domains.Domain
|
||||
('py:class', 'NullTranslations'), # gettext.NullTranslations
|
||||
('py:class', 'Path'), # sphinx.environment.BuildEnvironment.doc2path
|
||||
('py:class', 'RoleFunction'), # sphinx.domains.Domain
|
||||
('py:class', 'RSTState'), # sphinx.utils.parsing.nested_parse_to_nodes
|
||||
('py:class', 'Theme'), # sphinx.application.TemplateBridge
|
||||
|
@ -519,7 +519,7 @@ class Builder:
|
||||
if path.isfile(docutilsconf):
|
||||
self.env.note_dependency(docutilsconf)
|
||||
|
||||
filename = self.env.doc2path(docname)
|
||||
filename = str(self.env.doc2path(docname))
|
||||
filetype = get_filetype(self.app.config.source_suffix, filename)
|
||||
publisher = self.app.registry.get_publisher(self.app, filetype)
|
||||
self.env.temp_data['_parser'] = publisher.parser
|
||||
|
@ -134,7 +134,7 @@ class ChangesBuilder(Builder):
|
||||
with open(targetfn, 'w', encoding='utf-8') as f:
|
||||
text = ''.join(hl(i + 1, line) for (i, line) in enumerate(lines))
|
||||
ctx = {
|
||||
'filename': self.env.doc2path(docname, False),
|
||||
'filename': str(self.env.doc2path(docname, False)),
|
||||
'text': text,
|
||||
}
|
||||
f.write(self.templates.render('changes/rstsource.html', ctx))
|
||||
|
@ -611,7 +611,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
title = self.render_partial(title_node)['title'] if title_node else ''
|
||||
|
||||
# Suffix for the document
|
||||
source_suffix = self.env.doc2path(docname, False)[len(docname):]
|
||||
source_suffix = str(self.env.doc2path(docname, False))[len(docname):]
|
||||
|
||||
# the name for the copied source
|
||||
if self.config.html_copy_source:
|
||||
@ -976,7 +976,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
def index_page(self, pagename: str, doctree: nodes.document, title: str) -> None:
|
||||
# only index pages with title
|
||||
if self.indexer is not None and title:
|
||||
filename = self.env.doc2path(pagename, base=False)
|
||||
filename = str(self.env.doc2path(pagename, base=False))
|
||||
metadata = self.env.metadata.get(pagename, {})
|
||||
if 'no-search' in metadata or 'nosearch' in metadata:
|
||||
self.indexer.feed(pagename, filename, '', new_document(''))
|
||||
|
@ -28,6 +28,7 @@ from sphinx.util.nodes import get_node_line
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from requests import Response
|
||||
@ -82,7 +83,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
||||
filename = self.env.doc2path(result.docname, False)
|
||||
|
||||
linkstat: dict[str, str | int] = {
|
||||
'filename': filename, 'lineno': result.lineno,
|
||||
'filename': str(filename), 'lineno': result.lineno,
|
||||
'status': result.status, 'code': result.code,
|
||||
'uri': result.uri, 'info': result.message,
|
||||
}
|
||||
@ -149,7 +150,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
||||
self.json_outfile.write(json.dumps(data))
|
||||
self.json_outfile.write('\n')
|
||||
|
||||
def write_entry(self, what: str, docname: str, filename: str, line: int,
|
||||
def write_entry(self, what: str, docname: str, filename: Path, line: int,
|
||||
uri: str) -> None:
|
||||
self.txt_outfile.write(f'{filename}:{line}: [{what}] {uri}\n')
|
||||
|
||||
@ -225,7 +226,7 @@ class HyperlinkCollector(SphinxPostTransform):
|
||||
class Hyperlink(NamedTuple):
|
||||
uri: str
|
||||
docname: str
|
||||
docpath: str
|
||||
docpath: Path
|
||||
lineno: int
|
||||
|
||||
|
||||
|
@ -145,7 +145,7 @@ class TocTree(SphinxDirective):
|
||||
continue
|
||||
|
||||
if docname not in frozen_all_docnames:
|
||||
if excluded(self.env.doc2path(docname, False)):
|
||||
if excluded(str(self.env.doc2path(docname, False))):
|
||||
message = __('toctree contains reference to excluded document %r')
|
||||
subtype = 'excluded'
|
||||
else:
|
||||
|
@ -413,13 +413,13 @@ class BuildEnvironment:
|
||||
"""
|
||||
return self.project.path2doc(filename)
|
||||
|
||||
def doc2path(self, docname: str, base: bool = True) -> str:
|
||||
def doc2path(self, docname: str, base: bool = True) -> Path:
|
||||
"""Return the filename for the document name.
|
||||
|
||||
If *base* is True, return absolute path under self.srcdir.
|
||||
If *base* is False, return relative path to self.srcdir.
|
||||
"""
|
||||
return self.project.doc2path(docname, base)
|
||||
return self.project.doc2path(docname, absolute=base)
|
||||
|
||||
def relfn2path(self, filename: str, docname: str | None = None) -> tuple[str, str]:
|
||||
"""Return paths to a file referenced from a document, relative to
|
||||
@ -628,7 +628,7 @@ class BuildEnvironment:
|
||||
|
||||
doctree = pickle.loads(serialised)
|
||||
doctree.settings.env = self
|
||||
doctree.reporter = LoggingReporter(self.doc2path(docname))
|
||||
doctree.reporter = LoggingReporter(str(self.doc2path(docname)))
|
||||
return doctree
|
||||
|
||||
@functools.cached_property
|
||||
@ -650,7 +650,7 @@ class BuildEnvironment:
|
||||
try:
|
||||
doctree = self._write_doc_doctree_cache.pop(docname)
|
||||
doctree.settings.env = self
|
||||
doctree.reporter = LoggingReporter(self.doc2path(docname))
|
||||
doctree.reporter = LoggingReporter(str(self.doc2path(docname)))
|
||||
except KeyError:
|
||||
doctree = self.get_doctree(docname)
|
||||
|
||||
|
@ -319,7 +319,7 @@ def _toctree_entry(
|
||||
ref, location=toctreenode, type='toc', subtype='no_title')
|
||||
except KeyError:
|
||||
# this is raised if the included file does not exist
|
||||
ref_path = env.doc2path(ref, False)
|
||||
ref_path = str(env.doc2path(ref, False))
|
||||
if excluded(ref_path):
|
||||
message = __('toctree contains reference to excluded document %r')
|
||||
elif not included(ref_path):
|
||||
|
@ -249,7 +249,7 @@ class Autosummary(SphinxDirective):
|
||||
docname = posixpath.join(tree_prefix, real_name)
|
||||
docname = posixpath.normpath(posixpath.join(dirname, docname))
|
||||
if docname not in self.env.found_docs:
|
||||
if excluded(self.env.doc2path(docname, False)):
|
||||
if excluded(str(self.env.doc2path(docname, False))):
|
||||
msg = __('autosummary references excluded document %r. Ignored.')
|
||||
else:
|
||||
msg = __('autosummary: stub file not found %r. '
|
||||
@ -802,7 +802,7 @@ def process_generate_options(app: Sphinx) -> None:
|
||||
|
||||
if genfiles is True:
|
||||
env = app.builder.env
|
||||
genfiles = [env.doc2path(x, base=False) for x in env.found_docs
|
||||
genfiles = [str(env.doc2path(x, base=False)) for x in env.found_docs
|
||||
if os.path.isfile(env.doc2path(x))]
|
||||
elif genfiles is False:
|
||||
pass
|
||||
|
@ -373,7 +373,7 @@ Doctest summary
|
||||
try:
|
||||
filename = relpath(node.source, self.env.srcdir).rsplit(':docstring of ', maxsplit=1)[0] # type: ignore[arg-type] # noqa: E501
|
||||
except Exception:
|
||||
filename = self.env.doc2path(docname, False)
|
||||
filename = str(self.env.doc2path(docname, False))
|
||||
return filename
|
||||
|
||||
@staticmethod
|
||||
|
@ -4,13 +4,13 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx.locale import __
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.matching import get_matching_files
|
||||
from sphinx.util.osutil import path_stabilize, relpath
|
||||
from sphinx.util.osutil import path_stabilize
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
@ -24,7 +24,7 @@ class Project:
|
||||
|
||||
def __init__(self, srcdir: str | os.PathLike[str], source_suffix: Iterable[str]) -> None:
|
||||
#: Source directory.
|
||||
self.srcdir = srcdir
|
||||
self.srcdir = Path(srcdir)
|
||||
|
||||
#: source_suffix. Same as :confval:`source_suffix`.
|
||||
self.source_suffix = tuple(source_suffix)
|
||||
@ -34,8 +34,8 @@ class Project:
|
||||
self.docnames: set[str] = set()
|
||||
|
||||
# Bijective mapping between docnames and (srcdir relative) paths.
|
||||
self._path_to_docname: dict[str, str] = {}
|
||||
self._docname_to_path: dict[str, str] = {}
|
||||
self._path_to_docname: dict[Path, str] = {}
|
||||
self._docname_to_path: dict[str, Path] = {}
|
||||
|
||||
def restore(self, other: Project) -> None:
|
||||
"""Take over a result of last build."""
|
||||
@ -60,22 +60,25 @@ class Project:
|
||||
):
|
||||
if docname := self.path2doc(filename):
|
||||
if docname in self.docnames:
|
||||
pattern = os.path.join(self.srcdir, docname) + '.*'
|
||||
files = [relpath(f, self.srcdir) for f in glob(pattern)]
|
||||
files = [
|
||||
str(f.relative_to(self.srcdir))
|
||||
for f in self.srcdir.glob(f'{docname}.*')
|
||||
]
|
||||
logger.warning(
|
||||
__(
|
||||
'multiple files found for the document "%s": %r\n'
|
||||
'multiple files found for the document "%s": %s\n'
|
||||
'Use %r for the build.'
|
||||
),
|
||||
docname,
|
||||
files,
|
||||
', '.join(files),
|
||||
self.doc2path(docname, absolute=True),
|
||||
once=True,
|
||||
)
|
||||
elif os.access(os.path.join(self.srcdir, filename), os.R_OK):
|
||||
elif os.access(self.srcdir / filename, os.R_OK):
|
||||
self.docnames.add(docname)
|
||||
self._path_to_docname[filename] = docname
|
||||
self._docname_to_path[docname] = filename
|
||||
path = Path(filename)
|
||||
self._path_to_docname[path] = docname
|
||||
self._docname_to_path[docname] = path
|
||||
else:
|
||||
logger.warning(
|
||||
__('Ignored unreadable document %r.'), filename, location=docname
|
||||
@ -91,18 +94,19 @@ class Project:
|
||||
try:
|
||||
return self._path_to_docname[filename] # type: ignore[index]
|
||||
except KeyError:
|
||||
if os.path.isabs(filename):
|
||||
path = Path(filename)
|
||||
if path.is_absolute():
|
||||
with contextlib.suppress(ValueError):
|
||||
filename = os.path.relpath(filename, self.srcdir)
|
||||
path = path.relative_to(self.srcdir)
|
||||
|
||||
for suffix in self.source_suffix:
|
||||
if os.path.basename(filename).endswith(suffix):
|
||||
return path_stabilize(filename).removesuffix(suffix)
|
||||
if path.name.endswith(suffix):
|
||||
return path_stabilize(path).removesuffix(suffix)
|
||||
|
||||
# the file does not have a docname
|
||||
return None
|
||||
|
||||
def doc2path(self, docname: str, absolute: bool) -> str:
|
||||
def doc2path(self, docname: str, absolute: bool) -> Path:
|
||||
"""Return the filename for the document name.
|
||||
|
||||
If *absolute* is True, return as an absolute path.
|
||||
@ -112,8 +116,8 @@ class Project:
|
||||
filename = self._docname_to_path[docname]
|
||||
except KeyError:
|
||||
# Backwards compatibility: the document does not exist
|
||||
filename = docname + self._first_source_suffix
|
||||
filename = Path(docname + self._first_source_suffix)
|
||||
|
||||
if absolute:
|
||||
return os.path.join(self.srcdir, filename)
|
||||
return self.srcdir / filename
|
||||
return filename
|
||||
|
@ -50,9 +50,9 @@ def docname_join(basedocname: str, docname: str) -> str:
|
||||
return posixpath.normpath(posixpath.join('/' + basedocname, '..', docname))[1:]
|
||||
|
||||
|
||||
def get_filetype(source_suffix: dict[str, str], filename: str) -> str:
|
||||
def get_filetype(source_suffix: dict[str, str], filename: str | os.PathLike) -> str:
|
||||
for suffix, filetype in source_suffix.items():
|
||||
if filename.endswith(suffix):
|
||||
if os.fspath(filename).endswith(suffix):
|
||||
# If default filetype (None), considered as restructuredtext.
|
||||
return filetype or 'restructuredtext'
|
||||
raise FiletypeNotFoundError
|
||||
|
@ -10,6 +10,7 @@ import time
|
||||
import wsgiref.handlers
|
||||
from base64 import b64encode
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest import mock
|
||||
@ -1061,7 +1062,7 @@ def test_connection_contention(get_adapter, app, capsys):
|
||||
wqueue: Queue[CheckRequest] = Queue()
|
||||
rqueue: Queue[CheckResult] = Queue()
|
||||
for _ in range(link_count):
|
||||
wqueue.put(CheckRequest(0, Hyperlink(f"http://{address}", "test", "test.rst", 1)))
|
||||
wqueue.put(CheckRequest(0, Hyperlink(f"http://{address}", "test", Path("test.rst"), 1)))
|
||||
|
||||
begin = time.time()
|
||||
checked: list[CheckResult] = []
|
||||
|
@ -14,7 +14,7 @@ def _doctree_for_test(builder, docname: str) -> nodes.document:
|
||||
builder.env.prepare_settings(docname)
|
||||
publisher = create_publisher(builder.app, 'restructuredtext')
|
||||
with sphinx_domains(builder.env):
|
||||
publisher.set_source(source_path=builder.env.doc2path(docname))
|
||||
publisher.set_source(source_path=str(builder.env.doc2path(docname)))
|
||||
publisher.publish()
|
||||
return publisher.document
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Tests project module."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@ -64,15 +65,15 @@ def test_project_doc2path(app):
|
||||
project.discover()
|
||||
|
||||
# absolute path
|
||||
assert project.doc2path('index', absolute=True) == str(app.srcdir / 'index.rst')
|
||||
assert project.doc2path('index', absolute=True) == app.srcdir / 'index.rst'
|
||||
|
||||
# relative path
|
||||
assert project.doc2path('index', absolute=False) == 'index.rst'
|
||||
assert project.doc2path('index', absolute=False) == Path('index.rst')
|
||||
|
||||
# first source_suffix is used for missing file
|
||||
assert project.doc2path('foo', absolute=False) == 'foo.rst'
|
||||
assert project.doc2path('foo', absolute=False) == Path('foo.rst')
|
||||
|
||||
# matched source_suffix is used if exists
|
||||
(app.srcdir / 'bar.txt').touch()
|
||||
project.discover()
|
||||
assert project.doc2path('bar', absolute=False) == 'bar.txt'
|
||||
assert project.doc2path('bar', absolute=False) == Path('bar.txt')
|
||||
|
Loading…
Reference in New Issue
Block a user