Navigation

"""Helpers to convert docstring to various format.:githublink:`%|py|5`"""importosimportsysfromcollectionsimportdequeimportwarningsimportpickleimportplatformfromhtmlimportescapeashtmlescapefromioimportStringIOfromdocutils.parsers.rstimportrolesfromdocutils.languagesimportenasdocutils_enfromdocutilsimportnodesfromdocutils.utilsimportReporterfromsphinx.applicationimportSphinxfromsphinx.environmentimportBuildEnvironmentfromsphinx.errorsimportExtensionErrorfromsphinx.ext.extlinksimportsetup_link_rolesfromsphinx.transformsimportSphinxTransformerfromsphinx.util.docutilsimportis_html5_writer_availablefromsphinx.writers.htmlimportHTMLWriterfromsphinx.util.build_phaseimportBuildPhasefromsphinx.util.loggingimportprefixed_warningsfromsphinx.projectimportProjectfromsphinx.errorsimportApplicationErrorfromsphinx.util.loggingimportgetLoggerfrom..sphinxext.sphinx_doctree_builderimportDocTreeBuilder,DocTreeWriter,DocTreeTranslatorfrom..sphinxext.sphinx_md_builderimportMdBuilder,MdWriter,MdTranslatorfrom..sphinxext.sphinx_latex_builderimportEnhancedLaTeXBuilder,EnhancedLaTeXWriter,EnhancedLaTeXTranslatorfrom..sphinxext.sphinx_rst_builderimportRstBuilder,RstWriter,RstTranslatorfrom._single_file_html_builderimportCustomSingleFileHTMLBuilder

