Skip to content

Instantly share code, notes, and snippets.

@Cilyan
Created Jan 7, 2021
Embed
What would you like to do?
Modification of sphinx.ext.ifconfig to run as a Transform as early as possible. By processing the conditions early and removing the nodes that will not go to the final doctree, the goal is to reduce the amount of unexpected errors and warnings that later transforms may face when processing nodes that are not going to be included anyway.
"""
sphinxcontrib.ifearly
~~~~~~~~~~~~~~~~~~~~~
Provides the ``ifearly`` directive that allows to write documentation
that is included depending on configuration variables.
Usage::
.. ifearly:: releaselevel in ('alpha', 'beta', 'rc')
This stuff is only included in the built docs for unstable versions.
The argument for ``ifearly`` is a plain Python expression, evaluated in the
namespace of the project configuration (that is, all variables from
``conf.py`` are available.)
The difference with the ``ifconfig`` directive, is that ``ifearly`` is
evaluated early in the transform process, so that content that is not
included in the final documentation is not evaluated. On the contrary,
the ``ifconfig`` directive is evaluated late. Content is always evaluated,
possibly raising errors and side-effect, but finally removed just before the
resulting document is written.
Original code from ``sphinx.ext.ifconfig``
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from typing import Any, Dict, List
from docutils import nodes
from docutils.nodes import Node
import sphinx
from sphinx.application import Sphinx
from sphinx.transforms import SphinxTransform
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import nested_parse_with_titles
class ifearly(nodes.Element):
pass
class IfEarly(SphinxDirective):
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {} # type: Dict
def run(self) -> List[Node]:
node = ifearly()
node.document = self.state.document
self.set_source_info(node)
node['expr'] = self.arguments[0]
nested_parse_with_titles(self.state, self.content, node)
return [node]
class IfEarlyTransform(SphinxTransform):
# run as early as possible
default_priority = 5
def apply(self) -> None:
ns = {confval.name: confval.value for confval in self.app.config}
ns.update(self.app.config.__dict__.copy())
ns['builder'] = self.app.builder.name
for node in self.document.traverse(ifearly):
try:
res = eval(node['expr'], ns)
except Exception as err:
# handle exceptions in a clean fashion
from traceback import format_exception_only
msg = ''.join(format_exception_only(err.__class__, err))
newnode = self.document.doctree.reporter.error(
'Exception occurred in ifearly expression: \n%s' % msg,
base_node=node
)
node.replace_self(newnode)
else:
if not res:
node.replace_self([])
else:
node.replace_self(node.children)
def setup(app: Sphinx) -> Dict[str, Any]:
app.add_node(ifearly)
app.add_directive('ifearly', IfEarly)
app.add_transform(IfEarlyTransform)
return {'version': '0.1.0', 'parallel_read_safe': True}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment