mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Mark `Builder.write()
` as final (#12767)
This commit is contained in:
parent
705d5ddd9f
commit
d135d2eba3
@ -149,6 +149,12 @@ Bugs fixed
|
||||
* #12995: Significantly improve performance when building the search index
|
||||
for Chinese languages.
|
||||
Patch by Adam Turner.
|
||||
* #12767: :py:meth:`.Builder.write` is typed as ``final``, meaning that the
|
||||
:event:`write-started` event may be relied upon by extensions.
|
||||
A new :py:meth:`.Builder.write_documents` method has been added to
|
||||
control how documents are written.
|
||||
This is intended for builders that do not output a file for each document.
|
||||
Patch by Adam Turner.
|
||||
|
||||
|
||||
Testing
|
||||
|
11
doc/_static/diagrams/sphinx_build_flow.dot
vendored
11
doc/_static/diagrams/sphinx_build_flow.dot
vendored
@ -27,10 +27,6 @@ digraph build {
|
||||
"Builder.build_update":p1 -> "Builder.build";
|
||||
|
||||
"Builder.build" -> "Builder.read";
|
||||
"Builder.write" [
|
||||
shape=record
|
||||
label = "<p1> Builder.write | Builder._write_serial | Builder._write_parallel"
|
||||
];
|
||||
"Builder.build" -> "Builder.write";
|
||||
"Builder.build" -> "Builder.finish";
|
||||
|
||||
@ -39,8 +35,13 @@ digraph build {
|
||||
|
||||
"Builder.write":p1 -> "Builder.prepare_writing";
|
||||
"Builder.write":p1 -> "Builder.copy_assets";
|
||||
"Builder.write":p1 -> "Builder.write_doc";
|
||||
"Builder.write_documents" [
|
||||
shape=record
|
||||
label = "<p1> Builder.write_documents | Builder._write_serial | Builder._write_parallel"
|
||||
];
|
||||
"Builder.write":p1 -> "Builder.write_documents";
|
||||
|
||||
"Builder.write_documents":p1 -> "Builder.write_doc";
|
||||
"Builder.write_doc" -> "Builder.get_relative_uri";
|
||||
|
||||
"Builder.get_relative_uri" -> "Builder.get_target_uri";
|
||||
|
@ -83,9 +83,9 @@ digraph events {
|
||||
|
||||
// during write phase
|
||||
"write-started"[style=filled fillcolor="#D5FFFF" color=blue penwidth=2];
|
||||
"Builder.build":write -> "write-started";
|
||||
"Builder.write" [label = "Builder.write()"]
|
||||
"Builder.build":write -> "Builder.write";
|
||||
"Builder.write" -> "write-started";
|
||||
write_each_doc [shape="ellipse", label="for updated"];
|
||||
"Builder.write" -> write_each_doc;
|
||||
"ReferenceResolver" [
|
||||
@ -120,6 +120,6 @@ digraph events {
|
||||
{rank=same; "env-get-outdated" "env-before-read-docs" "env-get-updated"};
|
||||
{rank=same; "env-purge-doc" "source-read" "doctree-read", "merge_each_process"};
|
||||
{rank=same; "env-updated" "env-check-consistency"};
|
||||
{rank=same; "env-merge-info" "write-started" "Builder.write"};
|
||||
{rank=same; "env-merge-info" "Builder.write"};
|
||||
{rank=max; "build-finished"};
|
||||
}
|
||||
|
@ -39,20 +39,23 @@ Builder API
|
||||
.. automethod:: read
|
||||
.. automethod:: read_doc
|
||||
.. automethod:: write_doctree
|
||||
.. automethod:: write
|
||||
|
||||
.. rubric:: Overridable Methods
|
||||
.. rubric:: Abstract Methods
|
||||
|
||||
These must be implemented in builder sub-classes:
|
||||
|
||||
.. automethod:: get_outdated_docs
|
||||
.. automethod:: prepare_writing
|
||||
.. automethod:: write_doc
|
||||
.. automethod:: get_target_uri
|
||||
|
||||
.. rubric:: Overridable Methods
|
||||
|
||||
These methods can be overridden in builder sub-classes:
|
||||
|
||||
.. automethod:: init
|
||||
.. automethod:: write
|
||||
.. automethod:: write_documents
|
||||
.. automethod:: prepare_writing
|
||||
.. automethod:: copy_assets
|
||||
.. automethod:: get_relative_uri
|
||||
.. automethod:: finish
|
||||
|
@ -41,7 +41,7 @@ from sphinx import directives # NoQA: F401 isort:skip
|
||||
from sphinx import roles # NoQA: F401 isort:skip
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Sequence
|
||||
from collections.abc import Iterable, Sequence, Set
|
||||
|
||||
from docutils.nodes import Node
|
||||
|
||||
@ -664,6 +664,7 @@ class Builder:
|
||||
if _cache:
|
||||
self.env._write_doc_doctree_cache[docname] = doctree
|
||||
|
||||
@final
|
||||
def write(
|
||||
self,
|
||||
build_docnames: Iterable[str] | None,
|
||||
@ -685,11 +686,12 @@ class Builder:
|
||||
logger.debug(__('docnames to write: %s'), ', '.join(sorted(docnames)))
|
||||
|
||||
# add all toctree-containing files that may have changed
|
||||
for docname in list(docnames):
|
||||
extra = {self.config.root_doc}
|
||||
for docname in docnames:
|
||||
for tocdocname in self.env.files_to_rebuild.get(docname, set()):
|
||||
if tocdocname in self.env.found_docs:
|
||||
docnames.add(tocdocname)
|
||||
docnames.add(self.config.root_doc)
|
||||
extra.add(tocdocname)
|
||||
docnames |= extra
|
||||
|
||||
# sort to ensure deterministic toctree generation
|
||||
self.env.toctree_includes = dict(sorted(self.env.toctree_includes.items()))
|
||||
@ -700,12 +702,21 @@ class Builder:
|
||||
with progress_message(__('copying assets'), nonl=False):
|
||||
self.copy_assets()
|
||||
|
||||
self.write_documents(docnames)
|
||||
|
||||
def write_documents(self, docnames: Set[str]) -> None:
|
||||
"""Write all documents in *docnames*.
|
||||
|
||||
This method can be overridden if a builder does not create
|
||||
output files for each document.
|
||||
"""
|
||||
sorted_docnames = sorted(docnames)
|
||||
if self.parallel_ok:
|
||||
# number of subprocesses is parallel-1 because the main process
|
||||
# is busy loading doctrees and doing write_doc_serialized()
|
||||
self._write_parallel(sorted(docnames), nproc=self.app.parallel - 1)
|
||||
self._write_parallel(sorted_docnames, nproc=self.app.parallel - 1)
|
||||
else:
|
||||
self._write_serial(sorted(docnames))
|
||||
self._write_serial(sorted_docnames)
|
||||
|
||||
def _write_serial(self, docnames: Sequence[str]) -> None:
|
||||
with (
|
||||
@ -769,9 +780,9 @@ class Builder:
|
||||
tasks.join()
|
||||
logger.info('')
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
def prepare_writing(self, docnames: Set[str]) -> None:
|
||||
"""A place where you can add logic before :meth:`write_doc` is run"""
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
def copy_assets(self) -> None:
|
||||
"""Where assets (images, static files, etc) are copied before writing"""
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import html
|
||||
from os import path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sphinx import package_dir
|
||||
from sphinx.builders import Builder
|
||||
@ -16,6 +16,8 @@ from sphinx.util.fileutil import copy_asset_file
|
||||
from sphinx.util.osutil import ensuredir, os_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Set
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.typing import ExtensionMetadata
|
||||
|
||||
@ -46,7 +48,7 @@ class ChangesBuilder(Builder):
|
||||
'versionremoved': 'removed',
|
||||
}
|
||||
|
||||
def write(self, *ignored: Any) -> None:
|
||||
def write_documents(self, _docnames: Set[str]) -> None:
|
||||
version = self.config.version
|
||||
domain = self.env.domains.changeset_domain
|
||||
libchanges: dict[str, list[tuple[str, str, int]]] = {}
|
||||
|
@ -29,9 +29,6 @@ class DummyBuilder(Builder):
|
||||
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
|
||||
return ''
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
pass
|
||||
|
||||
def write_doc(self, docname: str, doctree: nodes.document) -> None:
|
||||
pass
|
||||
|
||||
|
@ -21,6 +21,8 @@ from sphinx.util.fileutil import copy_asset_file
|
||||
from sphinx.util.osutil import make_filename
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Set
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.typing import ExtensionMetadata
|
||||
|
||||
@ -120,7 +122,7 @@ class Epub3Builder(_epub_base.EpubBuilder):
|
||||
metadata['epub_version'] = self.config.epub_version
|
||||
return metadata
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
def prepare_writing(self, docnames: Set[str]) -> None:
|
||||
super().prepare_writing(docnames)
|
||||
|
||||
writing_mode = self.config.epub_writing_mode
|
||||
|
@ -158,9 +158,6 @@ class I18nBuilder(Builder):
|
||||
def get_outdated_docs(self) -> set[str]:
|
||||
return self.env.found_docs
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
return
|
||||
|
||||
def compile_catalogs(self, catalogs: set[CatalogInfo], message: str) -> None:
|
||||
return
|
||||
|
||||
|
@ -64,7 +64,7 @@ from sphinx.writers.html import HTMLWriter
|
||||
from sphinx.writers.html5 import HTML5Translator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Iterator
|
||||
from collections.abc import Iterator, Set
|
||||
from typing import TypeAlias
|
||||
|
||||
from docutils.nodes import Node
|
||||
@ -420,7 +420,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self._publisher.publish()
|
||||
return self._publisher.writer.parts
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
def prepare_writing(self, docnames: Set[str]) -> None:
|
||||
# create the search indexer
|
||||
self.indexer = None
|
||||
if self.search:
|
||||
@ -965,9 +965,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
node.replace_self(reference)
|
||||
reference.append(node)
|
||||
|
||||
def load_indexer(self, docnames: Iterable[str]) -> None:
|
||||
def load_indexer(self, docnames: Set[str]) -> None:
|
||||
assert self.indexer is not None
|
||||
keep = set(self.env.all_docs) - set(docnames)
|
||||
keep = set(self.env.all_docs).difference(docnames)
|
||||
try:
|
||||
searchindexfn = path.join(self.outdir, self.searchindex_filename)
|
||||
if self.indexer_dumps_unicode:
|
||||
|
@ -38,7 +38,7 @@ from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter
|
||||
from docutils import nodes # isort:skip
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterable, Set
|
||||
|
||||
from docutils.nodes import Node
|
||||
|
||||
@ -291,13 +291,17 @@ class LaTeXBuilder(Builder):
|
||||
)
|
||||
f.write(highlighter.get_stylesheet())
|
||||
|
||||
def prepare_writing(self, docnames: Set[str]) -> None:
|
||||
self.init_document_data()
|
||||
self.write_stylesheet()
|
||||
|
||||
def copy_assets(self) -> None:
|
||||
self.copy_support_files()
|
||||
|
||||
if self.config.latex_additional_files:
|
||||
self.copy_latex_additional_files()
|
||||
|
||||
def write(self, *ignored: Any) -> None:
|
||||
def write_documents(self, _docnames: Set[str]) -> None:
|
||||
docwriter = LaTeXWriter(self)
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
@ -309,10 +313,6 @@ class LaTeXBuilder(Builder):
|
||||
read_config_files=True,
|
||||
).get_default_values()
|
||||
|
||||
self.init_document_data()
|
||||
self.write_stylesheet()
|
||||
self.copy_assets()
|
||||
|
||||
for entry in self.document_data:
|
||||
docname, targetname, title, author, themename = entry[:5]
|
||||
theme = self.themes.get(themename)
|
||||
|
@ -20,6 +20,8 @@ from sphinx.util.osutil import ensuredir, make_filename_from_project
|
||||
from sphinx.writers.manpage import ManualPageTranslator, ManualPageWriter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Set
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.config import Config
|
||||
from sphinx.util.typing import ExtensionMetadata
|
||||
@ -55,7 +57,7 @@ class ManualPageBuilder(Builder):
|
||||
return ''
|
||||
|
||||
@progress_message(__('writing'))
|
||||
def write(self, *ignored: Any) -> None:
|
||||
def write_documents(self, _docnames: Set[str]) -> None:
|
||||
docwriter = ManualPageWriter(self)
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
|
@ -16,6 +16,8 @@ from sphinx.util.display import progress_message
|
||||
from sphinx.util.nodes import inline_all_toctrees
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Set
|
||||
|
||||
from docutils.nodes import Node
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
@ -160,11 +162,8 @@ class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
|
||||
'display_toc': display_toc,
|
||||
}
|
||||
|
||||
def write(self, *ignored: Any) -> None:
|
||||
docnames = self.env.all_docs
|
||||
|
||||
with progress_message(__('preparing documents')):
|
||||
self.prepare_writing(docnames) # type: ignore[arg-type]
|
||||
def write_documents(self, _docnames: Set[str]) -> None:
|
||||
self.prepare_writing(self.env.all_docs.keys())
|
||||
|
||||
with progress_message(__('assembling single document'), nonl=False):
|
||||
doctree = self.assemble_doctree()
|
||||
|
@ -25,7 +25,7 @@ from sphinx.util.osutil import SEP, copyfile, ensuredir, make_filename_from_proj
|
||||
from sphinx.writers.texinfo import TexinfoTranslator, TexinfoWriter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterable, Set
|
||||
|
||||
from docutils.nodes import Node
|
||||
|
||||
@ -71,7 +71,7 @@ class TexinfoBuilder(Builder):
|
||||
# ignore source path
|
||||
return self.get_target_uri(to, typ)
|
||||
|
||||
def init_document_data(self) -> None:
|
||||
def prepare_writing(self, _docnames: Set[str]) -> None:
|
||||
preliminary_document_data = [list(x) for x in self.config.texinfo_documents]
|
||||
if not preliminary_document_data:
|
||||
logger.warning(
|
||||
@ -98,9 +98,7 @@ class TexinfoBuilder(Builder):
|
||||
docname = docname.removesuffix(SEP + 'index')
|
||||
self.titles.append((docname, entry[2]))
|
||||
|
||||
def write(self, *ignored: Any) -> None:
|
||||
self.init_document_data()
|
||||
self.copy_assets()
|
||||
def write_documents(self, _docnames: Set[str]) -> None:
|
||||
for entry in self.document_data:
|
||||
docname, targetname, title, author = entry[:4]
|
||||
targetname += '.texi'
|
||||
|
@ -18,7 +18,7 @@ from sphinx.util.osutil import (
|
||||
from sphinx.writers.text import TextTranslator, TextWriter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Iterator, Set
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
@ -64,7 +64,7 @@ class TextBuilder(Builder):
|
||||
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
|
||||
return ''
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
def prepare_writing(self, docnames: Set[str]) -> None:
|
||||
self.writer = TextWriter(self)
|
||||
|
||||
def write_doc(self, docname: str, doctree: nodes.document) -> None:
|
||||
|
@ -20,7 +20,7 @@ from sphinx.util.osutil import (
|
||||
from sphinx.writers.xml import PseudoXMLWriter, XMLWriter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Iterator, Set
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.typing import ExtensionMetadata
|
||||
@ -68,7 +68,7 @@ class XMLBuilder(Builder):
|
||||
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
|
||||
return docname
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
def prepare_writing(self, docnames: Set[str]) -> None:
|
||||
self.writer = self._writer_class(self)
|
||||
|
||||
def write_doc(self, docname: str, doctree: nodes.document) -> None:
|
||||
|
@ -192,7 +192,7 @@ class CoverageBuilder(Builder):
|
||||
def get_outdated_docs(self) -> str:
|
||||
return 'coverage overview'
|
||||
|
||||
def write(self, *ignored: Any) -> None:
|
||||
def write_documents(self, _docnames: Set[str]) -> None:
|
||||
self.py_undoc: dict[str, dict[str, Any]] = {}
|
||||
self.py_undocumented: dict[str, Set[str]] = {}
|
||||
self.py_documented: dict[str, Set[str]] = {}
|
||||
|
@ -27,7 +27,7 @@ from sphinx.util.docutils import SphinxDirective
|
||||
from sphinx.util.osutil import relpath
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from collections.abc import Callable, Set
|
||||
|
||||
from docutils.nodes import Element, Node, TextElement
|
||||
|
||||
@ -355,13 +355,9 @@ Doctest summary
|
||||
if self.total_failures or self.setup_failures or self.cleanup_failures:
|
||||
self.app.statuscode = 1
|
||||
|
||||
def write(self, build_docnames: Iterable[str] | None, updated_docnames: Sequence[str],
|
||||
method: str = 'update') -> None:
|
||||
if build_docnames is None:
|
||||
build_docnames = sorted(self.env.all_docs)
|
||||
|
||||
def write_documents(self, docnames: Set[str]) -> None:
|
||||
logger.info(bold('running tests...'))
|
||||
for docname in build_docnames:
|
||||
for docname in sorted(docnames):
|
||||
# no need to resolve the doctree
|
||||
doctree = self.env.get_doctree(docname)
|
||||
self.test_doc(docname, doctree)
|
||||
|
Loading…
Reference in New Issue
Block a user