summaryrefslogtreecommitdiff
path: root/sphinx/util/docutils.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/docutils.py')
-rw-r--r--sphinx/util/docutils.py140
1 files changed, 98 insertions, 42 deletions
diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py
index ccd6410ec..2d915a4cb 100644
--- a/sphinx/util/docutils.py
+++ b/sphinx/util/docutils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
sphinx.util.docutils
~~~~~~~~~~~~~~~~~~~~
@@ -8,9 +7,7 @@
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
-from __future__ import absolute_import
-import codecs
import os
import re
import types
@@ -19,6 +16,7 @@ from contextlib import contextmanager
from copy import copy
from distutils.version import LooseVersion
from os import path
+from typing import IO, cast
import docutils
from docutils import nodes
@@ -37,15 +35,18 @@ report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(
if False:
# For type annotation
- from typing import Any, Callable, Generator, List, Set, Tuple # NOQA
- from docutils.statemachine import State, ViewList # NOQA
+ from types import ModuleType # NOQA
+ from typing import Any, Callable, Generator, List, Set, Tuple, Type # 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.io import SphinxFileInput # NOQA
+ from sphinx.util.typing import RoleFunction # NOQA
__version_info__ = tuple(LooseVersion(docutils.__version__).version)
-additional_nodes = set() # type: Set[nodes.Node]
+additional_nodes = set() # type: Set[Type[nodes.Element]]
@contextmanager
@@ -53,39 +54,77 @@ def docutils_namespace():
# type: () -> Generator[None, None, None]
"""Create namespace for reST parsers."""
try:
- _directives = copy(directives._directives)
- _roles = copy(roles._roles)
+ _directives = copy(directives._directives) # type: ignore
+ _roles = copy(roles._roles) # type: ignore
yield
finally:
- directives._directives = _directives
- roles._roles = _roles
+ directives._directives = _directives # type: ignore
+ roles._roles = _roles # type: ignore
for node in list(additional_nodes):
unregister_node(node)
additional_nodes.discard(node)
+def is_directive_registered(name):
+ # type: (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
+ """Register a directive to docutils.
+
+ This modifies global state of docutils. So it is better to use this
+ inside ``docutils_namespace()`` to prevent side-effects.
+ """
+ directives.register_directive(name, directive)
+
+
+def is_role_registered(name):
+ # type: (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
+ """Register a role to docutils.
+
+ This modifies global state of docutils. So it is better to use this
+ inside ``docutils_namespace()`` to prevent side-effects.
+ """
+ roles.register_local_role(name, role)
+
+
+def unregister_role(name):
+ # type: (str) -> None
+ """Unregister a role from docutils."""
+ roles._roles.pop(name, None) # type: ignore
+
+
def is_node_registered(node):
- # type: (nodes.Node) -> bool
+ # type: (Type[nodes.Element]) -> bool
"""Check the *node* is already registered."""
return hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__)
def register_node(node):
- # type: (nodes.Node) -> None
+ # type: (Type[nodes.Element]) -> None
"""Register a node to docutils.
This modifies global state of some visitors. So it is better to use this
inside ``docutils_namespace()`` to prevent side-effects.
"""
if not hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__):
- nodes._add_node_class_names([node.__name__])
+ nodes._add_node_class_names([node.__name__]) # type: ignore
additional_nodes.add(node)
def unregister_node(node):
- # type: (nodes.Node) -> None
+ # type: (Type[nodes.Element]) -> None
"""Unregister a node from docutils.
This is inverse of ``nodes._add_nodes_class_names()``.
@@ -108,7 +147,7 @@ def patched_get_language():
from docutils.languages import get_language
def patched_get_language(language_code, reporter=None):
- # type: (unicode, Reporter) -> Any
+ # type: (str, Reporter) -> Any
return get_language(language_code)
try:
@@ -121,12 +160,12 @@ def patched_get_language():
@contextmanager
def using_user_docutils_conf(confdir):
- # type: (unicode) -> Generator[None, None, None]
+ # type: (str) -> Generator[None, None, None]
"""Let docutils know the location of ``docutils.conf`` for Sphinx."""
try:
docutilsconfig = os.environ.get('DOCUTILSCONFIG', None)
if confdir:
- os.environ['DOCUTILSCONFIG'] = path.join(path.abspath(confdir), 'docutils.conf') # type: ignore # NOQA
+ os.environ['DOCUTILSCONFIG'] = path.join(path.abspath(confdir), 'docutils.conf')
yield
finally:
@@ -138,7 +177,7 @@ def using_user_docutils_conf(confdir):
@contextmanager
def patch_docutils(confdir=None):
- # type: (unicode) -> Generator[None, None, None]
+ # type: (str) -> Generator[None, None, None]
"""Patch to docutils temporarily."""
with patched_get_language(), using_user_docutils_conf(confdir):
yield
@@ -148,7 +187,7 @@ class ElementLookupError(Exception):
pass
-class sphinx_domains(object):
+class sphinx_domains:
"""Monkey-patch directive and role dispatch, so that domain-specific
markup takes precedence.
"""
@@ -163,7 +202,7 @@ class sphinx_domains(object):
self.enable()
def __exit__(self, type, value, traceback):
- # type: (unicode, unicode, unicode) -> None
+ # type: (str, str, str) -> None
self.disable()
def enable(self):
@@ -171,8 +210,8 @@ class sphinx_domains(object):
self.directive_func = directives.directive
self.role_func = roles.role
- directives.directive = self.lookup_directive
- roles.role = self.lookup_role
+ directives.directive = self.lookup_directive # type: ignore
+ roles.role = self.lookup_role # type: ignore
def disable(self):
# type: () -> None
@@ -180,7 +219,7 @@ class sphinx_domains(object):
roles.role = self.role_func
def lookup_domain_element(self, type, name):
- # type: (unicode, unicode) -> Tuple[Any, List]
+ # type: (str, str) -> Any
"""Lookup a markup element (directive or role), given its name which can
be a full name (with domain).
"""
@@ -209,30 +248,30 @@ class sphinx_domains(object):
raise ElementLookupError
def lookup_directive(self, name, lang_module, document):
- # type: (unicode, unicode, nodes.document) -> Tuple[Any, List]
+ # type: (str, ModuleType, nodes.document) -> Tuple[Type[Directive], List[nodes.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: (unicode, unicode, int, Any) -> Tuple[Any, List]
+ # type: (str, ModuleType, int, Reporter) -> Tuple[RoleFunction, List[nodes.system_message]] # NOQA
try:
return self.lookup_domain_element('role', name)
except ElementLookupError:
return self.role_func(name, lang_module, lineno, reporter)
-class WarningStream(object):
+class WarningStream:
def write(self, text):
- # type: (unicode) -> None
+ # type: (str) -> None
matched = report_re.search(text)
if not matched:
logger.warning(text.rstrip("\r\n"))
else:
location, type, level = matched.groups()
message = report_re.sub('', text).rstrip()
- logger.log(type, message, location=location) # type: ignore
+ logger.log(type, message, location=location)
class LoggingReporter(Reporter):
@@ -246,10 +285,10 @@ class LoggingReporter(Reporter):
def __init__(self, source, report_level=Reporter.WARNING_LEVEL,
halt_level=Reporter.SEVERE_LEVEL, debug=False,
error_handler='backslashreplace'):
- # type: (unicode, int, int, bool, unicode) -> None
- stream = WarningStream()
- Reporter.__init__(self, source, report_level, halt_level,
- stream, debug, error_handler=error_handler)
+ # type: (str, int, int, bool, str) -> None
+ stream = cast(IO, WarningStream())
+ super().__init__(source, report_level, halt_level,
+ stream, debug, error_handler=error_handler)
class NullReporter(Reporter):
@@ -257,7 +296,7 @@ class NullReporter(Reporter):
def __init__(self):
# type: () -> None
- Reporter.__init__(self, '', 999, 4)
+ super().__init__('', 999, 4)
def is_html5_writer_available():
@@ -285,21 +324,21 @@ def directive_helper(obj, has_content=None, argument_spec=None, **option_spec):
@contextmanager
def switch_source_input(state, content):
- # type: (State, ViewList) -> Generator[None, None, None]
+ # type: (State, StringList) -> Generator[None, None, None]
"""Switch current source input of state temporarily."""
try:
# remember the original ``get_source_and_line()`` method
- get_source_and_line = state.memo.reporter.get_source_and_line
+ get_source_and_line = state.memo.reporter.get_source_and_line # type: ignore
# replace it by new one
state_machine = StateMachine([], None)
state_machine.input_lines = content
- state.memo.reporter.get_source_and_line = state_machine.get_source_and_line
+ state.memo.reporter.get_source_and_line = state_machine.get_source_and_line # type: ignore # NOQA
yield
finally:
# restore the method
- state.memo.reporter.get_source_and_line = get_source_and_line
+ state.memo.reporter.get_source_and_line = get_source_and_line # type: ignore
class SphinxFileOutput(FileOutput):
@@ -308,18 +347,18 @@ class SphinxFileOutput(FileOutput):
def __init__(self, **kwargs):
# type: (Any) -> None
self.overwrite_if_changed = kwargs.pop('overwrite_if_changed', False)
- FileOutput.__init__(self, **kwargs)
+ super().__init__(**kwargs)
def write(self, data):
- # type: (unicode) -> unicode
+ # type: (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 codecs.open(self.destination_path, encoding=self.encoding) as f:
+ with open(self.destination_path, encoding=self.encoding) as f:
# skip writing: content not changed
if f.read() == data:
return data
- return FileOutput.write(self, data)
+ return super().write(data)
class SphinxDirective(Directive):
@@ -344,13 +383,30 @@ class SphinxDirective(Directive):
return self.env.config
+class SphinxTranslator(nodes.NodeVisitor):
+ """A base class for Sphinx translators.
+
+ This class provides helper methods for Sphinx translators.
+
+ .. note:: The subclasses of this class might not work with docutils.
+ This class is strongly coupled with Sphinx.
+ """
+
+ def __init__(self, document, builder):
+ # type: (nodes.document, Builder) -> None
+ super().__init__(document)
+ self.builder = builder
+ self.config = builder.config
+ self.settings = document.settings
+
+
# cache a vanilla instance of nodes.document
# Used in new_document() function
__document_cache__ = None # type: nodes.document
def new_document(source_path, settings=None):
- # type: (unicode, Any) -> nodes.document
+ # type: (str, Any) -> 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