diff options
Diffstat (limited to 'sphinx/util/docutils.py')
-rw-r--r-- | sphinx/util/docutils.py | 140 |
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 |