From cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:25:40 +0200 Subject: Adding upstream version 7.2.6. Signed-off-by: Daniel Baumann --- sphinx/project.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 sphinx/project.py (limited to 'sphinx/project.py') diff --git a/sphinx/project.py b/sphinx/project.py new file mode 100644 index 0000000..57813fa --- /dev/null +++ b/sphinx/project.py @@ -0,0 +1,112 @@ +"""Utility function and classes for Sphinx projects.""" + +from __future__ import annotations + +import contextlib +import os +from glob import glob +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 + +if TYPE_CHECKING: + from collections.abc import Iterable + +logger = logging.getLogger(__name__) +EXCLUDE_PATHS = ['**/_sources', '.#*', '**/.#*', '*.lproj/**'] + + +class Project: + """A project is the source code set of the Sphinx document(s).""" + + def __init__(self, srcdir: str | os.PathLike[str], source_suffix: Iterable[str]) -> None: + #: Source directory. + self.srcdir = srcdir + + #: source_suffix. Same as :confval:`source_suffix`. + self.source_suffix = tuple(source_suffix) + self._first_source_suffix = next(iter(self.source_suffix), "") + + #: The name of documents belonging to this 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] = {} + + def restore(self, other: Project) -> None: + """Take over a result of last build.""" + self.docnames = other.docnames + self._path_to_docname = other._path_to_docname + self._docname_to_path = other._docname_to_path + + def discover(self, exclude_paths: Iterable[str] = (), + include_paths: Iterable[str] = ("**",)) -> set[str]: + """Find all document files in the source directory and put them in + :attr:`docnames`. + """ + + self.docnames.clear() + self._path_to_docname.clear() + self._docname_to_path.clear() + + for filename in get_matching_files( + self.srcdir, + include_paths, + [*exclude_paths] + EXCLUDE_PATHS, + ): + 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)] + logger.warning(__('multiple files found for the document "%s": %r\n' + 'Use %r for the build.'), + docname, files, self.doc2path(docname, absolute=True), + once=True) + elif os.access(os.path.join(self.srcdir, filename), os.R_OK): + self.docnames.add(docname) + self._path_to_docname[filename] = docname + self._docname_to_path[docname] = filename + else: + logger.warning(__("Ignored unreadable document %r."), + filename, location=docname) + + return self.docnames + + def path2doc(self, filename: str | os.PathLike[str]) -> str | None: + """Return the docname for the filename if the file is a document. + + *filename* should be absolute or relative to the source directory. + """ + try: + return self._path_to_docname[filename] # type: ignore[index] + except KeyError: + if os.path.isabs(filename): + with contextlib.suppress(ValueError): + filename = os.path.relpath(filename, self.srcdir) + + for suffix in self.source_suffix: + if os.path.basename(filename).endswith(suffix): + return path_stabilize(filename).removesuffix(suffix) + + # the file does not have a docname + return None + + def doc2path(self, docname: str, absolute: bool) -> str: + """Return the filename for the document name. + + If *absolute* is True, return as an absolute path. + Else, return as a relative path to the source directory. + """ + try: + filename = self._docname_to_path[docname] + except KeyError: + # Backwards compatibility: the document does not exist + filename = docname + self._first_source_suffix + + if absolute: + return os.path.join(self.srcdir, filename) + return filename -- cgit v1.2.3