Source code for pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper

"""
Helpers to convert docstring to various format.


:githublink:`%|py|5`
"""
import os
import sys
from collections import deque
import warnings
import pickle
import platform
from html import escape as htmlescape
from io import StringIO
from docutils.parsers.rst import roles
from docutils.languages import en as docutils_en
from docutils import nodes
from docutils.utils import Reporter
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.errors import ExtensionError
from sphinx.ext.extlinks import setup_link_roles
from sphinx.transforms import SphinxTransformer
from sphinx.util.docutils import is_html5_writer_available
from sphinx.writers.html import HTMLWriter
from sphinx.util.build_phase import BuildPhase
from sphinx.util.logging import prefixed_warnings
from sphinx.project import Project
from sphinx.errors import ApplicationError
from sphinx.util.logging import getLogger
from ..sphinxext.sphinx_doctree_builder import DocTreeBuilder, DocTreeWriter, DocTreeTranslator
from ..sphinxext.sphinx_md_builder import MdBuilder, MdWriter, MdTranslator
from ..sphinxext.sphinx_latex_builder import EnhancedLaTeXBuilder, EnhancedLaTeXWriter, EnhancedLaTeXTranslator
from ..sphinxext.sphinx_rst_builder import RstBuilder, RstWriter, RstTranslator
from ._single_file_html_builder import CustomSingleFileHTMLBuilder


