summaryrefslogtreecommitdiffstats
path: root/sphinx/io.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/io.py')
-rw-r--r--sphinx/io.py189
1 files changed, 189 insertions, 0 deletions
diff --git a/sphinx/io.py b/sphinx/io.py
new file mode 100644
index 0000000..4874fdf
--- /dev/null
+++ b/sphinx/io.py
@@ -0,0 +1,189 @@
+"""Input/Output files"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+import docutils
+from docutils import nodes
+from docutils.core import Publisher
+from docutils.io import FileInput, Input, NullOutput
+from docutils.readers import standalone
+from docutils.transforms.references import DanglingReferences
+from docutils.writers import UnfilteredWriter
+
+from sphinx import addnodes
+from sphinx.transforms import AutoIndexUpgrader, DoctreeReadEvent, SphinxTransformer
+from sphinx.transforms.i18n import (
+ Locale,
+ PreserveTranslatableMessages,
+ RemoveTranslatableInline,
+)
+from sphinx.transforms.references import SphinxDomains
+from sphinx.util import logging
+from sphinx.util.docutils import LoggingReporter
+from sphinx.versioning import UIDTransform
+
+if TYPE_CHECKING:
+ from docutils.frontend import Values
+ from docutils.parsers import Parser
+ from docutils.transforms import Transform
+
+ from sphinx.application import Sphinx
+ from sphinx.environment import BuildEnvironment
+
+
+logger = logging.getLogger(__name__)
+
+
+class SphinxBaseReader(standalone.Reader):
+ """
+ A base class of readers for Sphinx.
+
+ This replaces reporter by Sphinx's on generating document.
+ """
+
+ transforms: list[type[Transform]] = []
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ from sphinx.application import Sphinx
+ if len(args) > 0 and isinstance(args[0], Sphinx):
+ self._app = args[0]
+ self._env = self._app.env
+ args = args[1:]
+
+ super().__init__(*args, **kwargs)
+
+ def setup(self, app: Sphinx) -> None:
+ self._app = app # hold application object only for compatibility
+ self._env = app.env
+
+ def get_transforms(self) -> list[type[Transform]]:
+ transforms = super().get_transforms() + self.transforms
+
+ # remove transforms which is not needed for Sphinx
+ unused = [DanglingReferences]
+ for transform in unused:
+ if transform in transforms:
+ transforms.remove(transform)
+
+ return transforms
+
+ def new_document(self) -> nodes.document:
+ """
+ Creates a new document object which has a special reporter object good
+ for logging.
+ """
+ document = super().new_document()
+ document.__class__ = addnodes.document # replace the class with patched version
+
+ # substitute transformer
+ document.transformer = SphinxTransformer(document)
+ document.transformer.set_environment(self.settings.env)
+
+ # substitute reporter
+ reporter = document.reporter
+ document.reporter = LoggingReporter.from_reporter(reporter)
+
+ return document
+
+
+class SphinxStandaloneReader(SphinxBaseReader):
+ """
+ A basic document reader for Sphinx.
+ """
+
+ def setup(self, app: Sphinx) -> None:
+ self.transforms = self.transforms + app.registry.get_transforms()
+ super().setup(app)
+
+ def read(self, source: Input, parser: Parser, settings: Values) -> nodes.document:
+ self.source = source
+ if not self.parser:
+ self.parser = parser
+ self.settings = settings
+ self.input = self.read_source(settings.env)
+ self.parse()
+ return self.document
+
+ def read_source(self, env: BuildEnvironment) -> str:
+ """Read content from source and do post-process."""
+ content = self.source.read()
+
+ # emit "source-read" event
+ arg = [content]
+ env.events.emit('source-read', env.docname, arg)
+ return arg[0]
+
+
+class SphinxI18nReader(SphinxBaseReader):
+ """
+ A document reader for i18n.
+
+ This returns the source line number of original text as current source line number
+ to let users know where the error happened.
+ Because the translated texts are partial and they don't have correct line numbers.
+ """
+
+ def setup(self, app: Sphinx) -> None:
+ super().setup(app)
+
+ self.transforms = self.transforms + app.registry.get_transforms()
+ unused = [PreserveTranslatableMessages, Locale, RemoveTranslatableInline,
+ AutoIndexUpgrader, SphinxDomains, DoctreeReadEvent,
+ UIDTransform]
+ for transform in unused:
+ if transform in self.transforms:
+ self.transforms.remove(transform)
+
+
+class SphinxDummyWriter(UnfilteredWriter):
+ """Dummy writer module used for generating doctree."""
+
+ supported = ('html',) # needed to keep "meta" nodes
+
+ def translate(self) -> None:
+ pass
+
+
+def SphinxDummySourceClass(source: Any, *args: Any, **kwargs: Any) -> Any:
+ """Bypass source object as is to cheat Publisher."""
+ return source
+
+
+class SphinxFileInput(FileInput):
+ """A basic FileInput for Sphinx."""
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ kwargs['error_handler'] = 'sphinx'
+ super().__init__(*args, **kwargs)
+
+
+def create_publisher(app: Sphinx, filetype: str) -> Publisher:
+ reader = SphinxStandaloneReader()
+ reader.setup(app)
+
+ parser = app.registry.create_source_parser(app, filetype)
+ if parser.__class__.__name__ == 'CommonMarkParser' and parser.settings_spec == ():
+ # a workaround for recommonmark
+ # If recommonmark.AutoStrictify is enabled, the parser invokes reST parser
+ # internally. But recommonmark-0.4.0 does not provide settings_spec for reST
+ # parser. As a workaround, this copies settings_spec for RSTParser to the
+ # CommonMarkParser.
+ from docutils.parsers.rst import Parser as RSTParser
+
+ parser.settings_spec = RSTParser.settings_spec
+
+ pub = Publisher(
+ reader=reader,
+ parser=parser,
+ writer=SphinxDummyWriter(),
+ source_class=SphinxFileInput,
+ destination=NullOutput(),
+ )
+ # Propagate exceptions by default when used programmatically:
+ defaults = {"traceback": True, **app.env.settings}
+ # Set default settings
+ if docutils.__version_info__[:2] >= (0, 19):
+ pub.get_settings(**defaults)
+ else:
+ pub.settings = pub.setup_option_parser(**defaults).get_default_values()
+ return pub