from contextlib import redirect_stderr import io import os import sys import tempfile from typing import List, Optional, Tuple from docutils.core import publish_parts from .runpython import run_cmd _dummy_extensions = [ "sphinx.ext.autodoc", "sphinx.ext.coverage", "sphinx.ext.githubpages", "sphinx.ext.graphviz", "sphinx.ext.ifconfig", "sphinx.ext.mathjax", "sphinx.ext.todo", "sphinx.ext.viewcode", "matplotlib.sphinxext.plot_directive", "sphinx_issues", "sphinx_runpython.blocdefs.sphinx_blocref_extension", "sphinx_runpython.blocdefs.sphinx_exref_extension", "sphinx_runpython.blocdefs.sphinx_faqref_extension", "sphinx_runpython.blocdefs.sphinx_mathdef_extension", "sphinx_runpython.collapse", "sphinx_runpython.docassert", "sphinx_runpython.epkg", "sphinx_runpython.runpython", "sphinx_runpython.quote", "sphinx_runpython.sphinx_rst_builder", ] _dummy_epkg_dictionary = { "dot": "https://en.wikipedia.org/wiki/DOT_(graph_description_language)", "DOT": "https://en.wikipedia.org/wiki/DOT_(graph_description_language)", "JIT": "https://en.wikipedia.org/wiki/Just-in-time_compilation", "git": "https://git-scm.com/", "Graphviz": "https://graphviz.org/", "HTML": "https://simple.wikipedia.org/wiki/HTML", "nested_parse_with_titles": "https://www.sphinx-doc.org/en/master/extdev/markupapi.html?highlight=nested_parse_with_titles", "numpy": ( "https://www.numpy.org/", ("https://docs.scipy.org/doc/numpy/reference/generated/numpy.{0}.html", 1), ("https://docs.scipy.org/doc/numpy/reference/generated/numpy.{0}.{1}.html", 2), ), "pandas": ( "https://pandas.pydata.org/docs/", ("https://pandas.pydata.org/docs/reference/api/pandas.{0}.html", 1), ( "'https://pandas.pydata.org/docs/reference/api/pandas.{0}.{1}.html", 2, ), ), "pandoc": "https://pandoc.org/", "Pandoc": "https://pandoc.org/", "PNG": "https://en.wikipedia.org/wiki/PNG", "pypandoc": "https://github.com/JessicaTegner/pypandoc", "python": "https://www.python.org/", "RST": "https://fr.wikipedia.org/wiki/ReStructuredText", "sphinx": "https://www.sphinx-doc.org/en/master/", "sphinx-gallery": "https://github.com/sphinx-gallery/sphinx-gallery", "SVG": "https://en.wikipedia.org/wiki/SVG", "viz.js": "https://visjs.org/", "*py": ( "https://docs.python.org/3/", ("https://docs.python.org/3/library/{0}.html", 1), ("https://docs.python.org/3/library/{0}.html#{0}.{1}", 2), ("https://docs.python.org/3/library/{0}.html#{0}.{1}.{2}", 3), ), "*pyf": (("https://docs.python.org/3/library/functions.html#{0}", 1),), } _dummy_conf = """ import os import sys from sphinx_runpython import __version__ source_suffix = ".rst" master_doc = "index" project = "rst2html" pygments_style = "sphinx" todo_include_todos = True html_theme = "alabaster" html_theme_options = {} """ def _rst2html_sphinx( rst: str, writer_name: str = "html", report_level: int = 0, new_extensions: Optional[List[str]] = None, **kwargs, ) -> Tuple[str, str]: if "writer" in kwargs: raise ValueError("'writer' is not a valid argument, please use 'writer_name'.") with tempfile.TemporaryDirectory() as folder: index = os.path.join(folder, "index.rst") with open(index, "w", encoding="utf-8") as f: f.write(rst) conf = os.path.join(folder, "conf.py") with open(conf, "w", encoding="utf-8") as f: f.write(_dummy_conf) if new_extensions is None: f.write(f"\nextensions = {_dummy_extensions}\n") else: f.write(f"\nextensions = {_dummy_extensions + new_extensions}\n") f.write(f"\nepkg_dictionary = {_dummy_epkg_dictionary}\n") fout = os.path.join(folder, "output") rep = " -v" * report_level cmd = f"{sys.executable} -m sphinx -b {writer_name} {folder} {fout}{rep}" out, err = run_cmd(cmd, wait=True) html = os.path.join(fout, f"index.{writer_name}") if not os.path.exists(html): raise RuntimeError( f"Unable to find output {html!r}\n--STDOUT--\n{out}\n--STDERR--\n{err}" ) with open(html, "r", encoding="utf-8") as f: content = f.read() return content, err def rst2html( rst: str, writer_name: str = "html", report_level: int = 0, return_warnings: bool = False, use_sphinx: bool = True, new_extensions: Optional[List[str]] = None, **kwargs, ) -> Tuple[str, str]: """ Converts a RST string into HTML or RST. :param rst: RST string :param report_level: filter output, 0 means everything :param writer_name: writer name :param kwargs: additional values to add to the configuration :param return_warnings: return the warnings as well :param use_sphinx: run sphinx from the command line and returns the results, for that configuration :param new_extensions: new extensions to add :return: output and warnings """ if use_sphinx: content, err = _rst2html_sphinx( rst, writer_name=writer_name, report_level=report_level, new_extensions=new_extensions, **kwargs, ) if return_warnings: return content, err return content docutils_kwargs = { "writer_name": writer_name, "settings_overrides": { "_disable_config": True, "report_level": report_level, }, } target = io.StringIO() with redirect_stderr(target): parts = publish_parts(rst, **docutils_kwargs) html = parts["html_body"] warning = target.getvalue().strip() if "System Message: ERROR" in html: raise RuntimeError( f"rst2html failed, warnings:\n{warning}\n-----\nOUTPUT\n{html}" ) if return_warnings: return html, warning return html