Merge pull request #6431 from tk0miya/refactor_type_annotation3

Migrate to py3 style type annotation: sphinx.util (part2)
This commit is contained in:
Takeshi KOMIYA 2019-06-03 22:21:01 +09:00 committed by GitHub
commit 127d4db0d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 210 additions and 396 deletions

View File

@ -10,23 +10,22 @@
import sys
import warnings
from typing import Any, Dict
from docutils.utils import get_source_line
from sphinx import addnodes
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning
from sphinx.transforms import SphinxTransform
from sphinx.util import import_object
if False:
# For type annotation
from typing import Any, Dict # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.application import Sphinx
def deprecate_source_parsers(app, config):
# type: (Sphinx, Config) -> None
def deprecate_source_parsers(app: "Sphinx", config: Config) -> None:
if config.source_parsers:
warnings.warn('The config variable "source_parsers" is deprecated. '
'Please update your extension for the parser and remove the setting.',
@ -37,8 +36,7 @@ def deprecate_source_parsers(app, config):
app.add_source_parser(suffix, parser)
def register_application_for_autosummary(app):
# type: (Sphinx) -> None
def register_application_for_autosummary(app: "Sphinx") -> None:
"""Register application object to autosummary module.
Since Sphinx-1.7, documenters and attrgetters are registered into
@ -55,8 +53,7 @@ class IndexEntriesMigrator(SphinxTransform):
"""Migrating indexentries from old style (4columns) to new style (5columns)."""
default_priority = 700
def apply(self, **kwargs):
# type: (Any) -> None
def apply(self, **kwargs) -> None:
for node in self.document.traverse(addnodes.index):
for i, entries in enumerate(node['entries']):
if len(entries) == 4:
@ -66,8 +63,7 @@ class IndexEntriesMigrator(SphinxTransform):
node['entries'][i] = entries + (None,)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_transform(IndexEntriesMigrator)
app.connect('config-inited', deprecate_source_parsers)
app.connect('builder-inited', register_application_for_autosummary)

View File

@ -11,6 +11,7 @@
import os
import re
import sys
from typing import Dict
try:
# check if colorama is installed to support color on Windows
@ -18,23 +19,17 @@ try:
except ImportError:
colorama = None
if False:
# For type annotation
from typing import Dict # NOQA
_ansi_re = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm')
codes = {} # type: Dict[str, str]
def terminal_safe(s):
# type: (str) -> str
def terminal_safe(s: str) -> str:
"""safely encode a string for printing to the terminal."""
return s.encode('ascii', 'backslashreplace').decode('ascii')
def get_terminal_width():
# type: () -> int
def get_terminal_width() -> int:
"""Borrowed from the py lib."""
try:
import termios
@ -53,8 +48,7 @@ def get_terminal_width():
_tw = get_terminal_width()
def term_width_line(text):
# type: (str) -> str
def term_width_line(text: str) -> str:
if not codes:
# if no coloring, don't output fancy backspaces
return text + '\n'
@ -63,8 +57,7 @@ def term_width_line(text):
return text.ljust(_tw + len(text) - len(_ansi_re.sub('', text))) + '\r'
def color_terminal():
# type: () -> bool
def color_terminal() -> bool:
if sys.platform == 'win32' and colorama is not None:
colorama.init()
return True
@ -80,21 +73,18 @@ def color_terminal():
return False
def nocolor():
# type: () -> None
def nocolor() -> None:
if sys.platform == 'win32' and colorama is not None:
colorama.deinit()
codes.clear()
def coloron():
# type: () -> None
def coloron() -> None:
codes.update(_orig_codes)
def colorize(name, text, input_mode=False):
# type: (str, str, bool) -> str
def escseq(name):
def colorize(name: str, text: str, input_mode: bool = False) -> str:
def escseq(name: str) -> str:
# Wrap escape sequence with ``\1`` and ``\2`` to let readline know
# it is non-printable characters
# ref: https://tiswww.case.edu/php/chet/readline/readline.html
@ -109,15 +99,12 @@ def colorize(name, text, input_mode=False):
return escseq(name) + text + escseq('reset')
def strip_colors(s):
# type: (str) -> str
def strip_colors(s: str) -> str:
return re.compile('\x1b.*?m').sub('', s)
def create_color_func(name):
# type: (str) -> None
def inner(text):
# type: (str) -> str
def create_color_func(name: str) -> None:
def inner(text: str) -> str:
return colorize(name, text)
globals()[name] = inner

View File

@ -10,23 +10,23 @@
"""
import warnings
from typing import List, Tuple, cast
from typing import Any, Dict, List, Tuple, Type, Union
from typing import cast
from docutils import nodes
from docutils.nodes import Node
from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.util.typing import TextlikeNode
if False:
# For type annotation
from typing import Any, Dict, Type, Union # NOQA
from sphinx.directive import ObjectDescription # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import TextlikeNode # NOQA
from sphinx.environment import BuildEnvironment
from sphinx.directive import ObjectDescription
def _is_single_paragraph(node):
# type: (nodes.field_body) -> bool
def _is_single_paragraph(node: nodes.field_body) -> bool:
"""True if the node only contains one paragraph (and system messages)."""
if len(node) == 0:
return False
@ -55,9 +55,8 @@ class Field:
is_grouped = False
is_typed = False
def __init__(self, name, names=(), label=None, has_arg=True, rolename=None,
bodyrolename=None):
# type: (str, Tuple[str, ...], str, bool, str, str) -> None
def __init__(self, name: str, names: Tuple[str, ...] = (), label: str = None,
has_arg: bool = True, rolename: str = None, bodyrolename: str = None) -> None:
self.name = name
self.names = names
self.label = label
@ -65,15 +64,9 @@ class Field:
self.rolename = rolename
self.bodyrolename = bodyrolename
def make_xref(self,
rolename, # type: str
domain, # type: str
target, # type: str
innernode=addnodes.literal_emphasis, # type: Type[TextlikeNode]
contnode=None, # type: nodes.Node
env=None, # type: BuildEnvironment
):
# type: (...) -> nodes.Node
def make_xref(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = addnodes.literal_emphasis,
contnode: Node = None, env: "BuildEnvironment" = None) -> Node:
if not rolename:
return contnode or innernode(target, target)
refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
@ -83,28 +76,16 @@ class Field:
env.get_domain(domain).process_field_xref(refnode)
return refnode
def make_xrefs(self,
rolename, # type: str
domain, # type: str
target, # type: str
innernode=addnodes.literal_emphasis, # type: Type[TextlikeNode]
contnode=None, # type: nodes.Node
env=None, # type: BuildEnvironment
):
# type: (...) -> List[nodes.Node]
def make_xrefs(self, rolename: str, domain: str, target: str,
innernode: Type[TextlikeNode] = addnodes.literal_emphasis,
contnode: Node = None, env: "BuildEnvironment" = None) -> List[Node]:
return [self.make_xref(rolename, domain, target, innernode, contnode, env)]
def make_entry(self, fieldarg, content):
# type: (str, List[nodes.Node]) -> Tuple[str, List[nodes.Node]]
def make_entry(self, fieldarg: str, content: List[Node]) -> Tuple[str, List[Node]]:
return (fieldarg, content)
def make_field(self,
types, # type: Dict[str, List[nodes.Node]]
domain, # type: str
item, # type: Tuple
env=None, # type: BuildEnvironment
):
# type: (...) -> nodes.field
def make_field(self, types: Dict[str, List[Node]], domain: str,
item: Tuple, env: "BuildEnvironment" = None) -> nodes.field:
fieldarg, content = item
fieldname = nodes.field_name('', self.label)
if fieldarg:
@ -138,19 +119,13 @@ class GroupedField(Field):
is_grouped = True
list_type = nodes.bullet_list
def __init__(self, name, names=(), label=None, rolename=None,
can_collapse=False):
# type: (str, Tuple[str, ...], str, str, bool) -> None
def __init__(self, name: str, names: Tuple[str, ...] = (), label: str = None,
rolename: str = None, can_collapse: bool = False) -> None:
super().__init__(name, names, label, True, rolename)
self.can_collapse = can_collapse
def make_field(self,
types, # type: Dict[str, List[nodes.Node]]
domain, # type: str
items, # type: Tuple
env=None, # type: BuildEnvironment
):
# type: (...) -> nodes.field
def make_field(self, types: Dict[str, List[Node]], domain: str,
items: Tuple, env: "BuildEnvironment" = None) -> nodes.field:
fieldname = nodes.field_name('', self.label)
listnode = self.list_type()
for fieldarg, content in items:
@ -191,22 +166,16 @@ class TypedField(GroupedField):
"""
is_typed = True
def __init__(self, name, names=(), typenames=(), label=None,
rolename=None, typerolename=None, can_collapse=False):
# type: (str, Tuple[str, ...], Tuple[str, ...], str, str, str, bool) -> None
def __init__(self, name: str, names: Tuple[str, ...] = (), typenames: Tuple[str, ...] = (),
label: str = None, rolename: str = None, typerolename: str = None,
can_collapse: bool = False) -> None:
super().__init__(name, names, label, rolename, can_collapse)
self.typenames = typenames
self.typerolename = typerolename
def make_field(self,
types, # type: Dict[str, List[nodes.Node]]
domain, # type: str
items, # type: Tuple
env=None, # type: BuildEnvironment
):
# type: (...) -> nodes.field
def handle_item(fieldarg, content):
# type: (str, str) -> nodes.paragraph
def make_field(self, types: Dict[str, List[Node]], domain: str,
items: Tuple, env: "BuildEnvironment" = None) -> nodes.field:
def handle_item(fieldarg: str, content: str) -> nodes.paragraph:
par = nodes.paragraph()
par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
addnodes.literal_strong, env=env))
@ -246,13 +215,11 @@ class DocFieldTransformer:
"""
typemap = None # type: Dict[str, Tuple[Field, bool]]
def __init__(self, directive):
# type: (ObjectDescription) -> None
def __init__(self, directive: "ObjectDescription") -> None:
self.directive = directive
self.typemap = directive.get_field_type_map()
def preprocess_fieldtypes(self, types):
# type: (List[Field]) -> Dict[str, Tuple[Field, bool]]
def preprocess_fieldtypes(self, types: List[Field]) -> Dict[str, Tuple[Field, bool]]:
warnings.warn('DocFieldTransformer.preprocess_fieldtypes() is deprecated.',
RemovedInSphinx40Warning)
typemap = {}
@ -265,16 +232,14 @@ class DocFieldTransformer:
typemap[name] = typed_field, True
return typemap
def transform_all(self, node):
# type: (addnodes.desc_content) -> None
def transform_all(self, node: addnodes.desc_content) -> None:
"""Transform all field list children of a node."""
# don't traverse, only handle field lists that are immediate children
for child in node:
if isinstance(child, nodes.field_list):
self.transform(child)
def transform(self, node):
# type: (nodes.field_list) -> None
def transform(self, node: nodes.field_list) -> None:
"""Transform a single field list *node*."""
typemap = self.typemap

View File

@ -16,33 +16,33 @@ from contextlib import contextmanager
from copy import copy
from distutils.version import LooseVersion
from os import path
from typing import IO, cast
from types import ModuleType
from typing import Any, Callable, Dict, Generator, IO, List, Set, Tuple, Type
from typing import cast
import docutils
from docutils import nodes
from docutils.io import FileOutput
from docutils.nodes import Element, Node, system_message
from docutils.parsers.rst import Directive, directives, roles, convert_directive_function
from docutils.statemachine import StateMachine
from docutils.parsers.rst.states import Inliner
from docutils.statemachine import StateMachine, State, StringList
from docutils.utils import Reporter, unescape
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.errors import ExtensionError, SphinxError
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.typing import RoleFunction
logger = logging.getLogger(__name__)
report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\\d+)?\\) ')
if False:
# For type annotation
from types import ModuleType # NOQA
from typing import Any, Callable, Dict, Generator, List, Set, Tuple, Type # NOQA
from docutils.parsers.rst.states import Inliner # NOQA
from docutils.statemachine import State, StringList # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import RoleFunction # NOQA
from sphinx.builders import Builder
from sphinx.config import Config
from sphinx.environment import BuildEnvironment
__version_info__ = tuple(LooseVersion(docutils.__version__).version)
@ -50,8 +50,7 @@ additional_nodes = set() # type: Set[Type[nodes.Element]]
@contextmanager
def docutils_namespace():
# type: () -> Generator[None, None, None]
def docutils_namespace() -> Generator[None, None, None]:
"""Create namespace for reST parsers."""
try:
_directives = copy(directives._directives) # type: ignore
@ -67,14 +66,12 @@ def docutils_namespace():
additional_nodes.discard(node)
def is_directive_registered(name):
# type: (str) -> bool
def is_directive_registered(name: str) -> bool:
"""Check the *name* directive is already registered."""
return name in directives._directives # type: ignore
def register_directive(name, directive):
# type: (str, Type[Directive]) -> None
def register_directive(name: str, directive: Type[Directive]) -> None:
"""Register a directive to docutils.
This modifies global state of docutils. So it is better to use this
@ -83,14 +80,12 @@ def register_directive(name, directive):
directives.register_directive(name, directive)
def is_role_registered(name):
# type: (str) -> bool
def is_role_registered(name: str) -> bool:
"""Check the *name* role is already registered."""
return name in roles._roles # type: ignore
def register_role(name, role):
# type: (str, RoleFunction) -> None
def register_role(name: str, role: RoleFunction) -> None:
"""Register a role to docutils.
This modifies global state of docutils. So it is better to use this
@ -99,20 +94,17 @@ def register_role(name, role):
roles.register_local_role(name, role)
def unregister_role(name):
# type: (str) -> None
def unregister_role(name: str) -> None:
"""Unregister a role from docutils."""
roles._roles.pop(name, None) # type: ignore
def is_node_registered(node):
# type: (Type[nodes.Element]) -> bool
def is_node_registered(node: Type[Element]) -> bool:
"""Check the *node* is already registered."""
return hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__)
def register_node(node):
# type: (Type[nodes.Element]) -> None
def register_node(node: Type[Element]) -> None:
"""Register a node to docutils.
This modifies global state of some visitors. So it is better to use this
@ -123,8 +115,7 @@ def register_node(node):
additional_nodes.add(node)
def unregister_node(node):
# type: (Type[nodes.Element]) -> None
def unregister_node(node: Type[Element]) -> None:
"""Unregister a node from docutils.
This is inverse of ``nodes._add_nodes_class_names()``.
@ -137,8 +128,7 @@ def unregister_node(node):
@contextmanager
def patched_get_language():
# type: () -> Generator[None, None, None]
def patched_get_language() -> Generator[None, None, None]:
"""Patch docutils.languages.get_language() temporarily.
This ignores the second argument ``reporter`` to suppress warnings.
@ -146,8 +136,7 @@ def patched_get_language():
"""
from docutils.languages import get_language
def patched_get_language(language_code, reporter=None):
# type: (str, Reporter) -> Any
def patched_get_language(language_code: str, reporter: Reporter = None) -> Any:
return get_language(language_code)
try:
@ -159,8 +148,7 @@ def patched_get_language():
@contextmanager
def using_user_docutils_conf(confdir):
# type: (str) -> Generator[None, None, None]
def using_user_docutils_conf(confdir: str) -> Generator[None, None, None]:
"""Let docutils know the location of ``docutils.conf`` for Sphinx."""
try:
docutilsconfig = os.environ.get('DOCUTILSCONFIG', None)
@ -176,8 +164,7 @@ def using_user_docutils_conf(confdir):
@contextmanager
def patch_docutils(confdir=None):
# type: (str) -> Generator[None, None, None]
def patch_docutils(confdir: str = None) -> Generator[None, None, None]:
"""Patch to docutils temporarily."""
with patched_get_language(), using_user_docutils_conf(confdir):
yield
@ -191,35 +178,30 @@ class sphinx_domains:
"""Monkey-patch directive and role dispatch, so that domain-specific
markup takes precedence.
"""
def __init__(self, env):
# type: (BuildEnvironment) -> None
def __init__(self, env: "BuildEnvironment") -> None:
self.env = env
self.directive_func = None # type: Callable
self.roles_func = None # type: Callable
def __enter__(self):
# type: () -> None
def __enter__(self) -> None:
self.enable()
def __exit__(self, type, value, traceback):
# type: (str, str, str) -> None
def __exit__(self, exc_type: Type[Exception], exc_value: Exception, traceback: Any) -> bool: # NOQA
self.disable()
return True
def enable(self):
# type: () -> None
def enable(self) -> None:
self.directive_func = directives.directive
self.role_func = roles.role
directives.directive = self.lookup_directive # type: ignore
roles.role = self.lookup_role # type: ignore
def disable(self):
# type: () -> None
def disable(self) -> None:
directives.directive = self.directive_func
roles.role = self.role_func
def lookup_domain_element(self, type, name):
# type: (str, str) -> Any
def lookup_domain_element(self, type: str, name: str) -> Any:
"""Lookup a markup element (directive or role), given its name which can
be a full name (with domain).
"""
@ -247,15 +229,13 @@ class sphinx_domains:
raise ElementLookupError
def lookup_directive(self, name, lang_module, document):
# type: (str, ModuleType, nodes.document) -> Tuple[Type[Directive], List[nodes.system_message]] # NOQA
def lookup_directive(self, name: str, lang_module: ModuleType, document: nodes.document) -> Tuple[Type[Directive], List[system_message]]: # NOQA
try:
return self.lookup_domain_element('directive', name)
except ElementLookupError:
return self.directive_func(name, lang_module, document)
def lookup_role(self, name, lang_module, lineno, reporter):
# type: (str, ModuleType, int, Reporter) -> Tuple[RoleFunction, List[nodes.system_message]] # NOQA
def lookup_role(self, name: str, lang_module: ModuleType, lineno: int, reporter: Reporter) -> Tuple[RoleFunction, List[system_message]]: # NOQA
try:
return self.lookup_domain_element('role', name)
except ElementLookupError:
@ -263,8 +243,7 @@ class sphinx_domains:
class WarningStream:
def write(self, text):
# type: (str) -> None
def write(self, text: str) -> None:
matched = report_re.search(text)
if not matched:
logger.warning(text.rstrip("\r\n"))
@ -276,16 +255,14 @@ class WarningStream:
class LoggingReporter(Reporter):
@classmethod
def from_reporter(cls, reporter):
# type: (Reporter) -> LoggingReporter
def from_reporter(cls, reporter: Reporter) -> "LoggingReporter":
"""Create an instance of LoggingReporter from other reporter object."""
return cls(reporter.source, reporter.report_level, reporter.halt_level,
reporter.debug_flag, reporter.error_handler)
def __init__(self, source, report_level=Reporter.WARNING_LEVEL,
halt_level=Reporter.SEVERE_LEVEL, debug=False,
error_handler='backslashreplace'):
# type: (str, int, int, bool, str) -> None
def __init__(self, source: str, report_level: int = Reporter.WARNING_LEVEL,
halt_level: int = Reporter.SEVERE_LEVEL, debug: bool = False,
error_handler: str = 'backslashreplace') -> None:
stream = cast(IO, WarningStream())
super().__init__(source, report_level, halt_level,
stream, debug, error_handler=error_handler)
@ -294,18 +271,15 @@ class LoggingReporter(Reporter):
class NullReporter(Reporter):
"""A dummy reporter; write nothing."""
def __init__(self):
# type: () -> None
def __init__(self) -> None:
super().__init__('', 999, 4)
def is_html5_writer_available():
# type: () -> bool
def is_html5_writer_available() -> bool:
return __version_info__ > (0, 13, 0)
def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
# type: (Any, bool, Tuple[int, int, bool], Any) -> Any
def directive_helper(obj: Any, has_content: bool = None, argument_spec: Tuple[int, int, bool] = None, **option_spec) -> Any: # NOQA
warnings.warn('function based directive support is now deprecated. '
'Use class based directive instead.',
RemovedInSphinx30Warning)
@ -323,8 +297,7 @@ def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
@contextmanager
def switch_source_input(state, content):
# type: (State, StringList) -> Generator[None, None, None]
def switch_source_input(state: State, content: StringList) -> Generator[None, None, None]:
"""Switch current source input of state temporarily."""
try:
# remember the original ``get_source_and_line()`` method
@ -344,13 +317,11 @@ def switch_source_input(state, content):
class SphinxFileOutput(FileOutput):
"""Better FileOutput class for Sphinx."""
def __init__(self, **kwargs):
# type: (Any) -> None
def __init__(self, **kwargs) -> None:
self.overwrite_if_changed = kwargs.pop('overwrite_if_changed', False)
super().__init__(**kwargs)
def write(self, data):
# type: (str) -> str
def write(self, data: str) -> str:
if (self.destination_path and self.autoclose and 'b' not in self.mode and
self.overwrite_if_changed and os.path.exists(self.destination_path)):
with open(self.destination_path, encoding=self.encoding) as f:
@ -371,19 +342,16 @@ class SphinxDirective(Directive):
"""
@property
def env(self):
# type: () -> BuildEnvironment
def env(self) -> "BuildEnvironment":
"""Reference to the :class:`.BuildEnvironment` object."""
return self.state.document.settings.env
@property
def config(self):
# type: () -> Config
def config(self) -> "Config":
"""Reference to the :class:`.Config` object."""
return self.env.config
def set_source_info(self, node):
# type: (nodes.Node) -> None
def set_source_info(self, node: Node) -> None:
"""Set source and line number to the node."""
node.source, node.line = self.state_machine.get_source_and_line(self.lineno)
@ -406,8 +374,9 @@ class SphinxRole:
content = None #: A list of strings, the directive content for customization
#: (from the "role" directive).
def __call__(self, name, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def __call__(self, name: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
self.rawtext = rawtext
self.text = unescape(text)
self.lineno = lineno
@ -427,24 +396,20 @@ class SphinxRole:
return self.run()
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
raise NotImplementedError
@property
def env(self):
# type: () -> BuildEnvironment
def env(self) -> "BuildEnvironment":
"""Reference to the :class:`.BuildEnvironment` object."""
return self.inliner.document.settings.env
@property
def config(self):
# type: () -> Config
def config(self) -> "Config":
"""Reference to the :class:`.Config` object."""
return self.env.config
def set_source_info(self, node, lineno=None):
# type: (nodes.Node, int) -> None
def set_source_info(self, node: Node, lineno: int = None) -> None:
if lineno is None:
lineno = self.lineno
@ -466,8 +431,9 @@ class ReferenceRole(SphinxRole):
# \x00 means the "<" was backslash-escaped
explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)
def __call__(self, name, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def __call__(self, name: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
matched = self.explicit_title_re.match(text)
if matched:
self.has_explicit_title = True
@ -490,8 +456,7 @@ class SphinxTranslator(nodes.NodeVisitor):
This class is strongly coupled with Sphinx.
"""
def __init__(self, document, builder):
# type: (nodes.document, Builder) -> None
def __init__(self, document: nodes.document, builder: "Builder") -> None:
super().__init__(document)
self.builder = builder
self.config = builder.config
@ -503,8 +468,7 @@ class SphinxTranslator(nodes.NodeVisitor):
__document_cache__ = None # type: nodes.document
def new_document(source_path, settings=None):
# type: (str, Any) -> nodes.document
def new_document(source_path: str, settings: Any = None) -> nodes.document:
"""Return a new empty document object. This is an alternative of docutils'.
This is a simple wrapper for ``docutils.utils.new_document()``. It

View File

@ -10,20 +10,20 @@
import os
import re
import zlib
from typing import Callable, IO, Iterator
from sphinx.util import logging
if False:
# For type annotation
from typing import Callable, IO, Iterator # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import Inventory # NOQA
from sphinx.util.typing import Inventory
BUFSIZE = 16 * 1024
logger = logging.getLogger(__name__)
if False:
# For type annotation
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
class InventoryFileReader:
"""A file reader for inventory file.
@ -31,21 +31,18 @@ class InventoryFileReader:
This reader supports mixture of texts and compressed texts.
"""
def __init__(self, stream):
# type: (IO) -> None
def __init__(self, stream: IO) -> None:
self.stream = stream
self.buffer = b''
self.eof = False
def read_buffer(self):
# type: () -> None
def read_buffer(self) -> None:
chunk = self.stream.read(BUFSIZE)
if chunk == b'':
self.eof = True
self.buffer += chunk
def readline(self):
# type: () -> str
def readline(self) -> str:
pos = self.buffer.find(b'\n')
if pos != -1:
line = self.buffer[:pos].decode()
@ -59,15 +56,13 @@ class InventoryFileReader:
return line
def readlines(self):
# type: () -> Iterator[str]
def readlines(self) -> Iterator[str]:
while not self.eof:
line = self.readline()
if line:
yield line
def read_compressed_chunks(self):
# type: () -> Iterator[bytes]
def read_compressed_chunks(self) -> Iterator[bytes]:
decompressor = zlib.decompressobj()
while not self.eof:
self.read_buffer()
@ -75,8 +70,7 @@ class InventoryFileReader:
self.buffer = b''
yield decompressor.flush()
def read_compressed_lines(self):
# type: () -> Iterator[str]
def read_compressed_lines(self) -> Iterator[str]:
buf = b''
for chunk in self.read_compressed_chunks():
buf += chunk
@ -89,8 +83,7 @@ class InventoryFileReader:
class InventoryFile:
@classmethod
def load(cls, stream, uri, joinfunc):
# type: (IO, str, Callable) -> Inventory
def load(cls, stream: IO, uri: str, joinfunc: Callable) -> Inventory:
reader = InventoryFileReader(stream)
line = reader.readline().rstrip()
if line == '# Sphinx inventory version 1':
@ -101,8 +94,7 @@ class InventoryFile:
raise ValueError('invalid inventory header: %s' % line)
@classmethod
def load_v1(cls, stream, uri, join):
# type: (InventoryFileReader, str, Callable) -> Inventory
def load_v1(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory:
invdata = {} # type: Inventory
projname = stream.readline().rstrip()[11:]
version = stream.readline().rstrip()[11:]
@ -120,8 +112,7 @@ class InventoryFile:
return invdata
@classmethod
def load_v2(cls, stream, uri, join):
# type: (InventoryFileReader, str, Callable) -> Inventory
def load_v2(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory:
invdata = {} # type: Inventory
projname = stream.readline().rstrip()[11:]
version = stream.readline().rstrip()[11:]
@ -150,10 +141,8 @@ class InventoryFile:
return invdata
@classmethod
def dump(cls, filename, env, builder):
# type: (str, BuildEnvironment, Builder) -> None
def escape(string):
# type: (str) -> str
def dump(cls, filename: str, env: "BuildEnvironment", builder: "Builder") -> None:
def escape(string: str) -> str:
return re.sub("\\s+", " ", string)
with open(os.path.join(filename), 'wb') as f:

View File

@ -10,10 +10,7 @@
"""
import re
if False:
# For type annotation
from typing import Any, Dict, IO, List, Match, Union # NOQA
from typing import Any, Dict, IO, List, Match, Union
_str_re = re.compile(r'"(\\\\|\\"|[^"])*"')
_int_re = re.compile(r'\d+')
@ -35,10 +32,8 @@ ESCAPE_DICT = {
ESCAPED = re.compile(r'\\u.{4}|\\.')
def encode_string(s):
# type: (str) -> str
def replace(match):
# type: (Match) -> str
def encode_string(s: str) -> str:
def replace(match: Match) -> str:
s = match.group(0)
try:
return ESCAPE_DICT[s]
@ -55,8 +50,7 @@ def encode_string(s):
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
def decode_string(s):
# type: (str) -> str
def decode_string(s: str) -> str:
return ESCAPED.sub(lambda m: eval('"' + m.group() + '"'), s)
@ -78,8 +72,7 @@ do import static with
double in super""".split())
def dumps(obj, key=False):
# type: (Any, bool) -> str
def dumps(obj: Any, key: bool = False) -> str:
if key:
if not isinstance(obj, str):
obj = str(obj)
@ -107,13 +100,11 @@ def dumps(obj, key=False):
raise TypeError(type(obj))
def dump(obj, f):
# type: (Any, IO) -> None
def dump(obj: Any, f: IO) -> None:
f.write(dumps(obj))
def loads(x):
# type: (str) -> Any
def loads(x: str) -> Any:
"""Loader that can read the JS subset the indexer produces."""
nothing = object()
i = 0
@ -205,6 +196,5 @@ def loads(x):
return obj
def load(f):
# type: (IO) -> Any
def load(f: IO) -> Any:
return loads(f.read())

View File

@ -11,13 +11,10 @@
import json
import warnings
from collections import UserString
from typing import Any, IO
from sphinx.deprecation import RemovedInSphinx40Warning
if False:
# For type annotation
from typing import Any, IO # NOQA
warnings.warn('sphinx.util.jsonimpl is deprecated',
RemovedInSphinx40Warning, stacklevel=2)
@ -25,30 +22,25 @@ warnings.warn('sphinx.util.jsonimpl is deprecated',
class SphinxJSONEncoder(json.JSONEncoder):
"""JSONEncoder subclass that forces translation proxies."""
def default(self, obj):
# type: (Any) -> str
def default(self, obj: Any) -> str:
if isinstance(obj, UserString):
return str(obj)
return super().default(obj)
def dump(obj, fp, *args, **kwds):
# type: (Any, IO, Any, Any) -> None
def dump(obj: Any, fp: IO, *args, **kwds) -> None:
kwds['cls'] = SphinxJSONEncoder
json.dump(obj, fp, *args, **kwds)
def dumps(obj, *args, **kwds):
# type: (Any, Any, Any) -> str
def dumps(obj: Any, *args, **kwds) -> str:
kwds['cls'] = SphinxJSONEncoder
return json.dumps(obj, *args, **kwds)
def load(*args, **kwds):
# type: (Any, Any) -> Any
def load(*args, **kwds) -> Any:
return json.load(*args, **kwds)
def loads(*args, **kwds):
# type: (Any, Any) -> Any
def loads(*args, **kwds) -> Any:
return json.loads(*args, **kwds)

View File

@ -12,8 +12,10 @@ import logging
import logging.handlers
from collections import defaultdict
from contextlib import contextmanager
from typing import Any, Dict, Generator, IO, List, Tuple, Type, Union
from docutils import nodes
from docutils.nodes import Node
from docutils.utils import get_source_line
from sphinx.errors import SphinxWarning
@ -21,8 +23,7 @@ from sphinx.util.console import colorize
if False:
# For type annotation
from typing import Any, Dict, Generator, IO, List, Tuple, Type, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.application import Sphinx
NAMESPACE = 'sphinx'
@ -54,8 +55,7 @@ COLOR_MAP = defaultdict(lambda: 'blue',
})
def getLogger(name):
# type: (str) -> SphinxLoggerAdapter
def getLogger(name: str) -> "SphinxLoggerAdapter":
"""Get logger wrapped by :class:`sphinx.util.logging.SphinxLoggerAdapter`.
Sphinx logger always uses ``sphinx.*`` namespace to be independent from
@ -77,8 +77,7 @@ def getLogger(name):
return SphinxLoggerAdapter(logger, {})
def convert_serializable(records):
# type: (List[logging.LogRecord]) -> None
def convert_serializable(records: List[logging.LogRecord]) -> None:
"""Convert LogRecord serializable."""
for r in records:
# extract arguments to a message and clear them
@ -95,8 +94,7 @@ class SphinxLogRecord(logging.LogRecord):
prefix = ''
location = None # type: Any
def getMessage(self):
# type: () -> str
def getMessage(self) -> str:
message = super().getMessage()
location = getattr(self, 'location', None)
if location:
@ -120,20 +118,17 @@ class SphinxWarningLogRecord(SphinxLogRecord):
class SphinxLoggerAdapter(logging.LoggerAdapter):
"""LoggerAdapter allowing ``type`` and ``subtype`` keywords."""
def log(self, level, msg, *args, **kwargs):
# type: (Union[int, str], str, Any, Any) -> None
def log(self, level: Union[int, str], msg: str, *args, **kwargs) -> None:
if isinstance(level, int):
super().log(level, msg, *args, **kwargs)
else:
levelno = LEVEL_NAMES[level]
super().log(levelno, msg, *args, **kwargs)
def verbose(self, msg, *args, **kwargs):
# type: (str, Any, Any) -> None
def verbose(self, msg: str, *args, **kwargs) -> None:
self.log(VERBOSE, msg, *args, **kwargs)
def process(self, msg, kwargs): # type: ignore
# type: (str, Dict) -> Tuple[str, Dict]
def process(self, msg: str, kwargs: Dict) -> Tuple[str, Dict]: # type: ignore
extra = kwargs.setdefault('extra', {})
if 'type' in kwargs:
extra['type'] = kwargs.pop('type')
@ -148,8 +143,7 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
return msg, kwargs
def handle(self, record):
# type: (logging.LogRecord) -> None
def handle(self, record: logging.LogRecord) -> None:
self.logger.handle(record)
@ -161,8 +155,7 @@ class WarningStreamHandler(logging.StreamHandler):
class NewLineStreamHandler(logging.StreamHandler):
"""StreamHandler which switches line terminator by record.nonl flag."""
def emit(self, record):
# type: (logging.LogRecord) -> None
def emit(self, record: logging.LogRecord) -> None:
try:
self.acquire()
if getattr(record, 'nonl', False):
@ -177,16 +170,13 @@ class NewLineStreamHandler(logging.StreamHandler):
class MemoryHandler(logging.handlers.BufferingHandler):
"""Handler buffering all logs."""
def __init__(self):
# type: () -> None
def __init__(self) -> None:
super().__init__(-1)
def shouldFlush(self, record):
# type: (logging.LogRecord) -> bool
def shouldFlush(self, record: logging.LogRecord) -> bool:
return False # never flush
def flushTo(self, logger):
# type: (logging.Logger) -> None
def flushTo(self, logger: logging.Logger) -> None:
self.acquire()
try:
for record in self.buffer:
@ -195,15 +185,13 @@ class MemoryHandler(logging.handlers.BufferingHandler):
finally:
self.release()
def clear(self):
# type: () -> List[logging.LogRecord]
def clear(self) -> List[logging.LogRecord]:
buffer, self.buffer = self.buffer, []
return buffer
@contextmanager
def pending_warnings():
# type: () -> Generator
def pending_warnings() -> Generator[logging.Handler, None, None]:
"""Contextmanager to pend logging warnings temporary.
Similar to :func:`pending_logging`.
@ -231,8 +219,7 @@ def pending_warnings():
@contextmanager
def pending_logging():
# type: () -> Generator
def pending_logging() -> Generator[MemoryHandler, None, None]:
"""Contextmanager to pend logging all logs temporary.
For example::
@ -264,8 +251,7 @@ def pending_logging():
@contextmanager
def skip_warningiserror(skip=True):
# type: (bool) -> Generator
def skip_warningiserror(skip: bool = True) -> Generator[None, None, None]:
"""contextmanager to skip WarningIsErrorFilter for a while."""
logger = logging.getLogger(NAMESPACE)
@ -285,8 +271,7 @@ def skip_warningiserror(skip=True):
@contextmanager
def prefixed_warnings(prefix):
# type: (str) -> Generator
def prefixed_warnings(prefix: str) -> Generator[None, None, None]:
"""Prepend prefix to all records for a while.
For example::
@ -332,13 +317,11 @@ def prefixed_warnings(prefix):
class LogCollector:
def __init__(self):
# type: () -> None
def __init__(self) -> None:
self.logs = [] # type: List[logging.LogRecord]
@contextmanager
def collect(self):
# type: () -> Generator
def collect(self) -> Generator[None, None, None]:
with pending_logging() as memhandler:
yield
@ -348,16 +331,14 @@ class LogCollector:
class InfoFilter(logging.Filter):
"""Filter error and warning messages."""
def filter(self, record):
# type: (logging.LogRecord) -> bool
def filter(self, record: logging.LogRecord) -> bool:
if record.levelno < logging.WARNING:
return True
else:
return False
def is_suppressed_warning(type, subtype, suppress_warnings):
# type: (str, str, List[str]) -> bool
def is_suppressed_warning(type: str, subtype: str, suppress_warnings: List[str]) -> bool:
"""Check the warning is suppressed or not."""
if type is None:
return False
@ -379,13 +360,11 @@ def is_suppressed_warning(type, subtype, suppress_warnings):
class WarningSuppressor(logging.Filter):
"""Filter logs by `suppress_warnings`."""
def __init__(self, app):
# type: (Sphinx) -> None
def __init__(self, app: "Sphinx") -> None:
self.app = app
super().__init__()
def filter(self, record):
# type: (logging.LogRecord) -> bool
def filter(self, record: logging.LogRecord) -> bool:
type = getattr(record, 'type', None)
subtype = getattr(record, 'subtype', None)
@ -405,13 +384,11 @@ class WarningSuppressor(logging.Filter):
class WarningIsErrorFilter(logging.Filter):
"""Raise exception if warning emitted."""
def __init__(self, app):
# type: (Sphinx) -> None
def __init__(self, app: "Sphinx") -> None:
self.app = app
super().__init__()
def filter(self, record):
# type: (logging.LogRecord) -> bool
def filter(self, record: logging.LogRecord) -> bool:
if getattr(record, 'skip_warningsiserror', False):
# disabled by DisableWarningIsErrorFilter
return True
@ -433,8 +410,7 @@ class WarningIsErrorFilter(logging.Filter):
class DisableWarningIsErrorFilter(logging.Filter):
"""Disable WarningIsErrorFilter if this filter installed."""
def filter(self, record):
# type: (logging.LogRecord) -> bool
def filter(self, record: logging.LogRecord) -> bool:
record.skip_warningsiserror = True # type: ignore
return True
@ -442,13 +418,11 @@ class DisableWarningIsErrorFilter(logging.Filter):
class MessagePrefixFilter(logging.Filter):
"""Prepend prefix to all records."""
def __init__(self, prefix):
# type: (str) -> None
def __init__(self, prefix: str) -> None:
self.prefix = prefix
super().__init__()
def filter(self, record):
# type: (logging.LogRecord) -> bool
def filter(self, record: logging.LogRecord) -> bool:
if self.prefix:
record.msg = self.prefix + ' ' + record.msg
return True
@ -462,13 +436,11 @@ class SphinxLogRecordTranslator(logging.Filter):
"""
LogRecordClass = None # type: Type[logging.LogRecord]
def __init__(self, app):
# type: (Sphinx) -> None
def __init__(self, app: "Sphinx") -> None:
self.app = app
super().__init__()
def filter(self, record): # type: ignore
# type: (SphinxWarningLogRecord) -> bool
def filter(self, record: SphinxWarningLogRecord) -> bool: # type: ignore
if isinstance(record, logging.LogRecord):
# force subclassing to handle location
record.__class__ = self.LogRecordClass # type: ignore
@ -500,8 +472,7 @@ class WarningLogRecordTranslator(SphinxLogRecordTranslator):
LogRecordClass = SphinxWarningLogRecord
def get_node_location(node):
# type: (nodes.Node) -> str
def get_node_location(node: Node) -> str:
(source, line) = get_source_line(node)
if source and line:
return "%s:%s" % (source, line)
@ -514,8 +485,7 @@ def get_node_location(node):
class ColorizeFormatter(logging.Formatter):
def format(self, record):
# type: (logging.LogRecord) -> str
def format(self, record: logging.LogRecord) -> str:
message = super().format(record)
color = getattr(record, 'color', None)
if color is None:
@ -529,13 +499,11 @@ class ColorizeFormatter(logging.Formatter):
class SafeEncodingWriter:
"""Stream writer which ignores UnicodeEncodeError silently"""
def __init__(self, stream):
# type: (IO) -> None
def __init__(self, stream: IO) -> None:
self.stream = stream
self.encoding = getattr(stream, 'encoding', 'ascii') or 'ascii'
def write(self, data):
# type: (str) -> None
def write(self, data: str) -> None:
try:
self.stream.write(data)
except UnicodeEncodeError:
@ -543,25 +511,21 @@ class SafeEncodingWriter:
# non-encodable characters, then decode them.
self.stream.write(data.encode(self.encoding, 'replace').decode(self.encoding))
def flush(self):
# type: () -> None
def flush(self) -> None:
if hasattr(self.stream, 'flush'):
self.stream.flush()
class LastMessagesWriter:
"""Stream writer which memories last 10 messages to save trackback"""
def __init__(self, app, stream):
# type: (Sphinx, IO) -> None
def __init__(self, app: "Sphinx", stream: IO) -> None:
self.app = app
def write(self, data):
# type: (str) -> None
def write(self, data: str) -> None:
self.app.messagelog.append(data)
def setup(app, status, warning):
# type: (Sphinx, IO, IO) -> None
def setup(app: "Sphinx", status: IO, warning: IO) -> None:
"""Setup root logger for Sphinx"""
logger = logging.getLogger(NAMESPACE)
logger.setLevel(logging.DEBUG)

View File

@ -9,16 +9,12 @@
"""
import re
from typing import Callable, Dict, List, Match, Pattern
from sphinx.util.osutil import canon_path
if False:
# For type annotation
from typing import Callable, Dict, List, Match, Pattern # NOQA
def _translate_pattern(pat):
# type: (str) -> str
def _translate_pattern(pat: str) -> str:
"""Translate a shell-style glob pattern to a regular expression.
Adapted from the fnmatch module, but enhanced so that single stars don't
@ -64,8 +60,7 @@ def _translate_pattern(pat):
return res + '$'
def compile_matchers(patterns):
# type: (List[str]) -> List[Callable[[str], Match[str]]]
def compile_matchers(patterns: List[str]) -> List[Callable[[str], Match[str]]]:
return [re.compile(_translate_pattern(pat)).match for pat in patterns]
@ -76,17 +71,14 @@ class Matcher:
For example, "**/index.rst" matches with "index.rst"
"""
def __init__(self, patterns):
# type: (List[str]) -> None
def __init__(self, patterns: List[str]) -> None:
expanded = [pat[3:] for pat in patterns if pat.startswith('**/')]
self.patterns = compile_matchers(patterns + expanded)
def __call__(self, string):
# type: (str) -> bool
def __call__(self, string: str) -> bool:
return self.match(string)
def match(self, string):
# type: (str) -> bool
def match(self, string: str) -> bool:
string = canon_path(string)
return any(pat(string) for pat in self.patterns)
@ -97,16 +89,14 @@ DOTFILES = Matcher(['**/.*'])
_pat_cache = {} # type: Dict[str, Pattern]
def patmatch(name, pat):
# type: (str, str) -> Match[str]
def patmatch(name: str, pat: str) -> Match[str]:
"""Return if name matches pat. Adapted from fnmatch module."""
if pat not in _pat_cache:
_pat_cache[pat] = re.compile(_translate_pattern(pat))
return _pat_cache[pat].match(name)
def patfilter(names, pat):
# type: (List[str], str) -> List[str]
def patfilter(names: List[str], pat: str) -> List[str]:
"""Return the subset of the list NAMES that match PAT.
Adapted from fnmatch module.

View File

@ -20,8 +20,7 @@ DEPTH_CHUNK_START = b'tEXtDepth\x00'
IEND_CHUNK = b'\x00\x00\x00\x00IEND\xAE\x42\x60\x82'
def read_png_depth(filename):
# type: (str) -> int
def read_png_depth(filename: str) -> int:
"""Read the special tEXt chunk indicating the depth from a PNG file."""
with open(filename, 'rb') as f:
f.seek(- (LEN_IEND + LEN_DEPTH), 2)
@ -33,8 +32,7 @@ def read_png_depth(filename):
return struct.unpack('!i', depthchunk[14:18])[0]
def write_png_depth(filename, depth):
# type: (str, int) -> None
def write_png_depth(filename: str, depth: int) -> None:
"""Write the special tEXt chunk indicating the depth to a PNG file.
The chunk is placed immediately before the special IEND chunk.

View File

@ -13,6 +13,7 @@ import io
import sys
import textwrap
import warnings
from typing import Any, Callable
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.locale import __
@ -20,10 +21,6 @@ from sphinx.util import logging
from sphinx.util.console import terminal_safe
from sphinx.util.typing import NoneType
if False:
# For type annotation
from typing import Any, Callable # NOQA
logger = logging.getLogger(__name__)
@ -33,8 +30,7 @@ logger = logging.getLogger(__name__)
# convert_with_2to3():
# support for running 2to3 over config files
def convert_with_2to3(filepath):
# type: (str) -> str
def convert_with_2to3(filepath: str) -> str:
from lib2to3.refactor import RefactoringTool, get_fixers_from_package
from lib2to3.pgen2.parse import ParseError
fixers = get_fixers_from_package('lib2to3.fixes')
@ -62,8 +58,7 @@ class UnicodeMixin:
return self.__unicode__()
def execfile_(filepath, _globals, open=open):
# type: (str, Any, Callable) -> None
def execfile_(filepath: str, _globals: Any, open: Callable = open) -> None:
from sphinx.util.osutil import fs_encoding
with open(filepath, 'rb') as f:
source = f.read()

View File

@ -6,26 +6,23 @@
:license: BSD, see LICENSE for details.
"""
# (ab)use the Jinja parser for parsing our boolean expressions
from typing import Iterator, List
from jinja2 import nodes
from jinja2.environment import Environment
from jinja2.nodes import Node
from jinja2.parser import Parser
env = Environment()
if False:
# For type annotation
from typing import Iterator, List # NOQA
class BooleanParser(Parser):
"""
Only allow condition exprs and/or/not operations.
"""
def parse_compare(self):
# type: () -> nodes.Node
node = None # type: nodes.Node
def parse_compare(self) -> Node:
node = None # type: Node
token = self.stream.current
if token.type == 'name':
if token.value in ('true', 'false', 'True', 'False'):
@ -46,38 +43,31 @@ class BooleanParser(Parser):
class Tags:
def __init__(self, tags=None):
# type: (List[str]) -> None
def __init__(self, tags: List[str] = None) -> None:
self.tags = dict.fromkeys(tags or [], True)
def has(self, tag):
# type: (str) -> bool
def has(self, tag: str) -> bool:
return tag in self.tags
__contains__ = has
def __iter__(self):
# type: () -> Iterator[str]
def __iter__(self) -> Iterator[str]:
return iter(self.tags)
def add(self, tag):
# type: (str) -> None
def add(self, tag: str) -> None:
self.tags[tag] = True
def remove(self, tag):
# type: (str) -> None
def remove(self, tag: str) -> None:
self.tags.pop(tag, None)
def eval_condition(self, condition):
# type: (str) -> bool
def eval_condition(self, condition: str) -> bool:
# exceptions are handled by the caller
parser = BooleanParser(env, condition, state='variable')
expr = parser.parse_expression()
if not parser.stream.eos:
raise ValueError('chunk after expression')
def eval_node(node):
# type: (nodes.Node) -> bool
def eval_node(node: Node) -> bool:
if isinstance(node, nodes.CondExpr):
if eval_node(node.test): # type: ignore
return eval_node(node.expr1) # type: ignore

View File

@ -9,10 +9,7 @@
"""
import re
if False:
# For type annotation
from typing import Dict # NOQA
from typing import Dict
tex_replacements = [
# map TeX special chars
@ -78,20 +75,17 @@ tex_replace_map = {}
tex_hl_escape_map_new = {}
def escape(s):
# type: (str) -> str
def escape(s: str) -> str:
"""Escape text for LaTeX output."""
return s.translate(tex_escape_map)
def escape_abbr(text):
# type: (str) -> str
def escape_abbr(text: str) -> str:
"""Adjust spacing after abbreviations. Works with @ letter or other."""
return re.sub(r'\.(?=\s|$)', r'.\@{}', text)
def init():
# type: () -> None
def init() -> None:
for a, b in tex_replacements:
tex_escape_map[ord(a)] = b
tex_replace_map[ord(a)] = '_'