[docs]defupdate_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` """ifvaluesisNone:values=dict()lab=docutils_en.labelsif'versionmodified'notinlab:lab['versionmodified']=values.get('versionmodified','modified version')if'desc'notinlab:lab['desc']=values.get('desc','description')

[docs]defis_html(self):""" Tells if the translator is :epkg:`html` format. :githublink:`%|py|86` """returnself.base_classisHTMLTranslator

[docs]defis_rst(self):""" Tells if the translator is :epkg:`rst` format. :githublink:`%|py|92` """returnself.base_classisRstTranslator

[docs]defis_latex(self):""" Tells if the translator is :epkg:`latex` format. :githublink:`%|py|98` """returnself.base_classis_get_LaTeXTranslator()

[docs]defis_md(self):""" Tells if the translator is :epkg:`markdown` format. :githublink:`%|py|104` """returnself.base_classis_get_LaTeXTranslator()

[docs]defis_doctree(self):""" Tells if the translator is doctree format. :githublink:`%|py|110` """returnself.base_classis_get_LaTeXTranslator()

[docs]defadd_secnumber(self,node):""" Overwrites this method to catch errors due when it is a single document being processed. :githublink:`%|py|117` """ifnode.get('secnumber'):self.base_class.add_secnumber(self,node)eliflen(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)

defeval_expr(self,expr):rst=self.output_format=='rst'latex=self.output_formatin('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_formatin('doctree','doctree.txt')ifnot(rstorhtmlorlatexormdordoctree):raiseValueError(# pragma: no cover"Unknown output format '{0}'.".format(self.output_format))try:ev=eval(expr)exceptException:# pragma: no coverraiseValueError("Unable to interpret expression '{0}'".format(expr))returnevdefvisit_only(self,node):ev=self.eval_expr(node.attributes['expr'])ifev:passelse:raisenodes.SkipNodedefdepart_only(self,node):ev=self.eval_expr(node.attributes['expr'])ifev:passelse:# The program should not necessarily be here.passdefunknown_visit(self,node):# pragma: no coverraiseNotImplementedError("[_AdditionalVisitDepart] Unknown node: '{0}' in '{1}'".format(node.__class__.__name__,self.__class__.__name__))

[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)ifnodes_listisnotNone:forname,f1,f2innodes_list:setattr(self.__class__,"visit_"+name,f1)setattr(self.__class__,"depart_"+name,f2)self.base_class=HTMLTranslator

defvisit_field(self,node):ifnothasattr(self,'_fieldlist_row_index'):# needed when a docstring starts with :param:self._fieldlist_row_index=0returnHTMLTranslator.visit_field(self,node)defvisit_pending_xref(self,node):self.visit_Text(node)raisenodes.SkipNode

[docs]defwrite(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]classHTMLWriterWithCustomDirectives(_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]deftranslate(self):ifnothasattr(self.builder,"config"):raiseTypeError(# 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"inapp.srcdir:importsphinx.util.osutilfrom.conf_path_toolsimportcustom_ensuredirsphinx.util.osutil.ensuredir=custom_ensuredirsphinx.builders.ensuredir=custom_ensuredirbase_class.__init__(self,app=app)self.built_pages={}self.base_class=base_class

[docs]defcreate_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_classreturntranslator_class(*args)

[docs]defassemble_doctree(self,*args,**kwargs):""" Overwrites *assemble_doctree* to control the doctree. :githublink:`%|py|520` """fromsphinx.util.nodesimportinline_all_toctreesfromsphinx.util.consoleimportdarkgreenmaster=self.config.master_docifhasattr(self,"doctree_"):tree=self.doctree_else:raiseAttributeError("Attribute 'doctree_' is not present. Call method finalize().")tree=inline_all_toctrees(self,set(),master,tree,darkgreen,[master])tree['docname']=masterself.env.resolve_references(tree,master,self)self.fix_refuris(tree)returntree

[docs]deffix_refuris(self,tree):""" Overwrites *fix_refuris* to control the reference names. :githublink:`%|py|539` """fname="__"+self.config.master_doc+"__"forrefnodeintree.traverse(nodes.reference):if'refuri'notinrefnode:continuerefuri=refnode['refuri']hashindex=refuri.find('#')ifhashindex<0:continuehashindex=refuri.find('#',hashindex+1)ifhashindex>=0:refnode['refuri']=fname+refuri[hashindex:]

[docs]defget_target_uri(self,docname,typ=None):""" Overwrites *get_target_uri* to control the page name. :githublink:`%|py|555` """ifdocnameinself.env.all_docs:# all references are on the same page...returnself.config.master_doc+'#document-'+docnameelifdocnamein("genindex","search"):returnself.config.master_doc+'-#'+docnameelse:docs=", ".join(sorted("'{0}'".format(_)for_inself.env.all_docs))raiseValueError("docname='{0}' should be in 'self.env.all_docs' which contains:\n{1}".format(docname,docs))

[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]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]defhandle_page(self,pagename,addctx,templatename=None,outfilename=None,event_arg=None):""" Override *handle_page* to write into stream instead of files. :githublink:`%|py|716` """iftemplatenameisnotNone:raiseNotImplementedError("templatename must be None.")# pragma: no coverifnotoutfilename:outfilename=self.get_outfilename(pagename)ifoutfilenamenotinself.built_pages:self.built_pages[outfilename]=StringIO()self.built_pages[outfilename].write(self.writer.output)

[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]defhandle_page(self,pagename,addctx,templatename=None,outfilename=None,event_arg=None):""" Override *handle_page* to write into stream instead of files. :githublink:`%|py|758` """iftemplatenameisnotNone:raiseNotImplementedError("templatename must be None.")# pragma: no coverifnotoutfilename:outfilename=self.get_outfilename(pagename)ifoutfilenamenotinself.built_pages:self.built_pages[outfilename]=StringIO()self.built_pages[outfilename].write(self.writer.output)

[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]defhandle_page(self,pagename,addctx,templatename=None,outfilename=None,event_arg=None):""" Override *handle_page* to write into stream instead of files. :githublink:`%|py|799` """iftemplatenameisnotNone:raiseNotImplementedError("templatename must be None.")# pragma: no coverifnotoutfilename:outfilename=self.get_outfilename(pagename)ifoutfilenamenotinself.built_pages:self.built_pages[outfilename]=StringIO()self.built_pages[outfilename].write(self.writer.output)

[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)

defwrite_stylesheet(self):fromsphinx.highlightingimportPygmentsBridgehighlighter=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]def_get_filename(self,targetname,encoding='utf-8',overwrite_if_changed=True):ifnotisinstance(targetname,str):raiseTypeError(# pragma: no cover"targetname must be a string: {0}".format(targetname))destination=MemoryLatexBuilder.EnhancedStringIO()self.built_pages[targetname]=destinationreturndestination

[docs]defget_doctree(self,docname):"""Read the doctree for a file from the pickle and return it.:githublink:`%|py|879` """ifhasattr(self,"doctree_")anddocnameinself.doctree_:fromsphinx.util.docutilsimportWarningStreamdoctree=self.doctree_[docname]doctree.settings.env=selfdoctree.reporter=Reporter(self.doc2path(docname),2,5,stream=WarningStream())returndoctreeelse:ifhasattr(self,"self.doctree_"):available=list(sorted(self.doctree_))iflen(available)>10:available=available[10:]else:available=[]raiseKeyError("Unable to find doctree for '{0}'\nFirst documents:\n{1}.".format(docname,"\n".join(available)))

# return BuildEnvironment.get_doctree(self, docname)

[docs]defapply_post_transforms(self,doctree,docname):"""Apply all post-transforms.:githublink:`%|py|900` """# set env.docname during applying post-transformsself.temp_data['docname']=docnametransformer=SphinxTransformer(doctree)transformer.set_environment(self)transformer.add_transforms(self.app.post_transforms)transformer.apply_transforms()self.temp_data.clear()

[docs]defcreate_builder(self,name):""" Creates a builder, raises an exception if name is None. :githublink:`%|py|1248` """ifnameisNone:raiseValueError("Builder name cannot be None")returnself.registry.create_builder(self,name)

[docs]deffinalize(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))forimginimgs:img['save_uri']=img['uri']ifnotisinstance(self.env,_CustomBuildEnvironment):raiseTypeError(# pragma: no cover"self.env is not _CustomBuildEnvironment: '{0}'".format(type(self.env)))ifnotisinstance(self.builder.env,_CustomBuildEnvironment):raiseTypeError("self.builder.env is not _CustomBuildEnvironment: '{0}'".format(type(self.builder.env)))self.doctree_=doctreeself.builder.doctree_=doctreeself.env.doctree_[self.config.master_doc]=doctreeself.env.all_docs={self.config.master_doc:self.config.master_doc}ifexternal_docnames:fordocinexternal_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.fromsphinx.environment.collectors.assetimportloggeraslogger_assetlogger_asset.setLevel(40)# only errorsself._add_missing_ids(doctree)self.events.emit('doctree-read',doctree)logger_asset.setLevel(30)# back to warningsforimginimgs:img['uri']=img['save_uri']self.events.emit('doctree-resolved',doctree,self.config.master_doc)self.builder.write(None,None,'all')

[docs]defadd_domain(self,domain,override=True):self._added_objects.append(('domain',domain))try:# Sphinx >= 1.8Sphinx.add_domain(self,domain,override=override)exceptTypeError:# pragma: no cover# Sphinx < 1.8Sphinx.add_domain(self,domain)# For some reason, the directives are missing from the main catalog# in docutils.fork,vindomain.directives.items():self.add_directive("{0}:{1}".format(domain.name,k),v)ifdomain.namein('py','std','rst'):# We add the directive without the domain name as a prefix.self.add_directive(k,v)fork,vindomain.roles.items():self.add_role("{0}:{1}".format(domain.name,k),v)ifdomain.namein('py','std','rst'):# We add the role without the domain name as a prefix.self.add_role(k,v)

defoverride_domain(self,domain):self._added_objects.append(('domain-over',domain))try:Sphinx.override_domain(self,domain)exceptAttributeError:# Sphinx==3.0.0raiseAttributeError("override_domain not available in sphinx==3.0.0")

[docs]defdisconnect_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=Nonefoundi=Nonefori,coinenumerate(self._added_collectors):ifclname==co.__class__.__name__:found=cofoundi=ibreakiffoundisnotNoneandnotexc:returnNoneiffoundisNone:raiseValueError("Unable to find a collector '{0}' in \n{1}".format(clname,"\n".join(map(lambdax:x.__class__.__name__,self._added_collectors))))forvinfound.listener_ids.values():self.disconnect(v)delself._added_collectors[foundi]returnfound