Merge pull request #6664 from tk0miya/refactor_type_annotation_transforms

Migrate to py3 style type annotation: sphinx.transforms
This commit is contained in:
Takeshi KOMIYA 2019-10-12 22:33:35 +09:00 committed by GitHub
commit 804e1c919e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 210 deletions

View File

@ -9,8 +9,10 @@
""" """
import re import re
from typing import Any, Dict, Generator, List, Tuple
from docutils import nodes from docutils import nodes
from docutils.nodes import Element, Node, Text
from docutils.transforms import Transform, Transformer from docutils.transforms import Transform, Transformer
from docutils.transforms.parts import ContentsFilter from docutils.transforms.parts import ContentsFilter
from docutils.transforms.universal import SmartQuotes from docutils.transforms.universal import SmartQuotes
@ -18,6 +20,7 @@ from docutils.utils import normalize_language_tag
from docutils.utils.smartquotes import smartchars from docutils.utils.smartquotes import smartchars
from sphinx import addnodes from sphinx import addnodes
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging
@ -27,11 +30,9 @@ from sphinx.util.nodes import NodeMatcher, apply_source_workaround, is_smartquot
if False: if False:
# For type annotation # For type annotation
from typing import Any, Dict, Generator, List, Tuple # NOQA from sphinx.application import Sphinx
from sphinx.application import Sphinx # NOQA from sphinx.domain.std import StandardDomain
from sphinx.config import Config # NOQA from sphinx.environment import BuildEnvironment
from sphinx.domain.std import StandardDomain # NOQA
from sphinx.environment import BuildEnvironment # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -51,20 +52,17 @@ class SphinxTransform(Transform):
""" """
@property @property
def app(self): def app(self) -> "Sphinx":
# type: () -> Sphinx
"""Reference to the :class:`.Sphinx` object.""" """Reference to the :class:`.Sphinx` object."""
return self.env.app return self.env.app
@property @property
def env(self): def env(self) -> "BuildEnvironment":
# type: () -> BuildEnvironment
"""Reference to the :class:`.BuildEnvironment` object.""" """Reference to the :class:`.BuildEnvironment` object."""
return self.document.settings.env return self.document.settings.env
@property @property
def config(self): def config(self) -> Config:
# type: () -> Config
"""Reference to the :class:`.Config` object.""" """Reference to the :class:`.Config` object."""
return self.env.config return self.env.config
@ -77,12 +75,10 @@ class SphinxTransformer(Transformer):
document = None # type: nodes.document document = None # type: nodes.document
env = None # type: BuildEnvironment env = None # type: BuildEnvironment
def set_environment(self, env): def set_environment(self, env: "BuildEnvironment") -> None:
# type: (BuildEnvironment) -> None
self.env = env self.env = env
def apply_transforms(self): def apply_transforms(self) -> None:
# type: () -> None
if isinstance(self.document, nodes.document): if isinstance(self.document, nodes.document):
if not hasattr(self.document.settings, 'env') and self.env: if not hasattr(self.document.settings, 'env') and self.env:
self.document.settings.env = self.env self.document.settings.env = self.env
@ -108,8 +104,7 @@ class DefaultSubstitutions(SphinxTransform):
# run before the default Substitutions # run before the default Substitutions
default_priority = 210 default_priority = 210
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
# only handle those not otherwise defined in the document # only handle those not otherwise defined in the document
to_handle = default_substitutions - set(self.document.substitution_defs) to_handle = default_substitutions - set(self.document.substitution_defs)
for ref in self.document.traverse(nodes.substitution_reference): for ref in self.document.traverse(nodes.substitution_reference):
@ -132,8 +127,7 @@ class MoveModuleTargets(SphinxTransform):
""" """
default_priority = 210 default_priority = 210
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(nodes.target): for node in self.document.traverse(nodes.target):
if not node['ids']: if not node['ids']:
continue continue
@ -151,8 +145,7 @@ class HandleCodeBlocks(SphinxTransform):
""" """
default_priority = 210 default_priority = 210
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
# move doctest blocks out of blockquotes # move doctest blocks out of blockquotes
for node in self.document.traverse(nodes.block_quote): for node in self.document.traverse(nodes.block_quote):
if all(isinstance(child, nodes.doctest_block) for child if all(isinstance(child, nodes.doctest_block) for child
@ -176,8 +169,7 @@ class AutoNumbering(SphinxTransform):
""" """
default_priority = 210 default_priority = 210
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
domain = self.env.get_domain('std') # type: StandardDomain domain = self.env.get_domain('std') # type: StandardDomain
for node in self.document.traverse(nodes.Element): for node in self.document.traverse(nodes.Element):
@ -191,8 +183,7 @@ class SortIds(SphinxTransform):
""" """
default_priority = 261 default_priority = 261
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(nodes.section): for node in self.document.traverse(nodes.section):
if len(node['ids']) > 1 and node['ids'][0].startswith('id'): if len(node['ids']) > 1 and node['ids'][0].startswith('id'):
node['ids'] = node['ids'][1:] + [node['ids'][0]] node['ids'] = node['ids'][1:] + [node['ids'][0]]
@ -213,9 +204,8 @@ class ApplySourceWorkaround(SphinxTransform):
""" """
default_priority = 10 default_priority = 10
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None for node in self.document.traverse(): # type: Node
for node in self.document.traverse(): # type: nodes.Node
if isinstance(node, (nodes.TextElement, nodes.image)): if isinstance(node, (nodes.TextElement, nodes.image)):
apply_source_workaround(node) apply_source_workaround(node)
@ -226,8 +216,7 @@ class AutoIndexUpgrader(SphinxTransform):
""" """
default_priority = 210 default_priority = 210
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(addnodes.index): for node in self.document.traverse(addnodes.index):
if 'entries' in node and any(len(entry) == 4 for entry in node['entries']): if 'entries' in node and any(len(entry) == 4 for entry in node['entries']):
msg = __('4 column based index found. ' msg = __('4 column based index found. '
@ -244,18 +233,16 @@ class ExtraTranslatableNodes(SphinxTransform):
""" """
default_priority = 10 default_priority = 10
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
targets = self.config.gettext_additional_targets targets = self.config.gettext_additional_targets
target_nodes = [v for k, v in TRANSLATABLE_NODES.items() if k in targets] target_nodes = [v for k, v in TRANSLATABLE_NODES.items() if k in targets]
if not target_nodes: if not target_nodes:
return return
def is_translatable_node(node): def is_translatable_node(node: Node) -> bool:
# type: (nodes.Node) -> bool
return isinstance(node, tuple(target_nodes)) return isinstance(node, tuple(target_nodes))
for node in self.document.traverse(is_translatable_node): # type: nodes.Element for node in self.document.traverse(is_translatable_node): # type: Element
node['translatable'] = True node['translatable'] = True
@ -265,8 +252,7 @@ class UnreferencedFootnotesDetector(SphinxTransform):
""" """
default_priority = 200 default_priority = 200
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.footnotes: for node in self.document.footnotes:
if node['names'] == []: if node['names'] == []:
# footnote having duplicated number. It is already warned at parser. # footnote having duplicated number. It is already warned at parser.
@ -289,10 +275,9 @@ class FigureAligner(SphinxTransform):
""" """
default_priority = 700 default_priority = 700
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
matcher = NodeMatcher(nodes.table, nodes.figure) matcher = NodeMatcher(nodes.table, nodes.figure)
for node in self.document.traverse(matcher): # type: nodes.Element for node in self.document.traverse(matcher): # type: Element
node.setdefault('align', 'default') node.setdefault('align', 'default')
@ -300,8 +285,7 @@ class FilterSystemMessages(SphinxTransform):
"""Filter system messages from a doctree.""" """Filter system messages from a doctree."""
default_priority = 999 default_priority = 999
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
filterlevel = self.config.keep_warnings and 2 or 5 filterlevel = self.config.keep_warnings and 2 or 5
for node in self.document.traverse(nodes.system_message): for node in self.document.traverse(nodes.system_message):
if node['level'] < filterlevel: if node['level'] < filterlevel:
@ -316,8 +300,7 @@ class SphinxContentsFilter(ContentsFilter):
""" """
visit_pending_xref = ContentsFilter.ignore_node_but_process_children visit_pending_xref = ContentsFilter.ignore_node_but_process_children
def visit_image(self, node): def visit_image(self, node: nodes.image) -> None:
# type: (nodes.image) -> None
raise nodes.SkipNode raise nodes.SkipNode
@ -329,8 +312,7 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
""" """
default_priority = 750 default_priority = 750
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
if not self.is_available(): if not self.is_available():
return return
@ -339,8 +321,7 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
super().apply() super().apply()
def is_available(self): def is_available(self) -> bool:
# type: () -> bool
builders = self.config.smartquotes_excludes.get('builders', []) builders = self.config.smartquotes_excludes.get('builders', [])
languages = self.config.smartquotes_excludes.get('languages', []) languages = self.config.smartquotes_excludes.get('languages', [])
@ -365,8 +346,7 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
else: else:
return False return False
def get_tokens(self, txtnodes): def get_tokens(self, txtnodes: List[Text]) -> Generator[Tuple[str, str], None, None]:
# type: (List[nodes.Text]) -> Generator[Tuple[str, str], None, None]
# A generator that yields ``(texttype, nodetext)`` tuples for a list # A generator that yields ``(texttype, nodetext)`` tuples for a list
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``). # of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
@ -381,8 +361,7 @@ class DoctreeReadEvent(SphinxTransform):
"""Emit :event:`doctree-read` event.""" """Emit :event:`doctree-read` event."""
default_priority = 880 default_priority = 880
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
self.app.emit('doctree-read', self.document) self.app.emit('doctree-read', self.document)
@ -390,8 +369,7 @@ class ManpageLink(SphinxTransform):
"""Find manpage section numbers and names""" """Find manpage section numbers and names"""
default_priority = 999 default_priority = 999
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(addnodes.manpage): for node in self.document.traverse(addnodes.manpage):
manpage = ' '.join([str(x) for x in node.children manpage = ' '.join([str(x) for x in node.children
if isinstance(x, nodes.Text)]) if isinstance(x, nodes.Text)])
@ -417,8 +395,7 @@ deprecated_alias('sphinx.transforms',
RemovedInSphinx40Warning) RemovedInSphinx40Warning)
def setup(app): def setup(app: "Sphinx") -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(ApplySourceWorkaround) app.add_transform(ApplySourceWorkaround)
app.add_transform(ExtraTranslatableNodes) app.add_transform(ExtraTranslatableNodes)
app.add_transform(DefaultSubstitutions) app.add_transform(DefaultSubstitutions)

View File

@ -8,18 +8,16 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
from typing import Any, Dict, List
from typing import cast from typing import cast
from docutils import nodes from docutils import nodes
from docutils.nodes import Node
from sphinx import addnodes from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
from sphinx.application import Sphinx # NOQA
class RefOnlyListChecker(nodes.GenericNodeVisitor): class RefOnlyListChecker(nodes.GenericNodeVisitor):
"""Raise `nodes.NodeFound` if non-simple list item is encountered. """Raise `nodes.NodeFound` if non-simple list item is encountered.
@ -28,17 +26,14 @@ class RefOnlyListChecker(nodes.GenericNodeVisitor):
single reference in it. single reference in it.
""" """
def default_visit(self, node): def default_visit(self, node: Node) -> None:
# type: (nodes.Node) -> None
raise nodes.NodeFound raise nodes.NodeFound
def visit_bullet_list(self, node): def visit_bullet_list(self, node: nodes.bullet_list) -> None:
# type: (nodes.bullet_list) -> None
pass pass
def visit_list_item(self, node): def visit_list_item(self, node: nodes.list_item) -> None:
# type: (nodes.list_item) -> None children = [] # type: List[Node]
children = [] # type: List[nodes.Node]
for child in node.children: for child in node.children:
if not isinstance(child, nodes.Invisible): if not isinstance(child, nodes.Invisible):
children.append(child) children.append(child)
@ -53,8 +48,7 @@ class RefOnlyListChecker(nodes.GenericNodeVisitor):
raise nodes.NodeFound raise nodes.NodeFound
raise nodes.SkipChildren raise nodes.SkipChildren
def invisible_visit(self, node): def invisible_visit(self, node: Node) -> None:
# type: (nodes.Node) -> None
"""Invisible nodes should be ignored.""" """Invisible nodes should be ignored."""
pass pass
@ -67,13 +61,11 @@ class RefOnlyBulletListTransform(SphinxTransform):
""" """
default_priority = 100 default_priority = 100
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
if self.config.html_compact_lists: if self.config.html_compact_lists:
return return
def check_refonly_list(node): def check_refonly_list(node: Node) -> bool:
# type: (nodes.Node) -> bool
"""Check for list with only references in it.""" """Check for list with only references in it."""
visitor = RefOnlyListChecker(self.document) visitor = RefOnlyListChecker(self.document)
try: try:
@ -93,8 +85,7 @@ class RefOnlyBulletListTransform(SphinxTransform):
item.replace(para, compact_para) item.replace(para, compact_para)
def setup(app): def setup(app: Sphinx) -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(RefOnlyBulletListTransform) app.add_transform(RefOnlyBulletListTransform)
return { return {

View File

@ -10,13 +10,15 @@
from os import path from os import path
from textwrap import indent from textwrap import indent
from typing import Any, TypeVar from typing import Any, Dict, List, Tuple, TypeVar
from docutils import nodes from docutils import nodes
from docutils.io import StringInput from docutils.io import StringInput
from docutils.nodes import Element
from docutils.utils import relative_path from docutils.utils import relative_path
from sphinx import addnodes from sphinx import addnodes
from sphinx.config import Config
from sphinx.domains.std import make_glossary_term, split_term_classifiers from sphinx.domains.std import make_glossary_term, split_term_classifiers
from sphinx.locale import __, init as init_locale from sphinx.locale import __, init as init_locale
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
@ -29,18 +31,17 @@ from sphinx.util.nodes import (
if False: if False:
# For type annotation # For type annotation
from typing import Dict, List, Tuple # NOQA
from typing import Type # for python3.5.1 from typing import Type # for python3.5.1
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx
from sphinx.config import Config # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
N = TypeVar('N', bound=nodes.Node) N = TypeVar('N', bound=nodes.Node)
def publish_msgstr(app, source, source_path, source_line, config, settings): def publish_msgstr(app: "Sphinx", source: str, source_path: str, source_line: int,
# type: (Sphinx, str, str, int, Config, Any) -> nodes.Element config: Config, settings: Any) -> Element:
"""Publish msgstr (single line) into docutils document """Publish msgstr (single line) into docutils document
:param sphinx.application.Sphinx app: sphinx application :param sphinx.application.Sphinx app: sphinx application
@ -75,8 +76,7 @@ class PreserveTranslatableMessages(SphinxTransform):
""" """
default_priority = 10 # this MUST be invoked before Locale transform default_priority = 10 # this MUST be invoked before Locale transform
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(addnodes.translatable): for node in self.document.traverse(addnodes.translatable):
node.preserve_original_messages() node.preserve_original_messages()
@ -87,8 +87,7 @@ class Locale(SphinxTransform):
""" """
default_priority = 20 default_priority = 20
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
settings, source = self.document.settings, self.document['source'] settings, source = self.document.settings, self.document['source']
msgstr = '' msgstr = ''
@ -268,7 +267,7 @@ class Locale(SphinxTransform):
unexpected = ( unexpected = (
nodes.paragraph, # expected form of translation nodes.paragraph, # expected form of translation
nodes.title # generated by above "Subelements phase2" nodes.title # generated by above "Subelements phase2"
) # type: Tuple[Type[nodes.Element], ...] ) # type: Tuple[Type[Element], ...]
# following types are expected if # following types are expected if
# config.gettext_additional_targets is configured # config.gettext_additional_targets is configured
@ -279,8 +278,7 @@ class Locale(SphinxTransform):
continue # skip continue # skip
# auto-numbered foot note reference should use original 'ids'. # auto-numbered foot note reference should use original 'ids'.
def list_replace_or_append(lst, old, new): def list_replace_or_append(lst: List[N], old: N, new: N) -> None:
# type: (List[N], N, N) -> None
if old in lst: if old in lst:
lst[lst.index(old)] = new lst[lst.index(old)] = new
else: else:
@ -406,8 +404,7 @@ class Locale(SphinxTransform):
.format(old_xref_rawsources, new_xref_rawsources), .format(old_xref_rawsources, new_xref_rawsources),
location=node) location=node)
def get_ref_key(node): def get_ref_key(node: addnodes.pending_xref) -> Tuple[str, str, str]:
# type: (addnodes.pending_xref) -> Tuple[str, str, str]
case = node["refdomain"], node["reftype"] case = node["refdomain"], node["reftype"]
if case == ('std', 'term'): if case == ('std', 'term'):
return None return None
@ -465,7 +462,7 @@ class Locale(SphinxTransform):
node['entries'] = new_entries node['entries'] = new_entries
# remove translated attribute that is used for avoiding double translation. # remove translated attribute that is used for avoiding double translation.
for translated in self.document.traverse(NodeMatcher(translated=Any)): # type: nodes.Element # NOQA for translated in self.document.traverse(NodeMatcher(translated=Any)): # type: Element # NOQA
translated.delattr('translated') translated.delattr('translated')
@ -475,8 +472,7 @@ class RemoveTranslatableInline(SphinxTransform):
""" """
default_priority = 999 default_priority = 999
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
from sphinx.builders.gettext import MessageCatalogBuilder from sphinx.builders.gettext import MessageCatalogBuilder
if isinstance(self.app.builder, MessageCatalogBuilder): if isinstance(self.app.builder, MessageCatalogBuilder):
return return
@ -487,8 +483,7 @@ class RemoveTranslatableInline(SphinxTransform):
inline.parent += inline.children inline.parent += inline.children
def setup(app): def setup(app: "Sphinx") -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(PreserveTranslatableMessages) app.add_transform(PreserveTranslatableMessages)
app.add_transform(Locale) app.add_transform(Locale)
app.add_transform(RemoveTranslatableInline) app.add_transform(RemoveTranslatableInline)

View File

@ -8,23 +8,22 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
from typing import Any, Dict, List, Tuple
from typing import cast from typing import cast
from docutils import nodes from docutils import nodes
from docutils.nodes import Element
from sphinx import addnodes from sphinx import addnodes
from sphinx.addnodes import pending_xref
from sphinx.application import Sphinx
from sphinx.domains import Domain
from sphinx.errors import NoUri from sphinx.errors import NoUri
from sphinx.locale import __ from sphinx.locale import __
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.nodes import process_only_nodes from sphinx.util.nodes import process_only_nodes
if False:
# For type annotation
from typing import Any, Dict, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.domains import Domain # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -39,13 +38,11 @@ class SphinxPostTransform(SphinxTransform):
builders = () # type: Tuple[str, ...] builders = () # type: Tuple[str, ...]
formats = () # type: Tuple[str, ...] formats = () # type: Tuple[str, ...]
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
if self.is_supported(): if self.is_supported():
self.run(**kwargs) self.run(**kwargs)
def is_supported(self): def is_supported(self) -> bool:
# type: () -> bool
"""Check this transform working for current builder.""" """Check this transform working for current builder."""
if self.builders and self.app.builder.name not in self.builders: if self.builders and self.app.builder.name not in self.builders:
return False return False
@ -54,8 +51,7 @@ class SphinxPostTransform(SphinxTransform):
return True return True
def run(self, **kwargs): def run(self, **kwargs) -> None:
# type: (Any) -> None
"""main method of post transforms. """main method of post transforms.
Subclasses should override this method instead of ``apply()``. Subclasses should override this method instead of ``apply()``.
@ -70,8 +66,7 @@ class ReferencesResolver(SphinxPostTransform):
default_priority = 10 default_priority = 10
def run(self, **kwargs): def run(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(addnodes.pending_xref): for node in self.document.traverse(addnodes.pending_xref):
contnode = cast(nodes.TextElement, node[0].deepcopy()) contnode = cast(nodes.TextElement, node[0].deepcopy())
newnode = None newnode = None
@ -105,12 +100,11 @@ class ReferencesResolver(SphinxPostTransform):
newnode = contnode newnode = contnode
node.replace_self(newnode or contnode) node.replace_self(newnode or contnode)
def resolve_anyref(self, refdoc, node, contnode): def resolve_anyref(self, refdoc: str, node: pending_xref, contnode: Element) -> Element:
# type: (str, addnodes.pending_xref, nodes.TextElement) -> nodes.Element
"""Resolve reference generated by the "any" role.""" """Resolve reference generated by the "any" role."""
stddomain = self.env.get_domain('std') stddomain = self.env.get_domain('std')
target = node['reftarget'] target = node['reftarget']
results = [] # type: List[Tuple[str, nodes.Element]] results = [] # type: List[Tuple[str, Element]]
# first, try resolving as :doc: # first, try resolving as :doc:
doc_ref = stddomain.resolve_xref(self.env, refdoc, self.app.builder, doc_ref = stddomain.resolve_xref(self.env, refdoc, self.app.builder,
'doc', target, node, contnode) 'doc', target, node, contnode)
@ -155,8 +149,8 @@ class ReferencesResolver(SphinxPostTransform):
newnode[0]['classes'].append(res_role.replace(':', '-')) newnode[0]['classes'].append(res_role.replace(':', '-'))
return newnode return newnode
def warn_missing_reference(self, refdoc, typ, target, node, domain): def warn_missing_reference(self, refdoc: str, typ: str, target: str,
# type: (str, str, str, addnodes.pending_xref, Domain) -> None node: pending_xref, domain: Domain) -> None:
warn = node.get('refwarn') warn = node.get('refwarn')
if self.config.nitpicky: if self.config.nitpicky:
warn = True warn = True
@ -184,8 +178,7 @@ class ReferencesResolver(SphinxPostTransform):
class OnlyNodeTransform(SphinxPostTransform): class OnlyNodeTransform(SphinxPostTransform):
default_priority = 50 default_priority = 50
def run(self, **kwargs): def run(self, **kwargs) -> None:
# type: (Any) -> None
# A comment on the comment() nodes being inserted: replacing by [] would # A comment on the comment() nodes being inserted: replacing by [] would
# result in a "Losing ids" exception if there is a target node before # result in a "Losing ids" exception if there is a target node before
# the only node, so we make sure docutils can transfer the id to # the only node, so we make sure docutils can transfer the id to
@ -193,8 +186,7 @@ class OnlyNodeTransform(SphinxPostTransform):
process_only_nodes(self.document, self.app.builder.tags) process_only_nodes(self.document, self.app.builder.tags)
def setup(app): def setup(app: Sphinx) -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_post_transform(ReferencesResolver) app.add_post_transform(ReferencesResolver)
app.add_post_transform(OnlyNodeTransform) app.add_post_transform(OnlyNodeTransform)

View File

@ -9,20 +9,17 @@
""" """
import sys import sys
from typing import NamedTuple, Union from typing import Any, Dict, List, NamedTuple, Union
from docutils import nodes from docutils import nodes
from docutils.nodes import Node
from pygments.lexers import PythonConsoleLexer, guess_lexer from pygments.lexers import PythonConsoleLexer, guess_lexer
from sphinx import addnodes from sphinx import addnodes
from sphinx.application import Sphinx
from sphinx.ext import doctest from sphinx.ext import doctest
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
from sphinx.application import Sphinx # NOQA
HighlightSetting = NamedTuple('HighlightSetting', [('language', str), HighlightSetting = NamedTuple('HighlightSetting', [('language', str),
('force', bool), ('force', bool),
@ -39,8 +36,7 @@ class HighlightLanguageTransform(SphinxTransform):
""" """
default_priority = 400 default_priority = 400
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
visitor = HighlightLanguageVisitor(self.document, visitor = HighlightLanguageVisitor(self.document,
self.config.highlight_language) self.config.highlight_language)
self.document.walkabout(visitor) self.document.walkabout(visitor)
@ -50,44 +46,35 @@ class HighlightLanguageTransform(SphinxTransform):
class HighlightLanguageVisitor(nodes.NodeVisitor): class HighlightLanguageVisitor(nodes.NodeVisitor):
def __init__(self, document, default_language): def __init__(self, document: nodes.document, default_language: str) -> None:
# type: (nodes.document, str) -> None
self.default_setting = HighlightSetting(default_language, False, sys.maxsize) self.default_setting = HighlightSetting(default_language, False, sys.maxsize)
self.settings = [] # type: List[HighlightSetting] self.settings = [] # type: List[HighlightSetting]
super().__init__(document) super().__init__(document)
def unknown_visit(self, node): def unknown_visit(self, node: Node) -> None:
# type: (nodes.Node) -> None
pass pass
def unknown_departure(self, node): def unknown_departure(self, node: Node) -> None:
# type: (nodes.Node) -> None
pass pass
def visit_document(self, node): def visit_document(self, node: Node) -> None:
# type: (nodes.Node) -> None
self.settings.append(self.default_setting) self.settings.append(self.default_setting)
def depart_document(self, node): def depart_document(self, node: Node) -> None:
# type: (nodes.Node) -> None
self.settings.pop() self.settings.pop()
def visit_start_of_file(self, node): def visit_start_of_file(self, node: Node) -> None:
# type: (nodes.Node) -> None
self.settings.append(self.default_setting) self.settings.append(self.default_setting)
def depart_start_of_file(self, node): def depart_start_of_file(self, node: Node) -> None:
# type: (nodes.Node) -> None
self.settings.pop() self.settings.pop()
def visit_highlightlang(self, node): def visit_highlightlang(self, node: addnodes.highlightlang) -> None:
# type: (addnodes.highlightlang) -> None
self.settings[-1] = HighlightSetting(node['lang'], self.settings[-1] = HighlightSetting(node['lang'],
node['force'], node['force'],
node['linenothreshold']) node['linenothreshold'])
def visit_literal_block(self, node): def visit_literal_block(self, node: nodes.literal_block) -> None:
# type: (nodes.literal_block) -> None
setting = self.settings[-1] setting = self.settings[-1]
if 'language' not in node: if 'language' not in node:
node['language'] = setting.language node['language'] = setting.language
@ -105,8 +92,7 @@ class TrimDoctestFlagsTransform(SphinxTransform):
""" """
default_priority = HighlightLanguageTransform.default_priority + 1 default_priority = HighlightLanguageTransform.default_priority + 1
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
if not self.config.trim_doctest_flags: if not self.config.trim_doctest_flags:
return return
@ -118,8 +104,7 @@ class TrimDoctestFlagsTransform(SphinxTransform):
self.strip_doctest_flags(dbnode) self.strip_doctest_flags(dbnode)
@staticmethod @staticmethod
def strip_doctest_flags(node): def strip_doctest_flags(node: Union[nodes.literal_block, nodes.doctest_block]) -> None:
# type: (Union[nodes.literal_block, nodes.doctest_block]) -> None
source = node.rawsource source = node.rawsource
source = doctest.blankline_re.sub('', source) source = doctest.blankline_re.sub('', source)
source = doctest.doctestopt_re.sub('', source) source = doctest.doctestopt_re.sub('', source)
@ -127,8 +112,7 @@ class TrimDoctestFlagsTransform(SphinxTransform):
node[:] = [nodes.Text(source)] node[:] = [nodes.Text(source)]
@staticmethod @staticmethod
def is_pyconsole(node): def is_pyconsole(node: nodes.literal_block) -> bool:
# type: (nodes.literal_block) -> bool
if node.rawsource != node.astext(): if node.rawsource != node.astext():
return False # skip parsed-literal node return False # skip parsed-literal node
@ -147,8 +131,7 @@ class TrimDoctestFlagsTransform(SphinxTransform):
return False return False
def setup(app): def setup(app: Sphinx) -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_post_transform(HighlightLanguageTransform) app.add_post_transform(HighlightLanguageTransform)
app.add_post_transform(TrimDoctestFlagsTransform) app.add_post_transform(TrimDoctestFlagsTransform)

View File

@ -9,19 +9,17 @@
""" """
import warnings import warnings
from typing import Any, Dict
from docutils import nodes from docutils import nodes
from docutils.writers.docutils_xml import XMLTranslator from docutils.writers.docutils_xml import XMLTranslator
from sphinx.addnodes import math_block, displaymath from sphinx.addnodes import math_block, displaymath
from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.util import logging from sphinx.util import logging
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -35,8 +33,7 @@ class MathNodeMigrator(SphinxTransform):
""" """
default_priority = 999 default_priority = 999
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for math_node in self.document.traverse(nodes.math): for math_node in self.document.traverse(nodes.math):
# case: old styled ``math`` node generated by old extensions # case: old styled ``math`` node generated by old extensions
if len(math_node) == 0: if len(math_node) == 0:
@ -79,8 +76,7 @@ class MathNodeMigrator(SphinxTransform):
math_block_node += nodes.Text(latex, latex) math_block_node += nodes.Text(latex, latex)
def setup(app): def setup(app: Sphinx) -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_post_transform(MathNodeMigrator) app.add_post_transform(MathNodeMigrator)
return { return {

View File

@ -11,9 +11,11 @@
import os import os
from hashlib import sha1 from hashlib import sha1
from math import ceil from math import ceil
from typing import Any, Dict, List, Tuple
from docutils import nodes from docutils import nodes
from sphinx.application import Sphinx
from sphinx.locale import __ from sphinx.locale import __
from sphinx.transforms import SphinxTransform from sphinx.transforms import SphinxTransform
from sphinx.util import epoch_to_rfc1123, rfc1123_to_epoch from sphinx.util import epoch_to_rfc1123, rfc1123_to_epoch
@ -21,11 +23,6 @@ from sphinx.util import logging, requests
from sphinx.util.images import guess_mimetype, get_image_extension, parse_data_uri from sphinx.util.images import guess_mimetype, get_image_extension, parse_data_uri
from sphinx.util.osutil import ensuredir, movefile from sphinx.util.osutil import ensuredir, movefile
if False:
# For type annotation
from typing import Any, Dict, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -33,31 +30,26 @@ MAX_FILENAME_LEN = 32
class BaseImageConverter(SphinxTransform): class BaseImageConverter(SphinxTransform):
def apply(self, **kwargsj): def apply(self, **kwargs: Any) -> None:
# type: (Any) -> None
for node in self.document.traverse(nodes.image): for node in self.document.traverse(nodes.image):
if self.match(node): if self.match(node):
self.handle(node) self.handle(node)
def match(self, node): def match(self, node: nodes.image) -> bool:
# type: (nodes.image) -> bool
return True return True
def handle(self, node): def handle(self, node: nodes.image) -> None:
# type: (nodes.image) -> None
pass pass
@property @property
def imagedir(self): def imagedir(self) -> str:
# type: () -> str
return os.path.join(self.app.doctreedir, 'images') return os.path.join(self.app.doctreedir, 'images')
class ImageDownloader(BaseImageConverter): class ImageDownloader(BaseImageConverter):
default_priority = 100 default_priority = 100
def match(self, node): def match(self, node: nodes.image) -> bool:
# type: (nodes.image) -> bool
if self.app.builder.supported_image_types == []: if self.app.builder.supported_image_types == []:
return False return False
elif self.app.builder.supported_remote_images: elif self.app.builder.supported_remote_images:
@ -65,8 +57,7 @@ class ImageDownloader(BaseImageConverter):
else: else:
return '://' in node['uri'] return '://' in node['uri']
def handle(self, node): def handle(self, node: nodes.image) -> None:
# type: (nodes.image) -> None
try: try:
basename = os.path.basename(node['uri']) basename = os.path.basename(node['uri'])
if '?' in basename: if '?' in basename:
@ -123,8 +114,7 @@ class ImageDownloader(BaseImageConverter):
class DataURIExtractor(BaseImageConverter): class DataURIExtractor(BaseImageConverter):
default_priority = 150 default_priority = 150
def match(self, node): def match(self, node: nodes.image) -> bool:
# type: (nodes.image) -> bool
if self.app.builder.supported_remote_images == []: if self.app.builder.supported_remote_images == []:
return False return False
elif self.app.builder.supported_data_uri_images is True: elif self.app.builder.supported_data_uri_images is True:
@ -132,8 +122,7 @@ class DataURIExtractor(BaseImageConverter):
else: else:
return 'data:' in node['uri'] return 'data:' in node['uri']
def handle(self, node): def handle(self, node: nodes.image) -> None:
# type: (nodes.image) -> None
image = parse_data_uri(node['uri']) image = parse_data_uri(node['uri'])
ext = get_image_extension(image.mimetype) ext = get_image_extension(image.mimetype)
if ext is None: if ext is None:
@ -155,8 +144,7 @@ class DataURIExtractor(BaseImageConverter):
self.app.env.images.add_file(self.env.docname, path) self.app.env.images.add_file(self.env.docname, path)
def get_filename_for(filename, mimetype): def get_filename_for(filename: str, mimetype: str) -> str:
# type: (str, str) -> str
basename = os.path.basename(filename) basename = os.path.basename(filename)
return os.path.splitext(basename)[0] + get_image_extension(mimetype) return os.path.splitext(basename)[0] + get_image_extension(mimetype)
@ -195,15 +183,13 @@ class ImageConverter(BaseImageConverter):
#: ] #: ]
conversion_rules = [] # type: List[Tuple[str, str]] conversion_rules = [] # type: List[Tuple[str, str]]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs) -> None:
# type: (Any, Any) -> None
self.available = None # type: bool self.available = None # type: bool
# the converter is available or not. # the converter is available or not.
# Will be checked at first conversion # Will be checked at first conversion
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def match(self, node): def match(self, node: nodes.image) -> bool:
# type: (nodes.image) -> bool
if self.available is None: if self.available is None:
self.available = self.is_available() self.available = self.is_available()
@ -219,8 +205,7 @@ class ImageConverter(BaseImageConverter):
else: else:
return False return False
def get_conversion_rule(self, node): def get_conversion_rule(self, node: nodes.image) -> Tuple[str, str]:
# type: (nodes.image) -> Tuple[str, str]
for candidate in self.guess_mimetypes(node): for candidate in self.guess_mimetypes(node):
for supported in self.app.builder.supported_image_types: for supported in self.app.builder.supported_image_types:
rule = (candidate, supported) rule = (candidate, supported)
@ -229,13 +214,11 @@ class ImageConverter(BaseImageConverter):
return None return None
def is_available(self): def is_available(self) -> bool:
# type: () -> bool
"""Return the image converter is available or not.""" """Return the image converter is available or not."""
raise NotImplementedError() raise NotImplementedError()
def guess_mimetypes(self, node): def guess_mimetypes(self, node: nodes.image) -> List[str]:
# type: (nodes.image) -> List[str]
if '?' in node['candidates']: if '?' in node['candidates']:
return [] return []
elif '*' in node['candidates']: elif '*' in node['candidates']:
@ -244,8 +227,7 @@ class ImageConverter(BaseImageConverter):
else: else:
return node['candidates'].keys() return node['candidates'].keys()
def handle(self, node): def handle(self, node: nodes.image) -> None:
# type: (nodes.image) -> None
_from, _to = self.get_conversion_rule(node) _from, _to = self.get_conversion_rule(node)
if _from in node['candidates']: if _from in node['candidates']:
@ -268,8 +250,7 @@ class ImageConverter(BaseImageConverter):
self.env.original_image_uri[destpath] = srcpath self.env.original_image_uri[destpath] = srcpath
self.env.images.add_file(self.env.docname, destpath) self.env.images.add_file(self.env.docname, destpath)
def convert(self, _from, _to): def convert(self, _from: str, _to: str) -> bool:
# type: (str, str) -> bool
"""Convert a image file to expected format. """Convert a image file to expected format.
*_from* is a path for source image file, and *_to* is a path for *_from* is a path for source image file, and *_to* is a path for
@ -278,8 +259,7 @@ class ImageConverter(BaseImageConverter):
raise NotImplementedError() raise NotImplementedError()
def setup(app): def setup(app: Sphinx) -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_post_transform(ImageDownloader) app.add_post_transform(ImageDownloader)
app.add_post_transform(DataURIExtractor) app.add_post_transform(DataURIExtractor)

View File

@ -8,6 +8,8 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
from typing import Any, Dict
from docutils import nodes from docutils import nodes
from docutils.transforms.references import DanglingReferences, Substitutions from docutils.transforms.references import DanglingReferences, Substitutions
@ -15,8 +17,7 @@ from sphinx.transforms import SphinxTransform
if False: if False:
# For type annotation # For type annotation
from typing import Any, Dict # NOQA from sphinx.application import Sphinx
from sphinx.application import Sphinx # NOQA
class SubstitutionDefinitionsRemover(SphinxTransform): class SubstitutionDefinitionsRemover(SphinxTransform):
@ -25,8 +26,7 @@ class SubstitutionDefinitionsRemover(SphinxTransform):
# should be invoked after Substitutions process # should be invoked after Substitutions process
default_priority = Substitutions.default_priority + 1 default_priority = Substitutions.default_priority + 1
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for node in self.document.traverse(nodes.substitution_definition): for node in self.document.traverse(nodes.substitution_definition):
node.parent.remove(node) node.parent.remove(node)
@ -34,8 +34,7 @@ class SubstitutionDefinitionsRemover(SphinxTransform):
class SphinxDanglingReferences(DanglingReferences): class SphinxDanglingReferences(DanglingReferences):
"""DanglingReferences transform which does not output info messages.""" """DanglingReferences transform which does not output info messages."""
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
try: try:
reporter = self.document.reporter reporter = self.document.reporter
report_level = reporter.report_level report_level = reporter.report_level
@ -51,14 +50,12 @@ class SphinxDomains(SphinxTransform):
"""Collect objects to Sphinx domains for cross references.""" """Collect objects to Sphinx domains for cross references."""
default_priority = 850 default_priority = 850
def apply(self, **kwargs): def apply(self, **kwargs) -> None:
# type: (Any) -> None
for domain in self.env.domains.values(): for domain in self.env.domains.values():
domain.process_doc(self.env, self.env.docname, self.document) domain.process_doc(self.env, self.env.docname, self.document)
def setup(app): def setup(app: "Sphinx") -> Dict[str, Any]:
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(SubstitutionDefinitionsRemover) app.add_transform(SubstitutionDefinitionsRemover)
app.add_transform(SphinxDanglingReferences) app.add_transform(SphinxDanglingReferences)
app.add_transform(SphinxDomains) app.add_transform(SphinxDomains)