[docs]def _get_LaTeXTranslator(): try: from sphinx.writers.latex import LaTeXTranslator except ImportError: # pragma: no cover # Since sphinx 1.7.3 (circular reference). import sphinx.builders.latex.transforms from sphinx.writers.latex import LaTeXTranslator return LaTeXTranslator
if is_html5_writer_available(): from sphinx.writers.html5 import HTML5Translator as HTMLTranslator else: from sphinx.writers.html import HTMLTranslator # pragma: no cover
[docs]def update_docutils_languages(values=None): """ Updates ``docutils/languages/en.py`` with missing labels. It Does it for languages *en*. :param values: consider values in this dictionaries first :githublink:`%|py|58` """ if values is None: values = dict() lab = docutils_en.labels if 'versionmodified' not in lab: lab['versionmodified'] = values.get( 'versionmodified', 'modified version') if 'desc' not in lab: lab['desc'] = values.get('desc', 'description')
[docs]class _AdditionalVisitDepart: """ Additional visitors and departors. .. versionchanged:: 1.7 Update for Sphinx 1.7. :githublink:`%|py|75` """
[docs] def __init__(self, output_format): """ .. versionadded:: 1.7 :githublink:`%|py|80` """ self.output_format = output_format
[docs] def is_html(self): """ Tells if the translator is :epkg:`html` format. :githublink:`%|py|86` """ return self.base_class is HTMLTranslator
[docs] def is_rst(self): """ Tells if the translator is :epkg:`rst` format. :githublink:`%|py|92` """ return self.base_class is RstTranslator
[docs] def is_latex(self): """ Tells if the translator is :epkg:`latex` format. :githublink:`%|py|98` """ return self.base_class is _get_LaTeXTranslator()
[docs] def is_md(self): """ Tells if the translator is :epkg:`markdown` format. :githublink:`%|py|104` """ return self.base_class is _get_LaTeXTranslator()
[docs] def is_doctree(self): """ Tells if the translator is doctree format. :githublink:`%|py|110` """ return self.base_class is _get_LaTeXTranslator()
[docs] def add_secnumber(self, node): """ Overwrites this method to catch errors due when it is a single document being processed. :githublink:`%|py|117` """ if node.get('secnumber'): self.base_class.add_secnumber(self, node) elif len(node.parent['ids']) > 0: self.base_class.add_secnumber(self, node) else: n = len(self.builder.secnumbers) node.parent['ids'].append("custom_label_%d" % n) self.base_class.add_secnumber(self, node)
def eval_expr(self, expr): rst = self.output_format == 'rst' latex = self.output_format in ('latex', 'elatex') texinfo = [('index', 'A_AdditionalVisitDepart', 'B_AdditionalVisitDepart', # pylint: disable=W0612 'C_AdditionalVisitDepart', 'D_AdditionalVisitDepart', 'E_AdditionalVisitDepart', 'Miscellaneous')] html = self.output_format == 'html' md = self.output_format == 'md' doctree = self.output_format in ('doctree', 'doctree.txt') if not(rst or html or latex or md or doctree): raise ValueError( # pragma: no cover "Unknown output format '{0}'.".format(self.output_format)) try: ev = eval(expr) except Exception: # pragma: no cover raise ValueError( "Unable to interpret expression '{0}'".format(expr)) return ev def visit_only(self, node): ev = self.eval_expr(node.attributes['expr']) if ev: pass else: raise nodes.SkipNode def depart_only(self, node): ev = self.eval_expr(node.attributes['expr']) if ev: pass else: # The program should not necessarily be here. pass def unknown_visit(self, node): # pragma: no cover raise NotImplementedError("[_AdditionalVisitDepart] Unknown node: '{0}' in '{1}'".format( node.__class__.__name__, self.__class__.__name__))
[docs]class HTMLTranslatorWithCustomDirectives(_AdditionalVisitDepart, HTMLTranslator): """ See :class:`HTMLWriterWithCustomDirectives <pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper.HTMLWriterWithCustomDirectives>`. :githublink:`%|py|169` """
[docs] def __init__(self, builder, *args, **kwds): """ .. versionchanged:: 1.7 Does something specific for :epkg:`HTML`. only is a node. :githublink:`%|py|175` """ HTMLTranslator.__init__(self, builder, *args, **kwds) _AdditionalVisitDepart.__init__(self, 'html') nodes_list = getattr(builder, '_function_node', None) if nodes_list is not None: for name, f1, f2 in nodes_list: setattr(self.__class__, "visit_" + name, f1) setattr(self.__class__, "depart_" + name, f2) self.base_class = HTMLTranslator
def visit_field(self, node): if not hasattr(self, '_fieldlist_row_index'): # needed when a docstring starts with :param: self._fieldlist_row_index = 0 return HTMLTranslator.visit_field(self, node) def visit_pending_xref(self, node): self.visit_Text(node) raise nodes.SkipNode
[docs] def unknown_visit(self, node): # pragma: no cover raise NotImplementedError("[HTMLTranslatorWithCustomDirectives] Unknown node: '{0}' in '{1}'".format( node.__class__.__name__, self.__class__.__name__))
[docs]class RSTTranslatorWithCustomDirectives(_AdditionalVisitDepart, RstTranslator): """ See :class:`HTMLWriterWithCustomDirectives <pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper.HTMLWriterWithCustomDirectives>`. :githublink:`%|py|203` """
[docs] def __init__(self, builder, *args, **kwds): """ constructor :githublink:`%|py|208` """ RstTranslator.__init__(self, builder, *args, **kwds) _AdditionalVisitDepart.__init__(self, 'rst') for name, f1, f2 in builder._function_node: setattr(self.__class__, "visit_" + name, f1) setattr(self.__class__, "depart_" + name, f2) self.base_class = RstTranslator
[docs]class MDTranslatorWithCustomDirectives(_AdditionalVisitDepart, MdTranslator): """ See :class:`HTMLWriterWithCustomDirectives <pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper.HTMLWriterWithCustomDirectives>`. :githublink:`%|py|220` """
[docs] def __init__(self, builder, *args, **kwds): """ constructor :githublink:`%|py|225` """ MdTranslator.__init__(self, builder, *args, **kwds) _AdditionalVisitDepart.__init__(self, 'md') for name, f1, f2 in builder._function_node: setattr(self.__class__, "visit_" + name, f1) setattr(self.__class__, "depart_" + name, f2) self.base_class = MdTranslator
[docs]class DocTreeTranslatorWithCustomDirectives(DocTreeTranslator): """ See :class:`HTMLWriterWithCustomDirectives <pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper.HTMLWriterWithCustomDirectives>`. :githublink:`%|py|237` """
[docs] def __init__(self, builder, *args, **kwds): """ constructor :githublink:`%|py|242` """ DocTreeTranslator.__init__(self, builder, *args, **kwds) self.base_class = DocTreeTranslator
[docs]class LatexTranslatorWithCustomDirectives(_AdditionalVisitDepart, EnhancedLaTeXTranslator): """ See :class:`LatexWriterWithCustomDirectives <pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper.LatexWriterWithCustomDirectives>`. :githublink:`%|py|250` """
[docs] def __init__(self, builder, document, *args, **kwds): """ constructor :githublink:`%|py|255` """ if not hasattr(builder, "config"): builder, document = document, builder if not hasattr(builder, "config"): raise TypeError( # pragma: no cover "Builder has no config: {} - {}".format(type(builder), type(document))) EnhancedLaTeXTranslator.__init__( self, builder, document, *args, **kwds) _AdditionalVisitDepart.__init__(self, 'md') for name, f1, f2 in builder._function_node: setattr(self.__class__, "visit_" + name, f1) setattr(self.__class__, "depart_" + name, f2) self.base_class = EnhancedLaTeXTranslator
[docs]class _WriterWithCustomDirectives: """ Common class to :class:`HTMLWriterWithCustomDirectives <pyquickhelper.helpgen.sphinxm_convert_doc_sphinx_helper.HTMLWriterWithCustomDirectives>` and :class:`RSTWriterWithCustomDirectives`. :githublink:`%|py|273` """
[docs] def _init(self, base_class, translator_class, app=None): """ :param base_class: base class :param app: Sphinx application :githublink:`%|py|279` """ if app is None: self.app = _CustomSphinx(srcdir=None, confdir=None, outdir=None, doctreedir=None, buildername='memoryhtml') else: self.app = app builder = self.app.builder builder.fignumbers = {} base_class.__init__(self, builder) self.translator_class = translator_class self.builder.secnumbers = {} self.builder._function_node = [] self.builder.current_docname = None self.base_class = base_class
[docs] def connect_directive_node(self, name, f_visit, f_depart): """ Adds custom node to the translator. :param name: name of the directive :param f_visit: visit function :param f_depart: depart function :githublink:`%|py|301` """ if self.builder.format != "doctree": self.builder._function_node.append((name, f_visit, f_depart))
[docs] def add_configuration_options(self, new_options): """ Add new options. :param new_options: new options :githublink:`%|py|310` """ for k, v in new_options.items(): self.builder.config.values[k] = v
[docs] def write(self, document, destination): """ Processes a document into its final form. Translates `document` (a Docutils document tree) into the Writer's native format, and write it out to its `destination` (a `docutils.io.Output` subclass object). Normally not overridden or extended in subclasses. :githublink:`%|py|322` """ # trans = self.builder.create_translator(self.builder, document) # if not isinstance(trans, HTMLTranslatorWithCustomDirectives): # raise TypeError("The translator is not of a known type but '{0}'".format(type(trans))) self.base_class.write(self, document, destination)
[docs]class HTMLWriterWithCustomDirectives(_WriterWithCustomDirectives, HTMLWriter): """ This :epkg:`docutils` writer extends the HTML writer with custom directives implemented in this module, :class:`RunPythonDirective <pyquickhelper.sphinxext.sphinx_runpython_extension.RunPythonDirective>`, :class:`BlogPostDirective`. See `Write your own ReStructuredText-Writer <http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html>`_. This class needs to tell :epkg:`docutils` to call the added function when directives *runpython* or *blogpost* are met. :githublink:`%|py|339` """
[docs] def __init__(self, builder=None, app=None): # pylint: disable=W0231 """ :param builder: builder :param app: Sphinx application :githublink:`%|py|345` """ _WriterWithCustomDirectives._init( self, HTMLWriter, HTMLTranslatorWithCustomDirectives, app)
[docs] def translate(self): self.visitor = visitor = self.translator_class( self.builder, self.document) self.document.walkabout(visitor) self.output = visitor.astext() for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix', 'body_pre_docinfo', 'docinfo', 'body', 'fragment', 'body_suffix', 'meta', 'title', 'subtitle', 'header', 'footer', 'html_prolog', 'html_head', 'html_title', 'html_subtitle', 'html_body', ): setattr(self, attr, getattr(visitor, attr, None)) self.clean_meta = ''.join(visitor.meta[2:])
[docs]class RSTWriterWithCustomDirectives(_WriterWithCustomDirectives, RstWriter): """ This :epkg:`docutils` writer extends the :epkg:`RST` writer with custom directives implemented in this module. :githublink:`%|py|367` """
[docs] def __init__(self, builder=None, app=None): # pylint: disable=W0231 """ :param builder: builder :param app: Sphinx application :githublink:`%|py|373` """ _WriterWithCustomDirectives._init( self, RstWriter, RSTTranslatorWithCustomDirectives, app)
[docs] def translate(self): visitor = self.translator_class(self.builder, self.document) self.document.walkabout(visitor) self.output = visitor.body
[docs]class MDWriterWithCustomDirectives(_WriterWithCustomDirectives, MdWriter): """ This :epkg:`docutils` writer extends the :epkg:`MD` writer with custom directives implemented in this module. :githublink:`%|py|387` """
[docs] def __init__(self, builder=None, app=None): # pylint: disable=W0231 """ :param builder: builder :param app: Sphinx application :githublink:`%|py|393` """ _WriterWithCustomDirectives._init( self, MdWriter, MDTranslatorWithCustomDirectives, app)
[docs] def translate(self): visitor = self.translator_class(self.builder, self.document) self.document.walkabout(visitor) self.output = visitor.body
[docs]class DocTreeWriterWithCustomDirectives(_WriterWithCustomDirectives, DocTreeWriter): """ This :epkg:`docutils` writer creates a doctree writer with custom directives implemented in this module. :githublink:`%|py|407` """
[docs] def __init__(self, builder=None, app=None): # pylint: disable=W0231 """ :param builder: builder :param app: Sphinx application :githublink:`%|py|413` """ _WriterWithCustomDirectives._init( self, DocTreeWriter, DocTreeTranslatorWithCustomDirectives, app)
[docs] def translate(self): visitor = self.translator_class(self.builder, self.document) self.document.walkabout(visitor) self.output = visitor.body
[docs]class LatexWriterWithCustomDirectives(_WriterWithCustomDirectives, EnhancedLaTeXWriter): """ This :epkg:`docutils` writer extends the :epkg:`Latex` writer with custom directives implemented in this module. :githublink:`%|py|427` """
[docs] def __init__(self, builder=None, app=None): # pylint: disable=W0231 """ :param builder: builder :param app: Sphinx application :githublink:`%|py|433` """ _WriterWithCustomDirectives._init( self, EnhancedLaTeXWriter, LatexTranslatorWithCustomDirectives, app) if not hasattr(self.builder, "config"): raise TypeError( # pragma: no cover "Builder has no config: {}".format(type(self.builder)))
[docs] def translate(self): if not hasattr(self.builder, "config"): raise TypeError( # pragma: no cover "Builder has no config: {}".format(type(self.builder))) # The instruction # visitor = self.builder.create_translator(self.builder, self.document) # automatically adds methods visit_ and depart_ for translator # based on the list of registered extensions. Might be worth using it. visitor = self.translator_class(self.builder, self.document) self.document.walkabout(visitor) self.output = visitor.body
[docs]class _MemoryBuilder: """ Builds :epkg:`HTML` output in memory. The API is defined by the page :epkg:`builderapi`. :githublink:`%|py|458` """
[docs] def _init(self, base_class, app): """ Constructs the builder. Most of the parameter are static members of the class and cannot be overwritten (yet). :param base_class: base builder class :param app: :epkg:`Sphinx application` :githublink:`%|py|468` """ if "IMPOSSIBLE:TOFIND" in app.srcdir: import sphinx.util.osutil from .conf_path_tools import custom_ensuredir sphinx.util.osutil.ensuredir = custom_ensuredir sphinx.builders.ensuredir = custom_ensuredir base_class.__init__(self, app=app) self.built_pages = {} self.base_class = base_class
[docs] def iter_pages(self): """ Enumerate created pages. :return: iterator on tuple(name, content) :githublink:`%|py|484` """ for k, v in self.built_pages.items(): yield k, v.getvalue()
[docs] def create_translator(self, *args): """ Returns an instance of translator. This method returns an instance of ``default_translator_class`` by default. Users can replace the translator class with ``app.set_translator()`` API. :githublink:`%|py|493` """ translator_class = self.translator_class return translator_class(*args)
[docs] def _write_serial(self, docnames): """ Overwrites *_write_serial* to avoid writing on disk. :githublink:`%|py|500` """ from sphinx.util.logging import pending_warnings from sphinx.util import status_iterator with pending_warnings(): for docname in status_iterator(docnames, 'writing output... ', "darkgreen", len(docnames), self.app.verbosity): doctree = self.env.get_and_resolve_doctree(docname, self) self.write_doc_serialized(docname, doctree) self.write_doc(docname, doctree)
[docs] def _write_parallel(self, docnames, nproc): """ Not supported. :githublink:`%|py|513` """ raise NotImplementedError( "Use parallel=0 when creating the sphinx application.")
[docs] def assemble_doctree(self, *args, **kwargs): """ Overwrites *assemble_doctree* to control the doctree. :githublink:`%|py|520` """ from sphinx.util.nodes import inline_all_toctrees from sphinx.util.console import darkgreen master = self.config.master_doc if hasattr(self, "doctree_"): tree = self.doctree_ else: raise AttributeError( "Attribute 'doctree_' is not present. Call method finalize().") tree = inline_all_toctrees( self, set(), master, tree, darkgreen, [master]) tree['docname'] = master self.env.resolve_references(tree, master, self) self.fix_refuris(tree) return tree
[docs] def fix_refuris(self, tree): """ Overwrites *fix_refuris* to control the reference names. :githublink:`%|py|539` """ fname = "__" + self.config.master_doc + "__" for refnode in tree.traverse(nodes.reference): if 'refuri' not in refnode: continue refuri = refnode['refuri'] hashindex = refuri.find('#') if hashindex < 0: continue hashindex = refuri.find('#', hashindex + 1) if hashindex >= 0: refnode['refuri'] = fname + refuri[hashindex:]
[docs] def get_target_uri(self, docname, typ=None): """ Overwrites *get_target_uri* to control the page name. :githublink:`%|py|555` """ if docname in self.env.all_docs: # all references are on the same page... return self.config.master_doc + '#document-' + docname elif docname in ("genindex", "search"): return self.config.master_doc + '-#' + docname else: docs = ", ".join(sorted("'{0}'".format(_) for _ in self.env.all_docs)) raise ValueError( "docname='{0}' should be in 'self.env.all_docs' which contains:\n{1}".format(docname, docs))
[docs] def get_outfilename(self, pagename): """ Overwrites *get_target_uri* to control file names. :githublink:`%|py|570` """ return "{0}/{1}.m.html".format(self.outdir, pagename).replace("\\", "/")
[docs] def handle_page(self, pagename, addctx, templatename='page.html', outfilename=None, event_arg=None): """ Overrides *handle_page* to write into stream instead of files. :githublink:`%|py|577` """ from sphinx.util.osutil import relative_uri ctx = self.globalcontext.copy() if hasattr(self, "warning"): ctx['warn'] = self.warning elif hasattr(self, "warn"): ctx['warn'] = self.warn # current_page_name is backwards compatibility ctx['pagename'] = ctx['current_page_name'] = pagename ctx['encoding'] = self.config.html_output_encoding default_baseuri = self.get_target_uri(pagename) # in the singlehtml builder, default_baseuri still contains an #anchor # part, which relative_uri doesn't really like... default_baseuri = default_baseuri.rsplit('#', 1)[0] def pathto(otheruri, resource=False, baseuri=default_baseuri): if resource and '://' in otheruri: # allow non-local resources given by scheme return otheruri elif not resource: otheruri = self.get_target_uri(otheruri) uri = relative_uri(baseuri, otheruri) or '#' if uri == '#' and not self.allow_sharp_as_current_path: uri = baseuri return uri ctx['pathto'] = pathto def css_tag(css): attrs = [] for key in sorted(css.attributes): value = css.attributes[key] if value is not None: attrs.append('%s="%s"' % (key, htmlescape( # pylint: disable=W1505 value, True))) # pylint: disable=W1505 attrs.append('href="%s"' % pathto(css.filename, resource=True)) return '<link %s />' % ' '.join(attrs) ctx['css_tag'] = css_tag def hasdoc(name): if name in self.env.all_docs: return True elif name == 'search' and self.search: return True elif name == 'genindex' and self.get_builder_config('use_index', 'html'): return True return False ctx['hasdoc'] = hasdoc ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw) self.add_sidebars(pagename, ctx) ctx.update(addctx) self.update_page_context(pagename, templatename, ctx, event_arg) newtmpl = self.app.emit_firstresult('html-page-context', pagename, templatename, ctx, event_arg) if newtmpl: templatename = newtmpl try: output = self.templates.render(templatename, ctx) except UnicodeError: logger = getLogger("MockSphinxApp") logger.warning("[_CustomSphinx] A unicode error occurred when rendering the page %s. " "Please make sure all config values that contain " "non-ASCII content are Unicode strings.", pagename) return if not outfilename: outfilename = self.get_outfilename(pagename) # outfilename's path is in general different from self.outdir # ensuredir(path.dirname(outfilename)) if outfilename not in self.built_pages: self.built_pages[outfilename] = StringIO() self.built_pages[outfilename].write(output)
[docs]class MemoryHTMLBuilder(_MemoryBuilder, CustomSingleFileHTMLBuilder): """ Builds :epkg:`HTML` output in memory. The API is defined by the page :epkg:`builderapi`. :githublink:`%|py|658` """ name = 'memoryhtml' format = 'html' out_suffix = None # ".memory.html" supported_image_types = ['application/pdf', 'image/png', 'image/jpeg'] default_translator_class = HTMLTranslatorWithCustomDirectives translator_class = HTMLTranslatorWithCustomDirectives _writer_class = HTMLWriterWithCustomDirectives supported_remote_images = True supported_data_uri_images = True html_scaled_image_link = True
[docs] def __init__(self, app): # pylint: disable=W0231 """ Construct the builder. Most of the parameter are static members of the class and cannot be overwritten (yet). :param app: :epkg:`Sphinx application` :githublink:`%|py|677` """ _MemoryBuilder._init(self, CustomSingleFileHTMLBuilder, app)
[docs]class MemoryRSTBuilder(_MemoryBuilder, RstBuilder): """ Builds :epkg:`RST` output in memory. The API is defined by the page :epkg:`builderapi`. The writer simplifies the :epkg:`RST` syntax by replacing custom roles into true :epkg:`RST` syntax. :githublink:`%|py|689` """ name = 'memoryrst' format = 'rst' out_suffix = None # ".memory.rst" supported_image_types = ['application/pdf', 'image/png', 'image/jpeg'] default_translator_class = RSTTranslatorWithCustomDirectives translator_class = RSTTranslatorWithCustomDirectives _writer_class = RSTWriterWithCustomDirectives supported_remote_images = True supported_data_uri_images = True html_scaled_image_link = True
[docs] def __init__(self, app): # pylint: disable=W0231 """ Construct the builder. Most of the parameter are static members of the class and cannot be overwritten (yet). :param app: :epkg:`Sphinx application` :githublink:`%|py|709` """ _MemoryBuilder._init(self, RstBuilder, app)
[docs] def handle_page(self, pagename, addctx, templatename=None, outfilename=None, event_arg=None): """ Override *handle_page* to write into stream instead of files. :githublink:`%|py|716` """ if templatename is not None: raise NotImplementedError( "templatename must be None.") # pragma: no cover if not outfilename: outfilename = self.get_outfilename(pagename) if outfilename not in self.built_pages: self.built_pages[outfilename] = StringIO() self.built_pages[outfilename].write(self.writer.output)
[docs]class MemoryMDBuilder(_MemoryBuilder, MdBuilder): """ Builds :epkg:`MD` output in memory. The API is defined by the page :epkg:`builderapi`. :githublink:`%|py|732` """ name = 'memorymd' format = 'md' out_suffix = None # ".memory.rst" supported_image_types = ['application/pdf', 'image/png', 'image/jpeg'] default_translator_class = MDTranslatorWithCustomDirectives translator_class = MDTranslatorWithCustomDirectives _writer_class = MDWriterWithCustomDirectives supported_remote_images = True supported_data_uri_images = True html_scaled_image_link = True
[docs] def __init__(self, app): # pylint: disable=W0231 """ Construct the builder. Most of the parameter are static members of the class and cannot be overwritten (yet). :param app: :epkg:`Sphinx application` :githublink:`%|py|751` """ _MemoryBuilder._init(self, MdBuilder, app)
[docs] def handle_page(self, pagename, addctx, templatename=None, outfilename=None, event_arg=None): """ Override *handle_page* to write into stream instead of files. :githublink:`%|py|758` """ if templatename is not None: raise NotImplementedError( "templatename must be None.") # pragma: no cover if not outfilename: outfilename = self.get_outfilename(pagename) if outfilename not in self.built_pages: self.built_pages[outfilename] = StringIO() self.built_pages[outfilename].write(self.writer.output)
[docs]class MemoryDocTreeBuilder(_MemoryBuilder, DocTreeBuilder): """ Builds doctree output in memory. The API is defined by the page :epkg:`builderapi`. :githublink:`%|py|774` """ name = 'memorydoctree' format = 'doctree' out_suffix = None # ".memory.rst" default_translator_class = DocTreeTranslatorWithCustomDirectives translator_class = DocTreeTranslatorWithCustomDirectives _writer_class = DocTreeWriterWithCustomDirectives supported_remote_images = True supported_data_uri_images = True html_scaled_image_link = True
[docs] def __init__(self, app): # pylint: disable=W0231 """ Constructs the builder. Most of the parameter are static members of the class and cannot be overwritten (yet). :param app: :epkg:`Sphinx application` :githublink:`%|py|792` """ _MemoryBuilder._init(self, DocTreeBuilder, app)
[docs] def handle_page(self, pagename, addctx, templatename=None, outfilename=None, event_arg=None): """ Override *handle_page* to write into stream instead of files. :githublink:`%|py|799` """ if templatename is not None: raise NotImplementedError( "templatename must be None.") # pragma: no cover if not outfilename: outfilename = self.get_outfilename(pagename) if outfilename not in self.built_pages: self.built_pages[outfilename] = StringIO() self.built_pages[outfilename].write(self.writer.output)
[docs]class MemoryLatexBuilder(_MemoryBuilder, EnhancedLaTeXBuilder): """ Builds :epkg:`Latex` output in memory. The API is defined by the page :epkg:`builderapi`. :githublink:`%|py|815` """ name = 'memorylatex' format = 'tex' out_suffix = None # ".memory.tex" supported_image_types = ['image/png', 'image/jpeg', 'image/gif'] default_translator_class = LatexTranslatorWithCustomDirectives translator_class = LatexTranslatorWithCustomDirectives _writer_class = LatexWriterWithCustomDirectives supported_remote_images = True supported_data_uri_images = True html_scaled_image_link = True
[docs] def __init__(self, app): # pylint: disable=W0231 """ Constructs the builder. Most of the parameter are static members of the class and cannot be overwritten (yet). :param app: :epkg:`Sphinx application` :githublink:`%|py|834` """ _MemoryBuilder._init(self, EnhancedLaTeXBuilder, app)
def write_stylesheet(self): from sphinx.highlighting import PygmentsBridge highlighter = PygmentsBridge('latex', self.config.pygments_style) rows = [] rows.append('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n') rows.append('\\ProvidesPackage{sphinxhighlight}') rows.append( '[2016/05/29 stylesheet for highlighting with pygments]\n\n') rows.append(highlighter.get_stylesheet()) self.built_pages['sphinxhighlight.sty'] = StringIO() self.built_pages['sphinxhighlight.sty'].write("".join(rows))
[docs] class EnhancedStringIO(StringIO):
[docs] def write(self, content): if isinstance(content, str): StringIO.write(self, content) else: for line in content: StringIO.write(self, line)
[docs] def _get_filename(self, targetname, encoding='utf-8', overwrite_if_changed=True): if not isinstance(targetname, str): raise TypeError( # pragma: no cover "targetname must be a string: {0}".format(targetname)) destination = MemoryLatexBuilder.EnhancedStringIO() self.built_pages[targetname] = destination return destination
[docs]class _CustomBuildEnvironment(BuildEnvironment): """ Overrides some functionalities of `BuildEnvironment <http://www.sphinx-doc.org/en/stable/extdev/envapi.html>`_. :githublink:`%|py|870` """
[docs] def __init__(self, app): """ :githublink:`%|py|874` """ BuildEnvironment.__init__(self, app) self.doctree_ = {}
[docs] def get_doctree(self, docname): """ Read the doctree for a file from the pickle and return it. :githublink:`%|py|879` """ if hasattr(self, "doctree_") and docname in self.doctree_: from sphinx.util.docutils import WarningStream doctree = self.doctree_[docname] doctree.settings.env = self doctree.reporter = Reporter(self.doc2path( docname), 2, 5, stream=WarningStream()) return doctree else: if hasattr(self, "self.doctree_"): available = list(sorted(self.doctree_)) if len(available) > 10: available = available[10:] else: available = [] raise KeyError("Unable to find doctree for '{0}'\nFirst documents:\n{1}.".format( docname, "\n".join(available)))
# return BuildEnvironment.get_doctree(self, docname)
[docs] def apply_post_transforms(self, doctree, docname): """ Apply all post-transforms. :githublink:`%|py|900` """ # set env.docname during applying post-transforms self.temp_data['docname'] = docname transformer = SphinxTransformer(doctree) transformer.set_environment(self) transformer.add_transforms(self.app.post_transforms) transformer.apply_transforms() self.temp_data.clear()
[docs]class _CustomSphinx(Sphinx): """ Custom :epkg:`Sphinx` application to avoid using disk. :githublink:`%|py|914` """
[docs] def __init__(self, srcdir, confdir, outdir, doctreedir, buildername="memoryhtml", # pylint: disable=W0231 confoverrides=None, status=None, warning=None, freshenv=False, warningiserror=False, tags=None, verbosity=0, parallel=0, keep_going=False, new_extensions=None): """ Same constructor as :epkg:`Sphinx application`. Additional parameters: :param new_extensions: extensions to add to the application Some insights about domains: :: {'cpp': sphinx.domains.cpp.CPPDomain, 'js': sphinx.domains.javascript.JavaScriptDomain, 'std': sphinx.domains.std.StandardDomain, 'py': sphinx.domains.python.PythonDomain, 'rst': sphinx.domains.rst.ReSTDomain, 'c': sphinx.domains.c.CDomain} And builders: :: {'epub': ('epub', 'EpubBuilder'), 'singlehtml': ('html', 'SingleFileHTMLBuilder'), 'qthelp': ('qthelp', 'QtHelpBuilder'), 'epub3': ('epub3', 'Epub3Builder'), 'man': ('manpage', 'ManualPageBuilder'), 'dummy': ('dummy', 'DummyBuilder'), 'json': ('html', 'JSONHTMLBuilder'), 'html': ('html', 'StandaloneHTMLBuilder'), 'xml': ('xml', 'XMLBuilder'), 'texinfo': ('texinfo', 'TexinfoBuilder'), 'devhelp': ('devhelp', 'DevhelpBuilder'), 'web': ('html', 'PickleHTMLBuilder'), 'pickle': ('html', 'PickleHTMLBuilder'), 'htmlhelp': ('htmlhelp', 'HTMLHelpBuilder'), 'applehelp': ('applehelp', 'AppleHelpBuilder'), 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'), 'dirhtml': ('html', 'DirectoryHTMLBuilder'), 'latex': ('latex', 'LaTeXBuilder'), 'elatex': ('latex', 'EnchancedLaTeXBuilder'), 'text': ('text', 'TextBuilder'), 'changes': ('changes', 'ChangesBuilder'), 'websupport': ('websupport', 'WebSupportBuilder'), 'gettext': ('gettext', 'MessageCatalogBuilder'), 'pseudoxml': ('xml', 'PseudoXMLBuilder')} 'rst': ('rst', 'RstBuilder')} 'md': ('md', 'MdBuilder'), 'doctree': ('doctree', 'DocTreeBuilder')} :githublink:`%|py|969` """ # own purpose (to monitor) self._logger = getLogger("_CustomSphinx") self._added_objects = [] self._added_collectors = [] # from sphinx.domains.cpp import CPPDomain # from sphinx.domains.javascript import JavaScriptDomain # from sphinx.domains.python import PythonDomain # from sphinx.domains.std import StandardDomain # from sphinx.domains.rst import ReSTDomain # from sphinx.domains.c import CDomain from sphinx.registry import SphinxComponentRegistry self.phase = BuildPhase.INITIALIZATION self.verbosity = verbosity self.extensions = {} self.builder = None self.env = None self.project = None self.registry = SphinxComponentRegistry() self.post_transforms = [] self.html_themes = {} if doctreedir is None: doctreedir = "IMPOSSIBLE:TOFIND" if srcdir is None: srcdir = "IMPOSSIBLE:TOFIND" update_docutils_languages() self.srcdir = os.path.abspath(srcdir) self.confdir = os.path.abspath( confdir) if confdir is not None else None self.outdir = os.path.abspath(outdir) if confdir is not None else None self.doctreedir = os.path.abspath(doctreedir) self.parallel = parallel if self.srcdir == self.outdir: raise ApplicationError('Source directory and destination ' # pragma: no cover 'directory cannot be identical') if status is None: self._status = StringIO() self.quiet = True else: self._status = status self.quiet = False from sphinx.events import EventManager # logging.setup(self, self._status, self._warning) self.events = EventManager(self) # keep last few messages for traceback # This will be filled by sphinx.util.logging.LastMessagesWriter self.messagelog = deque(maxlen=10) # say hello to the world from sphinx import __display_version__ self.info('Running Sphinx v%s' % __display_version__) # pragma: no cover # notice for parallel build on macOS and py38+ if sys.version_info > (3, 8) and platform.system() == 'Darwin' and parallel > 1: self._logger.info( # pragma: no cover "For security reason, parallel mode is disabled on macOS and " "python3.8 and above. For more details, please read " "https://github.com/sphinx-doc/sphinx/issues/6803") # status code for command-line application self.statuscode = 0 # delayed import to speed up time from sphinx.application import builtin_extensions try: from sphinx.application import CONFIG_FILENAME, Config, Tags sphinx_version = 2 except ImportError: # Sphinx 3.0.0 from sphinx.config import CONFIG_FILENAME, Config, Tags sphinx_version = 3 # read config self.tags = Tags(tags) with warnings.catch_warnings(): warnings.simplefilter( "ignore", (DeprecationWarning, PendingDeprecationWarning)) if self.confdir is None: self.config = Config({}, confoverrides or {}) else: try: self.config = Config.read( self.confdir, confoverrides or {}, self.tags) except AttributeError: try: self.config = Config( # pylint: disable=E1121 confdir, confoverrides or {}, self.tags) except TypeError: try: self.config = Config(confdir, CONFIG_FILENAME, # pylint: disable=E1121 confoverrides or {}, self.tags) except TypeError: # Sphinx==3.0.0 self.config = Config({}, confoverrides or {}) self.sphinx__display_version__ = __display_version__ # create the environment if sphinx_version == 2: with warnings.catch_warnings(): warnings.simplefilter( "ignore", (DeprecationWarning, PendingDeprecationWarning, ImportWarning)) self.config.check_unicode() self.config.pre_init_values() # set up translation infrastructure self._init_i18n() # check the Sphinx version if requested if self.config.needs_sphinx and self.config.needs_sphinx > __display_version__: from sphinx.locale import _ from sphinx.application import VersionRequirementError raise VersionRequirementError( _('This project needs at least Sphinx v%s and therefore cannot ' 'be built with this version.') % self.config.needs_sphinx) # set confdir to srcdir if -C given (!= no confdir); a few pieces # of code expect a confdir to be set if self.confdir is None: self.confdir = self.srcdir # load all built-in extension modules for extension in builtin_extensions: try: with warnings.catch_warnings(): warnings.filterwarnings( "ignore", category=DeprecationWarning) self.setup_extension(extension) except Exception as e: if 'sphinx.builders.applehelp' not in str(e): # pragma: no cover mes = "Unable to run setup_extension '{0}'\nWHOLE LIST\n{1}".format( extension, "\n".join(builtin_extensions)) raise ExtensionError(mes) from e # load all user-given extension modules for extension in self.config.extensions: self.setup_extension(extension) # /1 addition to the original code # additional extensions if new_extensions: for extension in new_extensions: if isinstance(extension, str): self.setup_extension(extension) else: # We assume it is a module. dirname = os.path.dirname(extension.__file__) sys.path.insert(0, dirname) self.setup_extension(extension.__name__) del sys.path[0] # add default HTML builders self.add_builder(MemoryHTMLBuilder) self.add_builder(MemoryRSTBuilder) self.add_builder(MemoryMDBuilder) self.add_builder(MemoryLatexBuilder) self.add_builder(MemoryDocTreeBuilder) if isinstance(buildername, tuple): if len(buildername) != 2: raise ValueError( "The builder can be custom but it must be specifed as a 2-uple=(builder_name, builder_class).") self.add_builder(buildername[1]) buildername = buildername[0] # /1 end of addition # preload builder module (before init config values) self.preload_builder(buildername) # the config file itself can be an extension if self.config.setup: prefix = 'while setting up extension %s:' % "conf.py" if prefixed_warnings is not None: with prefixed_warnings(prefix): if callable(self.config.setup): self.config.setup(self) else: from sphinx.locale import _ from sphinx.application import ConfigError raise ConfigError( _("'setup' as currently defined in conf.py isn't a Python callable. " "Please modify its definition to make it a callable function. This is " "needed for conf.py to behave as a Sphinx extension.") ) elif callable(self.config.setup): self.config.setup(self) # now that we know all config values, collect them from conf.py noallowed = [] rem = [] for k in confoverrides: if k in {'initial_header_level', 'doctitle_xform', 'input_encoding', 'outdir', 'warnings_log', 'extensions'}: continue if k == 'override_image_directive': self.config.images_config["override_image_directive"] = True rem.append(k) continue if k not in self.config.values: noallowed.append(k) for k in rem: del confoverrides[k] if len(noallowed) > 0: raise ValueError( "The following configuration values are declared in any extension.\n--???--\n" "{0}\n--DECLARED--\n{1}".format( "\n".join(sorted(noallowed)), "\n".join(sorted(self.config.values)))) # now that we know all config values, collect them from conf.py self.config.init_values() self.events.emit('config-inited', self.config) # /2 addition to the original code # check extension versions if requested # self.config.needs_extensions = self.config.extensions if not hasattr(self.config, 'items'): def _citems(): for k, v in self.config.values.items(): yield k, v self.config.items = _citems # /2 end of addition # create the project self.project = Project(self.srcdir, self.config.source_suffix) # create the builder self.builder = self.create_builder(buildername) # set up the build environment self._init_env(freshenv) # set up the builder self._init_builder() if not isinstance(self.env, _CustomBuildEnvironment): raise TypeError( # pragma: no cover "self.env is not _CustomBuildEnvironment: '{0}' buildername='{1}'".format(type(self.env), buildername)) # addition self._extended_init_()
[docs] def _init_env(self, freshenv): if freshenv: self.env = _CustomBuildEnvironment(self) self.env.setup(self) if self.srcdir is not None and self.srcdir != "IMPOSSIBLE:TOFIND": self.env.find_files(self.config, self.builder) elif "IMPOSSIBLE:TOFIND" not in self.doctreedir: from sphinx.application import ENV_PICKLE_FILENAME filename = os.path.join(self.doctreedir, ENV_PICKLE_FILENAME) try: self.info('loading pickled environment... ', nonl=True) with open(filename, 'rb') as f: self.env = pickle.load(f) self.env.setup(self) self.info('done') except Exception as err: self.info('failed: %s' % err) self._init_env(freshenv=True) elif self.env is None: self.env = _CustomBuildEnvironment(self) if hasattr(self.env, 'setup'): self.env.setup(self) if not hasattr(self.env, 'project') or self.env.project is None: raise AttributeError("self.env.project is not initialized.")
[docs] def create_builder(self, name): """ Creates a builder, raises an exception if name is None. :githublink:`%|py|1248` """ if name is None: raise ValueError("Builder name cannot be None") return self.registry.create_builder(self, name)
[docs] def _extended_init_(self): """ Additional initialization steps. :githublink:`%|py|1257` """ if not hasattr(self, "domains"): self.domains = {} if not hasattr(self, "_events"): self._events = {} # Otherwise, role issue is missing. setup_link_roles(self)
[docs] def _lookup_doctree(self, doctree, node_type): for node in doctree.traverse(node_type): yield node
[docs] def _add_missing_ids(self, doctree): for i, node in enumerate(self._lookup_doctree(doctree, None)): stype = str(type(node)) if ('section' not in stype and 'title' not in stype and 'reference' not in stype): continue try: node['ids'][0] except IndexError: node['ids'] = ['missing%d' % i] except TypeError: pass
[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|1289` """ imgs = list(self._lookup_doctree(doctree, nodes.image)) for img in imgs: img['save_uri'] = img['uri'] if not isinstance(self.env, _CustomBuildEnvironment): raise TypeError( # pragma: no cover "self.env is not _CustomBuildEnvironment: '{0}'".format(type(self.env))) if not isinstance(self.builder.env, _CustomBuildEnvironment): raise TypeError("self.builder.env is not _CustomBuildEnvironment: '{0}'".format( type(self.builder.env))) self.doctree_ = doctree self.builder.doctree_ = doctree self.env.doctree_[self.config.master_doc] = doctree self.env.all_docs = {self.config.master_doc: self.config.master_doc} if external_docnames: for doc in external_docnames: self.env.all_docs[doc] = doc # This steps goes through many function including one # modifying paths in image node. # Look for node['candidates'] = candidates in Sphinx code. # If a path startswith('/'), it is removed. from sphinx.environment.collectors.asset import logger as logger_asset logger_asset.setLevel(40) # only errors self._add_missing_ids(doctree) self.events.emit('doctree-read', doctree) logger_asset.setLevel(30) # back to warnings for img in imgs: img['uri'] = img['save_uri'] self.events.emit('doctree-resolved', doctree, self.config.master_doc) self.builder.write(None, None, 'all')
def debug(self, message, *args, **kwargs): self._logger.debug(message, *args, **kwargs) def info(self, message='', nonl=False): self._logger.info(message, nonl=nonl) def warning(self, message='', nonl=False, name=None, type=None, subtype=None): if "is already registered" not in message: self._logger.warning( "[_CustomSphinx] {0} -- {1}".format(message, name), nonl=nonl, type=type, subtype=subtype)
[docs] def add_builder(self, builder, override=False): self._added_objects.append(('builder', builder)) if builder.name not in self.registry.builders: self.debug('[_CustomSphinx] adding builder: %r', builder) try: # Sphinx >= 1.8 self.registry.add_builder(builder, override=override) except TypeError: # pragma: no cover # Sphinx < 1.8 self.registry.add_builder(builder) else: self.debug('[_CustomSphinx] already added builder: %r', builder)
[docs] def setup_extension(self, extname): self._added_objects.append(('extension', extname)) logger = getLogger('sphinx.application') disa = logger.logger.disabled logger.logger.disabled = True # delayed import to speed up time try: with warnings.catch_warnings(): warnings.filterwarnings( "ignore", category=DeprecationWarning) self.registry.load_extension(self, extname) except Exception as e: raise ExtensionError( "Unable to setup extension '{0}'".format(extname)) from e finally: logger.logger = disa
[docs] def add_directive(self, name, obj, content=None, arguments=None, # pylint: disable=W0221 override=True, **options): self._added_objects.append(('directive', name)) if name == 'plot' and obj.__name__ == 'PlotDirective': old_run = obj.run def run(self): """ Run the plot directive. :githublink:`%|py|1377` """ logger = getLogger("MockSphinxApp") logger.info( '[MockSphinxApp] PlotDirective: {}'.format(self.content)) try: res = old_run(self) logger.info( '[MockSphinxApp] PlotDirective ok') return res except OSError as e: logger = getLogger("MockSphinxApp") logger.info( '[MockSphinxApp] PlotDirective failed: {}'.format(e)) return [] obj.run = run try: # Sphinx >= 1.8 Sphinx.add_directive(self, name, obj, content=content, # pylint: disable=E1123 arguments=arguments, override=override, **options) except TypeError: # Sphinx >= 3.0.0 Sphinx.add_directive(self, name, obj, override=override, **options) except ExtensionError: # pragma: no cover # Sphinx < 1.8 Sphinx.add_directive(self, name, obj, content=content, # pylint: disable=E1123 arguments=arguments, **options)
[docs] def add_domain(self, domain, override=True): self._added_objects.append(('domain', domain)) try: # Sphinx >= 1.8 Sphinx.add_domain(self, domain, override=override) except TypeError: # pragma: no cover # Sphinx < 1.8 Sphinx.add_domain(self, domain) # For some reason, the directives are missing from the main catalog # in docutils. for k, v in domain.directives.items(): self.add_directive("{0}:{1}".format(domain.name, k), v) if domain.name in ('py', 'std', 'rst'): # We add the directive without the domain name as a prefix. self.add_directive(k, v) for k, v in domain.roles.items(): self.add_role("{0}:{1}".format(domain.name, k), v) if domain.name in ('py', 'std', 'rst'): # We add the role without the domain name as a prefix. self.add_role(k, v)
def override_domain(self, domain): self._added_objects.append(('domain-over', domain)) try: Sphinx.override_domain(self, domain) except AttributeError: # Sphinx==3.0.0 raise AttributeError( "override_domain not available in sphinx==3.0.0")
[docs] def add_role(self, name, role, override=True): self._added_objects.append(('role', name)) self.debug('[_CustomSphinx] adding role: %r', (name, role)) roles.register_local_role(name, role)
[docs] def add_generic_role(self, name, nodeclass, override=True): self._added_objects.append(('generic_role', name)) self.debug("[_CustomSphinx] adding generic role: '%r'", (name, nodeclass)) role = roles.GenericRole(name, nodeclass) roles.register_local_role(name, role)
[docs] def add_node(self, node, override=True, **kwds): self._added_objects.append(('node', node)) self.debug('[_CustomSphinx] adding node: %r', (node, kwds)) nodes._add_node_class_names([node.__name__]) for key, val in kwds.items(): try: visit, depart = val except ValueError: raise ExtensionError(("Value for key '%r' must be a " "(visit, depart) function tuple") % key) translator = self.registry.translators.get(key) translators = [] if translator is not None: translators.append(translator) elif key == 'html': from sphinx.writers.html import HTMLTranslator translators.append(HTMLTranslator) if is_html5_writer_available(): from sphinx.writers.html5 import HTML5Translator translators.append(HTML5Translator) elif key == 'latex': translators.append(_get_LaTeXTranslator()) elif key == 'elatex': translators.append(EnhancedLaTeXBuilder) elif key == 'text': from sphinx.writers.text import TextTranslator translators.append(TextTranslator) elif key == 'man': from sphinx.writers.manpage import ManualPageTranslator translators.append(ManualPageTranslator) elif key == 'texinfo': from sphinx.writers.texinfo import TexinfoTranslator translators.append(TexinfoTranslator) for translator in translators: setattr(translator, 'visit_' + node.__name__, visit) if depart: setattr(translator, 'depart_' + node.__name__, depart)
[docs] def add_event(self, name): self._added_objects.append(('event', name)) Sphinx.add_event(self, name)
[docs] def add_config_value(self, name, default, rebuild, types_=()): # pylint: disable=W0221 self._added_objects.append(('config_value', name)) Sphinx.add_config_value(self, name, default, rebuild, types_)
[docs] def add_directive_to_domain(self, domain, name, obj, has_content=None, # pylint: disable=W0221 argument_spec=None, override=False, **option_spec): self._added_objects.append(('directive_to_domain', domain, name)) try: Sphinx.add_directive_to_domain(self, domain, name, obj, # pylint: disable=E1123 has_content=has_content, argument_spec=argument_spec, override=override, **option_spec) except TypeError: # Sphinx==3.0.0 Sphinx.add_directive_to_domain(self, domain, name, obj, override=override, **option_spec)
[docs] def add_role_to_domain(self, domain, name, role, override=False): self._added_objects.append(('roles_to_domain', domain, name)) Sphinx.add_role_to_domain(self, domain, name, role, override=override)
[docs] def add_transform(self, transform): self._added_objects.append(('transform', transform)) Sphinx.add_transform(self, transform)
[docs] def add_post_transform(self, transform): self._added_objects.append(('post_transform', transform)) Sphinx.add_post_transform(self, transform)
[docs] def add_js_file(self, filename, **kwargs): self._added_objects.append(('js', filename)) try: # Sphinx >= 1.8 Sphinx.add_js_file(self, filename, **kwargs) except AttributeError: # pragma: no cover # Sphinx < 1.8 Sphinx.add_javascript(self, filename, **kwargs)
[docs] def add_css_file(self, filename, **kwargs): self._added_objects.append(('css', filename)) try: # Sphinx >= 1.8 Sphinx.add_css_file(self, filename, **kwargs) except AttributeError: # pragma: no cover # Sphinx < 1.8 Sphinx.add_stylesheet(self, filename, **kwargs)
[docs] def add_latex_package(self, packagename, options=None): self._added_objects.append(('latex', packagename)) Sphinx.add_latex_package(self, packagename)
[docs] def add_object_type(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None, objname='', doc_field_types=None, override=False): if doc_field_types is None: doc_field_types = [] self._added_objects.append(('object', directivename, rolename)) Sphinx.add_object_type(self, directivename, rolename, indextemplate=indextemplate, parse_node=parse_node, ref_nodeclass=ref_nodeclass, objname=objname, doc_field_types=doc_field_types, override=override)
[docs] def add_env_collector(self, collector): """ See :epkg:`class Sphinx`. :githublink:`%|py|1556` """ self.debug( '[_CustomSphinx] adding environment collector: %r', collector) coll = collector() coll.enable(self) self._added_collectors.append(coll)
[docs] def disconnect_env_collector(self, clname, exc=True): """ Disables a collector given its class name. :param cl: name :param exc: raises an exception if not found :return: found collector :githublink:`%|py|1570` """ found = None foundi = None for i, co in enumerate(self._added_collectors): if clname == co.__class__.__name__: found = co foundi = i break if found is not None and not exc: return None if found is None: raise ValueError("Unable to find a collector '{0}' in \n{1}".format( clname, "\n".join(map(lambda x: x.__class__.__name__, self._added_collectors)))) for v in found.listener_ids.values(): self.disconnect(v) del self._added_collectors[foundi] return found