You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.1 KiB
198 lines
6.1 KiB
"""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: # type: ignore[type-arg]
|
|
self.source = source
|
|
if not self.parser: # type: ignore[has-type]
|
|
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 # type: ignore[misc]
|
|
|
|
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
|
|
|