Add SphinxPostTransform class (#6154)

* Add SphinxPostTransform

* Apply SphinxPostTransform to latex transforms
This commit is contained in:
Takeshi KOMIYA
2019-03-17 18:27:00 +09:00
committed by GitHub
parent fadab68ffc
commit b5959ca230
6 changed files with 97 additions and 50 deletions

View File

@@ -13,6 +13,7 @@ Incompatible changes
Deprecated
----------
* ``sphinx.builders.latex.LaTeXBuilder.apply_transforms()``
* ``sphinx.environment.NoUri``
* ``sphinx.ext.autodoc.importer.MockFinder``
* ``sphinx.ext.autodoc.importer.MockLoader``
@@ -28,6 +29,7 @@ For more details, see :ref:`deprecation APIs list <dev-deprecated-apis>`.
Features added
--------------
* Add a helper class ``sphinx.transforms.post_transforms.SphinxPostTransform``
* Add a helper method ``SphinxDirective.set_source_info()``
* #6180: Support ``--keep-going`` with BuildDoc setup command

View File

@@ -26,6 +26,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
* - ``sphinx.builders.latex.LaTeXBuilder.apply_transforms()``
- 2.1
- 4.0
- N/A
* - ``sphinx.environment.NoUri``
- 2.1
- 4.0

View File

@@ -15,6 +15,9 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily.
.. autoclass:: sphinx.transforms.SphinxTransform
:members:
.. autoclass:: sphinx.transforms.post_transforms.SphinxPostTransform
:members:
.. autoclass:: sphinx.util.docutils.SphinxDirective
:members:

View File

@@ -9,6 +9,7 @@
"""
import os
import warnings
from os import path
from docutils.frontend import OptionParser
@@ -16,17 +17,12 @@ from docutils.frontend import OptionParser
import sphinx.builders.latex.nodes # NOQA # Workaround: import this before writer to avoid ImportError
from sphinx import package_dir, addnodes, highlighting
from sphinx.builders import Builder
from sphinx.builders.latex.transforms import (
BibliographyTransform, CitationReferenceTransform, MathReferenceTransform,
FootnoteDocnameUpdater, LaTeXFootnoteTransform, LiteralBlockTransform,
ShowUrlsTransform, DocumentTargetTransform, IndexInSectionTitleTransform,
)
from sphinx.builders.latex.util import ExtBabel
from sphinx.config import ENUM
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import NoUri, SphinxError
from sphinx.locale import _, __
from sphinx.transforms import SphinxTransformer
from sphinx.util import texescape, logging, progress_message, status_iterator
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.util.docutils import SphinxFileOutput, new_document
@@ -264,7 +260,6 @@ class LaTeXBuilder(Builder):
docname, toctree_only,
appendices=((docclass != 'howto') and self.config.latex_appendices or []))
doctree['tocdepth'] = tocdepth
self.apply_transforms(doctree)
self.post_process_images(doctree)
self.update_doc_context(title, author)
@@ -340,15 +335,8 @@ class LaTeXBuilder(Builder):
def apply_transforms(self, doctree):
# type: (nodes.document) -> None
transformer = SphinxTransformer(doctree)
transformer.set_environment(self.env)
transformer.add_transforms([BibliographyTransform,
ShowUrlsTransform,
LaTeXFootnoteTransform,
LiteralBlockTransform,
DocumentTargetTransform,
IndexInSectionTitleTransform])
transformer.apply_transforms()
warnings.warn('LaTeXBuilder.apply_transforms() is deprecated.',
RemovedInSphinx40Warning)
def finish(self):
# type: () -> None
@@ -485,11 +473,10 @@ def default_latex_documents(config):
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.setup_extension('sphinx.builders.latex.transforms')
app.add_builder(LaTeXBuilder)
app.add_post_transform(CitationReferenceTransform)
app.add_post_transform(MathReferenceTransform)
app.connect('config-inited', validate_config_values)
app.add_transform(FootnoteDocnameUpdater)
app.add_config_value('latex_engine', default_latex_engine, None,
ENUM('pdflatex', 'xelatex', 'lualatex', 'platex'))

View File

@@ -17,11 +17,13 @@ from sphinx.builders.latex.nodes import (
captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography
)
from sphinx.transforms import SphinxTransform
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util.nodes import NodeMatcher
if False:
# For type annotation
from typing import Any, List, Set, Tuple # NOQA
from typing import Any, Dict, List, Set, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:')
@@ -38,7 +40,7 @@ class FootnoteDocnameUpdater(SphinxTransform):
node['docname'] = self.env.docname
class ShowUrlsTransform(SphinxTransform):
class ShowUrlsTransform(SphinxPostTransform):
"""Expand references to inline text or footnotes.
For more information, see :confval:`latex_show_urls`.
@@ -46,11 +48,12 @@ class ShowUrlsTransform(SphinxTransform):
.. note:: This transform is used for integrated doctree
"""
default_priority = 400
builders = ('latex',)
# references are expanded to footnotes (or not)
expanded = False
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
try:
# replace id_prefix temporarily
@@ -177,7 +180,7 @@ class FootnoteCollector(nodes.NodeVisitor):
self.footnote_refs.append(node)
class LaTeXFootnoteTransform(SphinxTransform):
class LaTeXFootnoteTransform(SphinxPostTransform):
"""Convert footnote definitions and references to appropriate form to LaTeX.
* Replace footnotes on restricted zone (e.g. headings) by footnotemark node.
@@ -345,8 +348,9 @@ class LaTeXFootnoteTransform(SphinxTransform):
"""
default_priority = 600
builders = ('latex',)
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
footnotes = list(self.document.traverse(nodes.footnote))
for node in footnotes:
@@ -486,7 +490,7 @@ class LaTeXFootnoteVisitor(nodes.NodeVisitor):
return None
class BibliographyTransform(SphinxTransform):
class BibliographyTransform(SphinxPostTransform):
"""Gather bibliography entries to tail of document.
Before::
@@ -517,8 +521,9 @@ class BibliographyTransform(SphinxTransform):
...
"""
default_priority = 750
builders = ('latex',)
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
citations = thebibliography()
for node in self.document.traverse(nodes.citation):
@@ -529,19 +534,17 @@ class BibliographyTransform(SphinxTransform):
self.document += citations
class CitationReferenceTransform(SphinxTransform):
class CitationReferenceTransform(SphinxPostTransform):
"""Replace pending_xref nodes for citation by citation_reference.
To handle citation reference easily on LaTeX writer, this converts
pending_xref nodes to citation_reference.
"""
default_priority = 5 # before ReferencesResolver
builders = ('latex',)
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
if self.app.builder.name != 'latex':
return
matcher = NodeMatcher(addnodes.pending_xref, refdomain='std', reftype='citation')
citations = self.env.get_domain('std').data['citations']
for node in self.document.traverse(matcher): # type: addnodes.pending_xref
@@ -552,19 +555,17 @@ class CitationReferenceTransform(SphinxTransform):
node.replace_self(citation_ref)
class MathReferenceTransform(SphinxTransform):
class MathReferenceTransform(SphinxPostTransform):
"""Replace pending_xref nodes for math by math_reference.
To handle math reference easily on LaTeX writer, this converts pending_xref
nodes to math_reference.
"""
default_priority = 5 # before ReferencesResolver
builders = ('latex',)
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
if self.app.builder.name != 'latex':
return
equations = self.env.get_domain('math').data['objects']
for node in self.document.traverse(addnodes.pending_xref):
if node['refdomain'] == 'math' and node['reftype'] in ('eq', 'numref'):
@@ -574,30 +575,26 @@ class MathReferenceTransform(SphinxTransform):
node.replace_self(refnode)
class LiteralBlockTransform(SphinxTransform):
class LiteralBlockTransform(SphinxPostTransform):
"""Replace container nodes for literal_block by captioned_literal_block."""
default_priority = 400
builders = ('latex',)
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
if self.app.builder.name != 'latex':
return
matcher = NodeMatcher(nodes.container, literal_block=True)
for node in self.document.traverse(matcher): # type: nodes.container
newnode = captioned_literal_block('', *node.children, **node.attributes)
node.replace_self(newnode)
class DocumentTargetTransform(SphinxTransform):
class DocumentTargetTransform(SphinxPostTransform):
"""Add :doc label to the first section of each document."""
default_priority = 400
builders = ('latex',)
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
if self.app.builder.name != 'latex':
return
for node in self.document.traverse(addnodes.start_of_file):
section = node.next_node(nodes.section)
if section:
@@ -639,3 +636,22 @@ class IndexInSectionTitleTransform(SphinxTransform):
# move the index node next to the section title
node.remove(index)
node.parent.insert(i + 1, index)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(FootnoteDocnameUpdater)
app.add_post_transform(BibliographyTransform)
app.add_post_transform(CitationReferenceTransform)
app.add_post_transform(DocumentTargetTransform)
app.add_post_transform(IndexInSectionTitleTransform)
app.add_post_transform(LaTeXFootnoteTransform)
app.add_post_transform(LiteralBlockTransform)
app.add_post_transform(MathReferenceTransform)
app.add_post_transform(ShowUrlsTransform)
return {
'version': 'builtin',
'parallel_read_safe': True,
'parallel_write_safe': True,
}

View File

@@ -29,14 +29,48 @@ if False:
logger = logging.getLogger(__name__)
class ReferencesResolver(SphinxTransform):
class SphinxPostTransform(SphinxTransform):
"""A base class of post-transforms.
Post transforms are invoked to modify the document to restructure it for outputting.
They do resolving references, convert images, special transformation for each output
formats and so on. This class helps to implement these post transforms.
"""
builders = () # type: Tuple[str, ...]
formats = () # type: Tuple[str, ...]
def apply(self, **kwargs):
# type: (Any) -> None
if self.is_supported():
self.run(**kwargs)
def is_supported(self):
# type: () -> bool
"""Check this transform working for current builder."""
if self.builders and self.app.builder.name not in self.builders:
return False
if self.formats and self.app.builder.format not in self.formats:
return False
return True
def run(self, **kwargs):
# type: (Any) -> None
"""main method of post transforms.
Subclasses should override this method instead of ``apply()``.
"""
raise NotImplementedError
class ReferencesResolver(SphinxPostTransform):
"""
Resolves cross-references on doctrees.
"""
default_priority = 10
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
for node in self.document.traverse(addnodes.pending_xref):
contnode = cast(nodes.TextElement, node[0].deepcopy())
@@ -147,10 +181,10 @@ class ReferencesResolver(SphinxTransform):
location=node, type='ref', subtype=typ)
class OnlyNodeTransform(SphinxTransform):
class OnlyNodeTransform(SphinxPostTransform):
default_priority = 50
def apply(self, **kwargs):
def run(self, **kwargs):
# type: (Any) -> None
# A comment on the comment() nodes being inserted: replacing by [] would
# result in a "Losing ids" exception if there is a target node before