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