Source code for pyquickhelper.helpgen.sphinxm_mock_app

"""
Helpers to convert docstring to various format.


:githublink:`%|py|5`
"""
import logging
import warnings
from docutils.parsers.rst.directives import directive as rst_directive
from .sphinxm_convert_doc_sphinx_helper import (
    HTMLWriterWithCustomDirectives, _CustomSphinx,
    MDWriterWithCustomDirectives, RSTWriterWithCustomDirectives,
    LatexWriterWithCustomDirectives, DocTreeWriterWithCustomDirectives
)
from ..sphinxext import get_default_extensions


[docs]class MockSphinxApp: """ Mocks :epkg:`Sphinx` application. In memory :epkg:`Sphinx` application. :githublink:`%|py|20` """
[docs] def __init__(self, writer, app, confoverrides, new_extensions=None): """ :param writer: see static method create :param app: see static method create :param confoverrides: default options :param new_extensions: additional extensions :githublink:`%|py|28` """ from sphinx.registry import SphinxComponentRegistry if confoverrides is None: confoverrides = {} self.app = app self.env = app.env self.new_options = {} self.writer = writer self.registry = SphinxComponentRegistry() self.mapping = {"<class 'sphinx.ext.todo.todo_node'>": "todo", "<class 'sphinx.ext.graphviz.graphviz'>": "graphviz", "<class 'sphinx.ext.mathbase.math'>": "math", "<class 'sphinx.ext.mathbase.displaymath'>": "displaymath", "<class 'sphinx.ext.mathbase.eqref'>": "eqref", } # delayed import to speed up import time from sphinx.config import Config self.mapping_connect = {} with warnings.catch_warnings(): warnings.simplefilter( "ignore", (DeprecationWarning, PendingDeprecationWarning)) try: self.config = Config( # pylint: disable=E1121 None, None, confoverrides, None) # pylint: disable=E1121 except TypeError: # Sphinx>=3.0.0 self.config = Config({}, confoverrides) self.confdir = "." self.doctreedir = "." self.srcdir = "." self.builder = writer.builder self._new_extensions = new_extensions if id(self.app) != id(self.writer.app): raise RuntimeError( "Different application in the writer is not allowed.")
@property def extensions(self): return self.app.extensions
[docs] def add_directive(self, name, cl, *args, **options): """ See :epkg:`class Sphinx`. :githublink:`%|py|73` """ # doc_directives.register_directive(name, cl) self.mapping[str(cl)] = name self.app.add_directive(name, cl, *args, **options)
[docs] def add_role(self, name, cl): """ See :epkg:`class Sphinx`. :githublink:`%|py|81` """ # doc_roles.register_canonical_role(name, cl) self.mapping[str(cl)] = name self.app.add_role(name, cl)
[docs] def add_builder(self, name, cl): """ See :epkg:`class Sphinx`. :githublink:`%|py|89` """ self.mapping[str(cl)] = name self.app.add_builder(name, cl)
[docs] def add_mapping(self, name, cl): """ See :epkg:`class Sphinx`. :githublink:`%|py|96` """ self.mapping[str(cl)] = name
[docs] def add_config_value(self, name, default, rebuild, types=()): """ See :epkg:`class Sphinx`. :githublink:`%|py|102` """ if name in self.config.values: # We do not add it a second time. return if rebuild in (False, True): rebuild = 'env' if rebuild else '' self.new_options[name] = (default, rebuild, types) self.config.values[name] = (default, rebuild, types)
[docs] def get_default_values(self): """ See :epkg:`class Sphinx`. :githublink:`%|py|114` """ return {k: v[0] for k, v in self.new_options.items()}
[docs] def add_node(self, node, **kwds): """ See :epkg:`class Sphinx`. :githublink:`%|py|120` """ self.app.add_node(node, **kwds)
[docs] def finalize(self, doctree, external_docnames=None): """ Finalizes the documentation after it was parsed. :param doctree: doctree (or pub.document), available after publication :param external_docnames: other docnames the doctree references :githublink:`%|py|129` """ self.app.finalize(doctree, external_docnames=external_docnames)
[docs] def setup_extension(self, extname): """ See :epkg:`class Sphinx`. :githublink:`%|py|135` """ self.app.setup_extension(extname)
[docs] def emit(self, event, *args): """ See :epkg:`class Sphinx`. :githublink:`%|py|141` """ return self.app.events.emit(event, *args)
[docs] def emit_firstresult(self, event, *args): """ See :epkg:`class Sphinx`. :githublink:`%|py|147` """ return self.app.events.emit_firstresult(event, self, *args)
[docs] def add_autodocumenter(self, cls): """ See :epkg:`class Sphinx`. :githublink:`%|py|153` """ from sphinx.ext.autodoc.directive import AutodocDirective self.registry.add_documenter(cls.objtype, cls) self.add_directive('auto' + cls.objtype, AutodocDirective)
[docs] def connect(self, node, func): """ See :epkg:`class Sphinx`. :githublink:`%|py|161` """ self.mapping_connect[node] = func self.app.connect(node, func)
[docs] def add_domain(self, domain): """ See :epkg:`class Sphinx`. :githublink:`%|py|168` """ if domain.name in self.app.domains: # We do not register it a second time. return self.app.domains[domain.name] = domain
def require_sphinx(self, version): # check the Sphinx version if requested # delayed import to speed up import time from sphinx import __display_version__ as sphinx__display_version__ from sphinx.application import VersionRequirementError if version > sphinx__display_version__[:3]: raise VersionRequirementError(version)
[docs] def add_event(self, name): """ See :epkg:`class Sphinx`. :githublink:`%|py|185` """ if name in self.app._events: # We do not raise an exception if already present. return self.app._events[name] = ''
[docs] def add_env_collector(self, collector): """ See :epkg:`class Sphinx`. :githublink:`%|py|194` """ self.app.add_env_collector(collector)
[docs] def add_js_file(self, jsfile): """ See :epkg:`class Sphinx`. :githublink:`%|py|200` """ try: # Sphinx >= 1.8 self.app.add_js_file(jsfile) except AttributeError: # Sphinx < 1.8 self.app.add_javascript(jsfile)
[docs] def add_css_file(self, css): """ See :epkg:`class Sphinx`. :githublink:`%|py|211` """ try: # Sphinx >= 1.8 self.app.add_css_file(css) except AttributeError: # Sphinx < 1.8 self.app.add_stylesheet(css)
[docs] def add_source_parser(self, ext, parser, exc=False): """ Registers a parser for a specific file extension. :param ext: file extension :param parser: parser :param exc: raises an exception if already done Example: :: app.add_source_parser(self, ext, parser) :githublink:`%|py|232` """ # delayed import to speed up import time from sphinx.errors import ExtensionError with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) try: self.app.add_source_parser(ext, parser) except TypeError as e: # Sphinx==3.0.0 self.app.add_source_parser(parser) except ExtensionError as e: if exc: raise logger = logging.getLogger("MockSphinxApp") logger.warning('[MockSphinxApp] {0}'.format(e))
[docs] def disconnect_env_collector(self, clname): """ Disconnects a collector given its class name. :param cl: name :return: found collector :githublink:`%|py|255` """ self.app.disconnect_env_collector(clname)
[docs] @staticmethod def create(writer="html", directives=None, confoverrides=None, new_extensions=None, load_bokeh=False, destination_path=None, fLOG=None): """ Creates a :class:`MockSphinxApp <pyquickhelper.helpgen.sphinxm_mock_app.MockSphinxApp>` for :epkg:`Sphinx`. :param writer: ``'sphinx'`` is the only allowed value :param directives: new directives to add (see below) :param confoverrides: initial options :param new_extensions: additional extensions to setup :param load_bokeh: load :epkg:`bokeh` extension, disabled by default as it is slow :param destination_path: some extension requires it :param fLOG: logging function :return: mockapp, writer, list of added nodes *directives* is None or a list of 2 or 5-uple: * a directive name (mandatory) * a directive class: see `Sphinx Directive <http://sphinx-doc.org/extdev/tutorial.html>`_, see also :class:`RunPythonDirective <pyquickhelper.sphinxext.sphinx_runpython_extension.RunPythonDirective>` as an example (mandatory) * a docutils node: see :class:`runpython_node <pyquickhelper.sphinxext.sphinx_runpython_extension.runpython_node>` as an example * two functions: see :func:`visit_runpython_node <pyquickhelper.sphinxext.sphinx_runpython_extension.visit_runpython_node>`, :func:`depart_runpython_node <pyquickhelper.sphinxext.sphinx_runpython_extension.depart_runpython_node>` as an example .. versionchanged:: 1.8 Parameter *load_bokeh* was added. :githublink:`%|py|286` """ logger = logging.getLogger('gdot') if not logger.disabled: logger.disabled = True restore = True else: restore = False with warnings.catch_warnings(): warnings.simplefilter( "ignore", (DeprecationWarning, PendingDeprecationWarning)) if confoverrides is None: confoverrides = {} if "extensions" not in confoverrides: confoverrides["extensions"] = get_default_extensions( load_bokeh=load_bokeh) if writer in ("sphinx", "custom", "HTMLWriterWithCustomDirectives", "html"): app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, doctreedir=None, buildername='memoryhtml', confoverrides=confoverrides, new_extensions=new_extensions) writer = HTMLWriterWithCustomDirectives( builder=app.builder, app=app) mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, new_extensions=new_extensions) elif writer == "rst": app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, doctreedir=None, buildername='memoryrst', confoverrides=confoverrides, new_extensions=new_extensions) writer = RSTWriterWithCustomDirectives( builder=app.builder, app=app) mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, new_extensions=new_extensions) elif writer == "md": app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, doctreedir=None, buildername='memorymd', confoverrides=confoverrides, new_extensions=new_extensions) writer = MDWriterWithCustomDirectives( builder=app.builder, app=app) mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, new_extensions=new_extensions) elif writer == "elatex": app = _CustomSphinx(srcdir=None, confdir=None, outdir=None, doctreedir=None, buildername='memorylatex', confoverrides=confoverrides, new_extensions=new_extensions) writer = LatexWriterWithCustomDirectives( builder=app.builder, app=app) mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, new_extensions=new_extensions) elif writer == "doctree": app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, doctreedir=None, buildername='memorydoctree', confoverrides=confoverrides, new_extensions=new_extensions) writer = DocTreeWriterWithCustomDirectives( builder=app.builder, app=app) mockapp = MockSphinxApp(writer, writer.app, confoverrides=confoverrides, new_extensions=new_extensions) elif isinstance(writer, tuple): # We expect ("builder_name", builder_class) app = _CustomSphinx(srcdir=None, confdir=None, outdir=destination_path, doctreedir=None, buildername=writer, confoverrides=confoverrides, new_extensions=new_extensions) if not hasattr(writer[1], "_writer_class"): raise AttributeError( "Class '{0}' does not have any attribute '_writer_class'.".format(writer[1])) writer = writer[1]._writer_class( # pylint: disable=E1101 builder=app.builder, app=app) # pylint: disable=E1101 mockapp = MockSphinxApp(writer, app, confoverrides=confoverrides, new_extensions=new_extensions) else: raise ValueError( "Writer must be 'html', 'rst', 'md', 'elatex', not '{0}'.".format(writer)) if restore: logger.disabled = False # titles title_names = [] title_names.append("todoext_node") title_names.append("todo_node") title_names.append("mathdef_node") title_names.append("blocref_node") title_names.append("faqref_node") title_names.append("nbref_node") title_names.append("exref_node") if directives is not None: for tu in directives: if len(tu) < 2: raise ValueError( "directives is a list of tuple with at least two elements, check the documentation") if len(tu) == 5: name, cl, node, f1, f2 = tu mockapp.add_node(node, html=(f1, f2)) # not necessary # nodes._add_node_class_names([node.__name__]) writer.connect_directive_node(node.__name__, f1, f2) elif len(tu) != 2: raise ValueError( "directives is a list of tuple with 2 or 5 elements, check the documentation") name, cl = tu[:2] mockapp.add_directive(name, cl) if fLOG: apps = [mockapp] if hasattr(writer, "app"): apps.append(writer.app) for app in apps: if hasattr(app, "_added_objects"): fLOG("[MockSphinxApp] list of added objects") for el in app._added_objects: fLOG("[MockSphinxApp]", el) if el[0] == "domain": fLOG("[MockSphinxApp] NAME", el[1].name) for ro in el[1].roles: fLOG("[MockSphinxApp] ROLES", ro) for ro in el[1].directives: fLOG("[MockSphinxApp] DIREC", ro) from docutils.parsers.rst.directives import _directives for res in sorted(_directives): fLOG("[MockSphinxApp] RST DIREC", res) class bb: def info(*args, line=0): # pylint: disable=E0211 fLOG("[MockSphinxApp] -- ", *args) class aa: def __init__(self): self.reporter = bb() self.current_line = 0 from docutils.parsers.rst.languages import en for dir_check in ['py:function']: res = rst_directive(dir_check, en, aa()) return mockapp, writer, title_names